Skip to content

Commit f984e7c

Browse files
committed
Address review comments and add unit test
1 parent f08b6d7 commit f984e7c

File tree

5 files changed

+171
-53
lines changed

5 files changed

+171
-53
lines changed

src/darwin/Framework/CHIP/MTRDevice.mm

+32-30
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
#include <app/BufferedReadCallback.h>
4848
#include <app/ClusterStateCache.h>
4949
#include <app/InteractionModelEngine.h>
50-
#include <lib/dnssd/ServiceNaming.h>
5150
#include <platform/LockTracker.h>
5251
#include <platform/PlatformManager.h>
5352

@@ -672,8 +671,7 @@ - (void)invalidate
672671
// subscription. In that case, _internalDeviceState will update when the
673672
// subscription is actually terminated.
674673

675-
[_connectivityMonitor stopMonitoring];
676-
_connectivityMonitor = nil;
674+
[self _stopConnectivityMonitoring];
677675

678676
os_unfair_lock_unlock(&self->_lock);
679677
}
@@ -868,8 +866,7 @@ - (void)_handleSubscriptionEstablished
868866
[self _changeState:MTRDeviceStateReachable];
869867

870868
// No need to monitor connectivity after subscription establishment
871-
[_connectivityMonitor stopMonitoring];
872-
_connectivityMonitor = nil;
869+
[self _stopConnectivityMonitoring];
873870

874871
os_unfair_lock_unlock(&self->_lock);
875872

@@ -1256,35 +1253,40 @@ - (void)_createDataVersionFilterListFromDictionary:(NSDictionary<MTRClusterPath
12561253

12571254
- (void)_setupConnectivityMonitoring
12581255
{
1259-
std::lock_guard lock(_lock);
1256+
// Dispatch to own queue first to avoid deadlock with syncGetCompressedFabricID
1257+
dispatch_async(self.queue, ^{
1258+
// Get the required info before setting up the connectivity monitor
1259+
NSNumber * compressedFabricID = [self->_deviceController syncGetCompressedFabricID];
1260+
if (!compressedFabricID) {
1261+
MTR_LOG_INFO("%@ could not get compressed fabricID", self);
1262+
return;
1263+
}
12601264

1261-
if (_connectivityMonitor) {
1262-
// already monitoring
1263-
return;
1264-
}
1265+
// Now lock for _connectivityMonitor
1266+
std::lock_guard lock(self->_lock);
1267+
if (self->_connectivityMonitor) {
1268+
// already monitoring
1269+
return;
1270+
}
12651271

1266-
// Get the required info before setting up the connectivity monitor
1267-
NSNumber * compressedFabricID = [_deviceController syncGetCompressedFabricID];
1268-
if (!compressedFabricID) {
1269-
MTR_LOG_INFO("%@ could not get compressed fabricID", self);
1270-
return;
1271-
}
1272+
self->_connectivityMonitor = [[MTRDeviceConnectivityMonitor alloc] initWithCompressedFabricID:compressedFabricID nodeID:self.nodeID];
1273+
[self->_connectivityMonitor startMonitoringWithHandler:^{
1274+
[self->_deviceController asyncDispatchToMatterQueue:^{
1275+
[self _triggerResubscribeWithReason:"read-through skipped while not subscribed" nodeLikelyReachable:YES];
1276+
}
1277+
errorHandler:nil];
1278+
} queue:self.queue];
1279+
});
1280+
}
12721281

1273-
char instanceName[chip::Dnssd::kMaxOperationalServiceNameSize];
1274-
chip::PeerId peerId(static_cast<chip::CompressedFabricId>(compressedFabricID.unsignedLongLongValue), static_cast<chip::NodeId>(_nodeID.unsignedLongLongValue));
1275-
CHIP_ERROR err = chip::Dnssd::MakeInstanceName(instanceName, sizeof(instanceName), peerId);
1276-
if (err != CHIP_NO_ERROR) {
1277-
MTR_LOG_ERROR("%@ could not make instance name", self);
1278-
return;
1279-
}
1282+
- (void)_stopConnectivityMonitoring
1283+
{
1284+
os_unfair_lock_assert_owner(&_lock);
12801285

1281-
_connectivityMonitor = [[MTRDeviceConnectivityMonitor alloc] initWithInstanceName:[NSString stringWithUTF8String:instanceName]];
1282-
[_connectivityMonitor startMonitoringWithHandler:^{
1283-
[self->_deviceController asyncDispatchToMatterQueue:^{
1284-
[self _triggerResubscribeWithReason:"read-through skipped while not subscribed" nodeLikelyReachable:YES];
1285-
}
1286-
errorHandler:nil];
1287-
} queue:_queue];
1286+
if (_connectivityMonitor) {
1287+
[_connectivityMonitor stopMonitoring];
1288+
_connectivityMonitor = nil;
1289+
}
12881290
}
12891291

12901292
// assume lock is held

src/darwin/Framework/CHIP/MTRDeviceConnectivityMonitor.h

+15-1
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,27 @@
1616

1717
#import <Foundation/Foundation.h>
1818

19+
#import "MTRDefines_Internal.h"
20+
1921
NS_ASSUME_NONNULL_BEGIN
2022

2123
typedef void (^MTRDeviceConnectivityMonitorHandler)(void);
2224

25+
/**
26+
* Class that a matter dns-sd instance name, and monitors connectivity to the device.
27+
*/
28+
MTR_TESTABLE
2329
@interface MTRDeviceConnectivityMonitor : NSObject
24-
- (instancetype)initWithInstanceName:(NSString *)instanceName;
30+
- (instancetype)initWithCompressedFabricID:(NSNumber *)compressedFabricID nodeID:(NSNumber *)nodeID;
31+
32+
/**
33+
* Any time a path becomes satisfied or route becomes viable, the registered handler will be called.
34+
*/
2535
- (void)startMonitoringWithHandler:(MTRDeviceConnectivityMonitorHandler)handler queue:(dispatch_queue_t)queue;
36+
37+
/**
38+
* Stops the monitoring. After this method returns no more calls to the handler will be made.
39+
*/
2640
- (void)stopMonitoring;
2741
@end
2842

src/darwin/Framework/CHIP/MTRDeviceConnectivityMonitor.mm

+32-22
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@
2222
#import <dns_sd.h>
2323
#import <os/lock.h>
2424

25-
@interface MTRDeviceConnectivityMonitor ()
26-
- (void)handleResolvedHostname:(const char *)hostName port:(uint16_t)port error:(DNSServiceErrorType)error;
27-
@end
25+
#include <lib/dnssd/ServiceNaming.h>
2826

2927
@implementation MTRDeviceConnectivityMonitor {
3028
NSString * _instanceName;
@@ -38,26 +36,36 @@ @implementation MTRDeviceConnectivityMonitor {
3836
namespace {
3937
constexpr char kLocalDot[] = "local.";
4038
constexpr char kOperationalType[] = "_matter._tcp";
39+
constexpr int64_t kSharedConnectionLingerIntervalSeconds = (10);
4140
}
4241

43-
static dispatch_once_t sConnecitivityMonitorOnceToken;
44-
static os_unfair_lock sConnectivityMonitorLock;
42+
static os_unfair_lock sConnectivityMonitorLock = OS_UNFAIR_LOCK_INIT;
4543
static NSUInteger sConnectivityMonitorCount;
4644
static DNSServiceRef sSharedResolverConnection;
4745
static dispatch_queue_t sSharedResolverQueue;
4846

4947
- (instancetype)initWithInstanceName:(NSString *)instanceName
5048
{
5149
if (self = [super init]) {
52-
dispatch_once(&sConnecitivityMonitorOnceToken, ^{
53-
sConnectivityMonitorLock = OS_UNFAIR_LOCK_INIT;
54-
});
5550
_instanceName = [instanceName copy];
5651
_connections = [NSMutableDictionary dictionary];
5752
}
5853
return self;
5954
}
6055

56+
- (instancetype)initWithCompressedFabricID:(NSNumber *)compressedFabricID nodeID:(NSNumber *)nodeID
57+
{
58+
char instanceName[chip::Dnssd::kMaxOperationalServiceNameSize];
59+
chip::PeerId peerId(static_cast<chip::CompressedFabricId>(compressedFabricID.unsignedLongLongValue), static_cast<chip::NodeId>(nodeID.unsignedLongLongValue));
60+
CHIP_ERROR err = chip::Dnssd::MakeInstanceName(instanceName, sizeof(instanceName), peerId);
61+
if (err != CHIP_NO_ERROR) {
62+
MTR_LOG_ERROR("%@ could not make instance name", self);
63+
return nil;
64+
}
65+
66+
return [self initWithInstanceName:[NSString stringWithUTF8String:instanceName]];
67+
}
68+
6169
- (void)dealloc
6270
{
6371
if (_resolver) {
@@ -76,7 +84,7 @@ + (DNSServiceRef)_sharedResolverConnection
7684

7785
if (!sSharedResolverConnection) {
7886
DNSServiceErrorType dnsError = DNSServiceCreateConnection(&sSharedResolverConnection);
79-
if (dnsError) {
87+
if (dnsError != kDNSServiceErr_NoError) {
8088
MTR_LOG_ERROR("MTRDeviceConnectivityMonitor: DNSServiceCreateConnection failed %d", dnsError);
8189
return NULL;
8290
}
@@ -99,7 +107,7 @@ - (void)_callHandler
99107
os_unfair_lock_assert_owner(&sConnectivityMonitorLock);
100108
MTRDeviceConnectivityMonitorHandler handlerToCall = self->_monitorHandler;
101109
if (handlerToCall) {
102-
dispatch_async(self->_handlerQueue, ^{ handlerToCall(); });
110+
dispatch_async(self->_handlerQueue, handlerToCall);
103111
}
104112
}
105113

@@ -151,36 +159,37 @@ - (void)handleResolvedHostname:(const char *)hostName port:(uint16_t)port error:
151159
}
152160
});
153161
nw_connection_start(connection);
162+
163+
_connections[hostNameString] = connection;
154164
}
155165
}
156166

157-
static void _resolveReplyCallback(
167+
static void ResolveCallback(
158168
DNSServiceRef sdRef,
159169
DNSServiceFlags flags,
160170
uint32_t interfaceIndex,
161171
DNSServiceErrorType errorCode,
162-
const char * fullname,
163-
const char * hosttarget,
172+
const char * fullName,
173+
const char * hostName,
164174
uint16_t port, /* In network byte order */
165175
uint16_t txtLen,
166176
const unsigned char * txtRecord,
167177
void * context)
168178
{
169179
auto * connectivityMonitor = (__bridge MTRDeviceConnectivityMonitor *) context;
170-
[connectivityMonitor handleResolvedHostname:hosttarget port:port error:errorCode];
180+
[connectivityMonitor handleResolvedHostname:hostName port:port error:errorCode];
171181
}
172182

173183
- (void)startMonitoringWithHandler:(MTRDeviceConnectivityMonitorHandler)handler queue:(dispatch_queue_t)queue
174184
{
175185
std::lock_guard lock(sConnectivityMonitorLock);
176186

177-
MTRDeviceConnectivityMonitorHandler handlerCopy = [handler copy];
178-
_monitorHandler = handlerCopy;
187+
_monitorHandler = handler;
179188
_handlerQueue = queue;
180189

181190
// If there's already a resolver running, just return
182191
if (_resolver) {
183-
MTR_LOG_INFO("%@ connectivity monitor updated handler", self);
192+
MTR_LOG_INFO("%@ connectivity monitor already running", self);
184193
return;
185194
}
186195

@@ -197,7 +206,7 @@ - (void)startMonitoringWithHandler:(MTRDeviceConnectivityMonitorHandler)handler
197206
_instanceName.UTF8String,
198207
kOperationalType,
199208
kLocalDot,
200-
_resolveReplyCallback,
209+
ResolveCallback,
201210
(__bridge void *) self);
202211
if (dnsError != kDNSServiceErr_NoError) {
203212
MTR_LOG_ERROR("%@ failed to create resolver", self);
@@ -207,8 +216,6 @@ - (void)startMonitoringWithHandler:(MTRDeviceConnectivityMonitorHandler)handler
207216
sConnectivityMonitorCount++;
208217
}
209218

210-
#define MTRDEVICECONNECTIVITYMONITOR_SHARED_CONNECTION_LINGER_INTERVAL (10)
211-
212219
- (void)_stopMonitoring
213220
{
214221
os_unfair_lock_assert_owner(&sConnectivityMonitorLock);
@@ -217,18 +224,21 @@ - (void)_stopMonitoring
217224
}
218225
[_connections removeAllObjects];
219226

227+
_monitorHandler = nil;
228+
_handlerQueue = nil;
229+
220230
if (_resolver) {
221231
DNSServiceRefDeallocate(_resolver);
222232
_resolver = NULL;
223233

224234
// If no monitor objects exist, schedule to deallocate shared connection and queue
225235
sConnectivityMonitorCount--;
226236
if (!sConnectivityMonitorCount) {
227-
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) (MTRDEVICECONNECTIVITYMONITOR_SHARED_CONNECTION_LINGER_INTERVAL * NSEC_PER_SEC)), sSharedResolverQueue, ^{
237+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kSharedConnectionLingerIntervalSeconds * NSEC_PER_SEC), sSharedResolverQueue, ^{
228238
std::lock_guard lock(sConnectivityMonitorLock);
229239

230240
if (!sConnectivityMonitorCount) {
231-
MTR_LOG_INFO("%@ Closing shared resolver connection", self);
241+
MTR_LOG_INFO("MTRDeviceConnectivityMonitor: Closing shared resolver connection");
232242
DNSServiceRefDeallocate(sSharedResolverConnection);
233243
sSharedResolverConnection = NULL;
234244
sSharedResolverQueue = nil;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* Copyright (c) 2023 Project CHIP Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import <XCTest/XCTest.h>
18+
19+
#import <Network/Network.h>
20+
#import <dns_sd.h>
21+
22+
#import "MTRDeviceConnectivityMonitor.h"
23+
24+
@interface MTRDeviceConnectivityMonitor (Test)
25+
- (instancetype)initWithInstanceName:(NSString *)instanceName;
26+
@end
27+
28+
@interface MTRDeviceConnectivityMonitorTests : XCTestCase
29+
@end
30+
31+
@implementation MTRDeviceConnectivityMonitorTests
32+
33+
static DNSServiceRef sSharedConnection;
34+
35+
+ (void)setUp
36+
{
37+
DNSServiceErrorType dnsError = DNSServiceCreateConnection(&sSharedConnection);
38+
XCTAssertEqual(dnsError, kDNSServiceErr_NoError);
39+
}
40+
41+
+ (void)tearDown
42+
{
43+
DNSServiceRefDeallocate(sSharedConnection);
44+
}
45+
46+
static char kLocalDot[] = "local.";
47+
static char kOperationalType[] = "_matter._tcp";
48+
49+
static void test001_MonitorTest_RegisterCallback(
50+
DNSServiceRef sdRef,
51+
DNSServiceFlags flags,
52+
DNSServiceErrorType errorCode,
53+
const char * name,
54+
const char * regtype,
55+
const char * domain,
56+
void * context)
57+
{
58+
}
59+
60+
- (void)test001_BasicMonitorTest
61+
{
62+
dispatch_queue_t testQueue = dispatch_queue_create("connectivity-monitor-test-queue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
63+
DNSServiceRef testAdvertiser;
64+
DNSServiceFlags flags = kDNSServiceFlagsNoAutoRename;
65+
char testInstanceName[] = "testinstance-name";
66+
char testHostName[] = "localhost";
67+
uint16_t testPort = htons(15000);
68+
DNSServiceErrorType dnsError = DNSServiceRegister(&testAdvertiser, flags, 0, testInstanceName, kOperationalType, kLocalDot, testHostName, testPort, 0, NULL, test001_MonitorTest_RegisterCallback, NULL);
69+
XCTAssertEqual(dnsError, kDNSServiceErr_NoError);
70+
71+
XCTestExpectation * connectivityMonitorCallbackExpectation = [self expectationWithDescription:@"Got connectivity monitor callback"];
72+
__block BOOL gotConnectivityMonitorCallback = NO;
73+
74+
MTRDeviceConnectivityMonitor * monitor = [[MTRDeviceConnectivityMonitor alloc] initWithInstanceName:@(testInstanceName)];
75+
[monitor startMonitoringWithHandler:^{
76+
if (!gotConnectivityMonitorCallback) {
77+
gotConnectivityMonitorCallback = YES;
78+
[connectivityMonitorCallbackExpectation fulfill];
79+
}
80+
} queue:testQueue];
81+
82+
[self waitForExpectations:@[ connectivityMonitorCallbackExpectation ] timeout:5];
83+
84+
[monitor stopMonitoring];
85+
DNSServiceRefDeallocate(testAdvertiser);
86+
}
87+
88+
@end

0 commit comments

Comments
 (0)