From 578d22c858839a9650ae953a2f7d5d9cd614aba6 Mon Sep 17 00:00:00 2001 From: Jeff Tung <100387939+jtung-apple@users.noreply.github.com> Date: Thu, 18 Apr 2024 18:05:52 -0700 Subject: [PATCH] [Darwin] MTRDevice should trigger resubscription on connectivity changes (#33016) * [Darwin] MTRDevice should trigger resubscription on connectivity changes * Address review comments and add unit test --- src/darwin/Framework/CHIP/MTRDevice.mm | 51 ++++ .../CHIP/MTRDeviceConnectivityMonitor.h | 43 +++ .../CHIP/MTRDeviceConnectivityMonitor.mm | 257 ++++++++++++++++++ .../Framework/CHIP/MTRDeviceController.mm | 7 + .../CHIP/MTRDeviceController_Internal.h | 2 + .../MTRDeviceConnectivityMonitorTests.m | 88 ++++++ .../Matter.xcodeproj/project.pbxproj | 12 + 7 files changed, 460 insertions(+) create mode 100644 src/darwin/Framework/CHIP/MTRDeviceConnectivityMonitor.h create mode 100644 src/darwin/Framework/CHIP/MTRDeviceConnectivityMonitor.mm create mode 100644 src/darwin/Framework/CHIPTests/MTRDeviceConnectivityMonitorTests.m diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index 5226759ebf4b51..d171c3982701ba 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -28,6 +28,7 @@ #import "MTRCommandTimedCheck.h" #import "MTRConversion.h" #import "MTRDefines_Internal.h" +#import "MTRDeviceConnectivityMonitor.h" #import "MTRDeviceControllerOverXPC.h" #import "MTRDeviceController_Internal.h" #import "MTRDevice_Internal.h" @@ -355,6 +356,7 @@ @implementation MTRDevice { // _setupSubscription or via the auto-resubscribe behavior of the // ReadClient). Nil if we have had no such failures. NSDate * _Nullable _lastSubscriptionFailureTime; + MTRDeviceConnectivityMonitor * _connectivityMonitor; } - (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller @@ -669,6 +671,8 @@ - (void)invalidate // subscription. In that case, _internalDeviceState will update when the // subscription is actually terminated. + [self _stopConnectivityMonitoring]; + os_unfair_lock_unlock(&self->_lock); } @@ -861,6 +865,9 @@ - (void)_handleSubscriptionEstablished [self _changeState:MTRDeviceStateReachable]; + // No need to monitor connectivity after subscription establishment + [self _stopConnectivityMonitoring]; + os_unfair_lock_unlock(&self->_lock); os_unfair_lock_lock(&self->_timeSyncLock); @@ -894,6 +901,9 @@ - (void)_handleResubscriptionNeeded // former case we recently had a subscription and do not want to be forcing // retries immediately. _lastSubscriptionFailureTime = [NSDate now]; + + // Set up connectivity monitoring in case network routability changes for the positive, to accellerate resubscription + [self _setupConnectivityMonitoring]; } - (void)_handleSubscriptionReset:(NSNumber * _Nullable)retryDelay @@ -1241,6 +1251,44 @@ - (void)_createDataVersionFilterListFromDictionary:(NSDictionary_deviceController syncGetCompressedFabricID]; + if (!compressedFabricID) { + MTR_LOG_INFO("%@ could not get compressed fabricID", self); + return; + } + + // Now lock for _connectivityMonitor + std::lock_guard lock(self->_lock); + if (self->_connectivityMonitor) { + // already monitoring + return; + } + + self->_connectivityMonitor = [[MTRDeviceConnectivityMonitor alloc] initWithCompressedFabricID:compressedFabricID nodeID:self.nodeID]; + [self->_connectivityMonitor startMonitoringWithHandler:^{ + [self->_deviceController asyncDispatchToMatterQueue:^{ + [self _triggerResubscribeWithReason:"read-through skipped while not subscribed" nodeLikelyReachable:YES]; + } + errorHandler:nil]; + } queue:self.queue]; + }); +} + +- (void)_stopConnectivityMonitoring +{ + os_unfair_lock_assert_owner(&_lock); + + if (_connectivityMonitor) { + [_connectivityMonitor stopMonitoring]; + _connectivityMonitor = nil; + } +} + // assume lock is held - (void)_setupSubscription { @@ -1462,6 +1510,9 @@ - (void)_setupSubscription callback->AdoptClusterStateCache(std::move(clusterStateCache)); callback.release(); }]; + + // Set up connectivity monitoring in case network becomes routable after any part of the subscription process goes into backoff retries. + [self _setupConnectivityMonitoring]; } #ifdef DEBUG diff --git a/src/darwin/Framework/CHIP/MTRDeviceConnectivityMonitor.h b/src/darwin/Framework/CHIP/MTRDeviceConnectivityMonitor.h new file mode 100644 index 00000000000000..ee07aae159ef29 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDeviceConnectivityMonitor.h @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2023 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 + +typedef void (^MTRDeviceConnectivityMonitorHandler)(void); + +/** + * Class that a matter dns-sd instance name, and monitors connectivity to the device. + */ +MTR_TESTABLE +@interface MTRDeviceConnectivityMonitor : NSObject +- (instancetype)initWithCompressedFabricID:(NSNumber *)compressedFabricID nodeID:(NSNumber *)nodeID; + +/** + * Any time a path becomes satisfied or route becomes viable, the registered handler will be called. + */ +- (void)startMonitoringWithHandler:(MTRDeviceConnectivityMonitorHandler)handler queue:(dispatch_queue_t)queue; + +/** + * Stops the monitoring. After this method returns no more calls to the handler will be made. + */ +- (void)stopMonitoring; +@end + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRDeviceConnectivityMonitor.mm b/src/darwin/Framework/CHIP/MTRDeviceConnectivityMonitor.mm new file mode 100644 index 00000000000000..38c73b63a899d3 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDeviceConnectivityMonitor.mm @@ -0,0 +1,257 @@ +/** + * Copyright (c) 2023 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 "MTRDeviceConnectivityMonitor.h" +#import "MTRLogging_Internal.h" +#import "MTRUnfairLock.h" + +#import +#import +#import + +#include + +@implementation MTRDeviceConnectivityMonitor { + NSString * _instanceName; + DNSServiceRef _resolver; + NSMutableDictionary * _connections; + + MTRDeviceConnectivityMonitorHandler _monitorHandler; + dispatch_queue_t _handlerQueue; +} + +namespace { +constexpr char kLocalDot[] = "local."; +constexpr char kOperationalType[] = "_matter._tcp"; +constexpr int64_t kSharedConnectionLingerIntervalSeconds = (10); +} + +static os_unfair_lock sConnectivityMonitorLock = OS_UNFAIR_LOCK_INIT; +static NSUInteger sConnectivityMonitorCount; +static DNSServiceRef sSharedResolverConnection; +static dispatch_queue_t sSharedResolverQueue; + +- (instancetype)initWithInstanceName:(NSString *)instanceName +{ + if (self = [super init]) { + _instanceName = [instanceName copy]; + _connections = [NSMutableDictionary dictionary]; + } + return self; +} + +- (instancetype)initWithCompressedFabricID:(NSNumber *)compressedFabricID nodeID:(NSNumber *)nodeID +{ + char instanceName[chip::Dnssd::kMaxOperationalServiceNameSize]; + chip::PeerId peerId(static_cast(compressedFabricID.unsignedLongLongValue), static_cast(nodeID.unsignedLongLongValue)); + CHIP_ERROR err = chip::Dnssd::MakeInstanceName(instanceName, sizeof(instanceName), peerId); + if (err != CHIP_NO_ERROR) { + MTR_LOG_ERROR("%@ could not make instance name", self); + return nil; + } + + return [self initWithInstanceName:[NSString stringWithUTF8String:instanceName]]; +} + +- (void)dealloc +{ + if (_resolver) { + DNSServiceRefDeallocate(_resolver); + } +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"", _instanceName]; +} + ++ (DNSServiceRef)_sharedResolverConnection +{ + os_unfair_lock_assert_owner(&sConnectivityMonitorLock); + + if (!sSharedResolverConnection) { + DNSServiceErrorType dnsError = DNSServiceCreateConnection(&sSharedResolverConnection); + if (dnsError != kDNSServiceErr_NoError) { + MTR_LOG_ERROR("MTRDeviceConnectivityMonitor: DNSServiceCreateConnection failed %d", dnsError); + return NULL; + } + sSharedResolverQueue = dispatch_queue_create("MTRDeviceConnectivityMonitor", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); + dnsError = DNSServiceSetDispatchQueue(sSharedResolverConnection, sSharedResolverQueue); + if (dnsError != kDNSServiceErr_NoError) { + MTR_LOG_ERROR("%@ cannot set dispatch queue on resolve", self); + DNSServiceRefDeallocate(sSharedResolverConnection); + sSharedResolverConnection = NULL; + sSharedResolverQueue = nil; + return NULL; + } + } + + return sSharedResolverConnection; +} + +- (void)_callHandler +{ + os_unfair_lock_assert_owner(&sConnectivityMonitorLock); + MTRDeviceConnectivityMonitorHandler handlerToCall = self->_monitorHandler; + if (handlerToCall) { + dispatch_async(self->_handlerQueue, handlerToCall); + } +} + +- (void)handleResolvedHostname:(const char *)hostName port:(uint16_t)port error:(DNSServiceErrorType)error +{ + std::lock_guard lock(sConnectivityMonitorLock); + + // dns_sd.h: must check and call deallocate if error is kDNSServiceErr_ServiceNotRunning + if (error == kDNSServiceErr_ServiceNotRunning) { + MTR_LOG_ERROR("%@ disconnected from dns-sd subsystem", self); + [self _stopMonitoring]; + return; + } + + // Create a nw_connection to monitor connectivity if the host name is not being monitored yet + NSString * hostNameString = [NSString stringWithUTF8String:hostName]; + if (!_connections[hostNameString]) { + char portString[6]; + snprintf(portString, sizeof(portString), "%d", ntohs(port)); + nw_endpoint_t endpoint = nw_endpoint_create_host(hostName, portString); + if (!endpoint) { + MTR_LOG_ERROR("%@ failed to create endpoint for %s:%s", self, hostName, portString); + return; + } + nw_parameters_t params = nw_parameters_create_secure_udp(NW_PARAMETERS_DISABLE_PROTOCOL, NW_PARAMETERS_DEFAULT_CONFIGURATION); + if (!params) { + MTR_LOG_ERROR("%@ failed to create udp parameters", self); + return; + } + nw_connection_t connection = nw_connection_create(endpoint, params); + if (!connection) { + MTR_LOG_ERROR("%@ failed to create connection for %s:%s", self, hostName, portString); + return; + } + nw_connection_set_queue(connection, sSharedResolverQueue); + nw_connection_set_path_changed_handler(connection, ^(nw_path_t _Nonnull path) { + nw_path_status_t status = nw_path_get_status(path); + if (status == nw_path_status_satisfied) { + MTR_LOG_INFO("%@ path is satisfied", self); + std::lock_guard lock(sConnectivityMonitorLock); + [self _callHandler]; + } + }); + nw_connection_set_viability_changed_handler(connection, ^(bool viable) { + if (viable) { + std::lock_guard lock(sConnectivityMonitorLock); + MTR_LOG_INFO("%@ connectivity now viable", self); + [self _callHandler]; + } + }); + nw_connection_start(connection); + + _connections[hostNameString] = connection; + } +} + +static void ResolveCallback( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char * fullName, + const char * hostName, + uint16_t port, /* In network byte order */ + uint16_t txtLen, + const unsigned char * txtRecord, + void * context) +{ + auto * connectivityMonitor = (__bridge MTRDeviceConnectivityMonitor *) context; + [connectivityMonitor handleResolvedHostname:hostName port:port error:errorCode]; +} + +- (void)startMonitoringWithHandler:(MTRDeviceConnectivityMonitorHandler)handler queue:(dispatch_queue_t)queue +{ + std::lock_guard lock(sConnectivityMonitorLock); + + _monitorHandler = handler; + _handlerQueue = queue; + + // If there's already a resolver running, just return + if (_resolver) { + MTR_LOG_INFO("%@ connectivity monitor already running", self); + return; + } + + MTR_LOG_INFO("%@ start connectivity monitoring for %@ (%lu monitoring objects)", self, _instanceName, static_cast(sConnectivityMonitorCount)); + + _resolver = [MTRDeviceConnectivityMonitor _sharedResolverConnection]; + if (!_resolver) { + MTR_LOG_ERROR("%@ failed to get shared resolver connection", self); + return; + } + DNSServiceErrorType dnsError = DNSServiceResolve(&_resolver, + kDNSServiceFlagsShareConnection, + kDNSServiceInterfaceIndexAny, + _instanceName.UTF8String, + kOperationalType, + kLocalDot, + ResolveCallback, + (__bridge void *) self); + if (dnsError != kDNSServiceErr_NoError) { + MTR_LOG_ERROR("%@ failed to create resolver", self); + return; + } + + sConnectivityMonitorCount++; +} + +- (void)_stopMonitoring +{ + os_unfair_lock_assert_owner(&sConnectivityMonitorLock); + for (NSString * hostName in _connections) { + nw_connection_cancel(_connections[hostName]); + } + [_connections removeAllObjects]; + + _monitorHandler = nil; + _handlerQueue = nil; + + if (_resolver) { + DNSServiceRefDeallocate(_resolver); + _resolver = NULL; + + // If no monitor objects exist, schedule to deallocate shared connection and queue + sConnectivityMonitorCount--; + if (!sConnectivityMonitorCount) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kSharedConnectionLingerIntervalSeconds * NSEC_PER_SEC), sSharedResolverQueue, ^{ + std::lock_guard lock(sConnectivityMonitorLock); + + if (!sConnectivityMonitorCount) { + MTR_LOG_INFO("MTRDeviceConnectivityMonitor: Closing shared resolver connection"); + DNSServiceRefDeallocate(sSharedResolverConnection); + sSharedResolverConnection = NULL; + sSharedResolverQueue = nil; + } + }); + } + } +} + +- (void)stopMonitoring +{ + MTR_LOG_INFO("%@ stop connectivity monitoring for %@", self, _instanceName); + std::lock_guard lock(sConnectivityMonitorLock); + [self _stopMonitoring]; +} +@end diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm index e9610de2fea0a7..6150b1cc748da1 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm @@ -1368,6 +1368,13 @@ - (nullable NSNumber *)compressedFabricID return @(_cppCommissioner->GetCompressedFabricId()); } +- (NSNumber * _Nullable)syncGetCompressedFabricID +{ + return [self syncRunOnWorkQueueWithReturnValue:^NSNumber * { + return [self compressedFabricID]; + } error:nil]; +} + - (CHIP_ERROR)isRunningOnFabric:(chip::FabricTable *)fabricTable fabricIndex:(chip::FabricIndex)fabricIndex isRunning:(BOOL *)isRunning diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h index aa9c8c1e906ee3..4235f17cb5a0dc 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h +++ b/src/darwin/Framework/CHIP/MTRDeviceController_Internal.h @@ -256,6 +256,8 @@ NS_ASSUME_NONNULL_BEGIN - (MTRDevice *)deviceForNodeID:(NSNumber *)nodeID; - (void)removeDevice:(MTRDevice *)device; +- (NSNumber * _Nullable)syncGetCompressedFabricID; + @end NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIPTests/MTRDeviceConnectivityMonitorTests.m b/src/darwin/Framework/CHIPTests/MTRDeviceConnectivityMonitorTests.m new file mode 100644 index 00000000000000..2c3cabc2ef39ee --- /dev/null +++ b/src/darwin/Framework/CHIPTests/MTRDeviceConnectivityMonitorTests.m @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2023 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 "MTRDeviceConnectivityMonitor.h" + +@interface MTRDeviceConnectivityMonitor (Test) +- (instancetype)initWithInstanceName:(NSString *)instanceName; +@end + +@interface MTRDeviceConnectivityMonitorTests : XCTestCase +@end + +@implementation MTRDeviceConnectivityMonitorTests + +static DNSServiceRef sSharedConnection; + ++ (void)setUp +{ + DNSServiceErrorType dnsError = DNSServiceCreateConnection(&sSharedConnection); + XCTAssertEqual(dnsError, kDNSServiceErr_NoError); +} + ++ (void)tearDown +{ + DNSServiceRefDeallocate(sSharedConnection); +} + +static char kLocalDot[] = "local."; +static char kOperationalType[] = "_matter._tcp"; + +static void test001_MonitorTest_RegisterCallback( + DNSServiceRef sdRef, + DNSServiceFlags flags, + DNSServiceErrorType errorCode, + const char * name, + const char * regtype, + const char * domain, + void * context) +{ +} + +- (void)test001_BasicMonitorTest +{ + dispatch_queue_t testQueue = dispatch_queue_create("connectivity-monitor-test-queue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); + DNSServiceRef testAdvertiser; + DNSServiceFlags flags = kDNSServiceFlagsNoAutoRename; + char testInstanceName[] = "testinstance-name"; + char testHostName[] = "localhost"; + uint16_t testPort = htons(15000); + DNSServiceErrorType dnsError = DNSServiceRegister(&testAdvertiser, flags, 0, testInstanceName, kOperationalType, kLocalDot, testHostName, testPort, 0, NULL, test001_MonitorTest_RegisterCallback, NULL); + XCTAssertEqual(dnsError, kDNSServiceErr_NoError); + + XCTestExpectation * connectivityMonitorCallbackExpectation = [self expectationWithDescription:@"Got connectivity monitor callback"]; + __block BOOL gotConnectivityMonitorCallback = NO; + + MTRDeviceConnectivityMonitor * monitor = [[MTRDeviceConnectivityMonitor alloc] initWithInstanceName:@(testInstanceName)]; + [monitor startMonitoringWithHandler:^{ + if (!gotConnectivityMonitorCallback) { + gotConnectivityMonitorCallback = YES; + [connectivityMonitorCallbackExpectation fulfill]; + } + } queue:testQueue]; + + [self waitForExpectations:@[ connectivityMonitorCallbackExpectation ] timeout:5]; + + [monitor stopMonitoring]; + DNSServiceRefDeallocate(testAdvertiser); +} + +@end diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 846db8bd08ee26..78ff18793ebd5c 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -264,6 +264,9 @@ 75A202E52BA8DBAC00A771DD /* reporting.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 75A202E42BA8DBAC00A771DD /* reporting.cpp */; }; 75A202E62BA8DBAC00A771DD /* reporting.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 75A202E42BA8DBAC00A771DD /* reporting.cpp */; }; 75B0D01E2B71B47F002074DD /* MTRDeviceTestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 75B0D01D2B71B47F002074DD /* MTRDeviceTestDelegate.m */; }; + 75B3269C2BCDB9D600E17C4E /* MTRDeviceConnectivityMonitor.h in Headers */ = {isa = PBXBuildFile; fileRef = 75B3269B2BCDB9D600E17C4E /* MTRDeviceConnectivityMonitor.h */; }; + 75B3269E2BCDB9EA00E17C4E /* MTRDeviceConnectivityMonitor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 75B3269D2BCDB9EA00E17C4E /* MTRDeviceConnectivityMonitor.mm */; }; + 75B326A22BCF12E900E17C4E /* MTRDeviceConnectivityMonitorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 75B326A12BCF12E900E17C4E /* MTRDeviceConnectivityMonitorTests.m */; }; 75B765C12A1D71BC0014719B /* MTRAttributeSpecifiedCheck.h in Headers */ = {isa = PBXBuildFile; fileRef = 75B765C02A1D71BC0014719B /* MTRAttributeSpecifiedCheck.h */; }; 75B765C32A1D82D30014719B /* MTRAttributeSpecifiedCheck.mm in Sources */ = {isa = PBXBuildFile; fileRef = 75B765C22A1D82D30014719B /* MTRAttributeSpecifiedCheck.mm */; }; 8874C1322B69C7060084BEFD /* MTRMetricsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8874C1312B69C7060084BEFD /* MTRMetricsTests.m */; }; @@ -673,6 +676,9 @@ 75A202E42BA8DBAC00A771DD /* reporting.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = reporting.cpp; sourceTree = ""; }; 75B0D01C2B71B46F002074DD /* MTRDeviceTestDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRDeviceTestDelegate.h; sourceTree = ""; }; 75B0D01D2B71B47F002074DD /* MTRDeviceTestDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MTRDeviceTestDelegate.m; sourceTree = ""; }; + 75B3269B2BCDB9D600E17C4E /* MTRDeviceConnectivityMonitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRDeviceConnectivityMonitor.h; sourceTree = ""; }; + 75B3269D2BCDB9EA00E17C4E /* MTRDeviceConnectivityMonitor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDeviceConnectivityMonitor.mm; sourceTree = ""; }; + 75B326A12BCF12E900E17C4E /* MTRDeviceConnectivityMonitorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MTRDeviceConnectivityMonitorTests.m; sourceTree = ""; }; 75B765BF2A1D70F80014719B /* MTRAttributeSpecifiedCheck-src.zapt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "MTRAttributeSpecifiedCheck-src.zapt"; sourceTree = ""; }; 75B765C02A1D71BC0014719B /* MTRAttributeSpecifiedCheck.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRAttributeSpecifiedCheck.h; sourceTree = ""; }; 75B765C22A1D82D30014719B /* MTRAttributeSpecifiedCheck.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRAttributeSpecifiedCheck.mm; sourceTree = ""; }; @@ -1273,6 +1279,8 @@ 88EBF8CC27FABDD500686BC1 /* MTRDeviceAttestationDelegateBridge.mm */, 2C5EEEF4268A85C400CAE3D3 /* MTRDeviceConnectionBridge.h */, 2C5EEEF5268A85C400CAE3D3 /* MTRDeviceConnectionBridge.mm */, + 75B3269B2BCDB9D600E17C4E /* MTRDeviceConnectivityMonitor.h */, + 75B3269D2BCDB9EA00E17C4E /* MTRDeviceConnectivityMonitor.mm */, 991DC0822475F45400C13860 /* MTRDeviceController.h */, 5136660F28067D540025EDAE /* MTRDeviceController_Internal.h */, 991DC0872475F47D00C13860 /* MTRDeviceController.mm */, @@ -1372,6 +1380,7 @@ 99C65E0F267282F1003402F6 /* MTRControllerTests.m */, 51A2F1312A00402A00F03298 /* MTRDataValueParserTests.m */, 5AE6D4E327A99041001F2493 /* MTRDeviceTests.m */, + 75B326A12BCF12E900E17C4E /* MTRDeviceConnectivityMonitorTests.m */, 51D9CB0A2BA37DCE0049D6DB /* MTRDSTOffsetTests.m */, 3D0C484A29DA4FA0006D811F /* MTRErrorTests.m */, 5173A47829C0E82300F67F48 /* MTRFabricInfoTests.m */, @@ -1559,6 +1568,7 @@ 3D843717294979230070D20A /* MTRClusters_Internal.h in Headers */, 7596A85728788557004DAE0E /* MTRClusters.h in Headers */, 99D466E12798936D0089A18F /* MTRCommissioningParameters.h in Headers */, + 75B3269C2BCDB9D600E17C4E /* MTRDeviceConnectivityMonitor.h in Headers */, 5136661528067D550025EDAE /* MTRDeviceControllerFactory_Internal.h in Headers */, 515C1C70284F9FFB00A48F0C /* MTRFramework.h in Headers */, 7534F12928BFF20300390851 /* MTRDeviceAttestationDelegate_Internal.h in Headers */, @@ -1929,6 +1939,7 @@ 510470FB2A2F7DF60053EA7E /* MTRBackwardsCompatShims.mm in Sources */, 5173A47629C0E2ED00F67F48 /* MTRFabricInfo.mm in Sources */, 5ACDDD7D27CD16D200EFD68A /* MTRClusterStateCacheContainer.mm in Sources */, + 75B3269E2BCDB9EA00E17C4E /* MTRDeviceConnectivityMonitor.mm in Sources */, 513DDB8A2761F6F900DAA01A /* MTRAttributeTLVValueDecoder.mm in Sources */, 5117DD3829A931AE00FFA1AA /* MTROperationalBrowser.mm in Sources */, 514C79F02B62ADDA00DD6D7B /* descriptor.cpp in Sources */, @@ -1980,6 +1991,7 @@ 518D3F832AA132DC008E0007 /* MTRTestPerControllerStorage.m in Sources */, 51339B1F2A0DA64D00C798C1 /* MTRCertificateValidityTests.m in Sources */, 5173A47929C0E82300F67F48 /* MTRFabricInfoTests.m in Sources */, + 75B326A22BCF12E900E17C4E /* MTRDeviceConnectivityMonitorTests.m in Sources */, 5143851E2A65885500EDC8E6 /* MTRSwiftPairingTests.swift in Sources */, 75B0D01E2B71B47F002074DD /* MTRDeviceTestDelegate.m in Sources */, 3D0C484B29DA4FA0006D811F /* MTRErrorTests.m in Sources */,