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 */,