Skip to content

Commit

Permalink
[Darwin] MTRDevice should trigger resubscription on connectivity changes
Browse files Browse the repository at this point in the history
  • Loading branch information
jtung-apple committed Apr 16, 2024
1 parent c065cc7 commit 0d1a263
Show file tree
Hide file tree
Showing 6 changed files with 339 additions and 0 deletions.
46 changes: 46 additions & 0 deletions src/darwin/Framework/CHIP/MTRDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -46,6 +47,7 @@
#include <app/BufferedReadCallback.h>
#include <app/ClusterStateCache.h>
#include <app/InteractionModelEngine.h>
#include <lib/dnssd/ServiceNaming.h>
#include <platform/LockTracker.h>
#include <platform/PlatformManager.h>

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -1240,6 +1250,39 @@ - (void)_createDataVersionFilterListFromDictionary:(NSDictionary<MTRClusterPath
*count = maxDataVersionFilterSize;
}

- (void)_setupConnectivityMonitoring
{
std::lock_guard lock(_lock);

if (_connectivityMonitor) {
// already monitoring
return;
}

// Get the required info before setting up the connectivity monitor
NSNumber * compressedFabricID = [_deviceController syncGetCompressedFabricID];
if (!compressedFabricID) {
MTR_LOG_INFO("%@ could not get compressed fabricID", self);
return;
}

char instanceName[chip::Dnssd::kMaxOperationalServiceNameSize];
chip::PeerId peerId(static_cast<chip::CompressedFabricId>(compressedFabricID.unsignedLongLongValue), static_cast<chip::NodeId>(_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
{
Expand Down Expand Up @@ -1283,6 +1326,9 @@ - (void)_setupSubscription
dispatch_async(self.queue, ^{
[self _handleSubscriptionError:error];
[self _handleSubscriptionReset:retryDelay];

// On inability to get session, set up connectivity monitoring
[self _setupConnectivityMonitoring];
});
return;
}
Expand Down
29 changes: 29 additions & 0 deletions src/darwin/Framework/CHIP/MTRDeviceConnectivityMonitor.h
Original file line number Diff line number Diff line change
@@ -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 <Foundation/Foundation.h>

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
247 changes: 247 additions & 0 deletions src/darwin/Framework/CHIP/MTRDeviceConnectivityMonitor.mm
Original file line number Diff line number Diff line change
@@ -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 <Network/Network.h>
#import <dns_sd.h>
#import <os/lock.h>

@interface MTRDeviceConnectivityMonitor ()
- (void)handleResolvedHostname:(const char *)hostName port:(uint16_t)port error:(DNSServiceErrorType)error;
@end

@implementation MTRDeviceConnectivityMonitor {
NSString * _instanceName;
DNSServiceRef _resolver;
NSMutableDictionary<NSString *, nw_connection_t> * _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:@"<MTRDeviceConnectivityMonitor: %@>", _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<unsigned long>(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
7 changes: 7 additions & 0 deletions src/darwin/Framework/CHIP/MTRDeviceController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/darwin/Framework/CHIP/MTRDeviceController_Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ NS_ASSUME_NONNULL_BEGIN
- (MTRDevice *)deviceForNodeID:(NSNumber *)nodeID;
- (void)removeDevice:(MTRDevice *)device;

- (NSNumber * _Nullable)syncGetCompressedFabricID;

@end

NS_ASSUME_NONNULL_END
Loading

0 comments on commit 0d1a263

Please sign in to comment.