Skip to content

Commit

Permalink
Add per-controller control over operational advertising to Matter.fra…
Browse files Browse the repository at this point in the history
…mework. (#29018)
  • Loading branch information
bzbarsky-apple authored and pull[bot] committed Apr 4, 2024
1 parent 80a9ff7 commit 1717338
Show file tree
Hide file tree
Showing 6 changed files with 310 additions and 2 deletions.
3 changes: 2 additions & 1 deletion src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm
Original file line number Diff line number Diff line change
Expand Up @@ -851,11 +851,12 @@ - (MTRDeviceController * _Nullable)createController:(MTRDeviceControllerStartupP
return [self _startDeviceController:startupParameters
fabricChecker:^MTRDeviceControllerStartupParamsInternal *(
FabricTable * fabricTable, MTRDeviceController * controller, CHIP_ERROR & fabricError) {
auto advertiseOperational = self.advertiseOperational && startupParameters.shouldAdvertiseOperational;
auto * params =
[[MTRDeviceControllerStartupParamsInternal alloc] initForNewController:controller
fabricTable:fabricTable
keystore:self->_keystore
advertiseOperational:self.advertiseOperational
advertiseOperational:advertiseOperational
params:startupParameters
error:fabricError];
if (params != nil) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ MTR_NEWLY_AVAILABLE
*/
@property (nonatomic, copy, nullable) NSArray<MTRCertificateDERBytes> * certificationDeclarationCertificates;

/**
* Whether the controller should advertise its operational identity. Defaults
* to NO.
*/
@property (nonatomic, assign) BOOL shouldAdvertiseOperational;

/**
* Set an MTROperationalCertificateIssuer to call (on the provided queue) when
* operational certificates need to be provided during commissioning.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ - (instancetype)initWithStorageDelegate:(id<MTRDeviceControllerStorageDelegate>)

_productAttestationAuthorityCertificates = nil;
_certificationDeclarationCertificates = nil;
_shouldAdvertiseOperational = NO;

_ipk = ipk;
_vendorID = vendorID;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,6 @@ - (void)initStack:(MTRTestCertificateIssuer *)certificateIssuer

__auto_type * factoryParams = [[MTRDeviceControllerFactoryParams alloc] initWithStorage:storage];
factoryParams.port = @(kLocalPort);
factoryParams.shouldStartServer = YES;

BOOL ok = [factory startControllerFactory:factoryParams error:nil];
XCTAssertTrue(ok);
Expand Down
297 changes: 297 additions & 0 deletions src/darwin/Framework/CHIPTests/MTRControllerAdvertisingTests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
/*
* 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 <Matter/Matter.h>

// system dependencies
#import <XCTest/XCTest.h>
#import <dns_sd.h>

#import "MTRFabricInfoChecker.h"
#import "MTRTestKeys.h"
#import "MTRTestPerControllerStorage.h"

static const uint16_t kTestVendorId = 0xFFF1u;
static const uint16_t kTimeoutInSeconds = 3;

static NSString * NodeIDAsString(NSNumber * nodeID) { return [NSString stringWithFormat:@"%016llX", nodeID.unsignedLongLongValue]; }

@interface MTRControllerAdvertisingTestsOperationalBrowser : NSObject
@property NSSet<NSString *> * discoveredNodes;

- (instancetype)initWithExpectation:(XCTestExpectation *)expectation nodeIDToExpect:(NSNumber *)nodeIDToExpect;
- (void)discoveredNodeID:(NSString *)nodeID onCompressedFabricID:(NSString *)compressedFabricID;
@end

static void OnBrowse(DNSServiceRef aServiceRef, DNSServiceFlags aFlags, uint32_t aInterfaceId, DNSServiceErrorType aError,
const char * aName, const char * aType, const char * aDomain, void * aContext)
{
XCTAssertTrue(aError == kDNSServiceErr_NoError);

if (!(aFlags & kDNSServiceFlagsAdd)) {
return;
}

// 16 chars for compressed fabric id, 16 chars for node id, and the dash.
XCTAssertTrue(strlen(aName) == 33);

NSString * compressedFabricID = [[NSString alloc] initWithBytes:aName length:16 encoding:NSUTF8StringEncoding];
NSString * nodeID = [[NSString alloc] initWithBytes:aName + 17 length:16 encoding:NSUTF8StringEncoding];

__auto_type * self = (__bridge MTRControllerAdvertisingTestsOperationalBrowser *) aContext;
[self discoveredNodeID:nodeID onCompressedFabricID:compressedFabricID];
}

static const char kLocalDot[] = "local.";
static const char kOperationalType[] = "_matter._tcp";
static const DNSServiceFlags kBrowseFlags = 0;

@implementation MTRControllerAdvertisingTestsOperationalBrowser {
DNSServiceRef _browseRef;
// Key is compressed fabric id, value is the set of discovered node IDs.
NSMutableDictionary<NSString *, NSMutableSet<NSString *> *> * _allDiscoveredNodes;

XCTestExpectation * _expectation;
NSString * _nodeIDToExpect;
}

- (instancetype)initWithExpectation:(XCTestExpectation *)expectation nodeIDToExpect:(NSNumber *)nodeIDToExpect
{
XCTAssertNotNil([super init]);

_allDiscoveredNodes = [[NSMutableDictionary alloc] init];
_expectation = expectation;
_nodeIDToExpect = NodeIDAsString(nodeIDToExpect);

__auto_type err = DNSServiceBrowse(
&_browseRef, kBrowseFlags, kDNSServiceInterfaceIndexAny, kOperationalType, kLocalDot, OnBrowse, (__bridge void *) self);
XCTAssertTrue(err == kDNSServiceErr_NoError);

err = DNSServiceSetDispatchQueue(_browseRef, dispatch_get_main_queue());
XCTAssertTrue(err == kDNSServiceErr_NoError);

return self;
}

- (void)discoveredNodeID:(NSString *)nodeID onCompressedFabricID:(NSString *)compressedFabricID
{
if (_allDiscoveredNodes[compressedFabricID] == nil) {
_allDiscoveredNodes[compressedFabricID] = [[NSMutableSet alloc] init];
}
[_allDiscoveredNodes[compressedFabricID] addObject:nodeID];

// It would be nice to check the compressedFabricID, but computing the right
// expected value for it is a pain.
if ([nodeID isEqualToString:_nodeIDToExpect]) {
_discoveredNodes = [NSSet setWithSet:_allDiscoveredNodes[compressedFabricID]];
// Stop our browse so we get no more notifications.
DNSServiceRefDeallocate(_browseRef);
_browseRef = NULL;
[_expectation fulfill];
}
}

- (void)dealloc
{
if (_browseRef) {
DNSServiceRefDeallocate(_browseRef);
}
}

@end

@interface MTRControllerAdvertisingTests : XCTestCase
@end

@implementation MTRControllerAdvertisingTests {
dispatch_queue_t _storageQueue;
}

+ (void)tearDown
{
}

- (void)setUp
{
// Per-test setup, runs before each test.
[super setUp];
[self setContinueAfterFailure:NO];

_storageQueue = dispatch_queue_create("test.storage.queue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);

[self startFactory];
}

- (void)tearDown
{
// Per-test teardown, runs after each test.
[self stopFactory];
_storageQueue = nil;
[super tearDown];
}

- (void)startFactory
{
__auto_type * factory = [MTRDeviceControllerFactory sharedInstance];
XCTAssertNotNil(factory);

__auto_type * factoryParams = [[MTRDeviceControllerFactoryParams alloc] init];
factoryParams.shouldStartServer = YES;

NSError * error;
BOOL ok = [factory startControllerFactory:factoryParams error:&error];
XCTAssertNil(error);
XCTAssertTrue(ok);

XCTAssertTrue(factory.isRunning);
}

- (void)stopFactory
{
__auto_type * factory = [MTRDeviceControllerFactory sharedInstance];
[factory stopControllerFactory];
XCTAssertFalse(factory.isRunning);
}

// Test helpers

- (nullable MTRDeviceController *)startControllerWithRootKeys:(MTRTestKeys *)rootKeys
operationalKeys:(MTRTestKeys *)operationalKeys
fabricID:(NSNumber *)fabricID
nodeID:(NSNumber *)nodeID
storage:(MTRTestPerControllerStorage *)storage
advertiseOperational:(BOOL)advertiseOperational
error:(NSError * __autoreleasing *)error
{
XCTAssertTrue(error != NULL);

__auto_type * factory = [MTRDeviceControllerFactory sharedInstance];
XCTAssertNotNil(factory);

// Specify a fixed issuerID, so we get the same cert if we use the same keys.
__auto_type * root = [MTRCertificates createRootCertificate:rootKeys issuerID:@(1) fabricID:nil error:error];
XCTAssertNil(*error);
XCTAssertNotNil(root);

__auto_type * operational = [MTRCertificates createOperationalCertificate:rootKeys
signingCertificate:root
operationalPublicKey:operationalKeys.publicKey
fabricID:fabricID
nodeID:nodeID
caseAuthenticatedTags:nil
error:error];
XCTAssertNil(*error);
XCTAssertNotNil(operational);

__auto_type * params =
[[MTRDeviceControllerExternalCertificateStartupParameters alloc] initWithStorageDelegate:storage
storageDelegateQueue:_storageQueue
uniqueIdentifier:storage.controllerID
ipk:rootKeys.ipk
vendorID:@(kTestVendorId)
operationalKeypair:operationalKeys
operationalCertificate:operational
intermediateCertificate:nil
rootCertificate:root];
XCTAssertNotNil(params);

params.shouldAdvertiseOperational = advertiseOperational;

return [factory createController:params error:error];
}

- (void)test001_CheckAdvertisingAsExpected
{
__auto_type * factory = [MTRDeviceControllerFactory sharedInstance];
XCTAssertNotNil(factory);

__auto_type * rootKeys = [[MTRTestKeys alloc] init];
XCTAssertNotNil(rootKeys);

__auto_type * operationalKeys = [[MTRTestKeys alloc] init];
XCTAssertNotNil(operationalKeys);

// Pick some ids that no other test will be using.
NSNumber * nodeID1 = @(0x1827364554637281);
NSNumber * nodeID2 = @(0x8172635445362718);
NSNumber * nodeID3 = @(0x8811772266335544);
NSNumber * fabricID = @(0x1122334455667788);

__auto_type * browseExpectation = [self expectationWithDescription:@"Discovered our last controller"];
// Assume that since we start the controller with nodeID3 last, by the
// time we see its advertisements we will have seen the ones for the one
// with nodeID1 too, if it had any.
__auto_type * operationalBrowser =
[[MTRControllerAdvertisingTestsOperationalBrowser alloc] initWithExpectation:browseExpectation nodeIDToExpect:nodeID3];
XCTAssertNotNil(operationalBrowser);

__auto_type * storageDelegate1 = [[MTRTestPerControllerStorage alloc] initWithControllerID:[NSUUID UUID]];

NSError * error;
MTRDeviceController * controller1 = [self startControllerWithRootKeys:rootKeys
operationalKeys:operationalKeys
fabricID:fabricID
nodeID:nodeID1
storage:storageDelegate1
advertiseOperational:NO
error:&error];
XCTAssertNil(error);
XCTAssertNotNil(controller1);
XCTAssertTrue([controller1 isRunning]);
XCTAssertEqualObjects(controller1.controllerNodeID, nodeID1);

__auto_type * storageDelegate2 = [[MTRTestPerControllerStorage alloc] initWithControllerID:[NSUUID UUID]];

MTRDeviceController * controller2 = [self startControllerWithRootKeys:rootKeys
operationalKeys:operationalKeys
fabricID:fabricID
nodeID:nodeID2
storage:storageDelegate2
advertiseOperational:YES
error:&error];
XCTAssertNil(error);
XCTAssertNotNil(controller2);
XCTAssertTrue([controller2 isRunning]);
XCTAssertEqualObjects(controller2.controllerNodeID, nodeID2);

__auto_type * storageDelegate3 = [[MTRTestPerControllerStorage alloc] initWithControllerID:[NSUUID UUID]];

MTRDeviceController * controller3 = [self startControllerWithRootKeys:rootKeys
operationalKeys:operationalKeys
fabricID:fabricID
nodeID:nodeID3
storage:storageDelegate3
advertiseOperational:YES
error:&error];
XCTAssertNil(error);
XCTAssertNotNil(controller3);
XCTAssertTrue([controller3 isRunning]);
XCTAssertEqualObjects(controller3.controllerNodeID, nodeID3);

[self waitForExpectations:@[ browseExpectation ] timeout:kTimeoutInSeconds];

__auto_type * expectedDiscoveredNodes = [NSSet setWithArray:@[ NodeIDAsString(nodeID2), NodeIDAsString(nodeID3) ]];
XCTAssertEqualObjects(operationalBrowser.discoveredNodes, expectedDiscoveredNodes);

[controller1 shutdown];
XCTAssertFalse([controller1 isRunning]);
[controller2 shutdown];
XCTAssertFalse([controller2 isRunning]);
[controller3 shutdown];
XCTAssertFalse([controller3 isRunning]);
}

@end
4 changes: 4 additions & 0 deletions src/darwin/Framework/Matter.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@
517BF3F1282B62B800A8B7DB /* MTRCertificates.mm in Sources */ = {isa = PBXBuildFile; fileRef = 517BF3EF282B62B800A8B7DB /* MTRCertificates.mm */; };
517BF3F3282B62CB00A8B7DB /* MTRCertificateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 517BF3F2282B62CB00A8B7DB /* MTRCertificateTests.m */; };
518D3F832AA132DC008E0007 /* MTRTestPerControllerStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 518D3F812AA132DC008E0007 /* MTRTestPerControllerStorage.m */; };
518D3F852AA14006008E0007 /* MTRControllerAdvertisingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 518D3F842AA14006008E0007 /* MTRControllerAdvertisingTests.m */; };
519498322A25581C00B3BABE /* MTRSetupPayloadSerializerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 519498312A25581C00B3BABE /* MTRSetupPayloadSerializerTests.m */; };
51A2F1322A00402A00F03298 /* MTRDataValueParserTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 51A2F1312A00402A00F03298 /* MTRDataValueParserTests.m */; };
51B22C1E2740CB0A008D5055 /* MTRStructsObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 51B22C1D2740CB0A008D5055 /* MTRStructsObjc.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand Down Expand Up @@ -489,6 +490,7 @@
517BF3F2282B62CB00A8B7DB /* MTRCertificateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRCertificateTests.m; sourceTree = "<group>"; };
518D3F812AA132DC008E0007 /* MTRTestPerControllerStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRTestPerControllerStorage.m; sourceTree = "<group>"; };
518D3F822AA132DC008E0007 /* MTRTestPerControllerStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRTestPerControllerStorage.h; sourceTree = "<group>"; };
518D3F842AA14006008E0007 /* MTRControllerAdvertisingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRControllerAdvertisingTests.m; sourceTree = "<group>"; };
519498312A25581C00B3BABE /* MTRSetupPayloadSerializerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRSetupPayloadSerializerTests.m; sourceTree = "<group>"; };
51A2F1312A00402A00F03298 /* MTRDataValueParserTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRDataValueParserTests.m; sourceTree = "<group>"; };
51B22C1D2740CB0A008D5055 /* MTRStructsObjc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRStructsObjc.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1164,6 +1166,7 @@
519498312A25581C00B3BABE /* MTRSetupPayloadSerializerTests.m */,
5143851D2A65885500EDC8E6 /* MTRSwiftPairingTests.swift */,
51E95DF72A78110900A434F0 /* MTRPerControllerStorageTests.m */,
518D3F842AA14006008E0007 /* MTRControllerAdvertisingTests.m */,
B202529D2459E34F00F97062 /* Info.plist */,
5143851C2A65885400EDC8E6 /* MatterTests-Bridging-Header.h */,
);
Expand Down Expand Up @@ -1580,6 +1583,7 @@
51D10D2E2808E2CA00E8CA3D /* MTRTestStorage.m in Sources */,
7596A8512878709F004DAE0E /* MTRAsyncCallbackQueueTests.m in Sources */,
997DED1A26955D0200975E97 /* MTRThreadOperationalDatasetTests.mm in Sources */,
518D3F852AA14006008E0007 /* MTRControllerAdvertisingTests.m in Sources */,
51C8E3F82825CDB600D47D00 /* MTRTestKeys.m in Sources */,
51C984622A61CE2A00B0AD9A /* MTRFabricInfoChecker.m in Sources */,
99C65E10267282F1003402F6 /* MTRControllerTests.m in Sources */,
Expand Down

0 comments on commit 1717338

Please sign in to comment.