Skip to content

Commit

Permalink
Add Darwin API for starting multiple controllers on the same fabric.
Browse files Browse the repository at this point in the history
  • Loading branch information
bzbarsky-apple committed Jul 14, 2023
1 parent 118448d commit 30cc743
Show file tree
Hide file tree
Showing 11 changed files with 440 additions and 99 deletions.
1 change: 1 addition & 0 deletions src/darwin/Framework/CHIP/MTRDeviceController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ - (BOOL)startup:(MTRDeviceControllerStartupParamsInternal *)startupParams
// bring-up.
commissionerParams.removeFromFabricTableOnShutdown = false;
commissionerParams.deviceAttestationVerifier = _factory.deviceAttestationVerifier;
commissionerParams.permitMultiControllerFabrics = startupParams.isAdditionalController;

auto & factory = chip::Controller::DeviceControllerFactory::GetInstance();

Expand Down
16 changes: 16 additions & 0 deletions src/darwin/Framework/CHIP/MTRDeviceControllerFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,22 @@ API_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4))
- (MTRDeviceController * _Nullable)createControllerOnExistingFabric:(MTRDeviceControllerStartupParams *)startupParams
error:(NSError * __autoreleasing *)error;

/**
* Create a an additional MTRDeviceController on an existing fabric. Returns nil on failure.
*
* The fabric is identified by the root public key and fabric id in
* the startupParams.
*
* This method will fail if there is no such fabric.
*
* This method will fail if the MTRDeviceControllerStartupParams don't follow
* the rules for creating a controller on a new fabric. In particular, the
* vendor ID must not be nil.
*/
- (MTRDeviceController * _Nullable)createAdditionalControllerOnExistingFabric:(MTRDeviceControllerStartupParams *)startupParams
error:(NSError * __autoreleasing *)error
MTR_NEWLY_AVAILABLE;

/**
* Create a MTRDeviceController on a new fabric. Returns nil on failure.
*
Expand Down
75 changes: 44 additions & 31 deletions src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,19 @@ - (void)stopControllerFactory

- (MTRDeviceController * _Nullable)createControllerOnExistingFabric:(MTRDeviceControllerStartupParams *)startupParams
error:(NSError * __autoreleasing *)error
{
return [self _createControllerOnExistingFabric:startupParams isAdditional:NO error:error];
}

- (MTRDeviceController * _Nullable)createAdditionalControllerOnExistingFabric:(MTRDeviceControllerStartupParams *)startupParams
error:(NSError * __autoreleasing *)error
{
return [self _createControllerOnExistingFabric:startupParams isAdditional:YES error:error];
}

- (MTRDeviceController * _Nullable)_createControllerOnExistingFabric:(MTRDeviceControllerStartupParams *)startupParams
isAdditional:(BOOL)isAdditional
error:(NSError * __autoreleasing *)error
{
[self _assertCurrentQueueIsNotMatterQueue];

Expand Down Expand Up @@ -594,27 +607,36 @@ - (MTRDeviceController * _Nullable)createControllerOnExistingFabric:(MTRDeviceCo
return;
}

for (MTRDeviceController * existing in _controllers) {
BOOL isRunning = YES; // assume the worst
if ([existing isRunningOnFabric:fabricTable fabricIndex:fabric->GetFabricIndex() isRunning:&isRunning]
!= CHIP_NO_ERROR) {
MTR_LOG_ERROR("Can't tell what fabric a controller is running on. Not safe to start.");
fabricError = CHIP_ERROR_INTERNAL;
return;
}
if (!isAdditional) {
for (MTRDeviceController * existing in _controllers) {
BOOL isRunning = YES; // assume the worst
if ([existing isRunningOnFabric:fabricTable fabricIndex:fabric->GetFabricIndex() isRunning:&isRunning]
!= CHIP_NO_ERROR) {
MTR_LOG_ERROR("Can't tell what fabric a controller is running on. Not safe to start.");
fabricError = CHIP_ERROR_INTERNAL;
return;
}

if (isRunning) {
MTR_LOG_ERROR("Can't start on existing fabric: another controller is running on it");
fabricError = CHIP_ERROR_INCORRECT_STATE;
return;
if (isRunning) {
MTR_LOG_ERROR("Can't start on existing fabric: another controller is running on it");
fabricError = CHIP_ERROR_INCORRECT_STATE;
return;
}
}

params = [[MTRDeviceControllerStartupParamsInternal alloc] _initForExistingFabricEntry:fabricTable
fabricIndex:fabric->GetFabricIndex()
keystore:_keystore
advertiseOperational:self.advertiseOperational
params:startupParams];
} else {
params = [[MTRDeviceControllerStartupParamsInternal alloc] _initForNewFabricEntry:fabricTable
keystore:_keystore
advertiseOperational:self.advertiseOperational
params:startupParams
isAdditionalController:YES];
}

params = [[MTRDeviceControllerStartupParamsInternal alloc] initForExistingFabric:fabricTable
fabricIndex:fabric->GetFabricIndex()
keystore:_keystore
advertiseOperational:self.advertiseOperational
params:startupParams];
if (params == nil) {
fabricError = CHIP_ERROR_NO_MEMORY;
}
Expand Down Expand Up @@ -656,16 +678,6 @@ - (MTRDeviceController * _Nullable)createControllerOnNewFabric:(MTRDeviceControl
return nil;
}

if (startupParams.vendorID == nil) {
MTR_LOG_ERROR("Must provide vendor id when starting controller on new fabric");
return nil;
}

if (startupParams.intermediateCertificate != nil && startupParams.rootCertificate == nil) {
MTR_LOG_ERROR("Must provide a root certificate when using an intermediate certificate");
return nil;
}

// Create the controller, so we start the event loop, since we plan to do
// our fabric table operations there.
auto * controller = [self createController];
Expand Down Expand Up @@ -697,10 +709,11 @@ - (MTRDeviceController * _Nullable)createControllerOnNewFabric:(MTRDeviceControl
return;
}

params = [[MTRDeviceControllerStartupParamsInternal alloc] initForNewFabric:fabricTable
keystore:_keystore
advertiseOperational:self.advertiseOperational
params:startupParams];
params = [[MTRDeviceControllerStartupParamsInternal alloc] _initForNewFabricEntry:fabricTable
keystore:_keystore
advertiseOperational:self.advertiseOperational
params:startupParams
isAdditionalController:NO];
if (params == nil) {
fabricError = CHIP_ERROR_NO_MEMORY;
}
Expand Down
31 changes: 22 additions & 9 deletions src/darwin/Framework/CHIP/MTRDeviceControllerStartupParams.mm
Original file line number Diff line number Diff line change
Expand Up @@ -211,15 +211,26 @@ - (instancetype)initWithParams:(MTRDeviceControllerStartupParams *)params
return self;
}

- (instancetype)initForNewFabric:(chip::FabricTable *)fabricTable
keystore:(chip::Crypto::OperationalKeystore *)keystore
advertiseOperational:(BOOL)advertiseOperational
params:(MTRDeviceControllerStartupParams *)params
- (instancetype)_initForNewFabricEntry:(chip::FabricTable *)fabricTable
keystore:(chip::Crypto::OperationalKeystore *)keystore
advertiseOperational:(BOOL)advertiseOperational
params:(MTRDeviceControllerStartupParams *)params
isAdditionalController:(BOOL)isAdditionalController
{
if (!(self = [self initWithParams:params])) {
return nil;
}

if (self.vendorID == nil) {
MTR_LOG_ERROR("Must provide vendor id when starting controller on new fabric");
return nil;
}

if (self.intermediateCertificate != nil && self.rootCertificate == nil) {
MTR_LOG_ERROR("Must provide a root certificate when using an intermediate certificate");
return nil;
}

if (self.nocSigner == nil && self.operationalCertificate == nil) {
MTR_LOG_ERROR("No way to get an operational certificate: nocSigner and operationalCertificate are both nil");
return nil;
Expand Down Expand Up @@ -248,15 +259,16 @@ - (instancetype)initForNewFabric:(chip::FabricTable *)fabricTable
_fabricTable = fabricTable;
_keystore = keystore;
_advertiseOperational = advertiseOperational;
_isAdditionalController = isAdditionalController;

return self;
}

- (instancetype)initForExistingFabric:(FabricTable *)fabricTable
fabricIndex:(FabricIndex)fabricIndex
keystore:(chip::Crypto::OperationalKeystore *)keystore
advertiseOperational:(BOOL)advertiseOperational
params:(MTRDeviceControllerStartupParams *)params
- (instancetype)_initForExistingFabricEntry:(FabricTable *)fabricTable
fabricIndex:(FabricIndex)fabricIndex
keystore:(chip::Crypto::OperationalKeystore *)keystore
advertiseOperational:(BOOL)advertiseOperational
params:(MTRDeviceControllerStartupParams *)params
{
if (!(self = [self initWithParams:params])) {
return nil;
Expand Down Expand Up @@ -384,6 +396,7 @@ - (instancetype)initForExistingFabric:(FabricTable *)fabricTable
_fabricIndex.Emplace(fabricIndex);
_keystore = keystore;
_advertiseOperational = advertiseOperational;
_isAdditionalController = NO;

return self;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ MTR_HIDDEN

@property (nonatomic, assign, readonly) BOOL advertiseOperational;

@property (nonatomic, assign, readonly) BOOL isAdditionalController;

/**
* Helper method that checks that our keypairs match our certificates.
* Specifically:
Expand All @@ -73,21 +75,24 @@ MTR_HIDDEN
- (BOOL)keypairsMatchCertificates;

/**
* Initialize for controller bringup on a new fabric.
* Initialize for controller bringup on a new fabric entry (which might be a new
* fabric or an additional controller on an existing fabric).
*/
- (instancetype)initForNewFabric:(chip::FabricTable *)fabricTable
keystore:(chip::Crypto::OperationalKeystore *)keystore
advertiseOperational:(BOOL)advertiseOperational
params:(MTRDeviceControllerStartupParams *)params;
- (instancetype)_initForNewFabricEntry:(chip::FabricTable *)fabricTable
keystore:(chip::Crypto::OperationalKeystore *)keystore
advertiseOperational:(BOOL)advertiseOperational
params:(MTRDeviceControllerStartupParams *)params
isAdditionalController:(BOOL)isAdditionalController;

/**
* Initialize for controller bringup on an existing fabric.
* Initialize for controller bringup on an existing fabric entry, identified by
* the provided fabricIndex.
*/
- (instancetype)initForExistingFabric:(chip::FabricTable *)fabricTable
fabricIndex:(chip::FabricIndex)fabricIndex
keystore:(chip::Crypto::OperationalKeystore *)keystore
advertiseOperational:(BOOL)advertiseOperational
params:(MTRDeviceControllerStartupParams *)params;
- (instancetype)_initForExistingFabricEntry:(chip::FabricTable *)fabricTable
fabricIndex:(chip::FabricIndex)fabricIndex
keystore:(chip::Crypto::OperationalKeystore *)keystore
advertiseOperational:(BOOL)advertiseOperational
params:(MTRDeviceControllerStartupParams *)params;

/**
* Should use initForExistingFabric or initForNewFabric to initialize
Expand Down
114 changes: 107 additions & 7 deletions src/darwin/Framework/CHIPTests/MTRControllerTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
// system dependencies
#import <XCTest/XCTest.h>

#import "MTRFabricInfoChecker.h"
#import "MTRTestKeys.h"
#import "MTRTestOTAProvider.h"
#import "MTRTestStorage.h"
Expand Down Expand Up @@ -1524,8 +1525,6 @@ - (void)testControllerCATs
[controller shutdown];
XCTAssertFalse([controller isRunning]);

fprintf(stderr, "DOING TOO LONG TEST\n");

//
// Trying to bring up the same fabric with too-long CATs should fail, if we
// are taking the provided CATs into account.
Expand All @@ -1536,21 +1535,122 @@ - (void)testControllerCATs
controller = [factory createControllerOnExistingFabric:params error:nil];
XCTAssertNil(controller);

fprintf(stderr, "DOING INVALID TEST\n");

//
// Trying to bring up the same fabric with invalid CATs should fail, if we
// are taking the provided CATs into account.
//
params.nodeID = @(17);
params.operationalKeypair = operationalKeys;
params.caseAuthenticatedTags = invalidCATs;
fprintf(stderr, "BRINGING UP CONTROLLER\n");
controller = [factory createControllerOnExistingFabric:params error:nil];
fprintf(stderr, "CONTROLLER SHOULD BE NIL\n");
XCTAssertNil(controller);

fprintf(stderr, "STOPPING FACTORY\n");
[factory stopControllerFactory];
XCTAssertFalse([factory isRunning]);
}

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

__auto_type * storage = [[MTRTestStorage alloc] init];
__auto_type * factoryParams = [[MTRDeviceControllerFactoryParams alloc] initWithStorage:storage];
XCTAssertTrue([factory startControllerFactory:factoryParams error:nil]);
XCTAssertTrue([factory isRunning]);

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

__auto_type * params1 = [[MTRDeviceControllerStartupParams alloc] initWithIPK:rootKeys.ipk fabricID:@(1) nocSigner:rootKeys];
XCTAssertNotNil(params1);

params1.vendorID = @(kTestVendorId);

NSError * error;

// Start a main controller with node id 0x10001.
NSNumber * nodeID1 = @(0x10001);
params1.nodeID = nodeID1;
MTRDeviceController * controller1 = [factory createControllerOnNewFabric:params1 error:&error];
XCTAssertNil(error);
XCTAssertNotNil(controller1);
XCTAssertTrue([controller1 isRunning]);
XCTAssertEqualObjects(controller1.controllerNodeID, nodeID1);

// Start an additional controller with node id 0x10002.
NSNumber * nodeID2 = @(0x10002);
params1.nodeID = nodeID2;
MTRDeviceController * controller2 = [factory createAdditionalControllerOnExistingFabric:params1 error:&error];
XCTAssertNil(error);
XCTAssertNotNil(controller2);
XCTAssertTrue([controller2 isRunning]);
XCTAssertEqualObjects(controller2.controllerNodeID, nodeID2);

// Start an additional controller with node id 0x10003, using explicit
// certificates.
NSNumber * nodeID3 = @(0x10003);
__auto_type * root = [MTRCertificates createRootCertificate:rootKeys issuerID:nil fabricID:nil error:&error];
XCTAssertNil(error);
XCTAssertNotNil(root);

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

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

__auto_type * params2 = [[MTRDeviceControllerStartupParams alloc] initWithIPK:rootKeys.ipk
operationalKeypair:operationalKeys
operationalCertificate:operational
intermediateCertificate:nil
rootCertificate:root];
params2.vendorID = @(kTestVendorId);

MTRDeviceController * controller3 = [factory createAdditionalControllerOnExistingFabric:params2 error:&error];
XCTAssertNil(error);
XCTAssertNotNil(controller3);
XCTAssertTrue([controller3 isRunning]);
XCTAssertEqualObjects(controller3.controllerNodeID, nodeID3);

__auto_type fabrics = factory.knownFabrics;
CheckFabricInfo(fabrics, [NSMutableSet setWithArray:@[
@{
@"rootPublicKey" : [rootKeys publicKeyData],
@"vendorID" : @(kTestVendorId),
@"fabricID" : @(1),
@"nodeID" : nodeID1,
@"label" : @"",
@"hasIntermediateCertificate" : @(NO),
@"fabricIndex" : @(1)
},
@{
@"rootPublicKey" : [rootKeys publicKeyData],
@"vendorID" : @(kTestVendorId),
@"fabricID" : @(1),
@"nodeID" : nodeID2,
@"label" : @"",
@"hasIntermediateCertificate" : @(NO),
@"fabricIndex" : @(2)
},
@{
@"rootPublicKey" : [rootKeys publicKeyData],
@"vendorID" : @(kTestVendorId),
@"fabricID" : @(1),
@"nodeID" : nodeID3,
@"label" : @"",
@"hasIntermediateCertificate" : @(NO),
@"fabricIndex" : @(3)
}
]]);

[factory stopControllerFactory];
XCTAssertFalse([factory isRunning]);
}
Expand Down
Loading

0 comments on commit 30cc743

Please sign in to comment.