blob: 94ce4e9313116902c8644f4519d7e0eb1f908dc7 [file] [log] [blame]
/*
*
* Copyright (c) 2022 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.
*/
// module headers
#import <Matter/Matter.h>
#import "MTRErrorTestUtils.h"
#import "MTRTestKeys.h"
#import "MTRTestResetCommissioneeHelper.h"
#import "MTRTestStorage.h"
// system dependencies
#import <XCTest/XCTest.h>
static const uint16_t kPairingTimeoutInSeconds = 10;
static const uint16_t kTimeoutInSeconds = 3;
static uint64_t sDeviceId = 0x12344321;
static NSString * kOnboardingPayload = @"MT:Y.K90SO527JA0648G00";
static const uint16_t kLocalPort = 5541;
static const uint16_t kTestVendorId = 0xFFF1u;
// Singleton controller we use.
static MTRDeviceController * sController = nil;
// Keys we can use to restart the controller.
static MTRTestKeys * sTestKeys = nil;
// A no-op MTRDeviceAttestationDelegate which lets us test (by default, in CI)
// commissioning flows that have such a delegate.
@interface NoOpAttestationDelegate : NSObject <MTRDeviceAttestationDelegate>
@property (nonatomic) XCTestExpectation * expectation;
- (instancetype)initWithExpectation:(XCTestExpectation *)expectation;
@end
@implementation NoOpAttestationDelegate
- (instancetype)initWithExpectation:(XCTestExpectation *)expectation
{
if (!(self = [super init])) {
return nil;
}
_expectation = expectation;
return self;
}
- (void)deviceAttestationCompletedForController:(MTRDeviceController *)controller
opaqueDeviceHandle:(void *)opaqueDeviceHandle
attestationDeviceInfo:(MTRDeviceAttestationDeviceInfo *)attestationDeviceInfo
error:(NSError * _Nullable)error
{
[self.expectation fulfill];
// Hard-coded to what our example server app uses for now.
XCTAssertEqualObjects(attestationDeviceInfo.vendorID, @(0xFFF2));
XCTAssertEqualObjects(attestationDeviceInfo.productID, @(0x8001));
XCTAssertEqualObjects(attestationDeviceInfo.basicInformationVendorID, @(0xFFF1));
XCTAssertEqualObjects(attestationDeviceInfo.basicInformationProductID, @(0x8000));
[controller continueCommissioningDevice:opaqueDeviceHandle ignoreAttestationFailure:NO error:nil];
}
@end
@interface MTRPairingTestControllerDelegate : NSObject <MTRDeviceControllerDelegate>
@property (nonatomic, strong) XCTestExpectation * expectation;
@property (nonatomic, nullable) id<MTRDeviceAttestationDelegate> attestationDelegate;
@property (nonatomic, nullable) NSNumber * failSafeExtension;
@end
@implementation MTRPairingTestControllerDelegate
- (id)initWithExpectation:(XCTestExpectation *)expectation
attestationDelegate:(id<MTRDeviceAttestationDelegate>)attestationDelegate
failSafeExtension:(NSNumber *)failSafeExtension
{
self = [super init];
if (self) {
_expectation = expectation;
_attestationDelegate = attestationDelegate;
_failSafeExtension = failSafeExtension;
}
return self;
}
- (void)controller:(MTRDeviceController *)controller commissioningSessionEstablishmentDone:(NSError * _Nullable)error
{
XCTAssertEqual(error.code, 0);
__auto_type * params = [[MTRCommissioningParameters alloc] init];
params.deviceAttestationDelegate = self.attestationDelegate;
params.failSafeTimeout = self.failSafeExtension;
NSError * commissionError = nil;
[controller commissionNodeWithID:@(sDeviceId) commissioningParams:params error:&commissionError];
XCTAssertNil(commissionError);
// Keep waiting for onCommissioningComplete
}
- (void)controller:(MTRDeviceController *)controller commissioningComplete:(NSError * _Nullable)error
{
XCTAssertEqual(error.code, 0);
[_expectation fulfill];
_expectation = nil;
}
@end
// attestationDelegate and failSafeExtension can both be nil
static void DoPairingTest(XCTestCase * testcase, id<MTRDeviceAttestationDelegate> attestationDelegate, NSNumber * failSafeExtension)
{
// Don't reuse node ids, because that will confuse us.
++sDeviceId;
XCTestExpectation * expectation = [testcase expectationWithDescription:@"Commissioning Complete"];
__auto_type * controller = sController;
__auto_type * controllerDelegate = [[MTRPairingTestControllerDelegate alloc] initWithExpectation:expectation
attestationDelegate:attestationDelegate
failSafeExtension:failSafeExtension];
dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip.pairing", DISPATCH_QUEUE_SERIAL);
[controller setDeviceControllerDelegate:controllerDelegate queue:callbackQueue];
NSError * error;
__auto_type * payload = [MTRSetupPayload setupPayloadWithOnboardingPayload:kOnboardingPayload error:&error];
XCTAssertNotNil(payload);
XCTAssertNil(error);
[controller setupCommissioningSessionWithPayload:payload newNodeID:@(sDeviceId) error:&error];
XCTAssertNil(error);
[testcase waitForExpectations:@[ expectation ] timeout:kPairingTimeoutInSeconds];
ResetCommissionee([MTRBaseDevice deviceWithNodeID:@(sDeviceId) controller:controller], dispatch_get_main_queue(), testcase,
kTimeoutInSeconds);
}
@interface MTRPairingTests : XCTestCase
@end
static BOOL sStackInitRan = NO;
static BOOL sNeedsStackShutdown = YES;
@implementation MTRPairingTests
+ (void)tearDown
{
// Global teardown, runs once
if (sNeedsStackShutdown) {
// We don't need to worry about ResetCommissionee. If we get here,
// we're running only one of our test methods (using
// -only-testing:MatterTests/MTROTAProviderTests/testMethodName), since
// we did not run test999_TearDown.
[self shutdownStack];
}
}
- (void)setUp
{
// Per-test setup, runs before each test.
[super setUp];
[self setContinueAfterFailure:NO];
if (sStackInitRan == NO) {
[self initStack];
}
}
- (void)tearDown
{
// Per-test teardown, runs after each test.
[super tearDown];
}
- (void)initStack
{
sStackInitRan = YES;
__auto_type * factory = [MTRDeviceControllerFactory sharedInstance];
XCTAssertNotNil(factory);
__auto_type * storage = [[MTRTestStorage alloc] init];
__auto_type * factoryParams = [[MTRDeviceControllerFactoryParams alloc] initWithStorage:storage];
factoryParams.port = @(kLocalPort);
BOOL ok = [factory startControllerFactory:factoryParams error:nil];
XCTAssertTrue(ok);
__auto_type * testKeys = [[MTRTestKeys alloc] init];
XCTAssertNotNil(testKeys);
sTestKeys = testKeys;
// Needs to match what startControllerOnExistingFabric calls elsewhere in
// this file do.
__auto_type * params = [[MTRDeviceControllerStartupParams alloc] initWithIPK:testKeys.ipk fabricID:@(1) nocSigner:testKeys];
params.vendorID = @(kTestVendorId);
MTRDeviceController * controller = [factory createControllerOnNewFabric:params error:nil];
XCTAssertNotNil(controller);
sController = controller;
}
+ (void)shutdownStack
{
sNeedsStackShutdown = NO;
MTRDeviceController * controller = sController;
XCTAssertNotNil(controller);
[controller shutdown];
XCTAssertFalse([controller isRunning]);
[[MTRDeviceControllerFactory sharedInstance] stopControllerFactory];
}
- (void)test000_SetUp
{
// Nothing to do here; our setUp method handled this already. This test
// just exists to make the setup not look like it's happening inside other
// tests.
}
- (void)test001_PairWithoutAttestationDelegate
{
DoPairingTest(self, nil, nil);
}
- (void)test002_PairWithAttestationDelegateNoFailsafeExtension
{
XCTestExpectation * expectation = [self expectationWithDescription:@"Attestation delegate called"];
DoPairingTest(self, [[NoOpAttestationDelegate alloc] initWithExpectation:expectation], nil);
[self waitForExpectations:@[ expectation ] timeout:kTimeoutInSeconds];
}
- (void)test003_PairWithAttestationDelegateFailsafeExtensionShort
{
XCTestExpectation * expectation = [self expectationWithDescription:@"Attestation delegate called"];
// Extend by a time that is going to be smaller than the 60s default we
// already have set via CHIP_DEVICE_CONFIG_FAILSAFE_EXPIRY_LENGTH_SEC on the
// server side, minus whatever time that has likely passed.
DoPairingTest(self, [[NoOpAttestationDelegate alloc] initWithExpectation:expectation], @(30));
[self waitForExpectations:@[ expectation ] timeout:kTimeoutInSeconds];
}
- (void)test004_PairWithAttestationDelegateFailsafeExtensionLong
{
XCTestExpectation * expectation = [self expectationWithDescription:@"Attestation delegate called"];
// Extend by a time that is going to be larger than the 60s default we
// already have set via CHIP_DEVICE_CONFIG_FAILSAFE_EXPIRY_LENGTH_SEC on the
// server side.
DoPairingTest(self, [[NoOpAttestationDelegate alloc] initWithExpectation:expectation], @(90));
[self waitForExpectations:@[ expectation ] timeout:kTimeoutInSeconds];
}
- (void)test999_TearDown
{
[[self class] shutdownStack];
}
@end