From 27c30ff4bdea9659b102b4766a945140b2713b1a Mon Sep 17 00:00:00 2001 From: Jeff Tung <100387939+jtung-apple@users.noreply.github.com> Date: Mon, 15 Apr 2024 14:14:13 -0700 Subject: [PATCH] [Darwin] MTRDevice should trigger resubscription on connectivity changes --- src/darwin/Framework/CHIP/MTRDevice.mm | 49 ++++ .../CHIP/MTRDeviceConnectivityMonitor.h | 29 ++ .../CHIP/MTRDeviceConnectivityMonitor.mm | 247 ++++++++++++++++++ .../Framework/CHIP/MTRDeviceController.mm | 7 + .../CHIP/MTRDeviceController_Internal.h | 2 + .../Matter.xcodeproj/project.pbxproj | 8 + 6 files changed, 342 insertions(+) create mode 100644 src/darwin/Framework/CHIP/MTRDeviceConnectivityMonitor.h create mode 100644 src/darwin/Framework/CHIP/MTRDeviceConnectivityMonitor.mm diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index 7c73524c963635..068f723e7020b0 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" @@ -46,6 +47,7 @@ #include #include #include +#include #include #include @@ -354,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 @@ -668,6 +671,9 @@ - (void)invalidate // subscription. In that case, _internalDeviceState will update when the // subscription is actually terminated. + [_connectivityMonitor stopMonitoring]; + _connectivityMonitor = nil; + os_unfair_lock_unlock(&self->_lock); } @@ -860,6 +866,10 @@ - (void)_handleSubscriptionEstablished [self _changeState:MTRDeviceStateReachable]; + // No need to monitor connectivity after subscription establishment + [_connectivityMonitor stopMonitoring]; + _connectivityMonitor = nil; + os_unfair_lock_unlock(&self->_lock); os_unfair_lock_lock(&self->_timeSyncLock); @@ -893,6 +903,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 @@ -1240,6 +1253,39 @@ - (void)_createDataVersionFilterListFromDictionary:(NSDictionary(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; + } + + _connectivityMonitor = [[MTRDeviceConnectivityMonitor alloc] initWithInstanceName:[NSString stringWithUTF8String:instanceName]]; + [_connectivityMonitor startMonitoringWithHandler:^{ + [self->_deviceController asyncDispatchToMatterQueue:^{ + [self _triggerResubscribeWithReason:"read-through skipped while not subscribed" nodeLikelyReachable:YES]; + } + errorHandler:nil]; + } queue:_queue]; +} + // assume lock is held - (void)_setupSubscription { @@ -1461,6 +1507,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..4369f720f4fa3e --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDeviceConnectivityMonitor.h @@ -0,0 +1,29 @@ +/** + * 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 + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^MTRDeviceConnectivityMonitorHandler)(void); + +@interface MTRDeviceConnectivityMonitor : NSObject +- (instancetype)initWithInstanceName:(NSString *)instanceName; +- (void)startMonitoringWithHandler:(MTRDeviceConnectivityMonitorHandler)handler queue:(dispatch_queue_t)queue; +- (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..26bb6e27e55a4c --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDeviceConnectivityMonitor.mm @@ -0,0 +1,247 @@ +/** + * 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 + +@interface MTRDeviceConnectivityMonitor () +- (void)handleResolvedHostname:(const char *)hostName port:(uint16_t)port error:(DNSServiceErrorType)error; +@end + +@implementation MTRDeviceConnectivityMonitor { + NSString * _instanceName; + DNSServiceRef _resolver; + NSMutableDictionary * _connections; + + MTRDeviceConnectivityMonitorHandler _monitorHandler; + dispatch_queue_t _handlerQueue; +} + +namespace { +constexpr char kLocalDot[] = "local."; +constexpr char kOperationalType[] = "_matter._tcp"; +} + +static dispatch_once_t sConnecitivityMonitorOnceToken; +static os_unfair_lock sConnectivityMonitorLock; +static NSUInteger sConnectivityMonitorCount; +static DNSServiceRef sSharedResolverConnection; +static dispatch_queue_t sSharedResolverQueue; + +- (instancetype)initWithInstanceName:(NSString *)instanceName +{ + if (self = [super init]) { + dispatch_once(&sConnecitivityMonitorOnceToken, ^{ + sConnectivityMonitorLock = OS_UNFAIR_LOCK_INIT; + }); + _instanceName = [instanceName copy]; + _connections = [NSMutableDictionary dictionary]; + } + return self; +} + +- (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) { + 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); + } +} + +static void _resolveReplyCallback( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char * fullname, + const char * hosttarget, + uint16_t port, /* In network byte order */ + uint16_t txtLen, + const unsigned char * txtRecord, + void * context) +{ + auto * connectivityMonitor = (__bridge MTRDeviceConnectivityMonitor *) context; + [connectivityMonitor handleResolvedHostname:hosttarget port:port error:errorCode]; +} + +- (void)startMonitoringWithHandler:(MTRDeviceConnectivityMonitorHandler)handler queue:(dispatch_queue_t)queue +{ + std::lock_guard lock(sConnectivityMonitorLock); + + MTRDeviceConnectivityMonitorHandler handlerCopy = [handler copy]; + _monitorHandler = handlerCopy; + _handlerQueue = queue; + + // If there's already a resolver running, just return + if (_resolver) { + MTR_LOG_INFO("%@ connectivity monitor updated handler", 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, + _resolveReplyCallback, + (__bridge void *) self); + if (dnsError != kDNSServiceErr_NoError) { + MTR_LOG_ERROR("%@ failed to create resolver", self); + return; + } + + sConnectivityMonitorCount++; +} + +#define MTRDEVICECONNECTIVITYMONITOR_SHARED_CONNECTION_LINGER_INTERVAL (10) + +- (void)_stopMonitoring +{ + os_unfair_lock_assert_owner(&sConnectivityMonitorLock); + for (NSString * hostName in _connections) { + nw_connection_cancel(_connections[hostName]); + } + [_connections removeAllObjects]; + + 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, (int64_t) (MTRDEVICECONNECTIVITYMONITOR_SHARED_CONNECTION_LINGER_INTERVAL * NSEC_PER_SEC)), sSharedResolverQueue, ^{ + std::lock_guard lock(sConnectivityMonitorLock); + + if (!sConnectivityMonitorCount) { + MTR_LOG_INFO("%@ Closing shared resolver connection", self); + 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 87490eecf6e7a6..dc1db2547f6fae 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm @@ -1372,6 +1372,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/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 846db8bd08ee26..9b6b2a3a3eb46f 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -264,6 +264,8 @@ 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 */; }; 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 +675,8 @@ 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 = ""; }; 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 +1277,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 */, @@ -1559,6 +1565,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 +1936,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 */,