Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make MTRCommissioneeInfo and related types conform to NSSecureCoding
Browse files Browse the repository at this point in the history
Also conform to NSCopying, mark as sendable, and implement isEqual/hash.
ksperling-apple committed Jan 22, 2025
1 parent 1449b0c commit 3cff2a3
Showing 15 changed files with 286 additions and 19 deletions.
3 changes: 2 additions & 1 deletion src/darwin/Framework/CHIP/MTRCommissioneeInfo.h
Original file line number Diff line number Diff line change
@@ -24,8 +24,9 @@ NS_ASSUME_NONNULL_BEGIN
/**
* Information read from the commissionee device during commissioning.
*/
NS_SWIFT_SENDABLE
MTR_NEWLY_AVAILABLE
@interface MTRCommissioneeInfo : NSObject
@interface MTRCommissioneeInfo : NSObject <NSCopying, NSSecureCoding>

/**
* The product identity (VID / PID) of the commissionee.
46 changes: 46 additions & 0 deletions src/darwin/Framework/CHIP/MTRCommissioneeInfo.mm
Original file line number Diff line number Diff line change
@@ -19,9 +19,11 @@
#import "MTREndpointInfo_Internal.h"
#import "MTRDefines_Internal.h"
#import "MTRProductIdentity.h"
#import "MTRUtilities.h"

NS_ASSUME_NONNULL_BEGIN

MTR_DIRECT_MEMBERS
@implementation MTRCommissioneeInfo

- (instancetype)initWithCommissioningInfo:(const chip::Controller::ReadCommissioningInfo &)info
@@ -39,6 +41,50 @@ - (instancetype)initWithCommissioningInfo:(const chip::Controller::ReadCommissio
return self;
}

static NSString * const sProductIdentityCodingKey = @"pi";
static NSString * const sEndpointsCodingKey = @"ep";

- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
self = [super init];
_productIdentity = [coder decodeObjectOfClass:MTRProductIdentity.class forKey:sProductIdentityCodingKey];
VerifyOrReturnValue(_productIdentity != nil, nil);
_endpointsById = [coder decodeDictionaryWithKeysOfClass:NSNumber.class
objectsOfClass:MTREndpointInfo.class
forKey:sEndpointsCodingKey];
return self;
}

- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:_productIdentity forKey:sProductIdentityCodingKey];
[coder encodeObject:_endpointsById forKey:sEndpointsCodingKey];
}

+ (BOOL)supportsSecureCoding
{
return YES;
}

- (NSUInteger)hash
{
return _productIdentity.hash;
}

- (BOOL)isEqual:(id)object
{
VerifyOrReturnValue([object class] == [self class], NO);
MTRCommissioneeInfo * other = object;
VerifyOrReturnValue(MTREqualObjects(_productIdentity, other->_productIdentity), NO);
VerifyOrReturnValue(MTREqualObjects(_endpointsById, other->_endpointsById), NO);
return YES;
}

- (id)copyWithZone:(nullable NSZone *)zone
{
return self; // immutable
}

- (nullable MTREndpointInfo *)rootEndpoint
{
return self.endpointsById[@0];
3 changes: 3 additions & 0 deletions src/darwin/Framework/CHIP/MTRCommissioneeInfo_Internal.h
Original file line number Diff line number Diff line change
@@ -16,10 +16,13 @@

#import <Matter/MTRCommissioneeInfo.h>

#import "MTRDefines_Internal.h"

#include <controller/CommissioningDelegate.h>

NS_ASSUME_NONNULL_BEGIN

MTR_DIRECT_MEMBERS
@interface MTRCommissioneeInfo ()

- (instancetype)initWithCommissioningInfo:(const chip::Controller::ReadCommissioningInfo &)info;
6 changes: 5 additions & 1 deletion src/darwin/Framework/CHIP/MTRDeviceType.mm
Original file line number Diff line number Diff line change
@@ -24,6 +24,8 @@
#include <lib/support/CodeUtils.h>
#include <lib/support/SafeInt.h>

NS_ASSUME_NONNULL_BEGIN

using namespace chip;

MTR_DIRECT_MEMBERS
@@ -68,7 +70,7 @@ + (nullable MTRDeviceType *)deviceTypeForID:(NSNumber *)deviceTypeID
return [[MTRDeviceType alloc] initWithDeviceTypeData:deviceTypeData];
}

- (id)copyWithZone:(NSZone *)zone
- (id)copyWithZone:(nullable NSZone *)zone
{
return self; // immutable
}
@@ -92,3 +94,5 @@ - (NSString *)description
}

@end

NS_ASSUME_NONNULL_END
6 changes: 5 additions & 1 deletion src/darwin/Framework/CHIP/MTRDeviceTypeRevision.h
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN
*/
NS_SWIFT_SENDABLE
MTR_AVAILABLE(ios(17.6), macos(14.6), watchos(10.6), tvos(17.6))
@interface MTRDeviceTypeRevision : NSObject <NSCopying>
@interface MTRDeviceTypeRevision : NSObject <NSCopying> /* <NSSecureCoding> (see below) */

- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@@ -57,4 +57,8 @@ MTR_AVAILABLE(ios(17.6), macos(14.6), watchos(10.6), tvos(17.6))

@end

MTR_NEWLY_AVAILABLE
@interface MTRDeviceTypeRevision () <NSSecureCoding>
@end

NS_ASSUME_NONNULL_END
32 changes: 29 additions & 3 deletions src/darwin/Framework/CHIP/MTRDeviceTypeRevision.mm
Original file line number Diff line number Diff line change
@@ -25,6 +25,8 @@
#include <lib/support/CodeUtils.h>
#include <lib/support/SafeInt.h>

NS_ASSUME_NONNULL_BEGIN

using namespace chip;

MTR_DIRECT_MEMBERS
@@ -56,7 +58,7 @@ - (nullable instancetype)initWithDeviceTypeID:(NSNumber *)deviceTypeID revision:
return [self initInternalWithDeviceTypeID:[deviceTypeID copy] revision:[revision copy]];
}

- (instancetype)initWithDeviceTypeStruct:(MTRDescriptorClusterDeviceTypeStruct *)deviceTypeStruct
- (nullable instancetype)initWithDeviceTypeStruct:(MTRDescriptorClusterDeviceTypeStruct *)deviceTypeStruct
{
return [self initWithDeviceTypeID:deviceTypeStruct.deviceType revision:deviceTypeStruct.revision];
}
@@ -71,12 +73,34 @@ - (instancetype)initInternalWithDeviceTypeID:(NSNumber *)deviceTypeID revision:(
return self;
}

- (MTRDeviceType *)typeInformation
static NSString * const sTypeIdCodingKey = @"ty";
static NSString * const sRevisionCodingKey = @"re";

- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
self = [super init];
_deviceTypeID = @(static_cast<DeviceTypeId>([coder decodeInt64ForKey:sTypeIdCodingKey])); // int64_t encompasses uint32_t
_deviceTypeRevision = @(static_cast<uint16_t>([coder decodeIntegerForKey:sRevisionCodingKey]));
return self;
}

- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeInt64:static_cast<DeviceTypeId>(_deviceTypeID.unsignedLongLongValue) forKey:sTypeIdCodingKey];
[coder encodeInteger:static_cast<uint16_t>(_deviceTypeRevision.unsignedIntegerValue) forKey:sRevisionCodingKey];
}

+ (BOOL)supportsSecureCoding
{
return YES;
}

- (nullable MTRDeviceType *)typeInformation
{
return [MTRDeviceType deviceTypeForID:_deviceTypeID];
}

- (id)copyWithZone:(NSZone *)zone
- (id)copyWithZone:(nullable NSZone *)zone
{
// We have no mutable state.
return self;
@@ -106,3 +130,5 @@ - (NSString *)description
}

@end

NS_ASSUME_NONNULL_END
3 changes: 2 additions & 1 deletion src/darwin/Framework/CHIP/MTREndpointInfo.h
Original file line number Diff line number Diff line change
@@ -23,8 +23,9 @@ NS_ASSUME_NONNULL_BEGIN
/**
* Meta-data about an endpoint of a Matter node.
*/
NS_SWIFT_SENDABLE
MTR_NEWLY_AVAILABLE
@interface MTREndpointInfo : NSObject
@interface MTREndpointInfo : NSObject <NSCopying, NSSecureCoding>

- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
61 changes: 59 additions & 2 deletions src/darwin/Framework/CHIP/MTREndpointInfo.mm
Original file line number Diff line number Diff line change
@@ -26,6 +26,8 @@

#include <deque>

NS_ASSUME_NONNULL_BEGIN

using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
@@ -56,16 +58,69 @@ - (instancetype)initWithEndpointID:(NSNumber *)endpointID
return self;
}

- (NSNumber *)endpointID
static NSString * const sEndpointIDCodingKey = @"id";
static NSString * const sDeviceTypesCodingKey = @"dt";
static NSString * const sPartsListCodingKey = @"pl";
static NSString * const sChildrenCodingKey = @"ch";

- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
return @(_endpointID);
self = [super init];
_endpointID = static_cast<EndpointId>([coder decodeIntegerForKey:sEndpointIDCodingKey]);
_deviceTypes = [coder decodeArrayOfObjectsOfClass:MTRDeviceTypeRevision.class forKey:sDeviceTypesCodingKey];
VerifyOrReturnValue(_deviceTypes != nil, nil);
_partsList = [coder decodeArrayOfObjectsOfClass:NSNumber.class forKey:sPartsListCodingKey];
VerifyOrReturnValue(_partsList != nil, nil);
_children = [coder decodeArrayOfObjectsOfClass:MTREndpointInfo.class forKey:sChildrenCodingKey];
VerifyOrReturnValue(_children != nil, nil);
return self;
}

- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeInteger:_endpointID forKey:sEndpointIDCodingKey];
[coder encodeObject:_deviceTypes forKey:sDeviceTypesCodingKey];
[coder encodeObject:_partsList forKey:sPartsListCodingKey];
[coder encodeObject:_children forKey:sChildrenCodingKey];
}

+ (BOOL)supportsSecureCoding
{
return YES;
}

- (id)copyWithZone:(nullable NSZone *)zone
{
return self; // no (externally) mutable state
}

- (NSUInteger)hash
{
return _endpointID;
}

- (BOOL)isEqual:(id)object
{
VerifyOrReturnValue([object class] == [self class], NO);
MTREndpointInfo * other = object;
VerifyOrReturnValue(_endpointID == other->_endpointID, NO);
VerifyOrReturnValue([_deviceTypes isEqual:other->_deviceTypes], NO);
VerifyOrReturnValue([_partsList isEqual:other->_partsList], NO);
// Children are derived from PartsLists, so we don't need to compare them.
// This avoids a lot recursive comparisons when comparing a dictionary of endpoints.
return YES;
}

- (NSString *)description
{
return [NSString stringWithFormat:@"<%@ %u>", self.class, _endpointID];
}

- (NSNumber *)endpointID
{
return @(_endpointID);
}

+ (BOOL)populateChildrenForEndpoints:(NSDictionary<NSNumber *, MTREndpointInfo *> *)endpoints
{
// Populate the child list of each endpoint, ensuring no cycles (these are disallowed
@@ -210,3 +265,5 @@ + (BOOL)populateChildrenForEndpoints:(NSDictionary<NSNumber *, MTREndpointInfo *
}

@end

NS_ASSUME_NONNULL_END
2 changes: 1 addition & 1 deletion src/darwin/Framework/CHIP/MTREndpointInfo_Test.h
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ MTR_TESTABLE_DIRECT_MEMBERS

- (instancetype)initWithEndpointID:(NSNumber *)endpointID
deviceTypes:(NSArray<MTRDeviceTypeRevision *> *)deviceTypes
partsList:(NSArray<NSNumber *> *)partsList NS_DESIGNATED_INITIALIZER;
partsList:(NSArray<NSNumber *> *)partsList;

// Populates the children array for each endpoint in the provided dictionary.
// Returns YES if the endpoint hierarchy was populated correctly.
5 changes: 3 additions & 2 deletions src/darwin/Framework/CHIP/MTRProductIdentity.h
Original file line number Diff line number Diff line change
@@ -21,8 +21,9 @@ NS_ASSUME_NONNULL_BEGIN
/**
* A representation of a (vendor, product) pair that identifies a specific product.
*/
NS_SWIFT_SENDABLE
MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0))
@interface MTRProductIdentity : NSObject /* <NSCopying> (see below) */
@interface MTRProductIdentity : NSObject /* <NSCopying, NSSecureCoding> (see below) */

- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@@ -35,7 +36,7 @@ MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0))
@end

MTR_NEWLY_AVAILABLE
@interface MTRProductIdentity () <NSCopying>
@interface MTRProductIdentity () <NSCopying, NSSecureCoding>
@end

NS_ASSUME_NONNULL_END
49 changes: 43 additions & 6 deletions src/darwin/Framework/CHIP/MTRProductIdentity.mm
Original file line number Diff line number Diff line change
@@ -22,37 +22,74 @@
#include <lib/support/CodeUtils.h>

MTR_DIRECT_MEMBERS
@implementation MTRProductIdentity
@implementation MTRProductIdentity {
uint16_t _vendorID;
uint16_t _productID;
}

- (instancetype)initWithVendorID:(NSNumber *)vendorID productID:(NSNumber *)productID
{
self = [super init];
VerifyOrReturnValue(vendorID != nil && productID != nil, nil);
_vendorID = vendorID;
_productID = productID;
_vendorID = vendorID.unsignedShortValue;
_productID = productID.unsignedShortValue;
return self;
}

static NSString * const sVendorIDKey = @"v";
static NSString * const sProductIDKey = @"p";

- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
self = [super init];
_vendorID = static_cast<uint16_t>([coder decodeIntForKey:sVendorIDKey]);
_productID = static_cast<uint16_t>([coder decodeIntForKey:sProductIDKey]);
return self;
}

- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeInt:_vendorID forKey:sVendorIDKey];
[coder encodeInt:_productID forKey:sProductIDKey];
}

+ (BOOL)supportsSecureCoding
{
return YES;
}

- (id)copyWithZone:(NSZone *)zone
{
return self; // immutable
}

- (NSUInteger)hash
{
return _vendorID.hash ^ _productID.hash;
return (_vendorID << 16) | _productID;
}

- (BOOL)isEqual:(id)object
{
VerifyOrReturnValue([object class] == [self class], NO);
MTRProductIdentity * other = object;
return MTREqualObjects(_vendorID, other.vendorID) && MTREqualObjects(_productID, other.productID);
VerifyOrReturnValue(_vendorID == other->_vendorID, NO);
VerifyOrReturnValue(_productID == other->_productID, NO);
return YES;
}

- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: vid 0x%x pid 0x%x>", self.class, _vendorID.unsignedIntValue, _productID.unsignedIntValue];
return [NSString stringWithFormat:@"<%@: vid 0x%x pid 0x%x>", self.class, _vendorID, _productID];
}

- (NSNumber *)vendorID
{
return @(_vendorID);
}

- (NSNumber *)productID
{
return @(_productID);
}

@end
11 changes: 11 additions & 0 deletions src/darwin/Framework/CHIPTests/MTRDeviceTypeRevisionTests.m
Original file line number Diff line number Diff line change
@@ -82,4 +82,15 @@ - (void)testEqualityAndCopying
XCTAssertFalse([b isEqual:c]);
}

- (void)testSecureCoding
{
MTRDeviceTypeRevision * a = [[MTRDeviceTypeRevision alloc] initWithDeviceTypeID:@(MTRDeviceTypeIDTypeMicrowaveOvenID) revision:@1];
NSData * data = [NSKeyedArchiver archivedDataWithRootObject:a requiringSecureCoding:YES error:NULL];
MTRDeviceTypeRevision * b = [NSKeyedUnarchiver unarchivedObjectOfClass:MTRDeviceTypeRevision.class fromData:data error:NULL];
XCTAssertNotNil(b);
XCTAssertEqualObjects(b.deviceTypeID, a.deviceTypeID);
XCTAssertEqualObjects(b.deviceTypeRevision, a.deviceTypeRevision);
XCTAssertTrue([b isEqual:a]);
}

@end
55 changes: 55 additions & 0 deletions src/darwin/Framework/CHIPTests/MTREndpointInfoTests.m
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@
*/

#import "MTREndpointInfo_Test.h"
#import <Matter/Matter.h>

#import <XCTest/XCTest.h>

@@ -143,4 +144,58 @@ - (void)testPopulateChildrenInvalidNonTree
XCTAssertEqualObjects(Exclude(ChildEndpointIDs(endpoints[@5]), @6), (@[]));
}

- (void)testEqualityAndCopying
{
MTRDeviceTypeRevision * doorLock = [[MTRDeviceTypeRevision alloc] initWithDeviceTypeID:@0x0A revision:@1];
MTRDeviceTypeRevision * rootNode = [[MTRDeviceTypeRevision alloc] initWithDeviceTypeID:@0x16 revision:@1];
MTREndpointInfo * a1 = [[MTREndpointInfo alloc] initWithEndpointID:@1 deviceTypes:@[ rootNode ] partsList:@[]];
XCTAssertTrue([a1 isEqual:a1]);
XCTAssertTrue([a1 isEqual:[a1 copy]]);
XCTAssertFalse([a1 isEqual:nil]);
XCTAssertFalse([a1 isEqual:@"hello"]);
MTREndpointInfo * a2 = [[MTREndpointInfo alloc] initWithEndpointID:@1 deviceTypes:@[ rootNode ] partsList:@[]];
XCTAssertTrue([a1 isEqual:a2]);
XCTAssertTrue([a2 isEqual:a1]);
XCTAssertEqual(a1.hash, a2.hash);
MTREndpointInfo * b = [[MTREndpointInfo alloc] initWithEndpointID:@1 deviceTypes:@[ rootNode ] partsList:@[ @2 ]];
XCTAssertFalse([a1 isEqual:b]);
XCTAssertFalse([b isEqual:a1]);
MTREndpointInfo * c = [[MTREndpointInfo alloc] initWithEndpointID:@1 deviceTypes:@[ doorLock ] partsList:@[]];
XCTAssertFalse([a1 isEqual:c]);
XCTAssertFalse([c isEqual:a1]);
MTREndpointInfo * d = [[MTREndpointInfo alloc] initWithEndpointID:@2 deviceTypes:@[ rootNode ] partsList:@[]];
XCTAssertFalse([a1 isEqual:d]);
XCTAssertFalse([d isEqual:a1]);
}

- (void)testSecureCoding
{
NSDictionary<NSNumber *, MTREndpointInfo *> * endpoints = [self indexEndpoints:@[
MakeEndpoint(@0, @[ @1, @2, @3, @4, @5, @6 ]), // full-family pattern
MakeEndpoint(@1, @[ @2, @3 ]),
MakeEndpoint(@2, @[]),
MakeEndpoint(@3, @[]),
MakeEndpoint(@4, @[ @5, @6 ]), // full-family pattern
MakeEndpoint(@5, @[ @6 ]),
MakeEndpoint(@6, @[]),
]];
XCTAssertTrue([MTREndpointInfo populateChildrenForEndpoints:endpoints]);

NSData * data = [NSKeyedArchiver archivedDataWithRootObject:endpoints.allValues requiringSecureCoding:YES error:NULL];
NSArray<MTREndpointInfo *> * decodedEndpoints = [NSKeyedUnarchiver unarchivedArrayOfObjectsOfClass:MTREndpointInfo.class fromData:data error:NULL];

XCTAssertNotNil(decodedEndpoints);
XCTAssertEqualObjects(decodedEndpoints, endpoints.allValues);

// Deeply compare by hand as well, `children` is not checked by isEqual:
[decodedEndpoints enumerateObjectsUsingBlock:^(MTREndpointInfo * decoded, NSUInteger idx, BOOL * stop) {
MTREndpointInfo * original = endpoints.allValues[idx];
XCTAssertTrue([decoded isEqual:original]);
XCTAssertEqualObjects(decoded.endpointID, original.endpointID);
XCTAssertEqualObjects(decoded.deviceTypes, original.deviceTypes);
XCTAssertEqualObjects(decoded.partsList, original.partsList);
XCTAssertEqualObjects(decoded.children, original.children);
}];
}

@end
12 changes: 11 additions & 1 deletion src/darwin/Framework/CHIPTests/MTRPairingTests.m
Original file line number Diff line number Diff line change
@@ -148,7 +148,7 @@ - (void)controller:(MTRDeviceController *)controller readCommissioneeInfo:(MTRCo
if (self.shouldReadEndpointInformation) {
XCTAssertNotNil(info.endpointsById);
XCTAssertNotNil(info.rootEndpoint);
XCTAssertGreaterThanOrEqual(info.rootEndpoint.children.count, 2); // at least one application endpoint
XCTAssertGreaterThanOrEqual(info.rootEndpoint.children.count, 1); // at least one application endpoint
for (MTREndpointInfo * endpoint in info.endpointsById.allValues) {
XCTAssertGreaterThanOrEqual(endpoint.deviceTypes.count, 1);
XCTAssertNotNil(endpoint.children);
@@ -158,6 +158,16 @@ - (void)controller:(MTRDeviceController *)controller readCommissioneeInfo:(MTRCo
XCTAssertTrue([endpoint.partsList containsObject:child.endpointID]);
}
}

// There is currently no convenient way to initialize an MTRCommissioneeInfo
// object from basic ObjC data types, so we do some unit testing here.
NSData * data = [NSKeyedArchiver archivedDataWithRootObject:info requiringSecureCoding:YES error:NULL];
MTRCommissioneeInfo * decoded = [NSKeyedUnarchiver unarchivedObjectOfClass:MTRCommissioneeInfo.class fromData:data error:NULL];
XCTAssertNotNil(decoded);
XCTAssertTrue([decoded isEqual:info]);
XCTAssertEqualObjects(decoded.productIdentity, info.productIdentity);
XCTAssertEqualObjects(decoded.endpointsById, info.endpointsById);
XCTAssertEqualObjects(decoded.rootEndpoint.children, info.rootEndpoint.children);
} else {
XCTAssertNil(info.endpointsById);
XCTAssertNil(info.rootEndpoint);
11 changes: 11 additions & 0 deletions src/darwin/Framework/CHIPTests/MTRProductIdentityTests.m
Original file line number Diff line number Diff line change
@@ -50,4 +50,15 @@ - (void)testEqualityAndCopying
XCTAssertFalse([b isEqual:c]);
}

- (void)testSecureCoding
{
MTRProductIdentity * a = [[MTRProductIdentity alloc] initWithVendorID:@123 productID:@42];
NSData * data = [NSKeyedArchiver archivedDataWithRootObject:a requiringSecureCoding:YES error:NULL];
MTRProductIdentity * b = [NSKeyedUnarchiver unarchivedObjectOfClass:MTRProductIdentity.class fromData:data error:NULL];
XCTAssertNotNil(b);
XCTAssertEqualObjects(b.vendorID, a.vendorID);
XCTAssertEqualObjects(b.productID, a.productID);
XCTAssertTrue([b isEqual:a]);
}

@end

0 comments on commit 3cff2a3

Please sign in to comment.