From 9492299ca3d409bb0c82b1b616441e31fa0aa667 Mon Sep 17 00:00:00 2001 From: Karsten Sperling Date: Fri, 20 Dec 2024 13:23:29 +1300 Subject: [PATCH] Darwin: Add MTRCommissioningParameters.readEndpointInformation Endpoint information is made availalable to the delegate via an MTRCommissioneeInfo object containing MTREndpointInfo objects. --- .../MTRAttributeTLVValueDecoder_Internal.h | 13 +- .../MTRAttributeTLVValueDecoder_Internal.mm | 34 +++ .../CHIP/MTRCommissioningParameters.h | 9 +- .../Framework/CHIP/MTRDefines_Internal.h | 6 +- .../Framework/CHIP/MTRDeviceController.mm | 11 +- .../CHIP/MTRDeviceControllerDelegate.h | 43 +++- .../CHIP/MTRDeviceControllerDelegateBridge.mm | 55 +++-- .../CHIP/MTRDeviceController_Concrete.mm | 7 +- src/darwin/Framework/CHIP/MTREndpointInfo.h | 47 ++++ src/darwin/Framework/CHIP/MTREndpointInfo.mm | 210 ++++++++++++++++++ .../Framework/CHIP/MTREndpointInfo_Internal.h | 48 ++++ .../Framework/CHIP/MTREndpointInfo_Test.h | 38 ++++ .../CHIP/MTROperationalCredentialsDelegate.h | 12 +- src/darwin/Framework/CHIP/Matter.h | 1 + .../CHIPTests/MTREndpointInfoTests.m | 95 ++++++++ .../Framework/CHIPTests/MTRPairingTests.m | 69 +++++- .../Matter.xcodeproj/project.pbxproj | 24 ++ 17 files changed, 668 insertions(+), 54 deletions(-) create mode 100644 src/darwin/Framework/CHIP/MTRAttributeTLVValueDecoder_Internal.mm create mode 100644 src/darwin/Framework/CHIP/MTREndpointInfo.h create mode 100644 src/darwin/Framework/CHIP/MTREndpointInfo.mm create mode 100644 src/darwin/Framework/CHIP/MTREndpointInfo_Internal.h create mode 100644 src/darwin/Framework/CHIP/MTREndpointInfo_Test.h create mode 100644 src/darwin/Framework/CHIPTests/MTREndpointInfoTests.m diff --git a/src/darwin/Framework/CHIP/MTRAttributeTLVValueDecoder_Internal.h b/src/darwin/Framework/CHIP/MTRAttributeTLVValueDecoder_Internal.h index a9adc87bb76f2b..22d50cf515578e 100644 --- a/src/darwin/Framework/CHIP/MTRAttributeTLVValueDecoder_Internal.h +++ b/src/darwin/Framework/CHIP/MTRAttributeTLVValueDecoder_Internal.h @@ -1,6 +1,5 @@ -/* - * - * Copyright (c) 2021 Project CHIP Authors +/** + * Copyright (c) 2021-2024 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,17 +15,21 @@ * limitations under the License. */ -#pragma once - #import +#include #include #include #include NS_ASSUME_NONNULL_BEGIN +// Decodes an attribute value TLV into a typed ObjC value (see MTRStructsObjc.h) id _Nullable MTRDecodeAttributeValue(const chip::app::ConcreteAttributePath & aPath, chip::TLV::TLVReader & aReader, CHIP_ERROR * aError); +// Wrapper around the precending function that reads the attribute from a ClusterStateCache. +id _Nullable MTRDecodeAttributeValue(const chip::app::ConcreteAttributePath & aPath, const chip::app::ClusterStateCache & aCache, + CHIP_ERROR * aError); + NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRAttributeTLVValueDecoder_Internal.mm b/src/darwin/Framework/CHIP/MTRAttributeTLVValueDecoder_Internal.mm new file mode 100644 index 00000000000000..d92da2f088aaa5 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRAttributeTLVValueDecoder_Internal.mm @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "MTRAttributeTLVValueDecoder_Internal.h" + +NS_ASSUME_NONNULL_BEGIN + +using namespace chip; + +id _Nullable MTRDecodeAttributeValue(const chip::app::ConcreteAttributePath & aPath, + const chip::app::ClusterStateCache & aCache, + CHIP_ERROR * aError) +{ + TLV::TLVReader reader; + *aError = aCache.Get(aPath, reader); + VerifyOrReturnValue(*aError == CHIP_NO_ERROR, nil); + return MTRDecodeAttributeValue(aPath, reader, aError); +} + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRCommissioningParameters.h b/src/darwin/Framework/CHIP/MTRCommissioningParameters.h index 771dfe54fc8893..cf50a9f506f226 100644 --- a/src/darwin/Framework/CHIP/MTRCommissioningParameters.h +++ b/src/darwin/Framework/CHIP/MTRCommissioningParameters.h @@ -1,6 +1,5 @@ /** - * - * Copyright (c) 2022-2023 Project CHIP Authors + * Copyright (c) 2022-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. @@ -97,6 +96,12 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) */ @property (nonatomic, copy, nullable) NSString * countryCode MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0)); +/** + * Read device type information from all endpoints during commissioning. + * Defaults to NO. + */ +@property (nonatomic, assign) BOOL readEndpointInformation; + @end @interface MTRCommissioningParameters (Deprecated) diff --git a/src/darwin/Framework/CHIP/MTRDefines_Internal.h b/src/darwin/Framework/CHIP/MTRDefines_Internal.h index ba7d6be51d61f5..a93be2e6cf6f79 100644 --- a/src/darwin/Framework/CHIP/MTRDefines_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDefines_Internal.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2023 Project CHIP Authors + * Copyright (c) 2023-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. @@ -30,8 +30,12 @@ #ifdef DEBUG #define MTR_TESTABLE MTR_EXPORT +#define MTR_TESTABLE_DIRECT +#define MTR_TESTABLE_DIRECT_MEMBERS #else #define MTR_TESTABLE +#define MTR_TESTABLE_DIRECT MTR_DIRECT +#define MTR_TESTABLE_DIRECT_MEMBERS MTR_DIRECT_MEMBERS #endif // clang-format off diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm index 3b222a1e3eccfc..2380a5af846ff1 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm @@ -1,6 +1,5 @@ /** - * - * Copyright (c) 2020-2023 Project CHIP Authors + * Copyright (c) 2020-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. @@ -656,11 +655,13 @@ - (void)controller:(MTRDeviceController *)controller } logString:__PRETTY_FUNCTION__]; } -- (void)controller:(MTRDeviceController *)controller readCommissioningInfo:(MTRProductIdentity *)info +- (void)controller:(MTRDeviceController *)controller readCommissioneeInfo:(MTRCommissioneeInfo *)info { [self _callDelegatesWithBlock:^(id delegate) { - if ([delegate respondsToSelector:@selector(controller:readCommissioningInfo:)]) { - [delegate controller:controller readCommissioningInfo:info]; + if ([delegate respondsToSelector:@selector(controller:readCommissioneeInfo:)]) { + [delegate controller:controller readCommissioneeInfo:info]; + } else if ([delegate respondsToSelector:@selector(controller:readCommissioningInfo:)]) { + [delegate controller:controller readCommissioningInfo:info.productIdentity]; } } logString:__PRETTY_FUNCTION__]; } diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerDelegate.h b/src/darwin/Framework/CHIP/MTRDeviceControllerDelegate.h index 60bbab5dcc77da..6a52919edd6cb8 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerDelegate.h +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerDelegate.h @@ -1,6 +1,5 @@ /** - * - * Copyright (c) 2020-2023 Project CHIP Authors + * Copyright (c) 2020-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. @@ -22,6 +21,7 @@ NS_ASSUME_NONNULL_BEGIN @class MTRDeviceController; @class MTRDeviceTypeRevision; +@class MTREndpointInfo; @class MTRMetrics; @class MTRProductIdentity; @@ -34,6 +34,33 @@ typedef NS_ENUM(NSInteger, MTRCommissioningStatus) { = 3, } MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)); +/** + * Information read from the commissionee device during commissioning. + */ +MTR_NEWLY_AVAILABLE +@interface MTRCommissioneeInfo : NSObject + +/** + * The product identity (VID / PID) of the commissionee. + */ +@property (nonatomic, copy, readonly) MTRProductIdentity * productIdentity; + +/** + * Endpoint information for all endpoints of the commissionee. + * Will be present only if readEndpointInformation is set to YES on MTRCommissioningParameters. + * + * Use `rootEndpoint` and `-[MTREndpointInfo children]` to traverse endpoints in composition order. + */ +@property (nonatomic, copy, readonly, nullable) NSDictionary * endpointsById; + +/** + * Endpoint information for the root endpoint of the commissionee. + * Will be present only if readEndpointInformation is set to YES on MTRCommissioningParameters. + */ +@property (nonatomic, copy, readonly, nullable) MTREndpointInfo * rootEndpoint; + +@end + /** * The protocol definition for the MTRDeviceControllerDelegate. * @@ -87,14 +114,18 @@ MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)) metrics:(MTRMetrics *)metrics MTR_AVAILABLE(ios(17.6), macos(14.6), watchos(10.6), tvos(17.6)); /** - * Notify the delegate when commissioning infomation has been read from the Basic - * Information cluster of the commissionee. + * Notify the delegate when commissioning infomation has been read from the commissionee. * - * At the point when this notification happens, device attestation has not been performed yet, + * Note that this notification happens before device attestation is performed, * so the information delivered by this notification should not be trusted. */ - (void)controller:(MTRDeviceController *)controller - readCommissioningInfo:(MTRProductIdentity *)info MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0)); + readCommissioneeInfo:(MTRCommissioneeInfo *)info MTR_NEWLY_AVAILABLE; + +- (void)controller:(MTRDeviceController *)controller + readCommissioningInfo:(MTRProductIdentity *)info + MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0)) + MTR_NEWLY_DEPRECATED("Use controller:readCommissioneeInfo:"); /** * Notify the delegate when the suspended state changed of the controller, after this happens diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerDelegateBridge.mm b/src/darwin/Framework/CHIP/MTRDeviceControllerDelegateBridge.mm index 7aa1dccd9a7ae0..4228bdd4c14f2d 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerDelegateBridge.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerDelegateBridge.mm @@ -1,6 +1,5 @@ /** - * - * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020-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. @@ -18,6 +17,7 @@ #import "MTRDeviceControllerDelegateBridge.h" #import "MTRDeviceController.h" #import "MTRDeviceController_Internal.h" +#import "MTREndpointInfo_Internal.h" #import "MTRError_Internal.h" #import "MTRLogging_Internal.h" #import "MTRMetricKeys.h" @@ -26,6 +26,30 @@ using namespace chip::Tracing::DarwinFramework; +@implementation MTRCommissioneeInfo + +- (instancetype)initWithCommissioningInfo:(const chip::Controller::ReadCommissioningInfo &)info +{ + self = [super init]; + _productIdentity = [[MTRProductIdentity alloc] initWithVendorID:@(info.basic.vendorId) productID:@(info.basic.productId)]; + + // TODO: We should probably hold onto our MTRCommissioningParameters so we can look at `readEndpointInformation` + // instead of just reading whatever Descriptor cluster information happens to be in the cache. + auto * endpoints = [MTREndpointInfo endpointsFromAttributeCache:info.attributes]; + if (endpoints.count > 0) { + _endpointsById = endpoints; + } + + return self; +} + +- (MTREndpointInfo *)rootEndpoint +{ + return self.endpointsById[@0]; +} + +@end + MTRDeviceControllerDelegateBridge::MTRDeviceControllerDelegateBridge(void) : mDelegate(nil) { @@ -125,20 +149,21 @@ void MTRDeviceControllerDelegateBridge::OnReadCommissioningInfo(const chip::Controller::ReadCommissioningInfo & info) { MTRDeviceController * strongController = mController; - - chip::VendorId vendorId = info.basic.vendorId; - uint16_t productId = info.basic.productId; - - MTR_LOG("%@ DeviceControllerDelegate Read Commissioning Info. VendorId %u ProductId %u", strongController, vendorId, productId); - id strongDelegate = mDelegate; - if (strongDelegate && mQueue && strongController) { - if ([strongDelegate respondsToSelector:@selector(controller:readCommissioningInfo:)]) { - dispatch_async(mQueue, ^{ - auto * info = [[MTRProductIdentity alloc] initWithVendorID:@(vendorId) productID:@(productId)]; - [strongDelegate controller:strongController readCommissioningInfo:info]; - }); - } + VerifyOrReturn(strongDelegate && mQueue); + + // TODO: These checks are pointless since currently mController == mDelegate + BOOL wantCommissioneeInfo = [strongDelegate respondsToSelector:@selector(controller:readCommissioneeInfo:)]; + BOOL wantProductIdentity = [strongDelegate respondsToSelector:@selector(controller:readCommissioningInfo:)]; + if (wantCommissioneeInfo || wantProductIdentity) { + auto * commissioneeInfo = [[MTRCommissioneeInfo alloc] initWithCommissioningInfo:info]; + dispatch_async(mQueue, ^{ + if (wantCommissioneeInfo) { // prefer the newer delegate method over the deprecated one + [strongDelegate controller:strongController readCommissioneeInfo:commissioneeInfo]; + } else if (wantProductIdentity) { + [strongDelegate controller:strongController readCommissioningInfo:commissioneeInfo.productIdentity]; + } + }); } } diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm index b27844425baa50..b67b07e8b81ba3 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm @@ -1,6 +1,5 @@ /** - * - * Copyright (c) 2020-2023 Project CHIP Authors + * Copyright (c) 2020-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. @@ -34,6 +33,7 @@ #import "MTRDeviceController_Concrete.h" #import "MTRDevice_Concrete.h" #import "MTRDevice_Internal.h" +#import "MTREndpointInfo_Internal.h" #import "MTRError_Internal.h" #import "MTRKeypair.h" #import "MTRLogging_Internal.h" @@ -961,6 +961,9 @@ - (BOOL)commissionNodeWithID:(NSNumber *)nodeID auto block = ^BOOL { chip::Controller::CommissioningParameters params; + if (commissioningParams.readEndpointInformation) { + params.SetExtraReadPaths(MTREndpointInfo.requiredAttributePaths); + } if (commissioningParams.csrNonce) { params.SetCSRNonce(AsByteSpan(commissioningParams.csrNonce)); } diff --git a/src/darwin/Framework/CHIP/MTREndpointInfo.h b/src/darwin/Framework/CHIP/MTREndpointInfo.h new file mode 100644 index 00000000000000..d705e69767a80f --- /dev/null +++ b/src/darwin/Framework/CHIP/MTREndpointInfo.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 + +@class MTRDeviceTypeRevision; + +NS_ASSUME_NONNULL_BEGIN + +/** + * Meta-data about an endpoint of a Matter node. + */ +MTR_NEWLY_AVAILABLE +@interface MTREndpointInfo : NSObject + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@property (nonatomic, copy, readonly) NSNumber * endpointID; + +@property (nonatomic, copy, readonly) NSArray * deviceTypes; +@property (nonatomic, copy, readonly) NSArray * partsList; + +/** + * The direct children of this endpoint. This excludes indirect descendants + * even if they are listed in the PartsList attribute of this endpoint due + * to the Full-Family Pattern being used. Refer to Endpoint Composition Patterns + * in the Matter specification for details. + */ +@property (nonatomic, copy, readonly) NSArray * children; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTREndpointInfo.mm b/src/darwin/Framework/CHIP/MTREndpointInfo.mm new file mode 100644 index 00000000000000..0eb5adfe7602d4 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTREndpointInfo.mm @@ -0,0 +1,210 @@ +/** + * 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 "MTREndpointInfo_Internal.h" + +#import "MTRAttributeTLVValueDecoder_Internal.h" +#import "MTRDeviceTypeRevision.h" +#import "MTRLogging_Internal.h" +#import "MTRStructsObjc.h" + +#include +#include + +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters; + +enum class EndpointMark : uint8_t { + NotVisited = 0, + Visiting, + Visited, + ParentAssigned = NotVisited, // != Visited +}; + +MTR_DIRECT_MEMBERS +@implementation MTREndpointInfo { + EndpointId _endpointID; + EndpointMark _mark; // used by populateChildrenForEndpoints: +} + +- (instancetype)initWithEndpointID:(NSNumber *)endpointID + deviceTypes:(NSArray *)deviceTypes + partsList:(NSArray *)partsList +{ + self = [super init]; + _endpointID = endpointID.unsignedShortValue; + _deviceTypes = [deviceTypes copy]; + _partsList = [partsList copy]; + _children = @[]; + return self; +} + +- (NSNumber *)endpointID +{ + return @(_endpointID); +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@ %u>", self.class, _endpointID]; +} + ++ (BOOL)populateChildrenForEndpoints:(NSDictionary *)endpoints +{ + // Populate the child list of each endpoint, ensuring no cycles (these are disallowed + // by the spec, but we need to ensure we don't create a retain cycle even under invalid + // input). Conservatively assume all endpoints use the Full-Family Pattern. + // Refer to "Endpoint Composition" in the Matter specification for details. + MTREndpointInfo * root = endpoints[@0]; + if (root == nil) { + MTR_LOG_ERROR("Missing root endpoint, not populating endpoint hierarchy"); + return NO; + } + + // Perform a depth-first search with an explicit stack and create a list of endpoint + // IDs in reverse topological order. The _parentID field is used mark traversal states. + BOOL valid = YES; + std::deque deque; // stack followed by sorted list + deque.emplace_front(root->_endpointID); + for (;;) { + EndpointId endpointID = deque.front(); + MTREndpointInfo * endpoint = endpoints[@(endpointID)]; + if (endpoint->_mark == EndpointMark::NotVisited) { + endpoint->_mark = EndpointMark::Visiting; + for (NSNumber * partNumber in endpoint->_partsList) { + MTREndpointInfo * descendant = endpoints[partNumber]; + if (!descendant) { + MTR_LOG_ERROR("Warning: PartsList of endpoint %u references non-existant endpoint %u", + endpointID, partNumber.unsignedShortValue); + valid = NO; + } else if (descendant->_mark == EndpointMark::NotVisited) { + deque.emplace_front(descendant->_endpointID); + } else if (descendant->_mark == EndpointMark::Visiting) { + MTR_LOG_ERROR("Warning: Cyclic endpoint composition involving endpoints %u and %u", + descendant->_endpointID, endpointID); + valid = NO; + } + } + } else if (endpoint->_mark == EndpointMark::Visiting) { + endpoint->_mark = EndpointMark::Visited; + deque.pop_front(); // remove from stack + deque.emplace_back(endpointID); // add to sorted list + if (endpointID == root->_endpointID) { + break; // visited the root, DFS traversal done + } + } else /* endpoint->_mark == EndpointMark::Visited */ { + deque.pop_front(); // nothing else to do + } + } + if (deque.size() != endpoints.count) { + MTR_LOG_ERROR("Warning: Not all endpoints are descendants of the root endpoint"); + valid = NO; + } + + // Now iterate over the endpoints in reverse topological order, i.e. bottom up. This means + // that we will visit children before parents, so the first time we see and endpoint in a + // PartsList we can assign it as a child of the endpoint we're processing, and we can be sure + // that this is the closest parent, not some higher ancestor using the Full-Family Pattern. + NSMutableArray * children = [[NSMutableArray alloc] init]; + while (!deque.empty()) { + EndpointId endpointID = deque.front(); + MTREndpointInfo * endpoint = endpoints[@(endpointID)]; + deque.pop_front(); + + if (endpoint->_mark == EndpointMark::ParentAssigned) { + continue; // This endpoint is part of a cycle, don't populate its children. + } + + [children removeAllObjects]; + for (NSNumber * partNumber in endpoint->_partsList) { + MTREndpointInfo * descendant = endpoints[partNumber]; + if (descendant != nil && descendant->_mark != EndpointMark::ParentAssigned) { + descendant->_mark = EndpointMark::ParentAssigned; + [children addObject:descendant]; + } + } + endpoint->_children = [children copy]; + } + root->_mark = EndpointMark::ParentAssigned; + return valid; +} + ++ (NSDictionary *)endpointsFromAttributeCache:(const ClusterStateCache *)cache +{ + VerifyOrReturnValue(cache != nullptr, nil); + using namespace Descriptor::Attributes; + + NSMutableDictionary * endpoints = [[NSMutableDictionary alloc] init]; + cache->ForEachAttribute(Descriptor::Id, [&](const ConcreteAttributePath & path) -> CHIP_ERROR { + VerifyOrReturnError(path.mAttributeId == DeviceTypeList::Id, CHIP_NO_ERROR); + + CHIP_ERROR err = CHIP_NO_ERROR; + NSArray * deviceTypeList = MTRDecodeAttributeValue(path, *cache, &err); + if (!deviceTypeList) { + MTR_LOG_ERROR("Ignoring endpoint %u, failed to parse DeviceTypeList: %" CHIP_ERROR_FORMAT, path.mEndpointId, err.Format()); + return CHIP_NO_ERROR; + } + + NSMutableArray * deviceTypes = [[NSMutableArray alloc] initWithCapacity:deviceTypeList.count]; + for (MTRDescriptorClusterDeviceTypeStruct * deviceTypeStruct in deviceTypeList) { + MTRDeviceTypeRevision * type = [[MTRDeviceTypeRevision alloc] initWithDeviceTypeStruct:deviceTypeStruct]; + if (!type) { + MTR_LOG_ERROR("Ignoring invalid device type 0x%x rev %u for endpoint %u", + deviceTypeStruct.deviceType.unsignedIntValue, deviceTypeStruct.revision.unsignedShortValue, + path.mEndpointId); + continue; + } + [deviceTypes addObject:type]; + } + if (!deviceTypes) { + MTR_LOG_ERROR("Ignoring endpoint %u, no device types", path.mEndpointId); + return CHIP_NO_ERROR; + } + + ConcreteAttributePath partsListPath(path.mEndpointId, path.mClusterId, PartsList::Id); + NSArray * partsList = MTRDecodeAttributeValue(partsListPath, *cache, &err); + if (!partsList) { + MTR_LOG_ERROR("Ignoring PartsList for endpoint %u: %" CHIP_ERROR_FORMAT, path.mEndpointId, err.Format()); + partsList = @[]; + } + + MTREndpointInfo * endpoint = [[MTREndpointInfo alloc] initWithEndpointID:@(path.mEndpointId) + deviceTypes:deviceTypes + partsList:partsList]; + endpoints[endpoint.endpointID] = endpoint; + return CHIP_NO_ERROR; + }); + + if (endpoints.count > 0) { + [self populateChildrenForEndpoints:endpoints]; + } + return [endpoints copy]; +} + ++ (Span)requiredAttributePaths +{ + using namespace Descriptor::Attributes; + static constexpr AttributePathParams kPaths[] = { + AttributePathParams(Descriptor::Id, DeviceTypeList::Id), + AttributePathParams(Descriptor::Id, PartsList::Id), + }; + return Span(kPaths); +} + +@end diff --git a/src/darwin/Framework/CHIP/MTREndpointInfo_Internal.h b/src/darwin/Framework/CHIP/MTREndpointInfo_Internal.h new file mode 100644 index 00000000000000..298ee8dab5241b --- /dev/null +++ b/src/darwin/Framework/CHIP/MTREndpointInfo_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 "MTREndpointInfo_Test.h" + +#include +#include +#include + +NS_ASSUME_NONNULL_BEGIN + +MTR_DIRECT_MEMBERS +@interface MTREndpointInfo () + +/** + * Returns a dictionary of endpoint metadata for a node. + * + * The provided cache must contain the result of reading the + * DeviceTypeList and PartsList attributes of all endpoints + * (as exposed by `requiredAttributePaths`). + * + * Any relevant information will be copied out of the cache; + * the caller is free to deallocate the cache once this method returns. + */ ++ (NSDictionary *)endpointsFromAttributeCache:(const chip::app::ClusterStateCache *)cache; + +/** + * Returns the set of AttributePathParams that must be read + * to populate endpoint information for a node. + */ ++ (chip::Span)requiredAttributePaths; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTREndpointInfo_Test.h b/src/darwin/Framework/CHIP/MTREndpointInfo_Test.h new file mode 100644 index 00000000000000..543132d2e457fd --- /dev/null +++ b/src/darwin/Framework/CHIP/MTREndpointInfo_Test.h @@ -0,0 +1,38 @@ +/** + * 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 "MTRDefines_Internal.h" + +NS_ASSUME_NONNULL_BEGIN + +MTR_TESTABLE_DIRECT_MEMBERS +@interface MTREndpointInfo () + +- (instancetype)initWithEndpointID:(NSNumber *)endpointID + deviceTypes:(NSArray *)deviceTypes + partsList:(NSArray *)partsList NS_DESIGNATED_INITIALIZER; + +// Populates the children array for each endpoint in the provided dictionary. +// Returns YES if the endpoint hierarchy was populated correctly. +// A return value of NO indicates that there were some issues, but +// an effort has been made to populate a valid subset of the hierarchy. ++ (BOOL)populateChildrenForEndpoints:(NSDictionary *)endpoints; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.h b/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.h index 928c80ba85a0cb..86a054a550c504 100644 --- a/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.h +++ b/src/darwin/Framework/CHIP/MTROperationalCredentialsDelegate.h @@ -1,6 +1,5 @@ /** - * - * Copyright (c) 2021-2023 Project CHIP Authors + * Copyright (c) 2021-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. @@ -15,8 +14,6 @@ * limitations under the License. */ -#include - #import #import @@ -32,6 +29,8 @@ #include #include +#include + NS_ASSUME_NONNULL_BEGIN class MTROperationalCredentialsDelegate : public chip::Controller::OperationalCredentialsDelegate { @@ -63,11 +62,6 @@ class MTROperationalCredentialsDelegate : public chip::Controller::OperationalCr mCppCommissioner = cppCommissioner; } - chip::Optional GetCommissioningParameters() - { - return mCppCommissioner == nullptr ? chip::NullOptional : mCppCommissioner->GetCommissioningParameters(); - } - void SetOperationalCertificateIssuer( id operationalCertificateIssuer, dispatch_queue_t operationalCertificateIssuerQueue) { diff --git a/src/darwin/Framework/CHIP/Matter.h b/src/darwin/Framework/CHIP/Matter.h index 9387480e771c5b..55a4b31e816e63 100644 --- a/src/darwin/Framework/CHIP/Matter.h +++ b/src/darwin/Framework/CHIP/Matter.h @@ -52,6 +52,7 @@ #import #import #import +#import #import #import #import diff --git a/src/darwin/Framework/CHIPTests/MTREndpointInfoTests.m b/src/darwin/Framework/CHIPTests/MTREndpointInfoTests.m new file mode 100644 index 00000000000000..1ed79adca1ef99 --- /dev/null +++ b/src/darwin/Framework/CHIPTests/MTREndpointInfoTests.m @@ -0,0 +1,95 @@ +/** + * 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 "MTREndpointInfo_Test.h" + +#import + +@interface MTREndpointInfoTests : XCTestCase +@end + +@implementation MTREndpointInfoTests + +static MTREndpointInfo * MakeEndpoint(NSNumber * endpointID, NSArray * parts) +{ + return [[MTREndpointInfo alloc] initWithEndpointID:endpointID deviceTypes:@[] partsList:parts]; +} + +static NSArray * ChildEndpointIDs(MTREndpointInfo * endpoint) +{ + return [[endpoint.children valueForKey:@"endpointID"] sortedArrayUsingSelector:@selector(compare:)]; +} + +- (NSDictionary *)indexEndpoints:(NSArray *)endpoints +{ + NSMutableDictionary * indexed = [[NSMutableDictionary alloc] init]; + for (MTREndpointInfo * endpoint in endpoints) { + indexed[endpoint.endpointID] = endpoint; + } + XCTAssertEqual(indexed.count, endpoints.count, @"Duplicate endpoint IDs"); + return indexed; +} + +- (void)testPopulateChildren +{ + NSDictionary * endpoints = [self indexEndpoints:@[ + MakeEndpoint(@0, @[ @1, @2, @3, @4, @5, @6 ]), // full-family pattern + MakeEndpoint(@1, @[ @2, @3 ]), + MakeEndpoint(@2, @[]), + MakeEndpoint(@3, @[]), + MakeEndpoint(@4, @[ @5, @6 ]), // full-family pattern + MakeEndpoint(@5, @[ @6 ]), + MakeEndpoint(@6, @[]), + ]]; + XCTAssertTrue([MTREndpointInfo populateChildrenForEndpoints:endpoints]); + XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@0]), (@[ @1, @4 ])); + XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@1]), (@[ @2, @3 ])); + XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@2]), (@[])); + XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@3]), (@[])); + XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@4]), (@[ @5 ])); + XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@5]), (@[ @6 ])); + XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@6]), (@[])); +} + +- (void)testPopulateChildrenRootOnly +{ + NSDictionary * endpoints = [self indexEndpoints:@[ + MakeEndpoint(@0, @[]), + ]]; + XCTAssertTrue([MTREndpointInfo populateChildrenForEndpoints:endpoints]); + XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@0]), (@[])); +} + +- (void)testPopulateChildrenWithCompositionCycle +{ + NSDictionary * endpoints = [self indexEndpoints:@[ + MakeEndpoint(@0, @[ @1, @2, @3, @4, @5, @6 ]), // full-family pattern + MakeEndpoint(@1, @[ @2, @3 ]), + MakeEndpoint(@2, @[]), + MakeEndpoint(@3, @[]), + MakeEndpoint(@4, @[ @5, @6 ]), // full-family pattern + MakeEndpoint(@5, @[ @6 ]), + MakeEndpoint(@6, @[ @4 ]), // cycle 4 -> 5 -> 6 -> 4 + ]]; + XCTAssertFalse([MTREndpointInfo populateChildrenForEndpoints:endpoints]); + XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@0]), (@[ @1, @4 ])); + XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@1]), (@[ @2, @3 ])); + XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@2]), (@[])); + XCTAssertEqualObjects(ChildEndpointIDs(endpoints[@3]), (@[])); + // We make no promises about child lists for endpoints involved in a cycle +} + +@end diff --git a/src/darwin/Framework/CHIPTests/MTRPairingTests.m b/src/darwin/Framework/CHIPTests/MTRPairingTests.m index 675c04156e62ae..0447d58a5a3fc3 100644 --- a/src/darwin/Framework/CHIPTests/MTRPairingTests.m +++ b/src/darwin/Framework/CHIPTests/MTRPairingTests.m @@ -1,6 +1,5 @@ -/* - * - * Copyright (c) 2022 Project CHIP Authors +/** + * Copyright (c) 2022-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. @@ -107,6 +106,7 @@ @interface MTRPairingTestControllerDelegate : NSObject attestationDelegate; @property (nonatomic, nullable) NSNumber * failSafeExtension; +@property (nonatomic) BOOL shouldReadEndpointInformation; @property (nullable) NSError * commissioningCompleteError; @end @@ -131,6 +131,7 @@ - (void)controller:(MTRDeviceController *)controller commissioningSessionEstabli __auto_type * params = [[MTRCommissioningParameters alloc] init]; params.deviceAttestationDelegate = self.attestationDelegate; params.failSafeTimeout = self.failSafeExtension; + params.readEndpointInformation = self.shouldReadEndpointInformation; NSError * commissionError = nil; XCTAssertTrue([controller commissionNodeWithID:@(sDeviceId) commissioningParams:params error:&commissionError], @@ -139,6 +140,30 @@ - (void)controller:(MTRDeviceController *)controller commissioningSessionEstabli // Keep waiting for onCommissioningComplete } +- (void)controller:(MTRDeviceController *)controller readCommissioneeInfo:(MTRCommissioneeInfo *)info +{ + XCTAssertNotNil(info.productIdentity); + XCTAssertEqualObjects(info.productIdentity.vendorID, /* Test Vendor 1 */ @0xFFF1); + + if (self.shouldReadEndpointInformation) { + XCTAssertNotNil(info.endpointsById); + XCTAssertNotNil(info.rootEndpoint); + XCTAssertGreaterThanOrEqual(info.rootEndpoint.children.count, 2); // at least one application endpoint + for (MTREndpointInfo * endpoint in info.endpointsById.allValues) { + XCTAssertGreaterThanOrEqual(endpoint.deviceTypes.count, 1); + XCTAssertNotNil(endpoint.children); + XCTAssertNotNil(endpoint.partsList); + XCTAssertGreaterThanOrEqual(endpoint.partsList.count, endpoint.children.count); + for (MTREndpointInfo * child in endpoint.children) { + XCTAssertTrue([endpoint.partsList containsObject:child.endpointID]); + } + } + } else { + XCTAssertNil(info.endpointsById); + XCTAssertNil(info.rootEndpoint); + } +} + - (void)controller:(MTRDeviceController *)controller commissioningComplete:(NSError * _Nullable)error { self.commissioningCompleteError = error; @@ -152,20 +177,20 @@ @interface MTRPairingTestMonitoringControllerDelegate : NSObject ", self, MTR_YES_NO(_statusUpdateCalled), MTR_YES_NO(_commissioningSessionEstablishmentDoneCalled), MTR_YES_NO(_commissioningCompleteCalled), MTR_YES_NO(_readCommissioningInfoCalled)]; + return [NSString stringWithFormat:@"", self, MTR_YES_NO(_statusUpdateCalled), MTR_YES_NO(_commissioningSessionEstablishmentDoneCalled), MTR_YES_NO(_commissioningCompleteCalled), MTR_YES_NO(_readCommissioneeInfoCalled)]; } - (void)_checkIfAllCallbacksCalled { if (self.allCallbacksCalledExpectation) { - if (self.statusUpdateCalled && self.commissioningSessionEstablishmentDoneCalled && self.commissioningCompleteCalled && self.readCommissioningInfoCalled) { + if (self.statusUpdateCalled && self.commissioningSessionEstablishmentDoneCalled && self.commissioningCompleteCalled && self.readCommissioneeInfoCalled) { [self.allCallbacksCalledExpectation fulfill]; self.allCallbacksCalledExpectation = nil; } @@ -193,11 +218,12 @@ - (void)controller:(MTRDeviceController *)controller [self _checkIfAllCallbacksCalled]; } -- (void)controller:(MTRDeviceController *)controller readCommissioningInfo:(MTRProductIdentity *)info +- (void)controller:(MTRDeviceController *)controller readCommissioneeInfo:(MTRCommissioneeInfo *)info { - self.readCommissioningInfoCalled = YES; + self.readCommissioneeInfoCalled = YES; [self _checkIfAllCallbacksCalled]; } + @end @interface MTRPairingTests : MTRTestCase @@ -318,7 +344,7 @@ - (void)doPairingTestWithAttestationDelegate:(id)a XCTAssertTrue(monitoringControllerDelegate.statusUpdateCalled); XCTAssertTrue(monitoringControllerDelegate.commissioningSessionEstablishmentDoneCalled); XCTAssertTrue(monitoringControllerDelegate.commissioningCompleteCalled); - XCTAssertTrue(monitoringControllerDelegate.readCommissioningInfoCalled); + XCTAssertTrue(monitoringControllerDelegate.readCommissioneeInfoCalled); [sController removeDeviceControllerDelegate:monitoringControllerDelegate]; } @@ -454,4 +480,29 @@ - (void)test008_pairingAfterCancellation_DeviceAttestationVerification XCTAssertTrue(delegateCalled); } +- (void)test009_PairWithReadingEndpointInformation +{ + [self startServerApp]; + + XCTestExpectation * expectation = [self expectationWithDescription:@"Commissioning Complete"]; + __auto_type * controllerDelegate = [[MTRPairingTestControllerDelegate alloc] initWithExpectation:expectation + attestationDelegate:nil + failSafeExtension:nil]; + + // Endpoint info is validated by MTRPairingTestControllerDelegate + controllerDelegate.shouldReadEndpointInformation = YES; + + dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip.pairing", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); + [sController setDeviceControllerDelegate:controllerDelegate queue:callbackQueue]; + self.controllerDelegate = controllerDelegate; + + NSError * error; + __auto_type * payload = [MTRSetupPayload setupPayloadWithOnboardingPayload:kOnboardingPayload error:&error]; + XCTAssertTrue([sController setupCommissioningSessionWithPayload:payload newNodeID:@(++sDeviceId) error:&error]); + XCTAssertNil(error); + + [self waitForExpectations:@[ expectation ] timeout:kPairingTimeoutInSeconds]; + XCTAssertNil(controllerDelegate.commissioningCompleteError); +} + @end diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 3c729c115aa39e..9c7a9c1db4e5e3 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -120,6 +120,12 @@ 3D9F2FBF2D10DB4F003CA2BB /* MTRProductIdentity.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3D9F2FBD2D10DB4F003CA2BB /* MTRProductIdentity.mm */; }; 3D9F2FC12D10DCA2003CA2BB /* MTRProductIdentityTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D9F2FC02D10DCA2003CA2BB /* MTRProductIdentityTests.m */; }; 3D9F2FC32D10E207003CA2BB /* MTRDeviceTypeRevisionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D9F2FC22D10E207003CA2BB /* MTRDeviceTypeRevisionTests.m */; }; + 3D9F2FC62D10EA11003CA2BB /* MTREndpointInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3D9F2FC52D10EA11003CA2BB /* MTREndpointInfo.mm */; }; + 3D9F2FC72D10EA11003CA2BB /* MTREndpointInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 3D9F2FC42D10EA11003CA2BB /* MTREndpointInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3D9F2FC92D1105BA003CA2BB /* MTREndpointInfo_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 3D9F2FC82D1105BA003CA2BB /* MTREndpointInfo_Internal.h */; }; + 3D9F2FCE2D112295003CA2BB /* MTRAttributeTLVValueDecoder_Internal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3D9F2FCD2D112295003CA2BB /* MTRAttributeTLVValueDecoder_Internal.mm */; }; + 3D9F2FD02D11FE90003CA2BB /* MTREndpointInfoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D9F2FCF2D11FE90003CA2BB /* MTREndpointInfoTests.m */; }; + 3D9F2FD22D11FF27003CA2BB /* MTREndpointInfo_Test.h in Headers */ = {isa = PBXBuildFile; fileRef = 3D9F2FD12D11FF27003CA2BB /* MTREndpointInfo_Test.h */; }; 3DA1A3552ABAB3B4004F0BB9 /* MTRAsyncWorkQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 3DA1A3522ABAB3B4004F0BB9 /* MTRAsyncWorkQueue.h */; }; 3DA1A3562ABAB3B4004F0BB9 /* MTRAsyncWorkQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3DA1A3532ABAB3B4004F0BB9 /* MTRAsyncWorkQueue.mm */; }; 3DA1A3582ABABF6A004F0BB9 /* MTRAsyncWorkQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DA1A3572ABABF69004F0BB9 /* MTRAsyncWorkQueueTests.m */; }; @@ -618,6 +624,12 @@ 3D9F2FBD2D10DB4F003CA2BB /* MTRProductIdentity.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRProductIdentity.mm; sourceTree = ""; }; 3D9F2FC02D10DCA2003CA2BB /* MTRProductIdentityTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MTRProductIdentityTests.m; sourceTree = ""; }; 3D9F2FC22D10E207003CA2BB /* MTRDeviceTypeRevisionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MTRDeviceTypeRevisionTests.m; sourceTree = ""; }; + 3D9F2FC42D10EA11003CA2BB /* MTREndpointInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTREndpointInfo.h; sourceTree = ""; }; + 3D9F2FC52D10EA11003CA2BB /* MTREndpointInfo.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MTREndpointInfo.mm; sourceTree = ""; }; + 3D9F2FC82D1105BA003CA2BB /* MTREndpointInfo_Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTREndpointInfo_Internal.h; sourceTree = ""; }; + 3D9F2FCD2D112295003CA2BB /* MTRAttributeTLVValueDecoder_Internal.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRAttributeTLVValueDecoder_Internal.mm; sourceTree = ""; }; + 3D9F2FCF2D11FE90003CA2BB /* MTREndpointInfoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MTREndpointInfoTests.m; sourceTree = ""; }; + 3D9F2FD12D11FF27003CA2BB /* MTREndpointInfo_Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTREndpointInfo_Test.h; sourceTree = ""; }; 3DA1A3522ABAB3B4004F0BB9 /* MTRAsyncWorkQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRAsyncWorkQueue.h; sourceTree = ""; }; 3DA1A3532ABAB3B4004F0BB9 /* MTRAsyncWorkQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRAsyncWorkQueue.mm; sourceTree = ""; }; 3DA1A3572ABABF69004F0BB9 /* MTRAsyncWorkQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRAsyncWorkQueueTests.m; sourceTree = ""; }; @@ -1455,6 +1467,7 @@ 27A53C1527FBC6920053F131 /* MTRAttestationTrustStoreBridge.h */, 27A53C1627FBC6920053F131 /* MTRAttestationTrustStoreBridge.mm */, 513DDB852761F69300DAA01A /* MTRAttributeTLVValueDecoder_Internal.h */, + 3D9F2FCD2D112295003CA2BB /* MTRAttributeTLVValueDecoder_Internal.mm */, 75B765C02A1D71BC0014719B /* MTRAttributeSpecifiedCheck.h */, 51EF279E2A2A3EB100E33F75 /* MTRBackwardsCompatShims.h */, 510470FA2A2F7DF60053EA7E /* MTRBackwardsCompatShims.mm */, @@ -1550,6 +1563,10 @@ 5109E9B32CB8B5DF0006884B /* MTRDeviceType.mm */, 51D0B13A2B61B2F2006E3511 /* MTRDeviceTypeRevision.h */, 51D0B13B2B61B2F2006E3511 /* MTRDeviceTypeRevision.mm */, + 3D9F2FC42D10EA11003CA2BB /* MTREndpointInfo.h */, + 3D9F2FD12D11FF27003CA2BB /* MTREndpointInfo_Test.h */, + 3D9F2FC82D1105BA003CA2BB /* MTREndpointInfo_Internal.h */, + 3D9F2FC52D10EA11003CA2BB /* MTREndpointInfo.mm */, 5129BCFC26A9EE3300122DDF /* MTRError.h */, B2E0D7AB245B0B5C003C5B48 /* MTRError_Internal.h */, 51578AEA2D0B9DC0001716FF /* MTRError_Testable.h */, @@ -1633,6 +1650,7 @@ 5109E9B62CB8B83D0006884B /* MTRDeviceTypeTests.m */, 3D9F2FC22D10E207003CA2BB /* MTRDeviceTypeRevisionTests.m */, 51D9CB0A2BA37DCE0049D6DB /* MTRDSTOffsetTests.m */, + 3D9F2FCF2D11FE90003CA2BB /* MTREndpointInfoTests.m */, 3D0C484A29DA4FA0006D811F /* MTRErrorTests.m */, 51578AE82D0B9B1D001716FF /* MTRErrorMappingTests.m */, 5173A47829C0E82300F67F48 /* MTRFabricInfoTests.m */, @@ -1923,6 +1941,7 @@ 514C7A012B64223400DD6D7B /* MTRServerAttribute_Internal.h in Headers */, 3DFCB32C29678C9500332B35 /* MTRConversion.h in Headers */, 5A60370827EA1FF60020DB79 /* MTRClusterStateCacheContainer+XPC.h in Headers */, + 3D9F2FC92D1105BA003CA2BB /* MTREndpointInfo_Internal.h in Headers */, 5ACDDD7E27CD3F3A00EFD68A /* MTRClusterStateCacheContainer_Internal.h in Headers */, 5136661328067D550025EDAE /* MTRDeviceController_Internal.h in Headers */, 998F286D26D55E10001846C6 /* MTRKeypair.h in Headers */, @@ -1951,6 +1970,7 @@ 9B231B042C62EF650030EB37 /* (null) in Headers */, 515BE4ED2B72C0C5000BC1FD /* MTRUnfairLock.h in Headers */, 998F286F26D55EC5001846C6 /* MTRP256KeypairBridge.h in Headers */, + 3D9F2FC72D10EA11003CA2BB /* MTREndpointInfo.h in Headers */, CF3B63CF2CA31E71003C1C87 /* MTROTAImageTransferHandler.h in Headers */, 2C222ADF255C811800E446B9 /* MTRBaseDevice_Internal.h in Headers */, 514C7A022B64223400DD6D7B /* MTRServerEndpoint_Internal.h in Headers */, @@ -1968,6 +1988,7 @@ 7592BCF42CBEE98C00EB74A0 /* EmberMetadata.h in Headers */, 7592BCF52CBEE98C00EB74A0 /* CodegenDataModelProvider.h in Headers */, 7596A84828762783004DAE0E /* MTRAsyncCallbackWorkQueue.h in Headers */, + 3D9F2FD22D11FF27003CA2BB /* MTREndpointInfo_Test.h in Headers */, 5A7947E527C0129F00434CF2 /* MTRDeviceController+XPC.h in Headers */, 51E95DFB2A78443C00A434F0 /* MTRSessionResumptionStorageBridge.h in Headers */, B2E0D7B4245B0B5C003C5B48 /* MTRError_Internal.h in Headers */, @@ -2317,12 +2338,14 @@ 5ACDDD7D27CD16D200EFD68A /* MTRClusterStateCacheContainer.mm in Sources */, 75B3269E2BCDB9EA00E17C4E /* MTRDeviceConnectivityMonitor.mm in Sources */, 7534D1802CF8CE2000F64654 /* DefaultAttributePersistenceProvider.cpp in Sources */, + 3D9F2FC62D10EA11003CA2BB /* MTREndpointInfo.mm in Sources */, 9B5CCB5C2C6EC890009DD99B /* MTRDevice_XPC.mm in Sources */, 513DDB8A2761F6F900DAA01A /* MTRAttributeTLVValueDecoder.mm in Sources */, 5117DD3829A931AE00FFA1AA /* MTROperationalBrowser.mm in Sources */, 514C79F02B62ADDA00DD6D7B /* descriptor.cpp in Sources */, 5109E9B42CB8B5DF0006884B /* MTRDeviceType.mm in Sources */, 3D843757294AD25A0070D20A /* MTRCertificateInfo.mm in Sources */, + 3D9F2FCE2D112295003CA2BB /* MTRAttributeTLVValueDecoder_Internal.mm in Sources */, 5A7947E427C0129600434CF2 /* MTRDeviceController+XPC.mm in Sources */, 7592BD0B2CC6BCC300EB74A0 /* EmberAttributeDataBuffer.cpp in Sources */, 5A6FEC9027B563D900F25F42 /* MTRDeviceControllerOverXPC.mm in Sources */, @@ -2380,6 +2403,7 @@ 5173A47929C0E82300F67F48 /* MTRFabricInfoTests.m in Sources */, 3D9F2FC12D10DCA2003CA2BB /* MTRProductIdentityTests.m in Sources */, 75B326A22BCF12E900E17C4E /* MTRDeviceConnectivityMonitorTests.m in Sources */, + 3D9F2FD02D11FE90003CA2BB /* MTREndpointInfoTests.m in Sources */, 5143851E2A65885500EDC8E6 /* MTRSwiftPairingTests.swift in Sources */, 75B0D01E2B71B47F002074DD /* MTRDeviceTestDelegate.m in Sources */, 3D0C484B29DA4FA0006D811F /* MTRErrorTests.m in Sources */,