Skip to content

Commit

Permalink
Add support for PairWithQRCode to chip-tool-darwin. (#17888)
Browse files Browse the repository at this point in the history
* Adds setup of multiple controllers at startup in chip-tool-darwin.
* Adds support for multiple connected devices in TestCommandBridge.
* Uses test step identities to select the right controller and CHIPDevice.
* Implements PairWithQRCode, handling both cases when it's expected to succeed
  and cases when it's expected to fail.
* Enables tests that were blocked on lack of PairWithQRCode.
* Fixes non-fabric-filtered reads so the tests pass.
  • Loading branch information
bzbarsky-apple authored and pull[bot] committed Nov 14, 2023
1 parent 331a046 commit 1c5c047
Show file tree
Hide file tree
Showing 6 changed files with 34,474 additions and 18,737 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
#import <CHIP/CHIPDeviceController.h>
#include <commands/common/Command.h>
#include <commands/common/CredentialIssuerCommands.h>
#include <map>
#include <string>

#pragma once

Expand Down Expand Up @@ -71,7 +73,12 @@ class CHIPCommandBridge : public Command

CHIP_ERROR StartWaiting(chip::System::Clock::Timeout seconds);
void StopWaiting();
CHIPDeviceController * mController;

// Our three controllers: alpha, beta, gamma.
std::map<std::string, CHIPDeviceController *> mControllers;

// The current controller; the one the current command should be using.
CHIPDeviceController * mCurrentController;

std::condition_variable cvWaitingForResponse;
std::mutex cvWaitingForResponseMutex;
Expand Down
48 changes: 31 additions & 17 deletions examples/chip-tool-darwin/commands/common/CHIPCommandBridge.mm
Original file line number Diff line number Diff line change
Expand Up @@ -53,35 +53,49 @@

ipk = [nocSigner getIPK];

auto controllerParams = [[CHIPDeviceControllerStartupParams alloc] initWithKeypair:nocSigner];
controllerParams.vendorId = chip::VendorId::TestVendor1;
controllerParams.fabricId = 1;
controllerParams.ipk = ipk;

// We're not sure whether we're creating a new fabric or using an
// existing one, so just try both.
mController = [factory startControllerOnExistingFabric:controllerParams];
if (mController == nil) {
// Maybe we didn't have this fabric yet.
mController = [factory startControllerOnNewFabric:controllerParams];
}
if (mController == nil) {
ChipLogError(chipTool, "Controller startup failure.");
return CHIP_ERROR_INTERNAL;
constexpr const char * identities[] = { "alpha", "beta", "gamma" };
for (size_t i = 0; i < ArraySize(identities); ++i) {
auto controllerParams = [[CHIPDeviceControllerStartupParams alloc] initWithKeypair:nocSigner];
controllerParams.vendorId = chip::VendorId::TestVendor1;
controllerParams.fabricId = i + 1;
controllerParams.ipk = ipk;

// We're not sure whether we're creating a new fabric or using an
// existing one, so just try both.
auto controller = [factory startControllerOnExistingFabric:controllerParams];
if (controller == nil) {
// Maybe we didn't have this fabric yet.
controller = [factory startControllerOnNewFabric:controllerParams];
}
if (controller == nil) {
ChipLogError(chipTool, "Controller startup failure.");
return CHIP_ERROR_INTERNAL;
}

mControllers[identities[i]] = controller;
}

// Default to alpha.
SetIdentity("alpha");

ReturnLogErrorOnFailure(RunCommand());
ReturnLogErrorOnFailure(StartWaiting(GetWaitDuration()));

return CHIP_NO_ERROR;
}

CHIPDeviceController * CHIPCommandBridge::CurrentCommissioner() { return mController; }
void CHIPCommandBridge::SetIdentity(const char * name) { mCurrentController = mControllers[name]; }

CHIPDeviceController * CHIPCommandBridge::CurrentCommissioner() { return mCurrentController; }

CHIP_ERROR CHIPCommandBridge::ShutdownCommissioner()
{
ChipLogProgress(chipTool, "Shutting down controller");
[CurrentCommissioner() shutdown];
for (auto & pair : mControllers) {
[pair.second shutdown];
}
mControllers.clear();
mCurrentController = nil;

[[MatterControllerFactory sharedInstance] shutdown];

Expand Down
184 changes: 177 additions & 7 deletions examples/chip-tool-darwin/commands/tests/TestCommandBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,33 @@
#include <app/tests/suites/include/PICSChecker.h>
#include <app/tests/suites/include/ValueChecker.h>
#include <lib/support/UnitTestUtils.h>
#include <map>
#include <string>
#include <zap-generated/cluster/CHIPTestClustersObjc.h>

#import <CHIP/CHIP.h>
#import <CHIP/CHIPDevice_Internal.h>
#import <CHIP/CHIPError.h>
#import <CHIP/CHIPError_Internal.h>

class TestCommandBridge;

NS_ASSUME_NONNULL_BEGIN

@interface TestPairingDelegate : NSObject <CHIPDevicePairingDelegate>
@property TestCommandBridge * commandBridge;
@property chip::NodeId deviceId;
@property BOOL active; // Whether to pass on notifications to the commandBridge

- (void)onStatusUpdate:(CHIPPairingStatus)status;
- (void)onPairingComplete:(NSError * _Nullable)error;
- (void)onPairingDeleted:(NSError * _Nullable)error;
- (void)onCommissioningComplete:(NSError * _Nullable)error;

- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithTestCommandBridge:(TestCommandBridge *)commandBridge;
@end

NS_ASSUME_NONNULL_END

constexpr uint16_t kTimeoutInSeconds = 90;

Expand All @@ -39,6 +62,7 @@ class TestCommandBridge : public CHIPCommandBridge,
public:
TestCommandBridge(const char * _Nonnull commandName)
: CHIPCommandBridge(commandName)
, mPairingDelegate([[TestPairingDelegate alloc] initWithTestCommandBridge:this])
{
AddArgument("delayInMs", 0, UINT64_MAX, &mDelayInMs);
AddArgument("PICS", &mPICSFilePath);
Expand All @@ -63,6 +87,11 @@ class TestCommandBridge : public CHIPCommandBridge,

virtual void NextTest() = 0;

// Support for tests that asynchronously come up with a status of some
// sort. Subclasses are expected to compare the provided status to the
// expected status for the test.
virtual void OnStatusUpdate(const chip::app::StatusIB & status) = 0;

void Exit(std::string message, CHIP_ERROR err = CHIP_ERROR_INTERNAL) override
{
ChipLogError(chipTool, " ***** Test Failure: %s\n", message.c_str());
Expand Down Expand Up @@ -93,12 +122,12 @@ class TestCommandBridge : public CHIPCommandBridge,
// Disconnect our existing device; otherwise getConnectedDevice will
// just hand it right back to us without establishing a new CASE
// session.
if (mConnectedDevice != nil) {
auto device = [mConnectedDevice internalDevice];
if (GetConnectedDevice() != nil) {
auto device = [GetConnectedDevice() internalDevice];
if (device != nullptr) {
device->Disconnect();
}
mConnectedDevice = nil;
mConnectedDevices[mCurrentIdentity] = nil;
}

[controller getConnectedDevice:nodeId
Expand All @@ -107,11 +136,31 @@ class TestCommandBridge : public CHIPCommandBridge,
CHIP_ERROR err = [CHIPError errorToCHIPErrorCode:error];
VerifyOrReturn(CHIP_NO_ERROR == err, SetCommandExitStatus(err));

mConnectedDevice = device;
mConnectedDevices[mCurrentIdentity] = device;
NextTest();
}];
}

/////////// CommissionerCommands-like Interface /////////
CHIP_ERROR PairWithQRCode(chip::NodeId nodeId, const chip::CharSpan payload)
{
CHIPDeviceController * controller = CurrentCommissioner();
VerifyOrReturnError(controller != nil, CHIP_ERROR_INCORRECT_STATE);

[controller setPairingDelegate:mPairingDelegate queue:mCallbackQueue];
[mPairingDelegate setDeviceId:nodeId];
[mPairingDelegate setActive:YES];

NSString * payloadStr = [[NSString alloc] initWithBytes:payload.data() length:payload.size() encoding:NSUTF8StringEncoding];
NSError * err;
BOOL ok = [controller pairDevice:nodeId onboardingPayload:payloadStr error:&err];
if (ok == YES) {
return CHIP_NO_ERROR;
}

return [CHIPError errorToCHIPErrorCode:err];
}

/////////// SystemCommands Interface /////////
CHIP_ERROR ContinueOnChipMainThread(CHIP_ERROR err) override
{
Expand All @@ -124,11 +173,44 @@ class TestCommandBridge : public CHIPCommandBridge,
return CHIP_NO_ERROR;
}

CHIPDevice * _Nullable GetConnectedDevice(void) { return mConnectedDevice; }
CHIPDevice * _Nullable GetConnectedDevice(void) { return mConnectedDevices[mCurrentIdentity]; }

// PairingDeleted and PairingComplete need to be public so our pairing
// delegate can call them.
void PairingDeleted()
{
// This should not happen!
Exit("Unexpected deletion of pairing");
}

void PairingComplete(chip::NodeId nodeId, NSError * _Nullable error)
{
CHIP_ERROR err = [CHIPError errorToCHIPErrorCode:error];
if (err != CHIP_NO_ERROR) {
Exit("Pairing completed with error", err);
return;
}

CHIPDeviceController * controller = CurrentCommissioner();
VerifyOrReturn(controller != nil, Exit("No current commissioner"));

NSError * commissionError = nil;
[controller commissionDevice:nodeId commissioningParams:[[CHIPCommissioningParameters alloc] init] error:&commissionError];
err = [CHIPError errorToCHIPErrorCode:commissionError];
if (err != CHIP_NO_ERROR) {
Exit("Failed to kick off commissioning", err);
return;
}
}

void SetIdentity(const char * _Nonnull name)
{
mCurrentIdentity = name;
CHIPCommandBridge::SetIdentity(name);
}

protected:
dispatch_queue_t _Nullable mCallbackQueue;
CHIPDevice * _Nullable mConnectedDevice;

void Wait()
{
Expand Down Expand Up @@ -286,4 +368,92 @@ class TestCommandBridge : public CHIPCommandBridge,
Exit(std::string(itemName) + " expected to be null but isn't");
return false;
}

private:
TestPairingDelegate * _Nonnull mPairingDelegate;

// Currently selected identity ("alpha", "beta", "gamma").
std::string mCurrentIdentity;

// Set of our connected devices, keyed by identity.
std::map<std::string, CHIPDevice *> mConnectedDevices;
};

NS_ASSUME_NONNULL_BEGIN

@implementation TestPairingDelegate
- (void)onStatusUpdate:(CHIPPairingStatus)status
{
if (_active) {
if (status == kSecurePairingSuccess) {
NSLog(@"Secure pairing success");
} else if (status == kSecurePairingFailed) {
_active = NO;
NSLog(@"Secure pairing failed");
_commandBridge->OnStatusUpdate(chip::app::StatusIB(chip::Protocols::InteractionModel::Status::Failure));
}
}
}

- (void)onPairingComplete:(NSError * _Nullable)error
{
if (_active) {
_commandBridge->PairingComplete(_deviceId, error);
}
}

- (void)onPairingDeleted:(NSError * _Nullable)error
{
if (_active) {
_commandBridge->PairingDeleted();
}
}

- (void)onCommissioningComplete:(NSError * _Nullable)error
{
if (_active) {
_active = NO;
CHIP_ERROR err = [CHIPError errorToCHIPErrorCode:error];
_commandBridge->OnStatusUpdate([self convertToStatusIB:err]);
}
}

- (chip::app::StatusIB)convertToStatusIB:(CHIP_ERROR)err
{
using chip::app::StatusIB;
using namespace chip;
using namespace chip::Protocols::InteractionModel;
using namespace chip::app::Clusters::OperationalCredentials;

if (CHIP_ERROR_INVALID_PUBLIC_KEY == err) {
return StatusIB(Status::Failure, to_underlying(OperationalCertStatus::kInvalidPublicKey));
}
if (CHIP_ERROR_WRONG_NODE_ID == err) {
return StatusIB(Status::Failure, to_underlying(OperationalCertStatus::kInvalidNodeOpId));
}
if (CHIP_ERROR_UNSUPPORTED_CERT_FORMAT == err) {
return StatusIB(Status::Failure, to_underlying(OperationalCertStatus::kInvalidNOC));
}
if (CHIP_ERROR_FABRIC_EXISTS == err) {
return StatusIB(Status::Failure, to_underlying(OperationalCertStatus::kFabricConflict));
}
if (CHIP_ERROR_INVALID_FABRIC_ID == err) {
return StatusIB(Status::Failure, to_underlying(OperationalCertStatus::kInvalidFabricIndex));
}

return StatusIB(err);
}

- (instancetype)initWithTestCommandBridge:(TestCommandBridge *)commandBridge
{
if (!(self = [super init])) {
return nil;
}

_commandBridge = commandBridge;
_active = NO;
return self;
}
@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,30 @@ class {{filename}}: public TestCommandBridge
}
}

void OnStatusUpdate(const chip::app::StatusIB & status) override
{
switch (mTestIndex - 1)
{
{{#chip_tests_items}}
case {{index}}:
{{! No support for expectMultipleResponses yet }}
{{#chip_tests_item_responses}}
VerifyOrReturn(CheckValue("status", chip::to_underlying(status.mStatus), {{error}}));
{{#if error}}
{{#if clusterError}}
VerifyOrReturn(CheckValue("clusterStatus present", status.mClusterStatus.HasValue(), true));
VerifyOrReturn(CheckValue("clusterStatus value", status.mClusterStatus.Value(), {{clusterError}}));
{{/if}}
{{/if}}
{{/chip_tests_item_responses}}
break;
{{/chip_tests_items}}
}

// Go on to the next test.
WaitForMs(0);
}

chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(mTimeout.ValueOr({{chip_tests_config_get_default_value "timeout"}})); }

private:
Expand Down Expand Up @@ -102,6 +126,7 @@ class {{filename}}: public TestCommandBridge
{{#*inline "testCommand"}}Test{{asUpperCamelCase label}}_{{index}}{{/inline}}
CHIP_ERROR {{>testCommand}}()
{
SetIdentity("{{identity}}");
{{#if (isTestOnlyCluster cluster)}}
{{command}}(
{{#chip_tests_item_parameters}}
Expand Down Expand Up @@ -149,7 +174,7 @@ class {{filename}}: public TestCommandBridge
{{else if isReadAttribute}}
{{#if_is_fabric_scoped_struct attributeObject.type}}
CHIPReadParams * params = [[CHIPReadParams alloc] init];
params.fabricFiltered = [NSNumber numberWithBool:true];
params.fabricFiltered = [NSNumber numberWithBool:{{fabricFiltered}}];
{{/if_is_fabric_scoped_struct}}
[cluster readAttribute{{asUpperCamelCase attribute}}With
{{~#if_is_fabric_scoped_struct attributeObject.type~}}
Expand Down
Loading

0 comments on commit 1c5c047

Please sign in to comment.