From 058f1994f56fa0a390984848a2b8531d9b721f3f Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Wed, 31 Jan 2024 12:52:10 -0500 Subject: [PATCH] Add some APIs for defining local endpoints to Matter.framework. (#31673) * Add some APIs for defining local endpoints to Matter.framework. These are not hooked up to anything yet, in the sense that you can create an MTREndpointDescription but then can't do anything with it. APIs on MTRDeviceController that take an MTREndpointDescription and hook up the endpoint to actually be exposed will come later. * Address review comments. * Fix some build/lint issues. * Address more review comments. * Addressing more review comments. --- src/darwin/Framework/CHIP/Matter.h | 5 + .../CHIP/ServerEndpoint/MTRAccessGrant.h | 80 +++ .../CHIP/ServerEndpoint/MTRAccessGrant.mm | 155 ++++++ .../ServerEndpoint/MTRDeviceTypeRevision.h | 45 ++ .../ServerEndpoint/MTRDeviceTypeRevision.mm | 88 ++++ .../CHIP/ServerEndpoint/MTRServerAttribute.h | 63 +++ .../CHIP/ServerEndpoint/MTRServerAttribute.mm | 158 ++++++ .../MTRServerAttribute_Internal.h | 48 ++ .../CHIP/ServerEndpoint/MTRServerCluster.h | 111 ++++ .../CHIP/ServerEndpoint/MTRServerCluster.mm | 228 ++++++++ .../MTRServerCluster_Internal.h | 47 ++ .../CHIP/ServerEndpoint/MTRServerEndpoint.h | 87 ++++ .../CHIP/ServerEndpoint/MTRServerEndpoint.mm | 197 +++++++ .../MTRServerEndpoint_Internal.h | 39 ++ .../CHIPTests/MTRServerEndpointTests.m | 492 ++++++++++++++++++ .../Matter.xcodeproj/project.pbxproj | 66 ++- 16 files changed, 1908 insertions(+), 1 deletion(-) create mode 100644 src/darwin/Framework/CHIP/ServerEndpoint/MTRAccessGrant.h create mode 100644 src/darwin/Framework/CHIP/ServerEndpoint/MTRAccessGrant.mm create mode 100644 src/darwin/Framework/CHIP/ServerEndpoint/MTRDeviceTypeRevision.h create mode 100644 src/darwin/Framework/CHIP/ServerEndpoint/MTRDeviceTypeRevision.mm create mode 100644 src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute.h create mode 100644 src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute.mm create mode 100644 src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute_Internal.h create mode 100644 src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.h create mode 100644 src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.mm create mode 100644 src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster_Internal.h create mode 100644 src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.h create mode 100644 src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.mm create mode 100644 src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint_Internal.h create mode 100644 src/darwin/Framework/CHIPTests/MTRServerEndpointTests.m diff --git a/src/darwin/Framework/CHIP/Matter.h b/src/darwin/Framework/CHIP/Matter.h index 85f1399268f1a1..7000e9bade89c1 100644 --- a/src/darwin/Framework/CHIP/Matter.h +++ b/src/darwin/Framework/CHIP/Matter.h @@ -19,6 +19,7 @@ #define MTR_INCLUDED_FROM_UMBRELLA_HEADER +#import #import #import #import @@ -45,6 +46,7 @@ #import #import #import +#import #import #import #import @@ -56,6 +58,9 @@ #import #import #import +#import +#import +#import #import #import #import diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRAccessGrant.h b/src/darwin/Framework/CHIP/ServerEndpoint/MTRAccessGrant.h new file mode 100644 index 00000000000000..e0b25449755f16 --- /dev/null +++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRAccessGrant.h @@ -0,0 +1,80 @@ +/** + * 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 +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * An access grant, which can be represented as an entry in the Matter Access + * Control cluster. + */ +MTR_NEWLY_AVAILABLE +@interface MTRAccessGrant : NSObject + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +/** + * Grant access at the provided level to a specific node on the fabric. The + * provided nodeID must be an operational node identifier. + */ ++ (nullable MTRAccessGrant *)accessGrantForNodeID:(NSNumber *)nodeID privilege:(MTRAccessControlEntryPrivilege)privilege; + +/** + * Grant access to any node on the fabric that has a matching CASE Authenticated + * Tag in its operational certificate. The provided caseAuthenticatedTag must + * be a 32-bit unsigned integer with lower 16 bits not 0, per the Matter + * specification. + */ ++ (nullable MTRAccessGrant *)accessGrantForCASEAuthenticatedTag:(NSNumber *)caseAuthenticatedTag privilege:(MTRAccessControlEntryPrivilege)privilege; + +/** + * Grant access to any node on the fabric that is communicating with us via + * group messages sent to the given group. The provided groupID must be a valid + * group identifier in the range 1-65535. + */ ++ (nullable MTRAccessGrant *)accessGrantForGroupID:(NSNumber *)groupID privilege:(MTRAccessControlEntryPrivilege)privilege; + +/** + * Grant access to any node on the fabric, as long as it's communicating with us + * over a unicast authenticated channel. + */ ++ (MTRAccessGrant *)accessGrantForAllNodesWithPrivilege:(MTRAccessControlEntryPrivilege)privilege; + +/** + * The matter access control subject ID that access has been granted for. Nil + * when access has been granted for all subjects (e.g. via initForAllNodesWithPrivilege). + */ +@property (nonatomic, copy, readonly, nullable) NSNumber * subjectID; + +/** + * The privilege that has been granted + */ +@property (nonatomic, assign, readonly) MTRAccessControlEntryPrivilege grantedPrivilege; + +/** + * The type of authentication mode the access grant is + * for. MTRAccessControlEntryAuthModeCASE for unicast messages and + * MTRAccessControlEntryAuthModeGroup for groupcast ones. + */ +@property (nonatomic, assign, readonly) MTRAccessControlEntryAuthMode authenticationMode; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRAccessGrant.mm b/src/darwin/Framework/CHIP/ServerEndpoint/MTRAccessGrant.mm new file mode 100644 index 00000000000000..a4c0eebf17794a --- /dev/null +++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRAccessGrant.mm @@ -0,0 +1,155 @@ +/** + * 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 "MTRLogging_Internal.h" +#import + +#include +#include +#include +#include + +using namespace chip; + +MTR_DIRECT_MEMBERS +@implementation MTRAccessGrant + ++ (nullable MTRAccessGrant *)accessGrantForNodeID:(NSNumber *)nodeID privilege:(MTRAccessControlEntryPrivilege)privilege +{ + NodeId id = nodeID.unsignedLongLongValue; + if (!IsOperationalNodeId(id)) { + MTR_LOG_ERROR("MTRAccessGrant provided non-operational node ID: 0x%llx", id); + return nil; + } + + return [[MTRAccessGrant alloc] initWithSubject:[nodeID copy] privilege:privilege authenticationMode:MTRAccessControlEntryAuthModeCASE]; +} + ++ (nullable MTRAccessGrant *)accessGrantForCASEAuthenticatedTag:(NSNumber *)caseAuthenticatedTag privilege:(MTRAccessControlEntryPrivilege)privilege +{ + auto value = caseAuthenticatedTag.unsignedLongLongValue; + if (!CanCastTo(value)) { + MTR_LOG_ERROR("MTRAccessGrant provided too-large CAT value: 0x%llx", value); + return nil; + } + + CASEAuthTag tag = static_cast(value); + if (!IsValidCASEAuthTag(tag)) { + MTR_LOG_ERROR("MTRAccessGrant provided invalid CAT value: 0x%" PRIx32, tag); + return nil; + } + + return [[MTRAccessGrant alloc] initWithSubject:@(NodeIdFromCASEAuthTag(tag)) privilege:privilege authenticationMode:MTRAccessControlEntryAuthModeCASE]; +} + ++ (nullable MTRAccessGrant *)accessGrantForGroupID:(NSNumber *)groupID privilege:(MTRAccessControlEntryPrivilege)privilege +{ + auto value = groupID.unsignedLongLongValue; + if (!CanCastTo(value)) { + MTR_LOG_ERROR("MTRAccessGrant provided too-large group id: 0x%llx", value); + return nil; + } + + GroupId id = static_cast(value); + if (!IsValidGroupId(id)) { + MTR_LOG_ERROR("MTRAccessGrant provided invalid group id: 0x%" PRIx32, id); + return nil; + } + + return [[MTRAccessGrant alloc] initWithSubject:@(NodeIdFromGroupId(id)) privilege:privilege authenticationMode:MTRAccessControlEntryAuthModeGroup]; +} + ++ (MTRAccessGrant *)accessGrantForAllNodesWithPrivilege:(MTRAccessControlEntryPrivilege)privilege +{ + return [[MTRAccessGrant alloc] initWithSubject:nil privilege:privilege authenticationMode:MTRAccessControlEntryAuthModeCASE]; +} + +// initWithSubject assumes that the subject has already been validated and, if +// needed, copied from the input. +- (nullable instancetype)initWithSubject:(nullable NSNumber *)subject privilege:(MTRAccessControlEntryPrivilege)privilege authenticationMode:(MTRAccessControlEntryAuthMode)authenticationMode +{ + if (!(self = [super init])) { + return nil; + } + + _subjectID = subject; + _grantedPrivilege = privilege; + _authenticationMode = authenticationMode; + return self; +} + +- (id)copyWithZone:(NSZone *)zone +{ + // We have no mutable state. + return self; +} + +- (BOOL)isEqual:(id)object +{ + if ([object class] != [self class]) { + return NO; + } + + MTRAccessGrant * other = object; + + BOOL sameSubjectID = (_subjectID == nil && other.subjectID == nil) || [_subjectID isEqual:other.subjectID]; + return sameSubjectID && _grantedPrivilege == other.grantedPrivilege && _authenticationMode == other.authenticationMode; +} + +- (NSUInteger)hash +{ + return _subjectID.unsignedLongLongValue ^ _grantedPrivilege ^ _authenticationMode; +} + +- (NSString *)description +{ + NSString * privilege = @"Unknown"; + switch (_grantedPrivilege) { + case MTRAccessControlEntryPrivilegeView: + privilege = @"View"; + break; + case MTRAccessControlEntryPrivilegeProxyView: + privilege = @"ProxyView"; + break; + case MTRAccessControlEntryPrivilegeOperate: + privilege = @"Operate"; + break; + case MTRAccessControlEntryPrivilegeManage: + privilege = @"Manage"; + break; + case MTRAccessControlEntryPrivilegeAdminister: + privilege = @"Administer"; + break; + } + + if (_subjectID == nil) { + return [NSString stringWithFormat:@"<%@ all nodes can %@>", self.class, privilege]; + } + + NodeId nodeId = static_cast(_subjectID.unsignedLongLongValue); + if (IsGroupId(nodeId)) { + return [NSString stringWithFormat:@"<%@ group 0x%x can %@>", self.class, GroupIdFromNodeId(nodeId), privilege]; + } + + if (IsCASEAuthTag(nodeId)) { + return [NSString stringWithFormat:@"<%@ nodes with CASE Authenticated Tag 0x%08x can %@>", self.class, CASEAuthTagFromNodeId(nodeId), privilege]; + } + + return [NSString stringWithFormat:@"<%@ node 0x%016llx can %@>", self.class, nodeId, privilege]; +} + +@end diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRDeviceTypeRevision.h b/src/darwin/Framework/CHIP/ServerEndpoint/MTRDeviceTypeRevision.h new file mode 100644 index 00000000000000..7f4f88b0cfd643 --- /dev/null +++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRDeviceTypeRevision.h @@ -0,0 +1,45 @@ +/** + * 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 +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * A representation of a "device type revision" in the sense used in the Matter + * specification. This has an identifier and a version number. + */ +MTR_NEWLY_AVAILABLE +@interface MTRDeviceTypeRevision : NSObject + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +/** + * The provided deviceTypeID must be in the range 0xVVVV0000-0xVVVVBFFF, where + * VVVV is the vendor identifier (0 for standard device types). + * + * The provided deviceTypeRevision must be in the range 1-65535. + */ +- (nullable instancetype)initWithDeviceTypeID:(NSNumber *)deviceTypeID revision:(NSNumber *)revision; + +@property (nonatomic, copy, readonly) NSNumber * deviceTypeID; +@property (nonatomic, copy, readonly) NSNumber * deviceTypeRevision; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRDeviceTypeRevision.mm b/src/darwin/Framework/CHIP/ServerEndpoint/MTRDeviceTypeRevision.mm new file mode 100644 index 00000000000000..de5782c92e4aeb --- /dev/null +++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRDeviceTypeRevision.mm @@ -0,0 +1,88 @@ +/** + * 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 "MTRLogging_Internal.h" +#import + +#include +#include +#include + +using namespace chip; + +MTR_DIRECT_MEMBERS +@implementation MTRDeviceTypeRevision + +- (nullable instancetype)initWithDeviceTypeID:(NSNumber *)deviceTypeID revision:(NSNumber *)revision +{ + auto deviceTypeIDValue = deviceTypeID.unsignedLongLongValue; + if (!CanCastTo(deviceTypeIDValue)) { + MTR_LOG_ERROR("MTRDeviceTypeRevision provided too-large device type ID: 0x%llx", deviceTypeIDValue); + return nil; + } + + auto id = static_cast(deviceTypeIDValue); + if (!IsValidDeviceTypeId(id)) { + MTR_LOG_ERROR("MTRDeviceTypeRevision provided invalid device type ID: 0x%" PRIx32, id); + return nil; + } + + auto revisionValue = revision.unsignedLongLongValue; + if (!CanCastTo(revisionValue) || revisionValue < 1) { + MTR_LOG_ERROR("MTRDeviceTypeRevision provided invalid device type revision: 0x%llx", revisionValue); + return nil; + } + + return [self initInternalWithDeviceTypeID:[deviceTypeID copy] revision:[revision copy]]; +} + +// initInternalWithDeviceTypeID:revision assumes that the device type ID and device +// revision have already been validated and, if needed, copied from the input. +- (instancetype)initInternalWithDeviceTypeID:(NSNumber *)deviceTypeID revision:(NSNumber *)revision +{ + if (!(self = [super init])) { + return nil; + } + + _deviceTypeID = deviceTypeID; + _deviceTypeRevision = revision; + return self; +} + +- (id)copyWithZone:(NSZone *)zone +{ + // We have no mutable state. + return self; +} + +- (BOOL)isEqual:(id)object +{ + if ([object class] != [self class]) { + return NO; + } + + MTRDeviceTypeRevision * other = object; + + return [_deviceTypeID isEqual:other.deviceTypeID] && [_deviceTypeRevision isEqual:other.deviceTypeRevision]; +} + +- (NSUInteger)hash +{ + return _deviceTypeID.unsignedLongValue ^ _deviceTypeRevision.unsignedShortValue; +} + +@end diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute.h b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute.h new file mode 100644 index 00000000000000..46e95bfb39a7bd --- /dev/null +++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute.h @@ -0,0 +1,63 @@ +/** + * 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 +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * A representation of an attribute implemented on a server cluster by an + * MTRDeviceController. An attribute has an identifier and a value, and may or + * may not be writable. + */ +MTR_NEWLY_AVAILABLE +@interface MTRServerAttribute : NSObject + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +/** + * Initialize as a readonly attribute. The value is a data-value as documented + * in MTRBaseDevice.h. + * + * Will fail if the attribute ID is not valid per the Matter specification or + * the attribute value is not a valid data-value. + * + * requiredPrivilege is the privilege required to read the attribute. + */ +- (nullable instancetype)initReadonlyAttributeWithID:(NSNumber *)attributeID initialValue:(NSDictionary *)value requiredPrivilege:(MTRAccessControlEntryPrivilege)requiredPrivilege; + +/** + * Change the value of the attribute to a new value. The value is a data-value as documented + * in MTRBaseDevice.h. + * + * Will fail if the attribute is not a valid data-value. + */ +- (BOOL)setValue:(NSDictionary *)value; + +@property (nonatomic, copy, readonly) NSNumber * attributeID; +@property (nonatomic, copy, readonly) NSDictionary * value; +/** + * The privilege level necessary to read this attribute. + */ +@property (nonatomic, assign, readonly) MTRAccessControlEntryPrivilege requiredReadPrivilege; +@property (nonatomic, assign, readonly, getter=isWritable) BOOL writable; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute.mm b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute.mm new file mode 100644 index 00000000000000..0337d555d02f2a --- /dev/null +++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute.mm @@ -0,0 +1,158 @@ +/** + * 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 "MTRBaseDevice_Internal.h" +#import "MTRDefines_Internal.h" +#import "MTRDeviceController_Internal.h" +#import "MTRLogging_Internal.h" +#import "MTRServerAttribute_Internal.h" +#import "MTRServerEndpoint_Internal.h" +#import "NSDataSpanConversion.h" +#import + +#include +#include +#include +#include + +using namespace chip; + +MTR_DIRECT_MEMBERS +@implementation MTRServerAttribute { + MTRDeviceController * __weak _deviceController; +} + +- (nullable instancetype)initAttributeWithID:(NSNumber *)attributeID initialValue:(NSDictionary *)value requiredReadPrivilege:(MTRAccessControlEntryPrivilege)requiredReadPrivilege writable:(BOOL)writable +{ + auto attrIDValue = attributeID.unsignedLongLongValue; + if (!CanCastTo(attrIDValue)) { + MTR_LOG_ERROR("MTRServerAttribute provided too-large attribute ID: 0x%llx", attrIDValue); + return nil; + } + + auto attrId = static_cast(attrIDValue); + if (!IsValidAttributeId(attrId)) { + MTR_LOG_ERROR("MTRServerAttribute provided invalid attribute ID: 0x%" PRIx32, attrId); + return nil; + } + + return [self initWithAttributeID:[attributeID copy] value:[value copy] requiredReadPrivilege:requiredReadPrivilege writable:writable]; +} + +- (nullable instancetype)initReadonlyAttributeWithID:(NSNumber *)attributeID initialValue:(NSDictionary *)value requiredPrivilege:(MTRAccessControlEntryPrivilege)requiredPrivilege +{ + return [self initAttributeWithID:attributeID initialValue:value requiredReadPrivilege:requiredPrivilege writable:NO]; +} + +// initWithAttributeID:value:serializedValue:requiredReadPrivilege:writable: +// assumes that the attribute ID, value, serializedValue, have already been +// validated and, if needed, copied from the input. +- (nullable instancetype)initWithAttributeID:(NSNumber *)attributeID value:(NSDictionary *)value requiredReadPrivilege:(MTRAccessControlEntryPrivilege)requiredReadPrivilege writable:(BOOL)writable +{ + if (!(self = [super init])) { + return nil; + } + + _attributeID = attributeID; + _requiredReadPrivilege = requiredReadPrivilege; + _writable = writable; + _parentCluster = app::ConcreteClusterPath(kInvalidEndpointId, kInvalidClusterId); + + // Now call setValue to store the value and its serialization. + if ([self setValue:value] == NO) { + return nil; + } + + return self; +} + +- (BOOL)setValue:(NSDictionary *)value +{ + id serializedValue; + id dataType = value[MTRTypeKey]; + if ([MTRArrayValueType isEqual:dataType]) { + id dataValue = value[MTRValueKey]; + if (![dataValue isKindOfClass:NSArray.class]) { + MTR_LOG_ERROR("MTRServerAttribute value claims to be a list but isn't: %@", value); + return NO; + } + NSArray * dataValueList = dataValue; + auto * listValue = [NSMutableArray arrayWithCapacity:dataValueList.count]; + if (listValue == nil) { + return NO; + } + for (id item in dataValueList) { + NSError * encodingError; + NSData * encodedItem = MTREncodeTLVFromDataValueDictionary(item, &encodingError); + if (encodedItem == nil) { + return NO; + } + [listValue addObject:encodedItem]; + } + serializedValue = listValue; + } else { + NSError * encodingError; + serializedValue = MTREncodeTLVFromDataValueDictionary(value, &encodingError); + if (serializedValue == nil) { + return NO; + } + } + + // We serialized properly, so should be good to go on the value. + _value = [value copy]; + + MTRDeviceController * deviceController = _deviceController; + if (deviceController == nil) { + // We're not bound to a controller, so safe to directly update _serializedValue. + _serializedValue = serializedValue; + } else { + [deviceController asyncDispatchToMatterQueue:^{ + auto changed = ![self->_serializedValue isEqual:serializedValue]; + self->_serializedValue = serializedValue; + if (changed) { + MatterReportingAttributeChangeCallback(self->_parentCluster.mEndpointId, self->_parentCluster.mClusterId, static_cast(self->_attributeID.unsignedLongLongValue)); + } + } + errorHandler:nil]; + } + + return YES; +} + +- (BOOL)associateWithController:(MTRDeviceController *)controller +{ + MTRDeviceController * existingController = _deviceController; + if (existingController != nil) { +#if MTR_PER_CONTROLLER_STORAGE_ENABLED + MTR_LOG_ERROR("Cannot associate MTRServerAttribute with controller %@; already associated with controller %@", + controller.uniqueIdentifier, existingController.uniqueIdentifier); +#else + MTR_LOG_ERROR("Cannot associate MTRServerAttribute with controller; already associated with a different controller"); +#endif + return NO; + } + + _deviceController = controller; + + return YES; +} + +- (void)invalidate +{ + _deviceController = nil; +} + +@end diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute_Internal.h b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute_Internal.h new file mode 100644 index 00000000000000..dbc7cebb857a88 --- /dev/null +++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAttribute_Internal.h @@ -0,0 +1,48 @@ +/** + * 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 + +#import +#import + +#include + +@interface MTRServerAttribute () + +/** + * Mark this attribute as associated with a particular controller. + */ +- (BOOL)associateWithController:(MTRDeviceController *)controller; + +/** + * Mark this attribute as part of an Defunct-state endpoint. + */ +- (void)invalidate; + +/** + * serializedValue is either an NSData or an NSArray, depending on + * whether the attribute is list-typed. + */ +@property (nonatomic, strong, readonly) id serializedValue; + +/** + * parentCluster will have kInvalidClusterId for the cluster ID until the + * attribute is added to a cluster. + */ +@property (nonatomic, assign) chip::app::ConcreteClusterPath parentCluster; + +@end diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.h b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.h new file mode 100644 index 00000000000000..48604dd2a76797 --- /dev/null +++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.h @@ -0,0 +1,111 @@ +/** + * 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 +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * A representation of a server cluster implemented by an MTRDeviceController. + */ +MTR_NEWLY_AVAILABLE +@interface MTRServerCluster : NSObject + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +/** + * The provided clusterID must not be MTRClusterIDTypeDescriptorID; see + * newDescriptorCluster. + * + * Otherwise, it must be a valid cluster identifier. That means: + * + * * In the range 0-0x7FFF for standard clusters. + * * In the range 0xVVVVFC00-0xVVVVFFFE for vendor-specific clusters, where VVVV + * is the vendor identifier. + * + * The provided revision must be in the range 1-65535. + * + */ +- (nullable instancetype)initWithClusterID:(NSNumber *)clusterID revision:(NSNumber *)revision; + +/** + * Add an access grant to the cluster. If the same access grant is added + * multiple times, it will be treated as if it were added once (and removing + * it once will remove it). + */ +- (void)addAccessGrant:(MTRAccessGrant *)accessGrant; + +/** + * Remove an access grant from the cluster. + */ +- (void)removeAccessGrant:(MTRAccessGrant *)accessGrant; + +/** + * Add an attribute to the cluster. This can only be done before the endpoint + * the cluster is a part of has been added to a controller. + * + * The attribute must not have the same attribute ID as another attribute in + * this cluster. + * + * The attribute must not already be added to another cluster. + * + * If this cluster is the Descriptor cluster (id MTRClusterIDTypeDescriptorID), + * it must not define any values for DeviceTypeList, ServerList, ClientList, PartsList; + * those values will be determined automatically. + * + * For all clusters, the global AttributeList, AcceptedCommandList, + * GeneratedCommandList attributes will be determined automatically and must not + * be included in the attributes added on the cluster. + * + * For all clusters, the FeatureMap attribute will be assumed to be 0 unless + * otherwise specified and may be omitted from the attributes added to the cluster. + * + * For all clusters, ClusterRevision will be determined automatically based on + * this object's clusterRevision property, and must not be explicitly added to + * the cluster. + */ +- (BOOL)addAttribute:(MTRServerAttribute *)attribute; + +/** + * Create a cluster description for the descriptor cluster. This will set + * clusterRevision to the current version implemented by Matter.framework. + */ ++ (MTRServerCluster *)newDescriptorCluster; + +@property (nonatomic, copy, readonly) NSNumber * clusterID; + +@property (nonatomic, copy, readonly) NSNumber * clusterRevision; + +/** + * The list of entities that are allowed to access this cluster instance. This + * list is in addition to any endpoint-wide access grants that exist. + * + * Defaults to empty list, which means no additional access grants. + */ +@property (nonatomic, copy, readonly) NSArray * accessGrants; + +/** + * The list of attributes supported by the cluster. + */ +@property (nonatomic, copy, readonly) NSArray * attributes; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.mm b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.mm new file mode 100644 index 00000000000000..8af4ca975ffb3d --- /dev/null +++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster.mm @@ -0,0 +1,228 @@ +/** + * 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 +#import + +#include +#include +#include +#include + +using namespace chip; + +MTR_DIRECT_MEMBERS +@implementation MTRServerCluster { + /** + * The access grants our API consumer can manipulate directly. Never + * touched on the Matter queue. + */ + NSMutableSet * _accessGrants; + NSMutableArray * _attributes; + MTRDeviceController * __weak _deviceController; +} + +- (nullable instancetype)initWithClusterID:(NSNumber *)clusterID revision:(NSNumber *)revision +{ + auto clusterIDValue = clusterID.unsignedLongLongValue; + if (!CanCastTo(clusterIDValue)) { + MTR_LOG_ERROR("MTRServerCluster provided too-large cluster ID: 0x%llx", clusterIDValue); + return nil; + } + + auto id = static_cast(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:@(app::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; + } + + _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 +{ + 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)addAttribute:(MTRServerAttribute *)attribute +{ + MTRDeviceController * deviceController = _deviceController; + if (deviceController != nil) { + MTR_LOG_ERROR("Cannot add attribute on cluster %llx which is already in use", _clusterID.unsignedLongLongValue); + return NO; + } + + if (attribute.parentCluster.mClusterId != kInvalidClusterId) { + MTR_LOG_ERROR("Cannot add attribute to cluster %llu; already added to cluster %" PRIu32, _clusterID.unsignedLongLongValue, attribute.parentCluster.mClusterId); + 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; + } + } + + [_attributes addObject:attribute]; + attribute.parentCluster = app::ConcreteClusterPath(_parentEndpoint, static_cast(_clusterID.unsignedLongLongValue)); + return YES; +} + +- (BOOL)associateWithController:(MTRDeviceController *)controller +{ + MTRDeviceController * existingController = _deviceController; + if (existingController != nil) { +#if MTR_PER_CONTROLLER_STORAGE_ENABLED + MTR_LOG_ERROR("Cannot associate MTRServerCluster with controller %@; already associated with controller %@", + controller.uniqueIdentifier, existingController.uniqueIdentifier); +#else + MTR_LOG_ERROR("Cannot associate MTRServerCluster with controller; already associated with a different controller"); +#endif + 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]; + _deviceController = controller; + + return YES; +} + +- (void)invalidate +{ + for (MTRServerAttribute * attr in _attributes) { + [attr invalidate]; + } + + _deviceController = nil; +} + +- (NSArray *)accessGrants +{ + return [_accessGrants allObjects]; +} + +- (NSArray *)attributes +{ + return [_attributes copy]; +} + +- (void)setParentEndpoint:(EndpointId)endpoint +{ + _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.parentCluster = app::ConcreteClusterPath(endpoint, static_cast(_clusterID.unsignedLongLongValue)); + } +} + +@end diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster_Internal.h b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster_Internal.h new file mode 100644 index 00000000000000..5bd9ba0692ed4a --- /dev/null +++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerCluster_Internal.h @@ -0,0 +1,47 @@ +/** + * 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 + +#import +#import + +#include + +@interface MTRServerCluster () + +/** + * Mark this cluster as associated with a particular controller. + */ +- (BOOL)associateWithController:(MTRDeviceController *)controller; + +/** + * Mark this cluster as part of an Defunct-state endpoint. + */ +- (void)invalidate; + +/** + * The access grants the Matter stack can observe. Only modified while in + * Initializing state or on the Matter queue. + */ +@property (nonatomic, strong, readonly) NSSet * matterAccessGrants; + +/** + * parentEndpoint will be kInvalidEndpointId until the cluster is added to an endpoint. + */ +@property (nonatomic, assign) chip::EndpointId parentEndpoint; + +@end diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.h b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.h new file mode 100644 index 00000000000000..642dc5fb4ec658 --- /dev/null +++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.h @@ -0,0 +1,87 @@ +/** + * 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 +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * A representation of an endpoint implemented by an MTRDeviceController. + */ +MTR_NEWLY_AVAILABLE +@interface MTRServerEndpoint : NSObject + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +/** + * The provided endpointID must be in the range 1-65535. The list of device + * types provided must be nonempty (but may include vendor-specific device + * types). + */ +- (nullable instancetype)initWithEndpointID:(NSNumber *)endpointID deviceTypes:(NSArray *)deviceTypes; + +/** + * Add an access grant to the endpoint. If the same access grant is added + * multiple times, it will be treated as if it were added once (and removing + * it once will remove it). + */ +- (void)addAccessGrant:(MTRAccessGrant *)accessGrant; + +/** + * Remove an access grant from the endpoint. + */ +- (void)removeAccessGrant:(MTRAccessGrant *)accessGrant; + +/** + * Add a server cluster to the endpoint. This can only be done before the + * endpoint has been added to a controller. + * + * The cluster must not have the same cluster ID as another cluster on + * this endpoint. + * + * The cluster must not already be added to another endpoint. + */ +- (BOOL)addServerCluster:(MTRServerCluster *)serverCluster; + +@property (nonatomic, copy, readonly) NSNumber * endpointID; + +@property (nonatomic, copy, readonly) NSArray * deviceTypes; + +/** + * The list of entities that are allowed to access all clusters on this + * endpoint. If more fine-grained access control is desired, access grants + * should be defined on individual clusters. + * + * Defaults to empty list, which means no access granted. + */ +@property (nonatomic, copy, readonly) NSArray * accessGrants; + +/** + * A list of server clusters supported on this endpoint. The Descriptor cluster + * does not need to be included unless a TagList attribute is desired on it or + * unless it has a non-empty PartsList. If not included, the Descriptor cluster + * will be generated automatically. + */ +@property (nonatomic, copy, readonly) NSArray * serverClusters; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.mm b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.mm new file mode 100644 index 00000000000000..ee35e4da0bc0db --- /dev/null +++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint.mm @@ -0,0 +1,197 @@ +/** + * 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 + +#include +#include +#include + +using namespace chip; + +MTR_DIRECT_MEMBERS +@implementation MTRServerEndpoint { + /** + * The access grants our API consumer can manipulate directly. Never + * touched on the Matter queue. + */ + NSMutableSet * _accessGrants; + NSMutableArray * _serverClusters; + MTRDeviceController * __weak _deviceController; +} + +- (nullable instancetype)initWithEndpointID:(NSNumber *)endpointID deviceTypes:(NSArray *)deviceTypes +{ + auto endpointIDValue = endpointID.unsignedLongLongValue; + if (!CanCastTo(endpointIDValue)) { + MTR_LOG_ERROR("MTRServerEndpoint provided too-large endpoint ID: 0x%llx", endpointIDValue); + return nil; + } + + auto id = static_cast(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:@[]]; +} + +- (instancetype)initInternalWithEndpointID:(NSNumber *)endpointID deviceTypes:(NSArray *)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; + } + + if (serverCluster.parentEndpoint != kInvalidEndpointId) { + MTR_LOG_ERROR("Cannot add cluster to endpoint %llu; already added to endpoint %" PRIu32, _endpointID.unsignedLongLongValue, serverCluster.parentEndpoint); + return NO; + } + + for (MTRServerCluster * existingCluster in _serverClusters) { + if ([existingCluster.clusterID isEqual:serverCluster.clusterID]) { + MTR_LOG_ERROR("Cannot add second cluster with ID %llx on endpoint %llu", serverCluster.clusterID.unsignedLongLongValue, _endpointID.unsignedLongLongValue); + return NO; + } + } + + [_serverClusters addObject:serverCluster]; + serverCluster.parentEndpoint = static_cast(_endpointID.unsignedLongLongValue); + return YES; +} + +- (BOOL)associateWithController:(MTRDeviceController *)controller +{ + MTRDeviceController * existingController = _deviceController; + if (existingController != nil) { +#if MTR_PER_CONTROLLER_STORAGE_ENABLED + MTR_LOG_ERROR("Cannot associate MTRServerEndpoint with controller %@; already associated with controller %@", + controller.uniqueIdentifier, existingController.uniqueIdentifier); +#else + MTR_LOG_ERROR("Cannot associate MTRServerEndpoint with controller; already associated with a different controller"); +#endif + return NO; + } + + 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]; + _deviceController = controller; + + return YES; +} + +- (void)invalidate +{ + for (MTRServerCluster * cluster in _serverClusters) { + [cluster invalidate]; + } + + _deviceController = nil; +} + +- (NSArray *)accessGrants +{ + return [_accessGrants allObjects]; +} + +- (NSArray *)serverClusters +{ + return [_serverClusters copy]; +} + +@end diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint_Internal.h b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint_Internal.h new file mode 100644 index 00000000000000..97725c2b1afad5 --- /dev/null +++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerEndpoint_Internal.h @@ -0,0 +1,39 @@ +/** + * 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 +#import +#import + +@interface MTRServerEndpoint () + +/** + * Mark this endpoint as associated with a particular controller. + */ +- (BOOL)associateWithController:(MTRDeviceController *)controller; + +/** + * Mark this endpoint as being in a Defunct state. + */ +- (void)invalidate; + +/** + * The access grants the Matter stack can observe. Only modified while in + * Initializing state or on the Matter queue. + */ +@property (nonatomic, strong, readonly) NSSet * matterAccessGrants; + +@end diff --git a/src/darwin/Framework/CHIPTests/MTRServerEndpointTests.m b/src/darwin/Framework/CHIPTests/MTRServerEndpointTests.m new file mode 100644 index 00000000000000..57b47f8eb540b7 --- /dev/null +++ b/src/darwin/Framework/CHIPTests/MTRServerEndpointTests.m @@ -0,0 +1,492 @@ +/** + * 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 +#import + +@interface MTRServerEndpointTests : XCTestCase + +@end + +@implementation MTRServerEndpointTests + +- (void)testAccessGrant +{ + // Try to create an access grant with an invalid node ID + { + __auto_type * grant = [MTRAccessGrant accessGrantForNodeID:@(0) privilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertNil(grant); + } + + // Try to create an access grant with a group-range node ID + { + __auto_type * grant = [MTRAccessGrant accessGrantForNodeID:@(0xFFFFFFFFFFFF0001) privilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertNil(grant); + } + + // Try to create an access grant with a local node ID + { + __auto_type * grant = [MTRAccessGrant accessGrantForNodeID:@(0xFFFFFFFE00020002) privilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertNil(grant); + } + + // Try to create an access grant with a CAT-range node ID + { + __auto_type * grant = [MTRAccessGrant accessGrantForNodeID:@(0xFFFFFFFD00020002) privilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertNil(grant); + } + + // Try to create an access grant with an operational node ID + { + NSNumber * nodeID = @(2); + __auto_type * grant = [MTRAccessGrant accessGrantForNodeID:nodeID privilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertNotNil(grant); + XCTAssertEqualObjects(grant.subjectID, nodeID); + XCTAssertEqual(grant.grantedPrivilege, MTRAccessControlEntryPrivilegeView); + XCTAssertEqual(grant.authenticationMode, MTRAccessControlEntryAuthModeCASE); + XCTAssertEqualObjects([grant description], @""); + } + + // Try different privileges + { + NSNumber * nodeID = @(2); + __auto_type * grant = [MTRAccessGrant accessGrantForNodeID:nodeID privilege:MTRAccessControlEntryPrivilegeAdminister]; + XCTAssertNotNil(grant); + XCTAssertEqualObjects(grant.subjectID, nodeID); + XCTAssertEqual(grant.grantedPrivilege, MTRAccessControlEntryPrivilegeAdminister); + XCTAssertEqual(grant.authenticationMode, MTRAccessControlEntryAuthModeCASE); + XCTAssertEqualObjects([grant description], @""); + } + + // Try a CAT + { + __auto_type * grant = [MTRAccessGrant accessGrantForCASEAuthenticatedTag:@(0x00020003) privilege:MTRAccessControlEntryPrivilegeManage]; + XCTAssertNotNil(grant); + XCTAssertEqualObjects(grant.subjectID, @(0xFFFFFFFD00020003)); + XCTAssertEqual(grant.grantedPrivilege, MTRAccessControlEntryPrivilegeManage); + XCTAssertEqual(grant.authenticationMode, MTRAccessControlEntryAuthModeCASE); + XCTAssertEqualObjects([grant description], @""); + } + + // Try some invalid CATs + { + __auto_type * grant = [MTRAccessGrant accessGrantForCASEAuthenticatedTag:@(0x100000000) privilege:MTRAccessControlEntryPrivilegeManage]; + XCTAssertNil(grant); + } + + { + __auto_type * grant = [MTRAccessGrant accessGrantForCASEAuthenticatedTag:@(0x00020000) privilege:MTRAccessControlEntryPrivilegeManage]; + XCTAssertNil(grant); + } + + // Try a group ID + { + __auto_type * grant = [MTRAccessGrant accessGrantForGroupID:@(0x0005) privilege:MTRAccessControlEntryPrivilegeOperate]; + XCTAssertNotNil(grant); + XCTAssertEqualObjects(grant.subjectID, @(0xFFFFFFFFFFFF0005)); + XCTAssertEqual(grant.grantedPrivilege, MTRAccessControlEntryPrivilegeOperate); + XCTAssertEqual(grant.authenticationMode, MTRAccessControlEntryAuthModeGroup); + XCTAssertEqualObjects([grant description], @""); + } + + // Try an invalid group ID + { + __auto_type * grant = [MTRAccessGrant accessGrantForGroupID:@(0) privilege:MTRAccessControlEntryPrivilegeOperate]; + XCTAssertNil(grant); + } + + // Try a wildcard subject. + { + __auto_type * grant = [MTRAccessGrant accessGrantForAllNodesWithPrivilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertNotNil(grant); + XCTAssertNil(grant.subjectID); + XCTAssertEqual(grant.grantedPrivilege, MTRAccessControlEntryPrivilegeView); + XCTAssertEqual(grant.authenticationMode, MTRAccessControlEntryAuthModeCASE); + XCTAssertEqualObjects([grant description], @""); + } +} + +- (void)testServerAttribute +{ + __auto_type * unsignedIntValue = @{ + MTRTypeKey : MTRUnsignedIntegerValueType, + MTRValueKey : @(5), + }; + + __auto_type * listOfStringsValue = @{ + MTRTypeKey : MTRArrayValueType, + MTRValueKey : @[ + @{ + MTRTypeKey : MTRUTF8StringValueType, + MTRValueKey : @"str1", + }, + @{ + MTRTypeKey : MTRUTF8StringValueType, + MTRValueKey : @"str2", + }, + ], + }; + + // Basic readonly attribute. + { + __auto_type * attr = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:@(0) initialValue:unsignedIntValue requiredPrivilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertNotNil(attr); + XCTAssertEqualObjects(attr.attributeID, @(0)); + XCTAssertEqualObjects(attr.value, unsignedIntValue); + XCTAssertEqual(attr.writable, NO); + } + + // list-typed readonly attribute. + { + __auto_type * attr = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:@(5) initialValue:listOfStringsValue requiredPrivilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertNotNil(attr); + XCTAssertEqualObjects(attr.attributeID, @(5)); + XCTAssertEqualObjects(attr.value, listOfStringsValue); + XCTAssertEqual(attr.writable, NO); + } + + // Vendor-specific attribute. + { + NSNumber * vendorSpecificID = @(0xFFF10004); + __auto_type * attr = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:vendorSpecificID initialValue:unsignedIntValue requiredPrivilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertNotNil(attr); + XCTAssertEqualObjects(attr.attributeID, vendorSpecificID); + XCTAssertEqualObjects(attr.value, unsignedIntValue); + XCTAssertEqual(attr.writable, NO); + } + + // Invalid attribute ID + { + NSNumber * invalidID = @(0x00005000); + __auto_type * attr = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:invalidID initialValue:unsignedIntValue requiredPrivilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertNil(attr); + } + + // Invalid "global" attribute ID + { + NSNumber * invalidID = @(0x0000FFFF); + __auto_type * attr = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:invalidID initialValue:unsignedIntValue requiredPrivilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertNil(attr); + } + + // Invalid vendor prefix on attribute ID + { + NSNumber * invalidID = @(0xFFFF0000); + __auto_type * attr = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:invalidID initialValue:unsignedIntValue requiredPrivilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertNil(attr); + } +} + +- (void)testDeviceType +{ + // Invalid device type ID + { + __auto_type * deviceType = [[MTRDeviceTypeRevision alloc] initWithDeviceTypeID:@(0xC000) revision:@(1)]; + XCTAssertNil(deviceType); + } + + // Another invalid device type ID + { + __auto_type * deviceType = [[MTRDeviceTypeRevision alloc] initWithDeviceTypeID:@(0xFFFF1234) revision:@(1)]; + XCTAssertNil(deviceType); + } + + // Another invalid device type ID + { + __auto_type * deviceType = [[MTRDeviceTypeRevision alloc] initWithDeviceTypeID:@(0x100000000) revision:@(1)]; + XCTAssertNil(deviceType); + } + + // Invalid device type revision + { + __auto_type * deviceType = [[MTRDeviceTypeRevision alloc] initWithDeviceTypeID:@(0x1234) revision:@(0)]; + XCTAssertNil(deviceType); + } + + // Another invalid device type revision + { + __auto_type * deviceType = [[MTRDeviceTypeRevision alloc] initWithDeviceTypeID:@(0x1234) revision:@(0x10000)]; + XCTAssertNil(deviceType); + } + + // Valid device type + { + NSNumber * deviceTypeID = @(0x1234); + NSNumber * deviceTypeRevision = @(1); + __auto_type * deviceType = [[MTRDeviceTypeRevision alloc] initWithDeviceTypeID:deviceTypeID revision:deviceTypeRevision]; + XCTAssertNotNil(deviceType); + XCTAssertEqualObjects(deviceType.deviceTypeID, deviceTypeID); + XCTAssertEqualObjects(deviceType.deviceTypeRevision, deviceTypeRevision); + } +} + +- (void)testClusterDescription +{ + // Standard cluster. + { + NSNumber * clusterID = @(6); + NSNumber * clusterRevision = @(1); + __auto_type * cluster = [[MTRServerCluster alloc] initWithClusterID:clusterID revision:clusterRevision]; + XCTAssertNotNil(cluster); + XCTAssertEqualObjects(cluster.clusterID, clusterID); + XCTAssertEqualObjects(cluster.clusterRevision, clusterRevision); + XCTAssertEqualObjects(cluster.accessGrants, @[]); + XCTAssertEqualObjects(cluster.attributes, @[]); + } + + // Vendor-specific cluster. + { + NSNumber * clusterID = @(0xFFF1FC01); + NSNumber * clusterRevision = @(1); + __auto_type * cluster = [[MTRServerCluster alloc] initWithClusterID:clusterID revision:clusterRevision]; + XCTAssertNotNil(cluster); + XCTAssertEqualObjects(cluster.clusterID, clusterID); + XCTAssertEqualObjects(cluster.clusterRevision, clusterRevision); + XCTAssertEqualObjects(cluster.accessGrants, @[]); + XCTAssertEqualObjects(cluster.attributes, @[]); + } + + // Invalid "standard" cluster. + { + NSNumber * clusterID = @(0x8000); + NSNumber * clusterRevision = @(1); + __auto_type * cluster = [[MTRServerCluster alloc] initWithClusterID:clusterID revision:clusterRevision]; + XCTAssertNil(cluster); + } + + // Invalid vendor-specific cluster. + { + NSNumber * clusterID = @(0xFFF10002); + NSNumber * clusterRevision = @(1); + __auto_type * cluster = [[MTRServerCluster alloc] initWithClusterID:clusterID revision:clusterRevision]; + XCTAssertNil(cluster); + } + + // Cluster ID out of range. + { + NSNumber * clusterID = @(0x100000000); + NSNumber * clusterRevision = @(1); + __auto_type * cluster = [[MTRServerCluster alloc] initWithClusterID:clusterID revision:clusterRevision]; + XCTAssertNil(cluster); + } + + // Revision too small. + { + NSNumber * clusterID = @(6); + NSNumber * clusterRevision = @(0); + __auto_type * cluster = [[MTRServerCluster alloc] initWithClusterID:clusterID revision:clusterRevision]; + XCTAssertNil(cluster); + } + + // Revision too big. + { + NSNumber * clusterID = @(6); + NSNumber * clusterRevision = @(0x10000); + __auto_type * cluster = [[MTRServerCluster alloc] initWithClusterID:clusterID revision:clusterRevision]; + XCTAssertNil(cluster); + } + + // Descriptor cluster wrong method. + { + NSNumber * clusterID = @(0x001D); + NSNumber * clusterRevision = @(0x10000); + __auto_type * cluster = [[MTRServerCluster alloc] initWithClusterID:clusterID revision:clusterRevision]; + XCTAssertNil(cluster); + } + + __auto_type * emptyListValue = @{ + MTRTypeKey : MTRArrayValueType, + MTRValueKey : @[], + }; + + __auto_type * unsignedIntValue = @{ + MTRTypeKey : MTRUnsignedIntegerValueType, + MTRValueKey : @(2), + }; + + // Descriptor cluster right method + { + NSNumber * clusterID = @(0x001D); + __auto_type * cluster = [MTRServerCluster newDescriptorCluster]; + XCTAssertNotNil(cluster); + XCTAssertEqualObjects(cluster.clusterID, clusterID); + // Don't hardcode the cluster revision here; we want it to be able to + // change without updating this test. + XCTAssertEqualObjects(cluster.accessGrants, @[]); + XCTAssertEqualObjects(cluster.attributes, @[]); + + // Adding descriptor's list attributes should fail. + __auto_type * deviceTypeListAttribute = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:@(0) initialValue:emptyListValue requiredPrivilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertFalse([cluster addAttribute:deviceTypeListAttribute]); + + __auto_type * serverListAttribute = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:@(1) initialValue:emptyListValue requiredPrivilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertFalse([cluster addAttribute:serverListAttribute]); + + __auto_type * clientListAttribute = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:@(2) initialValue:emptyListValue requiredPrivilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertFalse([cluster addAttribute:clientListAttribute]); + + __auto_type * partsListAttribute = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:@(3) initialValue:emptyListValue requiredPrivilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertFalse([cluster addAttribute:partsListAttribute]); + + // Adding global attributes should fail. + __auto_type * attributeListAttribute = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:@(0xFFFB) initialValue:emptyListValue requiredPrivilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertFalse([cluster addAttribute:attributeListAttribute]); + + __auto_type * acceptedCommandListAttribute = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:@(0xFFF9) initialValue:emptyListValue requiredPrivilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertFalse([cluster addAttribute:acceptedCommandListAttribute]); + + __auto_type * generatedCommandListAttribute = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:@(0xFFF8) initialValue:emptyListValue requiredPrivilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertFalse([cluster addAttribute:generatedCommandListAttribute]); + + __auto_type * clusterRevisionAttribute = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:@(0xFFFD) initialValue:unsignedIntValue requiredPrivilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertFalse([cluster addAttribute:clusterRevisionAttribute]); + + // Adding random attributes should succeed. + __auto_type * randomListAttribute = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:@(0xFFF10000) initialValue:emptyListValue requiredPrivilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertTrue([cluster addAttribute:randomListAttribute]); + } + + // Adding some attributes + { + NSNumber * clusterID = @(0xFFF1FC01); + NSNumber * clusterRevision = @(1); + __auto_type * cluster = [[MTRServerCluster alloc] initWithClusterID:clusterID revision:clusterRevision]; + XCTAssertNotNil(cluster); + XCTAssertEqualObjects(cluster.accessGrants, @[]); + XCTAssertEqualObjects(cluster.attributes, @[]); + + __auto_type * grants = @[ + [MTRAccessGrant accessGrantForAllNodesWithPrivilege:MTRAccessControlEntryPrivilegeManage], + [MTRAccessGrant accessGrantForNodeID:@(1) privilege:MTRAccessControlEntryPrivilegeView], + ]; + for (MTRAccessGrant * grant in grants) { + [cluster addAccessGrant:grant]; + } + XCTAssertEqualObjects(cluster.accessGrants, grants); + + __auto_type * signedIntValue = @{ + MTRTypeKey : MTRSignedIntegerValueType, + MTRValueKey : @(5), + }; + + __auto_type * attributes = @[ [[MTRServerAttribute alloc] initReadonlyAttributeWithID:@(0) initialValue:signedIntValue requiredPrivilege:MTRAccessControlEntryPrivilegeView] ]; + XCTAssertNotNil(attributes); + for (MTRServerAttribute * attribute in attributes) { + XCTAssertTrue([cluster addAttribute:attribute]); + } + XCTAssertEqualObjects(cluster.attributes, attributes); + + __auto_type * otherCluster = [[MTRServerCluster alloc] initWithClusterID:clusterID revision:clusterRevision]; + + // Adding an already-added attribute should fail. + XCTAssertFalse([otherCluster addAttribute:attributes[0]]); + + MTRServerAttribute * otherAttribute = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:@(0) initialValue:signedIntValue requiredPrivilege:MTRAccessControlEntryPrivilegeView]; + + // Adding same-id attribute should fail. + XCTAssertFalse([cluster addAttribute:otherAttribute]); + + // Adding the same-id attribute to a different cluster should work. + XCTAssertTrue([otherCluster addAttribute:otherAttribute]); + + // Adding global attributes should fail. + __auto_type * attributeListAttribute = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:@(0xFFFB) initialValue:emptyListValue requiredPrivilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertFalse([cluster addAttribute:attributeListAttribute]); + + __auto_type * acceptedCommandListAttribute = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:@(0xFFF9) initialValue:emptyListValue requiredPrivilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertFalse([cluster addAttribute:acceptedCommandListAttribute]); + + __auto_type * generatedCommandListAttribute = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:@(0xFFF8) initialValue:emptyListValue requiredPrivilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertFalse([cluster addAttribute:generatedCommandListAttribute]); + + __auto_type * clusterRevisionAttribute = [[MTRServerAttribute alloc] initReadonlyAttributeWithID:@(0xFFFD) initialValue:unsignedIntValue requiredPrivilege:MTRAccessControlEntryPrivilegeView]; + XCTAssertFalse([cluster addAttribute:clusterRevisionAttribute]); + } +} + +- (void)testEndpointDescription +{ + NSArray * deviceTypes; + { + deviceTypes = @[ [[MTRDeviceTypeRevision alloc] initWithDeviceTypeID:@(0xFFF11234) revision:@(2)] ]; + XCTAssertNotNil(deviceTypes); + } + + // Invalid endpoint ID. + { + NSNumber * endpointID = @(0); + __auto_type * endpoint = [[MTRServerEndpoint alloc] initWithEndpointID:endpointID deviceTypes:deviceTypes]; + XCTAssertNil(endpoint); + } + + // Too-large endpoint ID. + { + NSNumber * endpointID = @(0x10000); + __auto_type * endpoint = [[MTRServerEndpoint alloc] initWithEndpointID:endpointID deviceTypes:deviceTypes]; + XCTAssertNil(endpoint); + } + + // Invalid device type list. + { + NSNumber * endpointID = @(1); + __auto_type * endpoint = [[MTRServerEndpoint alloc] initWithEndpointID:endpointID deviceTypes:@[]]; + XCTAssertNil(endpoint); + } + + // Valid endpoint definition. + { + NSNumber * endpointID = @(1); + __auto_type * endpoint = [[MTRServerEndpoint alloc] initWithEndpointID:endpointID deviceTypes:deviceTypes]; + XCTAssertNotNil(endpoint); + + XCTAssertEqualObjects(endpoint.endpointID, endpointID); + XCTAssertEqualObjects(endpoint.deviceTypes, deviceTypes); + XCTAssertEqualObjects(endpoint.accessGrants, @[]); + XCTAssertEqualObjects(endpoint.serverClusters, @[]); + + __auto_type * grants = @[ + [MTRAccessGrant accessGrantForAllNodesWithPrivilege:MTRAccessControlEntryPrivilegeManage], + [MTRAccessGrant accessGrantForGroupID:@(1) privilege:MTRAccessControlEntryPrivilegeAdminister], + ]; + for (MTRAccessGrant * grant in grants) { + [endpoint addAccessGrant:grant]; + } + XCTAssertEqualObjects(endpoint.accessGrants, grants); + + __auto_type * clusters = @[ + [[MTRServerCluster alloc] initWithClusterID:@(6) revision:@(1)], + ]; + for (MTRServerCluster * cluster in clusters) { + XCTAssertTrue([endpoint addServerCluster:cluster]); + } + XCTAssertEqualObjects(endpoint.serverClusters, clusters); + + __auto_type * otherEndpoint = [[MTRServerEndpoint alloc] initWithEndpointID:endpointID deviceTypes:deviceTypes]; + + // Adding an already-added cluster should fail. + XCTAssertFalse([otherEndpoint addServerCluster:clusters[0]]); + + MTRServerCluster * otherCluster = [[MTRServerCluster alloc] initWithClusterID:@(6) revision:@(1)]; + + // Adding same-id cluster should fail. + XCTAssertFalse([endpoint addServerCluster:otherCluster]); + + // Adding the same-id cluster to a different endpoint should work. + XCTAssertTrue([otherEndpoint addServerCluster:otherCluster]); + } +} + +@end diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index ea1b9b8bf338c4..65da008e6d9f83 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -150,6 +150,9 @@ 5143851E2A65885500EDC8E6 /* MTRSwiftPairingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5143851D2A65885500EDC8E6 /* MTRSwiftPairingTests.swift */; }; 514654492A72F9DF00904E61 /* MTRDemuxingStorage.mm in Sources */ = {isa = PBXBuildFile; fileRef = 514654482A72F9DF00904E61 /* MTRDemuxingStorage.mm */; }; 5146544B2A72F9F500904E61 /* MTRDemuxingStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = 5146544A2A72F9F500904E61 /* MTRDemuxingStorage.h */; }; + 514C7A012B64223400DD6D7B /* MTRServerAttribute_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 514C79FF2B64223400DD6D7B /* MTRServerAttribute_Internal.h */; }; + 514C7A022B64223400DD6D7B /* MTRServerEndpoint_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 514C7A002B64223400DD6D7B /* MTRServerEndpoint_Internal.h */; }; + 514C7A042B6436D500DD6D7B /* MTRServerCluster_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 514C7A032B6436D500DD6D7B /* MTRServerCluster_Internal.h */; }; 51565CAE2A79D42100469F18 /* MTRConversion.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51565CAD2A79D42100469F18 /* MTRConversion.mm */; }; 51565CB12A7AD77600469F18 /* MTRDeviceControllerDataStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 51565CAF2A7AD77600469F18 /* MTRDeviceControllerDataStore.h */; }; 51565CB22A7AD77600469F18 /* MTRDeviceControllerDataStore.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51565CB02A7AD77600469F18 /* MTRDeviceControllerDataStore.mm */; }; @@ -182,6 +185,17 @@ 51C984622A61CE2A00B0AD9A /* MTRFabricInfoChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = 51C984602A61CE2A00B0AD9A /* MTRFabricInfoChecker.m */; }; 51CFDDB12AC5F78F00DA7CA5 /* EmptyDataModelHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51CFDDB02AC5F78F00DA7CA5 /* EmptyDataModelHandler.cpp */; }; 51CFDDB22AC5F78F00DA7CA5 /* EmptyDataModelHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51CFDDB02AC5F78F00DA7CA5 /* EmptyDataModelHandler.cpp */; }; + 51D0B1272B617246006E3511 /* MTRServerEndpoint.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51D0B1252B617246006E3511 /* MTRServerEndpoint.mm */; }; + 51D0B1282B617246006E3511 /* MTRServerEndpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 51D0B1262B617246006E3511 /* MTRServerEndpoint.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 51D0B12A2B61766F006E3511 /* MTRServerEndpointTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 51D0B1292B61766F006E3511 /* MTRServerEndpointTests.m */; }; + 51D0B12E2B6177FD006E3511 /* MTRAccessGrant.h in Headers */ = {isa = PBXBuildFile; fileRef = 51D0B12C2B6177D9006E3511 /* MTRAccessGrant.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 51D0B12F2B617800006E3511 /* MTRAccessGrant.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51D0B12B2B6177D9006E3511 /* MTRAccessGrant.mm */; }; + 51D0B1382B618CC6006E3511 /* MTRServerAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = 51D0B1362B618CC6006E3511 /* MTRServerAttribute.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 51D0B1392B618CC6006E3511 /* MTRServerAttribute.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51D0B1372B618CC6006E3511 /* MTRServerAttribute.mm */; }; + 51D0B13C2B61B2F2006E3511 /* MTRDeviceTypeRevision.h in Headers */ = {isa = PBXBuildFile; fileRef = 51D0B13A2B61B2F2006E3511 /* MTRDeviceTypeRevision.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 51D0B13D2B61B2F2006E3511 /* MTRDeviceTypeRevision.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51D0B13B2B61B2F2006E3511 /* MTRDeviceTypeRevision.mm */; }; + 51D0B1402B61B3A4006E3511 /* MTRServerCluster.h in Headers */ = {isa = PBXBuildFile; fileRef = 51D0B13E2B61B3A3006E3511 /* MTRServerCluster.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 51D0B1412B61B3A4006E3511 /* MTRServerCluster.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51D0B13F2B61B3A3006E3511 /* MTRServerCluster.mm */; }; 51D10D2E2808E2CA00E8CA3D /* MTRTestStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 51D10D2D2808E2CA00E8CA3D /* MTRTestStorage.m */; }; 51E0FC102ACBBF230001E197 /* MTRSwiftDeviceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E0FC0F2ACBBF230001E197 /* MTRSwiftDeviceTests.swift */; }; 51E24E73274E0DAC007CCF6E /* MTRErrorTestUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51E24E72274E0DAC007CCF6E /* MTRErrorTestUtils.mm */; }; @@ -305,10 +319,10 @@ B4E2621B2AA0D02000DBA5BC /* SleepCommand.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4E262192AA0D01D00DBA5BC /* SleepCommand.mm */; }; B4E2621E2AA0D02D00DBA5BC /* WaitForCommissioneeCommand.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4E2621C2AA0D02A00DBA5BC /* WaitForCommissioneeCommand.mm */; }; B4FCD56A2B5EDBD300832859 /* MTRDiagnosticLogsType.h in Headers */ = {isa = PBXBuildFile; fileRef = B4FCD5692B5EDBD300832859 /* MTRDiagnosticLogsType.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B4FCD5732B611EB300832859 /* MTRDiagnosticLogsDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = B4C8E6B32B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.h */; }; B4FCD5702B603A6300832859 /* Commands.h in Headers */ = {isa = PBXBuildFile; fileRef = B4FCD56D2B603A6300832859 /* Commands.h */; }; B4FCD5712B603A6300832859 /* DownloadLogCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = B4FCD56E2B603A6300832859 /* DownloadLogCommand.h */; }; B4FCD5722B603A6300832859 /* DownloadLogCommand.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4FCD56F2B603A6300832859 /* DownloadLogCommand.mm */; }; + B4FCD5732B611EB300832859 /* MTRDiagnosticLogsDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = B4C8E6B32B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.h */; }; BA09EB43247477BA00605257 /* libCHIP.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BA09EB3F2474762900605257 /* libCHIP.a */; }; D4772A46285AE98400383630 /* MTRClusterConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = D4772A45285AE98300383630 /* MTRClusterConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; /* End PBXBuildFile section */ @@ -515,6 +529,9 @@ 5143851D2A65885500EDC8E6 /* MTRSwiftPairingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MTRSwiftPairingTests.swift; sourceTree = ""; }; 514654482A72F9DF00904E61 /* MTRDemuxingStorage.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDemuxingStorage.mm; sourceTree = ""; }; 5146544A2A72F9F500904E61 /* MTRDemuxingStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDemuxingStorage.h; sourceTree = ""; }; + 514C79FF2B64223400DD6D7B /* MTRServerAttribute_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRServerAttribute_Internal.h; sourceTree = ""; }; + 514C7A002B64223400DD6D7B /* MTRServerEndpoint_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRServerEndpoint_Internal.h; sourceTree = ""; }; + 514C7A032B6436D500DD6D7B /* MTRServerCluster_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRServerCluster_Internal.h; sourceTree = ""; }; 51565CAD2A79D42100469F18 /* MTRConversion.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRConversion.mm; sourceTree = ""; }; 51565CAF2A7AD77600469F18 /* MTRDeviceControllerDataStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDeviceControllerDataStore.h; sourceTree = ""; }; 51565CB02A7AD77600469F18 /* MTRDeviceControllerDataStore.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDeviceControllerDataStore.mm; sourceTree = ""; }; @@ -553,6 +570,17 @@ 51C984602A61CE2A00B0AD9A /* MTRFabricInfoChecker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRFabricInfoChecker.m; sourceTree = ""; }; 51C984612A61CE2A00B0AD9A /* MTRFabricInfoChecker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRFabricInfoChecker.h; sourceTree = ""; }; 51CFDDB02AC5F78F00DA7CA5 /* EmptyDataModelHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = EmptyDataModelHandler.cpp; path = ../controller/EmptyDataModelHandler.cpp; sourceTree = ""; }; + 51D0B1252B617246006E3511 /* MTRServerEndpoint.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRServerEndpoint.mm; sourceTree = ""; }; + 51D0B1262B617246006E3511 /* MTRServerEndpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRServerEndpoint.h; sourceTree = ""; }; + 51D0B1292B61766F006E3511 /* MTRServerEndpointTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRServerEndpointTests.m; sourceTree = ""; }; + 51D0B12B2B6177D9006E3511 /* MTRAccessGrant.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRAccessGrant.mm; sourceTree = ""; }; + 51D0B12C2B6177D9006E3511 /* MTRAccessGrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRAccessGrant.h; sourceTree = ""; }; + 51D0B1362B618CC6006E3511 /* MTRServerAttribute.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRServerAttribute.h; sourceTree = ""; }; + 51D0B1372B618CC6006E3511 /* MTRServerAttribute.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRServerAttribute.mm; sourceTree = ""; }; + 51D0B13A2B61B2F2006E3511 /* MTRDeviceTypeRevision.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDeviceTypeRevision.h; sourceTree = ""; }; + 51D0B13B2B61B2F2006E3511 /* MTRDeviceTypeRevision.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDeviceTypeRevision.mm; sourceTree = ""; }; + 51D0B13E2B61B3A3006E3511 /* MTRServerCluster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRServerCluster.h; sourceTree = ""; }; + 51D0B13F2B61B3A3006E3511 /* MTRServerCluster.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRServerCluster.mm; sourceTree = ""; }; 51D10D2D2808E2CA00E8CA3D /* MTRTestStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRTestStorage.m; sourceTree = ""; }; 51E0FC0F2ACBBF230001E197 /* MTRSwiftDeviceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MTRSwiftDeviceTests.swift; sourceTree = ""; }; 51E24E72274E0DAC007CCF6E /* MTRErrorTestUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRErrorTestUtils.mm; sourceTree = ""; }; @@ -1092,6 +1120,26 @@ path = TestHelpers; sourceTree = ""; }; + 51D0B1312B618C4F006E3511 /* ServerEndpoint */ = { + isa = PBXGroup; + children = ( + 51D0B12C2B6177D9006E3511 /* MTRAccessGrant.h */, + 51D0B12B2B6177D9006E3511 /* MTRAccessGrant.mm */, + 51D0B1362B618CC6006E3511 /* MTRServerAttribute.h */, + 51D0B1372B618CC6006E3511 /* MTRServerAttribute.mm */, + 514C79FF2B64223400DD6D7B /* MTRServerAttribute_Internal.h */, + 51D0B13A2B61B2F2006E3511 /* MTRDeviceTypeRevision.h */, + 51D0B13B2B61B2F2006E3511 /* MTRDeviceTypeRevision.mm */, + 51D0B1262B617246006E3511 /* MTRServerEndpoint.h */, + 51D0B1252B617246006E3511 /* MTRServerEndpoint.mm */, + 514C7A002B64223400DD6D7B /* MTRServerEndpoint_Internal.h */, + 51D0B13E2B61B3A3006E3511 /* MTRServerCluster.h */, + 51D0B13F2B61B3A3006E3511 /* MTRServerCluster.mm */, + 514C7A032B6436D500DD6D7B /* MTRServerCluster_Internal.h */, + ); + path = ServerEndpoint; + sourceTree = ""; + }; B20252832459E34F00F97062 = { isa = PBXGroup; children = ( @@ -1130,6 +1178,7 @@ 1EC4CE5825CC26AB00D7304F /* zap-generated */, B20252912459E34F00F97062 /* Info.plist */, B2E0D7A8245B0B5C003C5B48 /* Matter.h */, + 51D0B1312B618C4F006E3511 /* ServerEndpoint */, 7596A84628762783004DAE0E /* MTRAsyncCallbackWorkQueue.h */, 7596A84728762783004DAE0E /* MTRAsyncCallbackWorkQueue.mm */, 3DA1A3522ABAB3B4004F0BB9 /* MTRAsyncWorkQueue.h */, @@ -1285,6 +1334,7 @@ 51E0FC0F2ACBBF230001E197 /* MTRSwiftDeviceTests.swift */, 51E95DF72A78110900A434F0 /* MTRPerControllerStorageTests.m */, 518D3F842AA14006008E0007 /* MTRControllerAdvertisingTests.m */, + 51D0B1292B61766F006E3511 /* MTRServerEndpointTests.m */, B202529D2459E34F00F97062 /* Info.plist */, 5143851C2A65885400EDC8E6 /* MatterTests-Bridging-Header.h */, ); @@ -1428,6 +1478,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 51D0B1282B617246006E3511 /* MTRServerEndpoint.h in Headers */, 51565CB62A7B0D6600469F18 /* MTRDeviceControllerParameters.h in Headers */, 51565CB42A7AD78D00469F18 /* MTRDeviceControllerStorageDelegate.h in Headers */, 510A07492A685D3900A9241C /* Matter.apinotes in Headers */, @@ -1438,6 +1489,7 @@ 5136661628067D550025EDAE /* MTRDeviceControllerFactory.h in Headers */, 5178E6812AE098520069DF72 /* MTRCommandTimedCheck.h in Headers */, 7596A84B287636C1004DAE0E /* MTRDevice_Internal.h in Headers */, + 514C7A042B6436D500DD6D7B /* MTRServerCluster_Internal.h in Headers */, 5A6FEC9927B5C88900F25F42 /* MTRDeviceOverXPC.h in Headers */, B4FCD5732B611EB300832859 /* MTRDiagnosticLogsDownloader.h in Headers */, 51B22C222740CB1D008D5055 /* MTRCommandPayloadsObjc.h in Headers */, @@ -1465,6 +1517,7 @@ 7596A84F2877E6A9004DAE0E /* MTRCluster_Internal.h in Headers */, 991DC0842475F45400C13860 /* MTRDeviceController.h in Headers */, AF1CB86E2874B03B00865A96 /* MTROTAProviderDelegate.h in Headers */, + 51D0B1402B61B3A4006E3511 /* MTRServerCluster.h in Headers */, 754F3DF427FBB94B00E60580 /* MTREventTLVValueDecoder_Internal.h in Headers */, 3CF134AF289D90FF0017A19E /* MTROperationalCertificateIssuer.h in Headers */, 5178E6822AE098520069DF72 /* MTRCommissionableBrowserResult_Internal.h in Headers */, @@ -1481,7 +1534,9 @@ 9956064426420367000C28DE /* MTRSetupPayload_Internal.h in Headers */, 27A53C1727FBC6920053F131 /* MTRAttestationTrustStoreBridge.h in Headers */, 5A830D6C27CFCF590053B85D /* MTRDeviceControllerOverXPC_Internal.h in Headers */, + 51D0B13C2B61B2F2006E3511 /* MTRDeviceTypeRevision.h in Headers */, 88EBF8D027FABDD500686BC1 /* MTRDeviceAttestationDelegateBridge.h in Headers */, + 514C7A012B64223400DD6D7B /* MTRServerAttribute_Internal.h in Headers */, 3DFCB32C29678C9500332B35 /* MTRConversion.h in Headers */, 5A60370827EA1FF60020DB79 /* MTRClusterStateCacheContainer+XPC.h in Headers */, 5ACDDD7E27CD3F3A00EFD68A /* MTRClusterStateCacheContainer_Internal.h in Headers */, @@ -1495,6 +1550,7 @@ 2C8C8FC1253E0C2100797F05 /* MTRStorage.h in Headers */, AF1CB8702874B04C00865A96 /* MTROTAProviderDelegateBridge.h in Headers */, B2E0D7B5245B0B5C003C5B48 /* MTRQRCodeSetupPayloadParser.h in Headers */, + 51D0B1382B618CC6006E3511 /* MTRServerAttribute.h in Headers */, 1EC4CE6425CC276600D7304F /* MTRBaseClusters.h in Headers */, 3D843712294977000070D20A /* MTRCallbackBridgeBase.h in Headers */, 3DECCB742934C21B00585AEC /* MTRDefines.h in Headers */, @@ -1506,11 +1562,13 @@ 1E4D654E29C208DD00BC3478 /* MTRCommissionableBrowserResult.h in Headers */, 998F286F26D55EC5001846C6 /* MTRP256KeypairBridge.h in Headers */, 2C222ADF255C811800E446B9 /* MTRBaseDevice_Internal.h in Headers */, + 514C7A022B64223400DD6D7B /* MTRServerEndpoint_Internal.h in Headers */, 511913FC28C100EF009235E9 /* MTRBaseSubscriptionCallback.h in Headers */, 51565CB12A7AD77600469F18 /* MTRDeviceControllerDataStore.h in Headers */, 3D843713294977000070D20A /* NSDataSpanConversion.h in Headers */, 991DC08B247704DC00C13860 /* MTRLogging_Internal.h in Headers */, 51FE723F2ACDEF3E00437032 /* MTRCommandPayloadExtensions_Internal.h in Headers */, + 51D0B12E2B6177FD006E3511 /* MTRAccessGrant.h in Headers */, 1E4D655029C208DD00BC3478 /* MTRCommissionableBrowserDelegate.h in Headers */, 7596A84828762783004DAE0E /* MTRAsyncCallbackWorkQueue.h in Headers */, 5A7947E527C0129F00434CF2 /* MTRDeviceController+XPC.h in Headers */, @@ -1760,7 +1818,9 @@ 51029DF6293AA6100087AFB0 /* MTROperationalCertificateIssuer.mm in Sources */, 27A53C1827FBC6920053F131 /* MTRAttestationTrustStoreBridge.mm in Sources */, 998F287126D56940001846C6 /* MTRP256KeypairBridge.mm in Sources */, + 51D0B1412B61B3A4006E3511 /* MTRServerCluster.mm in Sources */, 5136661428067D550025EDAE /* MTRDeviceControllerFactory.mm in Sources */, + 51D0B1392B618CC6006E3511 /* MTRServerAttribute.mm in Sources */, 51B22C2A2740CB47008D5055 /* MTRCommandPayloadsObjc.mm in Sources */, 51F522682AE70734000C4050 /* MTRDeviceTypeMetadata.mm in Sources */, 75B765C32A1D82D30014719B /* MTRAttributeSpecifiedCheck.mm in Sources */, @@ -1777,8 +1837,10 @@ B2E0D7B7245B0B5C003C5B48 /* MTRQRCodeSetupPayloadParser.mm in Sources */, 514304202914CED9004DC7FE /* generic-callback-stubs.cpp in Sources */, 1EDCE546289049A100E41EC9 /* MTROTAHeader.mm in Sources */, + 51D0B13D2B61B2F2006E3511 /* MTRDeviceTypeRevision.mm in Sources */, 1EC4CE5D25CC26E900D7304F /* MTRBaseClusters.mm in Sources */, 51565CB22A7AD77600469F18 /* MTRDeviceControllerDataStore.mm in Sources */, + 51D0B12F2B617800006E3511 /* MTRAccessGrant.mm in Sources */, 1ED276E226C5812A00547A89 /* MTRCluster.mm in Sources */, B2E0D7B3245B0B5C003C5B48 /* MTRError.mm in Sources */, 51E51FC1282AD37A00FC978D /* MTRDeviceControllerStartupParams.mm in Sources */, @@ -1809,6 +1871,7 @@ 514654492A72F9DF00904E61 /* MTRDemuxingStorage.mm in Sources */, 1E4D655229C30A8700BC3478 /* MTRCommissionableBrowser.mm in Sources */, 3DA1A3562ABAB3B4004F0BB9 /* MTRAsyncWorkQueue.mm in Sources */, + 51D0B1272B617246006E3511 /* MTRServerEndpoint.mm in Sources */, 3DECCB722934AFE200585AEC /* MTRLogging.mm in Sources */, 7596A84528762729004DAE0E /* MTRDevice.mm in Sources */, ); @@ -1821,6 +1884,7 @@ 51742B4E29CB6B88009974FE /* MTRPairingTests.m in Sources */, 51669AF02913204400F4AA36 /* MTRBackwardsCompatTests.m in Sources */, 51D10D2E2808E2CA00E8CA3D /* MTRTestStorage.m in Sources */, + 51D0B12A2B61766F006E3511 /* MTRServerEndpointTests.m in Sources */, 7596A8512878709F004DAE0E /* MTRAsyncCallbackQueueTests.m in Sources */, 997DED1A26955D0200975E97 /* MTRThreadOperationalDatasetTests.mm in Sources */, 518D3F852AA14006008E0007 /* MTRControllerAdvertisingTests.m in Sources */,