blob: d7b08c7c0a44a4d5eb66573aa470045f5c5848e3 [file] [log] [blame]
/**
*
* Copyright (c) 2020 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 "MTRError.h"
#import "MTRError_Internal.h"
#import "MTRFramework.h"
#import "MTROnboardingPayloadParser.h"
#import "MTRSetupPayload_Internal.h"
#import "setup_payload/ManualSetupPayloadGenerator.h"
#import "setup_payload/QRCodeSetupPayloadGenerator.h"
#import <setup_payload/SetupPayload.h>
@implementation MTROptionalQRCodeInfo
@end
@implementation MTRSetupPayload {
chip::SetupPayload _chipSetupPayload;
}
+ (void)initialize
{
// Need to make sure we set up Platform memory stuff before we start
// serializing payloads.
MTRFrameworkInit();
}
- (MTRDiscoveryCapabilities)convertRendezvousFlags:(const chip::Optional<chip::RendezvousInformationFlags> &)value
{
if (!value.HasValue()) {
return MTRDiscoveryCapabilitiesUnknown;
}
NSUInteger flags = 0;
if (value.Value().Has(chip::RendezvousInformationFlag::kBLE)) {
flags |= MTRDiscoveryCapabilitiesBLE;
}
if (value.Value().Has(chip::RendezvousInformationFlag::kSoftAP)) {
flags |= MTRDiscoveryCapabilitiesSoftAP;
}
if (value.Value().Has(chip::RendezvousInformationFlag::kOnNetwork)) {
flags |= MTRDiscoveryCapabilitiesOnNetwork;
}
if (flags == MTRDiscoveryCapabilitiesUnknown) {
// OnNetwork has to be supported!
flags = MTRDiscoveryCapabilitiesOnNetwork;
}
return flags;
}
+ (chip::Optional<chip::RendezvousInformationFlags>)convertDiscoveryCapabilities:(MTRDiscoveryCapabilities)value
{
if (value == MTRDiscoveryCapabilitiesUnknown) {
return chip::NullOptional;
}
chip::RendezvousInformationFlags flags;
if (value & MTRDiscoveryCapabilitiesBLE) {
flags.Set(chip::RendezvousInformationFlag::kBLE);
}
if (value & MTRDiscoveryCapabilitiesSoftAP) {
flags.Set(chip::RendezvousInformationFlag::kSoftAP);
}
if (value & MTRDiscoveryCapabilitiesOnNetwork) {
flags.Set(chip::RendezvousInformationFlag::kOnNetwork);
}
return chip::MakeOptional(flags);
}
- (MTRCommissioningFlow)convertCommissioningFlow:(chip::CommissioningFlow)value
{
if (value == chip::CommissioningFlow::kStandard) {
return MTRCommissioningFlowStandard;
}
if (value == chip::CommissioningFlow::kUserActionRequired) {
return MTRCommissioningFlowUserActionRequired;
}
if (value == chip::CommissioningFlow::kCustom) {
return MTRCommissioningFlowCustom;
}
return MTRCommissioningFlowInvalid;
}
+ (chip::CommissioningFlow)unconvertCommissioningFlow:(MTRCommissioningFlow)value
{
if (value == MTRCommissioningFlowStandard) {
return chip::CommissioningFlow::kStandard;
}
if (value == MTRCommissioningFlowUserActionRequired) {
return chip::CommissioningFlow::kUserActionRequired;
}
if (value == MTRCommissioningFlowCustom) {
return chip::CommissioningFlow::kCustom;
}
// It's MTRCommissioningFlowInvalid ... now what? But in practice
// this is not called when we have MTRCommissioningFlowInvalid.
return chip::CommissioningFlow::kStandard;
}
- (instancetype)initWithSetupPayload:(chip::SetupPayload)setupPayload
{
if (self = [super init]) {
_chipSetupPayload = setupPayload;
_version = [NSNumber numberWithUnsignedChar:setupPayload.version];
_vendorID = [NSNumber numberWithUnsignedShort:setupPayload.vendorID];
_productID = [NSNumber numberWithUnsignedShort:setupPayload.productID];
_commissioningFlow = [self convertCommissioningFlow:setupPayload.commissioningFlow];
_discoveryCapabilities = [self convertRendezvousFlags:setupPayload.rendezvousInformation];
_hasShortDiscriminator = setupPayload.discriminator.IsShortDiscriminator();
if (_hasShortDiscriminator) {
_discriminator = [NSNumber numberWithUnsignedShort:setupPayload.discriminator.GetShortValue()];
} else {
_discriminator = [NSNumber numberWithUnsignedShort:setupPayload.discriminator.GetLongValue()];
}
_setupPasscode = [NSNumber numberWithUnsignedInt:setupPayload.setUpPINCode];
[self getSerialNumber:setupPayload];
}
return self;
}
- (instancetype)initWithSetupPasscode:(NSNumber *)setupPasscode discriminator:(NSNumber *)discriminator
{
if (self = [super init]) {
_version = @(0); // Only supported Matter version so far.
_vendorID = @(0); // Not available.
_productID = @(0); // Not available.
_commissioningFlow = MTRCommissioningFlowStandard;
// We are using a long discriminator, so have to have a known
// discoveryCapabilities to be a valid payload. Just default to "try
// all discovery methods".
_discoveryCapabilities = MTRDiscoveryCapabilitiesAllMask;
_hasShortDiscriminator = NO;
_discriminator = discriminator;
_setupPasscode = setupPasscode;
_serialNumber = nil;
}
return self;
}
- (void)getSerialNumber:(chip::SetupPayload)setupPayload
{
std::string serialNumberC;
CHIP_ERROR err = setupPayload.getSerialNumber(serialNumberC);
if (err == CHIP_NO_ERROR) {
_serialNumber = [NSString stringWithUTF8String:serialNumberC.c_str()];
}
}
- (NSArray<MTROptionalQRCodeInfo *> *)getAllOptionalVendorData:(NSError * __autoreleasing *)error
{
NSMutableArray<MTROptionalQRCodeInfo *> * allOptionalData = [NSMutableArray new];
std::vector<chip::OptionalQRCodeInfo> chipOptionalData = _chipSetupPayload.getAllOptionalVendorData();
for (chip::OptionalQRCodeInfo chipInfo : chipOptionalData) {
MTROptionalQRCodeInfo * info = [MTROptionalQRCodeInfo new];
info.tag = [NSNumber numberWithUnsignedChar:chipInfo.tag];
switch (chipInfo.type) {
case chip::optionalQRCodeInfoTypeString:
info.type = MTROptionalQRCodeInfoTypeString;
info.stringValue = [NSString stringWithUTF8String:chipInfo.data.c_str()];
break;
case chip::optionalQRCodeInfoTypeInt32:
info.type = MTROptionalQRCodeInfoTypeInt32;
info.integerValue = [NSNumber numberWithInt:chipInfo.int32];
break;
default:
if (error) {
*error = [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidArgument userInfo:nil];
}
return @[];
}
[allOptionalData addObject:info];
}
return allOptionalData;
}
+ (NSUInteger)generateRandomPIN
{
return [[MTRSetupPayload generateRandomSetupPasscode] unsignedIntValue];
}
+ (NSNumber *)generateRandomSetupPasscode
{
do {
// Make sure the thing we generate is in the right range.
uint32_t setupPIN = arc4random_uniform(chip::kSetupPINCodeMaximumValue) + 1;
if (chip::SetupPayload::IsValidSetupPIN(setupPIN)) {
return @(setupPIN);
}
// We got pretty unlikely with our random number generation. Just try
// again. The chance that this loop does not terminate in a reasonable
// amount of time is astronomically low, assuming arc4random_uniform is not
// broken.
} while (true);
// Not reached.
return @(chip::kSetupPINCodeUndefinedValue);
}
+ (bool)isQRCode:(NSString *)onboardingPayload
{
return [onboardingPayload hasPrefix:@"MT:"];
}
+ (MTRSetupPayload * _Nullable)setupPayloadWithOnboardingPayload:(NSString *)onboardingPayload
error:(NSError * __autoreleasing *)error
{
// TODO: Do we actually need the MTROnboardingPayloadParser abstraction?
MTRSetupPayload * payload = [MTROnboardingPayloadParser setupPayloadForOnboardingPayload:onboardingPayload error:error];
if (payload == nil) {
return nil;
}
bool isQRCode = [MTRSetupPayload isQRCode:onboardingPayload];
bool validPayload;
if (isQRCode) {
validPayload = payload->_chipSetupPayload.isValidQRCodePayload();
} else {
validPayload = payload->_chipSetupPayload.isValidManualCode();
}
if (!validPayload) {
if (error) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT];
}
return nil;
}
return payload;
}
#pragma mark - NSSecureCoding
static NSString * const MTRSetupPayloadCodingKeyVersion = @"MTRSP.ck.version";
static NSString * const MTRSetupPayloadCodingKeyVendorID = @"MTRSP.ck.vendorID";
static NSString * const MTRSetupPayloadCodingKeyProductID = @"MTRSP.ck.productID";
static NSString * const MTRSetupPayloadCodingKeyCommissioningFlow = @"MTRSP.ck.commissioningFlow";
static NSString * const MTRSetupPayloadCodingKeyDiscoveryCapabilities = @"MTRSP.ck.rendezvousFlags";
static NSString * const MTRSetupPayloadCodingKeyHasShortDiscriminator = @"MTRSP.ck.hasShortDiscriminator";
static NSString * const MTRSetupPayloadCodingKeyDiscriminator = @"MTRSP.ck.discriminator";
static NSString * const MTRSetupPayloadCodingKeySetupPasscode = @"MTRSP.ck.setupPINCode";
static NSString * const MTRSetupPayloadCodingKeySerialNumber = @"MTRSP.ck.serialNumber";
+ (BOOL)supportsSecureCoding
{
return YES;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.version forKey:MTRSetupPayloadCodingKeyVersion];
[coder encodeObject:self.vendorID forKey:MTRSetupPayloadCodingKeyVendorID];
[coder encodeObject:self.productID forKey:MTRSetupPayloadCodingKeyProductID];
// Casts are safe because commissioning flow, discoveryCapabilities, and
// hasShortDiscriminator values are all pretty small and non-negative.
[coder encodeInteger:static_cast<NSInteger>(self.commissioningFlow) forKey:MTRSetupPayloadCodingKeyCommissioningFlow];
// We used to encode the discovery capabilities as an NSNumber object, with
// nil representing "unknown". Keep doing that, for backwards compat.
[coder encodeObject:[MTRSetupPayload _boxDiscoveryCapabilities:self.discoveryCapabilities]
forKey:MTRSetupPayloadCodingKeyDiscoveryCapabilities];
[coder encodeInteger:static_cast<NSInteger>(self.hasShortDiscriminator) forKey:MTRSetupPayloadCodingKeyHasShortDiscriminator];
[coder encodeObject:self.discriminator forKey:MTRSetupPayloadCodingKeyDiscriminator];
[coder encodeObject:self.setupPasscode forKey:MTRSetupPayloadCodingKeySetupPasscode];
[coder encodeObject:self.serialNumber forKey:MTRSetupPayloadCodingKeySerialNumber];
}
- (instancetype _Nullable)initWithCoder:(NSCoder *)decoder
{
NSNumber * version = [decoder decodeObjectOfClass:[NSNumber class] forKey:MTRSetupPayloadCodingKeyVersion];
NSNumber * vendorID = [decoder decodeObjectOfClass:[NSNumber class] forKey:MTRSetupPayloadCodingKeyVendorID];
NSNumber * productID = [decoder decodeObjectOfClass:[NSNumber class] forKey:MTRSetupPayloadCodingKeyProductID];
NSInteger commissioningFlow = [decoder decodeIntegerForKey:MTRSetupPayloadCodingKeyCommissioningFlow];
MTRDiscoveryCapabilities discoveryCapabilities =
[MTRSetupPayload _unboxDiscoveryCapabilities:[decoder decodeObjectOfClass:[NSNumber class]
forKey:MTRSetupPayloadCodingKeyDiscoveryCapabilities]];
NSInteger hasShortDiscriminator = [decoder decodeIntegerForKey:MTRSetupPayloadCodingKeyHasShortDiscriminator];
NSNumber * discriminator = [decoder decodeObjectOfClass:[NSNumber class] forKey:MTRSetupPayloadCodingKeyDiscriminator];
NSNumber * setupPasscode = [decoder decodeObjectOfClass:[NSNumber class] forKey:MTRSetupPayloadCodingKeySetupPasscode];
NSString * serialNumber = [decoder decodeObjectOfClass:[NSString class] forKey:MTRSetupPayloadCodingKeySerialNumber];
MTRSetupPayload * payload = [[MTRSetupPayload alloc] init];
payload.version = version;
payload.vendorID = vendorID;
payload.productID = productID;
payload.commissioningFlow = static_cast<MTRCommissioningFlow>(commissioningFlow);
payload.discoveryCapabilities = discoveryCapabilities;
payload.hasShortDiscriminator = static_cast<BOOL>(hasShortDiscriminator);
payload.discriminator = discriminator;
payload.setupPasscode = setupPasscode;
payload.serialNumber = serialNumber;
return payload;
}
- (NSString * _Nullable)manualEntryCode
{
CHIP_ERROR err = CHIP_NO_ERROR;
std::string outDecimalString;
chip::SetupPayload payload;
/// The 11 digit manual pairing code only requires the version, VID_PID present flag,
/// discriminator, and the setup pincode.
payload.version = [self.version unsignedCharValue];
if (self.hasShortDiscriminator) {
payload.discriminator.SetShortValue([self.discriminator unsignedCharValue]);
} else {
payload.discriminator.SetLongValue([self.discriminator unsignedShortValue]);
}
payload.setUpPINCode = [self.setupPasscode unsignedIntValue];
err = chip::ManualSetupPayloadGenerator(payload).payloadDecimalStringRepresentation(outDecimalString);
if (err != CHIP_NO_ERROR) {
return nil;
}
return [NSString stringWithUTF8String:outDecimalString.c_str()];
}
- (NSString * _Nullable)qrCodeString:(NSError * __autoreleasing *)error
{
if (self.commissioningFlow == MTRCommissioningFlowInvalid) {
// No idea how to map this to the standard codes.
if (error != nil) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE];
}
return nil;
}
if (self.hasShortDiscriminator) {
// Can't create a QR code with a short discriminator.
if (error != nil) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE];
}
return nil;
}
if (self.discoveryCapabilities == MTRDiscoveryCapabilitiesUnknown) {
// Can't create a QR code if we don't know the discovery capabilities.
if (error != nil) {
*error = [MTRError errorForCHIPErrorCode:CHIP_ERROR_INCORRECT_STATE];
}
return nil;
}
chip::SetupPayload payload;
payload.version = [self.version unsignedCharValue];
payload.vendorID = [self.vendorID unsignedShortValue];
payload.productID = [self.productID unsignedShortValue];
payload.commissioningFlow = [MTRSetupPayload unconvertCommissioningFlow:self.commissioningFlow];
payload.rendezvousInformation = [MTRSetupPayload convertDiscoveryCapabilities:self.discoveryCapabilities];
payload.discriminator.SetLongValue([self.discriminator unsignedShortValue]);
payload.setUpPINCode = [self.setupPasscode unsignedIntValue];
if (self.serialNumber != nil) {
CHIP_ERROR err = payload.addSerialNumber(self.serialNumber.UTF8String);
if (err != CHIP_NO_ERROR) {
if (error != nil) {
*error = [MTRError errorForCHIPErrorCode:err];
}
return nil;
}
}
std::string outQRCodeString;
CHIP_ERROR err = chip::QRCodeSetupPayloadGenerator(payload).payloadBase38RepresentationWithAutoTLVBuffer(outQRCodeString);
if (err != CHIP_NO_ERROR) {
if (error != nil) {
*error = [MTRError errorForCHIPErrorCode:err];
}
return nil;
}
return [NSString stringWithUTF8String:outQRCodeString.c_str()];
}
+ (nullable NSNumber *)_boxDiscoveryCapabilities:(MTRDiscoveryCapabilities)discoveryCapabilities
{
if (discoveryCapabilities == MTRDiscoveryCapabilitiesUnknown) {
return nil;
}
return @(discoveryCapabilities);
}
+ (MTRDiscoveryCapabilities)_unboxDiscoveryCapabilities:(nullable NSNumber *)boxedDiscoveryCapabilities
{
if (boxedDiscoveryCapabilities == nil) {
return MTRDiscoveryCapabilitiesUnknown;
}
NSUInteger value = [boxedDiscoveryCapabilities unsignedIntegerValue];
if (value == MTRDiscoveryCapabilitiesUnknown) {
// The discovery capabilities were actually known
// (rendezvousInformation is not nil), and must support on-network.
return MTRDiscoveryCapabilitiesOnNetwork;
}
return static_cast<MTRDiscoveryCapabilities>(value);
}
@end
@implementation MTROptionalQRCodeInfo (Deprecated)
- (NSNumber *)infoType
{
return @(self.type);
}
- (void)setInfoType:(NSNumber *)infoType
{
self.type = static_cast<MTROptionalQRCodeInfoType>([infoType unsignedIntegerValue]);
}
@end
@implementation MTRSetupPayload (Deprecated)
- (nullable NSNumber *)rendezvousInformation
{
return [MTRSetupPayload _boxDiscoveryCapabilities:self.discoveryCapabilities];
}
- (void)setRendezvousInformation:(nullable NSNumber *)rendezvousInformation
{
self.discoveryCapabilities = [MTRSetupPayload _unboxDiscoveryCapabilities:rendezvousInformation];
}
- (NSNumber *)setUpPINCode
{
return self.setupPasscode;
}
- (void)setSetUpPINCode:(NSNumber *)setUpPINCode
{
self.setupPasscode = setUpPINCode;
}
- (instancetype)init
{
if (self = [super init]) {
_version = @(0); // Only supported Matter version so far.
_vendorID = @(0); // Not available.
_productID = @(0); // Not available.
_commissioningFlow = MTRCommissioningFlowStandard;
_discoveryCapabilities = MTRDiscoveryCapabilitiesUnknown;
_hasShortDiscriminator = NO;
_discriminator = @(0);
_setupPasscode = @(11111111); // Invalid passcode
_serialNumber = nil;
}
return self;
}
+ (instancetype)new
{
return [[self alloc] init];
}
@end