Add the ability to read extra attributes during commissioning to Matter.framework. (#40280)
* Add the ability to read extra attributes during commissioning to Matter.framework.
* Address review comments.
* Address review comments.
diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.mm b/src/darwin/Framework/CHIP/MTRBaseDevice.mm
index 0f6e939..37634ca 100644
--- a/src/darwin/Framework/CHIP/MTRBaseDevice.mm
+++ b/src/darwin/Framework/CHIP/MTRBaseDevice.mm
@@ -2762,7 +2762,7 @@
@end
@implementation MTRAttributePath
-- (instancetype)initWithPath:(const ConcreteDataAttributePath &)path
+- (instancetype)initWithPath:(const ConcreteAttributePath &)path
{
if (self = [super initWithPath:path]) {
_attribute = @(path.mAttributeId);
diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h b/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h
index 4cf02eb..cc9dd9a 100644
--- a/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h
+++ b/src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h
@@ -223,7 +223,7 @@
@end
@interface MTRAttributePath ()
-- (instancetype)initWithPath:(const chip::app::ConcreteDataAttributePath &)path;
+- (instancetype)initWithPath:(const chip::app::ConcreteAttributePath &)path;
@end
@interface MTREventPath ()
diff --git a/src/darwin/Framework/CHIP/MTRCommissioneeInfo.h b/src/darwin/Framework/CHIP/MTRCommissioneeInfo.h
index d2e6bd6..701027a 100644
--- a/src/darwin/Framework/CHIP/MTRCommissioneeInfo.h
+++ b/src/darwin/Framework/CHIP/MTRCommissioneeInfo.h
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#import <Matter/MTRBaseDevice.h> // For MTRAttributePath
#import <Matter/MTRDefines.h>
@class MTRProductIdentity;
@@ -47,6 +48,12 @@
*/
@property (nonatomic, copy, readonly, nullable) MTREndpointInfo * rootEndpoint;
+/**
+ * Attributes that were read from the commissionee. Will be present only if
+ * extraAttributesToRead is set on MTRCommissioningParameters.
+ */
+@property (nonatomic, copy, readonly, nullable) NSDictionary<MTRAttributePath *, NSDictionary<NSString *, id> *> * attributes MTR_PROVISIONALLY_AVAILABLE;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/src/darwin/Framework/CHIP/MTRCommissioneeInfo.mm b/src/darwin/Framework/CHIP/MTRCommissioneeInfo.mm
index 0456976..027bbb8 100644
--- a/src/darwin/Framework/CHIP/MTRCommissioneeInfo.mm
+++ b/src/darwin/Framework/CHIP/MTRCommissioneeInfo.mm
@@ -16,26 +16,90 @@
#import "MTRCommissioneeInfo_Internal.h"
+#import "MTRBaseDevice.h"
+#import "MTRBaseDevice_Internal.h"
#import "MTRDefines_Internal.h"
+#import "MTRDeviceDataValidation.h"
#import "MTREndpointInfo_Internal.h"
+#import "MTRLogging_Internal.h"
#import "MTRProductIdentity.h"
#import "MTRUtilities.h"
+#include <app/AttributePathParams.h>
+#include <lib/core/TLVReader.h>
+
NS_ASSUME_NONNULL_BEGIN
MTR_DIRECT_MEMBERS
@implementation MTRCommissioneeInfo
-- (instancetype)initWithCommissioningInfo:(const chip::Controller::ReadCommissioningInfo &)info
+- (instancetype)initWithCommissioningInfo:(const chip::Controller::ReadCommissioningInfo &)info commissioningParameters:(MTRCommissioningParameters *)commissioningParameters
{
self = [super init];
_productIdentity = [[MTRProductIdentity alloc] initWithVendorID:@(info.basic.vendorId) productID:@(info.basic.productId)];
- // TODO: We should probably hold onto our MTRCommissioningParameters so we can look at `readEndpointInformation`
- // instead of just reading whatever Descriptor cluster information happens to be in the cache.
- auto * endpoints = [MTREndpointInfo endpointsFromAttributeCache:info.attributes];
- if (endpoints.count > 0) {
- _endpointsById = endpoints;
+ if (commissioningParameters.readEndpointInformation) {
+ auto * endpoints = [MTREndpointInfo endpointsFromAttributeCache:info.attributes];
+ if (endpoints.count > 0) {
+ _endpointsById = endpoints;
+ }
+ }
+
+ if (commissioningParameters.extraAttributesToRead != nil && info.attributes != nullptr) {
+ NSMutableDictionary<MTRAttributePath *, NSDictionary<NSString *, id> *> * attributes = [[NSMutableDictionary alloc] init];
+
+ std::vector<chip::app::AttributePathParams> requestPaths;
+ for (MTRAttributeRequestPath * requestPath in commissioningParameters.extraAttributesToRead) {
+ [requestPath convertToAttributePathParams:requestPaths.emplace_back()];
+ }
+
+ info.attributes->ForEachAttribute([&](const chip::app::ConcreteAttributePath & path) -> CHIP_ERROR {
+ // Only grab paths that are included in extraAttributesToRead so that
+ // API consumers don't develop dependencies on implementation details
+ // (like which other attributes we happen to read).
+
+ // TODO: This means API consumers might duplicate attribute reads we
+ // already do. We should either offer guarantees about things like
+ // "network commissioning feature maps" that will always be present, or
+ // perhaps dedup in some way under the hood when issuing the
+ // reads.
+
+ // This is unfortunately not very efficient; if we have a lot of
+ // paths we may need a better way to do this.
+ bool isRequestedPath = false;
+ for (auto & requestPath : requestPaths) {
+ if (!requestPath.IsAttributePathSupersetOf(path)) {
+ continue;
+ }
+
+ isRequestedPath = true;
+ break;
+ }
+
+ if (!isRequestedPath) {
+ // Skip it.
+ return CHIP_NO_ERROR;
+ }
+
+ chip::TLV::TLVReader reader;
+ CHIP_ERROR err = info.attributes->Get(path, reader);
+ if (err != CHIP_NO_ERROR) {
+ // We actually got an error, not data. Just skip this path.
+ return CHIP_NO_ERROR;
+ }
+
+ auto value = MTRDecodeDataValueDictionaryFromCHIPTLV(&reader);
+ if (value == nil) {
+ // Decode errors can happen (e.g. invalid TLV); just skip this path.
+ return CHIP_NO_ERROR;
+ }
+
+ auto * mtrPath = [[MTRAttributePath alloc] initWithPath:path];
+ attributes[mtrPath] = value;
+ return CHIP_NO_ERROR;
+ });
+
+ _attributes = attributes;
}
return self;
@@ -43,6 +107,7 @@
static NSString * const sProductIdentityCodingKey = @"pi";
static NSString * const sEndpointsCodingKey = @"ep";
+static NSString * const sAttributesCodingKey = @"at";
- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
@@ -52,6 +117,34 @@
_endpointsById = [coder decodeDictionaryWithKeysOfClass:NSNumber.class
objectsOfClass:MTREndpointInfo.class
forKey:sEndpointsCodingKey];
+
+ // TODO: Can we do better about duplicating the set of classes that appear
+ // in data-values? We have this set in a bunch of places.... But here we need
+ // not just those, but also MTRAttributePath.
+ static NSSet * const sAttributeClasses = [NSSet setWithObjects:NSDictionary.class, NSArray.class, NSData.class, NSString.class, NSNumber.class, MTRAttributePath.class, nil];
+ _attributes = [coder decodeObjectOfClasses:sAttributeClasses forKey:sAttributesCodingKey];
+
+ if (_attributes != nil) {
+ // Check that the right types are in the right places.
+ if (![_attributes isKindOfClass:NSDictionary.class]) {
+ MTR_LOG_ERROR("MTRCommissioneeInfo decoding: attributes are not a dictionary: %@", _attributes);
+ return nil;
+ }
+
+ for (id key in _attributes) {
+ if (![key isKindOfClass:MTRAttributePath.class]) {
+ MTR_LOG_ERROR("MTRCommissioneeInfo decoding: expected MTRAttributePath but found %@", key);
+ return nil;
+ }
+
+ id value = _attributes[key];
+ if (![value isKindOfClass:NSDictionary.class] || !MTRDataValueDictionaryIsWellFormed(value)) {
+ MTR_LOG_ERROR("MTRCommissioneeInfo decoding: expected data-value dictionary but found %@", value);
+ return nil;
+ }
+ }
+ }
+
return self;
}
@@ -59,6 +152,7 @@
{
[coder encodeObject:_productIdentity forKey:sProductIdentityCodingKey];
[coder encodeObject:_endpointsById forKey:sEndpointsCodingKey];
+ [coder encodeObject:_attributes forKey:sAttributesCodingKey];
}
+ (BOOL)supportsSecureCoding
@@ -77,6 +171,8 @@
MTRCommissioneeInfo * other = object;
VerifyOrReturnValue(MTREqualObjects(_productIdentity, other->_productIdentity), NO);
VerifyOrReturnValue(MTREqualObjects(_endpointsById, other->_endpointsById), NO);
+ VerifyOrReturnValue(MTREqualObjects(_attributes, other->_attributes), NO);
+
return YES;
}
diff --git a/src/darwin/Framework/CHIP/MTRCommissioneeInfo_Internal.h b/src/darwin/Framework/CHIP/MTRCommissioneeInfo_Internal.h
index f892c5f..cbb92ca 100644
--- a/src/darwin/Framework/CHIP/MTRCommissioneeInfo_Internal.h
+++ b/src/darwin/Framework/CHIP/MTRCommissioneeInfo_Internal.h
@@ -15,6 +15,7 @@
*/
#import <Matter/MTRCommissioneeInfo.h>
+#import <Matter/MTRCommissioningParameters.h>
#import "MTRDefines_Internal.h"
@@ -25,7 +26,7 @@
MTR_DIRECT_MEMBERS
@interface MTRCommissioneeInfo ()
-- (instancetype)initWithCommissioningInfo:(const chip::Controller::ReadCommissioningInfo &)info;
+- (instancetype)initWithCommissioningInfo:(const chip::Controller::ReadCommissioningInfo &)info commissioningParameters:(MTRCommissioningParameters *)commissioningParameters;
@end
diff --git a/src/darwin/Framework/CHIP/MTRCommissioningParameters.h b/src/darwin/Framework/CHIP/MTRCommissioningParameters.h
index d61867a..94822da 100644
--- a/src/darwin/Framework/CHIP/MTRCommissioningParameters.h
+++ b/src/darwin/Framework/CHIP/MTRCommissioningParameters.h
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2022-2024 Project CHIP Authors
+ * Copyright (c) 2022-2025 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.
@@ -15,6 +15,7 @@
*/
#import <Foundation/Foundation.h>
+#import <Matter/MTRBaseDevice.h>
#import <Matter/MTRDefines.h>
NS_ASSUME_NONNULL_BEGIN
@@ -113,6 +114,13 @@
*/
@property (nonatomic, copy, nullable) NSNumber * acceptedTermsAndConditionsVersion MTR_PROVISIONALLY_AVAILABLE;
+/**
+ * List of attribute paths to read from the commissionee (in addition to
+ * whatever attributes are already read to handle readEndpointInformation being
+ * YES, or to handle other commissioning tasks).
+ */
+@property (nonatomic, copy, nullable) NSArray<MTRAttributeRequestPath *> * extraAttributesToRead MTR_UNSTABLE_API;
+
@end
@interface MTRCommissioningParameters (Deprecated)
diff --git a/src/darwin/Framework/CHIP/MTRCommissioningParameters.mm b/src/darwin/Framework/CHIP/MTRCommissioningParameters.mm
index 895c6fb..e76cb39 100644
--- a/src/darwin/Framework/CHIP/MTRCommissioningParameters.mm
+++ b/src/darwin/Framework/CHIP/MTRCommissioningParameters.mm
@@ -21,6 +21,25 @@
@implementation MTRCommissioningParameters : NSObject
+- (id)copyWithZone:(NSZone * _Nullable)zone
+{
+ auto other = [[MTRCommissioningParameters alloc] init];
+ other.csrNonce = self.csrNonce;
+ other.attestationNonce = self.attestationNonce;
+ other.wifiSSID = self.wifiSSID;
+ other.wifiCredentials = self.wifiCredentials;
+ other.threadOperationalDataset = self.threadOperationalDataset;
+ other.deviceAttestationDelegate = self.deviceAttestationDelegate;
+ other.failSafeTimeout = self.failSafeTimeout;
+ other.skipCommissioningComplete = self.skipCommissioningComplete;
+ other.countryCode = self.countryCode;
+ other.readEndpointInformation = self.readEndpointInformation;
+ other.acceptedTermsAndConditions = self.acceptedTermsAndConditions;
+ other.acceptedTermsAndConditionsVersion = self.acceptedTermsAndConditionsVersion;
+ other.extraAttributesToRead = self.extraAttributesToRead;
+ return other;
+}
+
@end
@implementation MTRCommissioningParameters (Deprecated)
diff --git a/src/darwin/Framework/CHIP/MTRCommissioningParameters_Internal.h b/src/darwin/Framework/CHIP/MTRCommissioningParameters_Internal.h
new file mode 100644
index 0000000..bf1d6d3
--- /dev/null
+++ b/src/darwin/Framework/CHIP/MTRCommissioningParameters_Internal.h
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2022-2024 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.
+ */
+
+/**
+ * We want to be able to copy MTRCommissioningParameters, but not commit to that
+ * as public API yet.
+ */
+@interface MTRCommissioningParameters () <NSCopying>
+@end
diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerDelegateBridge.h b/src/darwin/Framework/CHIP/MTRDeviceControllerDelegateBridge.h
index 425506f..9b1c22e 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceControllerDelegateBridge.h
+++ b/src/darwin/Framework/CHIP/MTRDeviceControllerDelegateBridge.h
@@ -15,6 +15,7 @@
* limitations under the License.
*/
+#import "MTRCommissioningParameters.h"
#import "MTRDeviceControllerDelegate.h"
#include <controller/CHIPDeviceController.h>
@@ -45,12 +46,16 @@
void SetDeviceNodeID(chip::NodeId deviceNodeId);
+ void SetCommissioningParameters(MTRCommissioningParameters * commissioningParameters);
+
private:
MTRDeviceController * __weak mController;
_Nullable id<MTRDeviceControllerDelegate> mDelegate;
_Nullable dispatch_queue_t mQueue;
chip::NodeId mDeviceNodeId;
+ MTRCommissioningParameters * mCommissioningParameters;
+
MTRCommissioningStatus MapStatus(chip::Controller::DevicePairingDelegate::Status status);
};
diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerDelegateBridge.mm b/src/darwin/Framework/CHIP/MTRDeviceControllerDelegateBridge.mm
index ff9d06d..b463973 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceControllerDelegateBridge.mm
+++ b/src/darwin/Framework/CHIP/MTRDeviceControllerDelegateBridge.mm
@@ -135,7 +135,7 @@
BOOL wantCommissioneeInfo = [strongDelegate respondsToSelector:@selector(controller:readCommissioneeInfo:)];
BOOL wantProductIdentity = [strongDelegate respondsToSelector:@selector(controller:readCommissioningInfo:)];
if (wantCommissioneeInfo || wantProductIdentity) {
- auto * commissioneeInfo = [[MTRCommissioneeInfo alloc] initWithCommissioningInfo:info];
+ auto * commissioneeInfo = [[MTRCommissioneeInfo alloc] initWithCommissioningInfo:info commissioningParameters:mCommissioningParameters];
dispatch_async(mQueue, ^{
if (wantCommissioneeInfo) { // prefer the newer delegate method over the deprecated one
[strongDelegate controller:strongController readCommissioneeInfo:commissioneeInfo];
@@ -144,6 +144,9 @@
}
});
}
+
+ // Don't hold on to the commissioning parameters now that we don't need them anymore.
+ mCommissioningParameters = nil;
}
void MTRDeviceControllerDelegateBridge::OnCommissioningComplete(chip::NodeId nodeId, CHIP_ERROR error)
@@ -214,3 +217,8 @@
{
mDeviceNodeId = deviceNodeId;
}
+
+void MTRDeviceControllerDelegateBridge::SetCommissioningParameters(MTRCommissioningParameters * commissioningParameters)
+{
+ mCommissioningParameters = commissioningParameters;
+}
diff --git a/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm
index 56f1ec2..9472ce8 100644
--- a/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm
+++ b/src/darwin/Framework/CHIP/MTRDeviceController_Concrete.mm
@@ -24,6 +24,7 @@
#import "MTRCommissionableBrowser.h"
#import "MTRCommissionableBrowserResult_Internal.h"
#import "MTRCommissioningParameters.h"
+#import "MTRCommissioningParameters_Internal.h"
#import "MTRConversion.h"
#import "MTRDeviceControllerDelegateBridge.h"
#import "MTRDeviceControllerFactory_Internal.h"
@@ -965,9 +966,22 @@
auto block = ^BOOL {
chip::Controller::CommissioningParameters params;
+
+ std::vector<chip::app::AttributePathParams> extraReadPaths;
if (commissioningParams.readEndpointInformation) {
- params.SetExtraReadPaths(MTREndpointInfo.requiredAttributePaths);
+ for (auto & path : MTREndpointInfo.requiredAttributePaths) {
+ extraReadPaths.emplace_back(path);
+ }
}
+ if (commissioningParams.extraAttributesToRead != nil) {
+ for (MTRAttributeRequestPath * path in commissioningParams.extraAttributesToRead) {
+ [path convertToAttributePathParams:extraReadPaths.emplace_back()];
+ }
+ }
+ if (!extraReadPaths.empty()) {
+ params.SetExtraReadPaths(chip::Span(extraReadPaths.data(), extraReadPaths.size()));
+ }
+
if (commissioningParams.csrNonce) {
params.SetCSRNonce(AsByteSpan(commissioningParams.csrNonce));
}
@@ -1076,6 +1090,8 @@
chip::NodeId deviceId = [nodeID unsignedLongLongValue];
self->_operationalCredentialsDelegate->SetDeviceID(deviceId);
+ self->_deviceControllerDelegateBridge->SetCommissioningParameters([commissioningParams copy]);
+
auto errorCode = self->_cppCommissioner->Commission(deviceId, params);
MATTER_LOG_METRIC(kMetricCommissionNode, errorCode);
return ![MTRDeviceController_Concrete checkForError:errorCode logMsg:kDeviceControllerErrorPairDevice error:error];
diff --git a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m
index 62d4bd9..2e3e8e4 100644
--- a/src/darwin/Framework/CHIPTests/MTRDeviceTests.m
+++ b/src/darwin/Framework/CHIPTests/MTRDeviceTests.m
@@ -32,6 +32,7 @@
#import "MTRDeviceTestDelegate.h"
#import "MTRDevice_Internal.h"
#import "MTRErrorTestUtils.h"
+#import "MTRSecureCodingTestHelpers.h"
#import "MTRTestCase+ServerAppRunner.h"
#import "MTRTestCase.h"
#import "MTRTestControllerDelegate.h"
@@ -3329,41 +3330,21 @@
XCTAssertTrue(storedAttributeCountDifferenceFromMTRDeviceReport > 300);
}
-- (NSData *)_encodeEncodable:(id<NSSecureCoding>)encodable
-{
- // We know all our encodables are in fact NSObject.
- NSObject * obj = (NSObject *) encodable;
-
- NSError * encodeError;
- NSData * encodedData = [NSKeyedArchiver archivedDataWithRootObject:encodable requiringSecureCoding:YES error:&encodeError];
- XCTAssertNil(encodeError, @"Failed to encode %@", NSStringFromClass(obj.class));
- return encodedData;
-}
-
- (void)doEncodeDecodeRoundTrip:(id<NSSecureCoding>)encodable
{
- NSData * encodedData = [self _encodeEncodable:encodable];
-
// We know all our encodables are in fact NSObject.
NSObject * obj = (NSObject *) encodable;
NSError * decodeError;
- id decodedValue = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithObject:obj.class] fromData:encodedData error:&decodeError];
+ id decodedValue = RoundTripEncodable(encodable, &decodeError);
XCTAssertNil(decodeError, @"Failed to decode %@", NSStringFromClass([obj class]));
- XCTAssertTrue([decodedValue isKindOfClass:obj.class], @"Expected %@ but got %@", NSStringFromClass(obj.class), NSStringFromClass([decodedValue class]));
-
XCTAssertEqualObjects(obj, decodedValue, @"Decoding for %@ did not round-trip correctly", NSStringFromClass([obj class]));
}
- (void)_ensureDecodeFails:(id<NSSecureCoding>)encodable
{
- NSData * encodedData = [self _encodeEncodable:encodable];
-
- // We know all our encodables are in fact NSObject.
- NSObject * obj = (NSObject *) encodable;
-
NSError * decodeError;
- id decodedValue = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithObject:obj.class] fromData:encodedData error:&decodeError];
+ id decodedValue = RoundTripEncodable(encodable, &decodeError);
XCTAssertNil(decodedValue);
XCTAssertNotNil(decodeError);
}
diff --git a/src/darwin/Framework/CHIPTests/MTRPairingTests.m b/src/darwin/Framework/CHIPTests/MTRPairingTests.m
index 4aade77..ba1356c 100644
--- a/src/darwin/Framework/CHIPTests/MTRPairingTests.m
+++ b/src/darwin/Framework/CHIPTests/MTRPairingTests.m
@@ -19,6 +19,7 @@
#import "MTRDefines_Internal.h"
#import "MTRErrorTestUtils.h"
+#import "MTRSecureCodingTestHelpers.h"
#import "MTRTestCase+ServerAppRunner.h"
#import "MTRTestCase.h"
#import "MTRTestDeclarations.h"
@@ -107,6 +108,7 @@
@property (nonatomic, nullable) id<MTRDeviceAttestationDelegate> attestationDelegate;
@property (nonatomic, nullable) NSNumber * failSafeExtension;
@property (nonatomic) BOOL shouldReadEndpointInformation;
+@property (nullable) NSArray<MTRAttributeRequestPath *> * extraAttributesToRead;
@property (nullable) NSError * commissioningCompleteError;
@end
@@ -132,6 +134,7 @@
params.deviceAttestationDelegate = self.attestationDelegate;
params.failSafeTimeout = self.failSafeExtension;
params.readEndpointInformation = self.shouldReadEndpointInformation;
+ params.extraAttributesToRead = self.extraAttributesToRead;
NSError * commissionError = nil;
XCTAssertTrue([controller commissionNodeWithID:@(sDeviceId) commissioningParams:params error:&commissionError],
@@ -145,6 +148,11 @@
XCTAssertNotNil(info.productIdentity);
XCTAssertEqualObjects(info.productIdentity.vendorID, /* Test Vendor 1 */ @0xFFF1);
+ NSError * decodeError;
+ id roundTrippedInfo = RoundTripEncodable(info, &decodeError);
+ XCTAssertNil(decodeError);
+ XCTAssertEqualObjects(info, roundTrippedInfo);
+
if (self.shouldReadEndpointInformation) {
XCTAssertNotNil(info.endpointsById);
XCTAssertNotNil(info.rootEndpoint);
@@ -161,8 +169,11 @@
// 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];
+ NSError * err;
+ NSData * data = [NSKeyedArchiver archivedDataWithRootObject:info requiringSecureCoding:YES error:&err];
+ XCTAssertNil(err);
+ MTRCommissioneeInfo * decoded = [NSKeyedUnarchiver unarchivedObjectOfClass:MTRCommissioneeInfo.class fromData:data error:&err];
+ XCTAssertNil(err);
XCTAssertNotNil(decoded);
XCTAssertTrue([decoded isEqual:info]);
XCTAssertEqualObjects(decoded.productIdentity, info.productIdentity);
@@ -172,6 +183,22 @@
XCTAssertNil(info.endpointsById);
XCTAssertNil(info.rootEndpoint);
}
+
+ if (self.extraAttributesToRead) {
+ // The attributes we tried to read should really have worked.
+ XCTAssertNotNil(info.attributes);
+ XCTAssertEqual(info.attributes.count, 2);
+ for (MTRAttributePath * path in info.attributes) {
+ XCTAssertEqualObjects(path.endpoint, @(0));
+ if ([path.cluster isEqual:@(MTRClusterIDTypeDescriptorID)]) {
+ XCTAssertEqualObjects(path.attribute, @(MTRAttributeIDTypeGlobalAttributeAttributeListID));
+ } else if ([path.cluster isEqual:@(MTRClusterIDTypeBasicInformationID)]) {
+ XCTAssertEqualObjects(path.attribute, @(MTRAttributeIDTypeClusterBasicInformationAttributeVendorNameID));
+ } else {
+ XCTFail("Unexpected cluster id %@", path.cluster);
+ }
+ }
+ }
}
- (void)controller:(MTRDeviceController *)controller commissioningComplete:(NSError * _Nullable)error
@@ -516,4 +543,35 @@
XCTAssertNil(controllerDelegate.commissioningCompleteError);
}
+- (void)test010_PairWithReadingExtraAttributes
+{
+ [self startServerApp];
+
+ XCTestExpectation * expectation = [self expectationWithDescription:@"Commissioning Complete"];
+ __auto_type * controllerDelegate = [[MTRPairingTestControllerDelegate alloc] initWithExpectation:expectation
+ attestationDelegate:nil
+ failSafeExtension:nil];
+
+ controllerDelegate.extraAttributesToRead = @[
+ [MTRAttributeRequestPath requestPathWithEndpointID:@(0)
+ clusterID:@(MTRClusterIDTypeDescriptorID)
+ attributeID:@(MTRAttributeIDTypeGlobalAttributeAttributeListID)],
+ [MTRAttributeRequestPath requestPathWithEndpointID:@(0)
+ clusterID:@(MTRClusterIDTypeBasicInformationID)
+ attributeID:@(MTRAttributeIDTypeClusterBasicInformationAttributeVendorNameID)],
+ ];
+
+ dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip.pairing", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
+ [sController setDeviceControllerDelegate:controllerDelegate queue:callbackQueue];
+ self.controllerDelegate = controllerDelegate;
+
+ NSError * error;
+ __auto_type * payload = [MTRSetupPayload setupPayloadWithOnboardingPayload:kOnboardingPayload error:&error];
+ XCTAssertTrue([sController setupCommissioningSessionWithPayload:payload newNodeID:@(++sDeviceId) error:&error]);
+ XCTAssertNil(error);
+
+ [self waitForExpectations:@[ expectation ] timeout:kPairingTimeoutInSeconds];
+ XCTAssertNil(controllerDelegate.commissioningCompleteError);
+}
+
@end
diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRSecureCodingTestHelpers.h b/src/darwin/Framework/CHIPTests/TestHelpers/MTRSecureCodingTestHelpers.h
new file mode 100644
index 0000000..bc7a6d1
--- /dev/null
+++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRSecureCodingTestHelpers.h
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2025 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 <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Helper method to round-trip an NSSecureCoding instance and return the
+ * (possibly nil) decoding result.
+ */
+id _Nullable RoundTripEncodable(id<NSSecureCoding> encodable, NSError * __autoreleasing * _Nullable decodeError);
+
+NS_ASSUME_NONNULL_END
diff --git a/src/darwin/Framework/CHIPTests/TestHelpers/MTRSecureCodingTestHelpers.m b/src/darwin/Framework/CHIPTests/TestHelpers/MTRSecureCodingTestHelpers.m
new file mode 100644
index 0000000..31aea46
--- /dev/null
+++ b/src/darwin/Framework/CHIPTests/TestHelpers/MTRSecureCodingTestHelpers.m
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2025 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 "MTRSecureCodingTestHelpers.h"
+
+#import <XCTest/XCTest.h>
+
+id _Nullable RoundTripEncodable(id<NSSecureCoding> encodable, NSError * __autoreleasing * decodeError)
+{
+ // We know all our encodables are in fact NSObject.
+ NSObject * obj = (NSObject *) encodable;
+
+ NSError * encodeError;
+ NSData * encodedData = [NSKeyedArchiver archivedDataWithRootObject:encodable requiringSecureCoding:YES error:&encodeError];
+ XCTAssertNil(encodeError, @"Failed to encode %@", NSStringFromClass(obj.class));
+ XCTAssertNotNil(encodedData);
+
+ id decodedValue = [NSKeyedUnarchiver unarchivedObjectOfClass:obj.class fromData:encodedData error:decodeError];
+ if (decodedValue != nil) {
+ XCTAssertTrue([decodedValue isKindOfClass:obj.class], @"Expected %@ but got %@", NSStringFromClass(obj.class), NSStringFromClass([decodedValue class]));
+ }
+ return decodedValue;
+}
diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj
index 8a4d44d..a13f20b 100644
--- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj
+++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj
@@ -149,6 +149,8 @@
5109E9C02CCAD64F0006884B /* MTRDeviceDataValidation.h in Headers */ = {isa = PBXBuildFile; fileRef = 5109E9BE2CCAD64F0006884B /* MTRDeviceDataValidation.h */; };
5109E9C12CCAD64F0006884B /* MTRDeviceDataValidation.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5109E9BF2CCAD64F0006884B /* MTRDeviceDataValidation.mm */; };
510A07492A685D3900A9241C /* Matter.apinotes in Headers */ = {isa = PBXBuildFile; fileRef = 510A07482A685D3900A9241C /* Matter.apinotes */; settings = {ATTRIBUTES = (Public, ); }; };
+ 510B18F22E315731000F9181 /* MTRSecureCodingTestHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 510B18F12E315731000F9181 /* MTRSecureCodingTestHelpers.m */; };
+ 510B18F42E3275D6000F9181 /* MTRCommissioningParameters_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 510B18F32E3275D6000F9181 /* MTRCommissioningParameters_Internal.h */; };
510CECA8297F72970064E0B3 /* MTROperationalCertificateIssuerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 510CECA6297F72470064E0B3 /* MTROperationalCertificateIssuerTests.m */; };
5117DD3829A931AE00FFA1AA /* MTROperationalBrowser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5117DD3629A931AD00FFA1AA /* MTROperationalBrowser.mm */; };
5117DD3929A931AE00FFA1AA /* MTROperationalBrowser.h in Headers */ = {isa = PBXBuildFile; fileRef = 5117DD3729A931AE00FFA1AA /* MTROperationalBrowser.h */; };
@@ -755,6 +757,9 @@
5109E9BE2CCAD64F0006884B /* MTRDeviceDataValidation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRDeviceDataValidation.h; sourceTree = "<group>"; };
5109E9BF2CCAD64F0006884B /* MTRDeviceDataValidation.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDeviceDataValidation.mm; sourceTree = "<group>"; };
510A07482A685D3900A9241C /* Matter.apinotes */ = {isa = PBXFileReference; lastKnownFileType = text.apinotes; name = Matter.apinotes; path = CHIP/Matter.apinotes; sourceTree = "<group>"; };
+ 510B18F02E315731000F9181 /* MTRSecureCodingTestHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRSecureCodingTestHelpers.h; sourceTree = "<group>"; };
+ 510B18F12E315731000F9181 /* MTRSecureCodingTestHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MTRSecureCodingTestHelpers.m; sourceTree = "<group>"; };
+ 510B18F32E3275D6000F9181 /* MTRCommissioningParameters_Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRCommissioningParameters_Internal.h; sourceTree = "<group>"; };
510CECA6297F72470064E0B3 /* MTROperationalCertificateIssuerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTROperationalCertificateIssuerTests.m; sourceTree = "<group>"; };
5117DD3629A931AD00FFA1AA /* MTROperationalBrowser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTROperationalBrowser.mm; sourceTree = "<group>"; };
5117DD3729A931AE00FFA1AA /* MTROperationalBrowser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTROperationalBrowser.h; sourceTree = "<group>"; };
@@ -1554,6 +1559,8 @@
75139A6C2B7FE19100E3A919 /* MTRTestDeclarations.h */,
3DF5219C2D62C3E5008F8E52 /* MTRMockCB.h */,
3DF5219D2D62C3E5008F8E52 /* MTRMockCB.m */,
+ 510B18F02E315731000F9181 /* MTRSecureCodingTestHelpers.h */,
+ 510B18F12E315731000F9181 /* MTRSecureCodingTestHelpers.m */,
);
path = TestHelpers;
sourceTree = "<group>";
@@ -1617,7 +1624,6 @@
7534D1762CF8CDDF00F64654 /* AttributePersistenceProvider.h */,
7534D1772CF8CDDF00F64654 /* AttributePersistenceProviderInstance.cpp */,
);
- name = persistence;
path = persistence;
sourceTree = "<group>";
};
@@ -1747,6 +1753,7 @@
3D010DD12D4091C800CFFA02 /* MTRCommissioneeInfo_Internal.h */,
3D010DCE2D408FA300CFFA02 /* MTRCommissioneeInfo.mm */,
99D466E02798936D0089A18F /* MTRCommissioningParameters.h */,
+ 510B18F32E3275D6000F9181 /* MTRCommissioningParameters_Internal.h */,
99AECC7F2798A57E00B6355B /* MTRCommissioningParameters.mm */,
3DFCB32B29678C9500332B35 /* MTRConversion.h */,
51565CAD2A79D42100469F18 /* MTRConversion.mm */,
@@ -2238,6 +2245,7 @@
998F286D26D55E10001846C6 /* MTRKeypair.h in Headers */,
1ED276E426C5832500547A89 /* MTRCluster.h in Headers */,
3D843711294977000070D20A /* NSStringSpanConversion.h in Headers */,
+ 510B18F42E3275D6000F9181 /* MTRCommissioningParameters_Internal.h in Headers */,
757DA3102DB0369100E4AD75 /* ota-provider-cluster.h in Headers */,
757DA3112DB0369100E4AD75 /* ota-provider-delegate.h in Headers */,
B4FCD56A2B5EDBD300832859 /* MTRDiagnosticLogsType.h in Headers */,
@@ -2726,6 +2734,7 @@
1E5801C328941C050033A199 /* MTRTestOTAProvider.m in Sources */,
5A6FEC9D27B5E48900F25F42 /* MTRXPCProtocolTests.m in Sources */,
3DB9DAE52D67EE5A00704FAB /* MTRBleTests.m in Sources */,
+ 510B18F22E315731000F9181 /* MTRSecureCodingTestHelpers.m in Sources */,
1EE0805E2A44875E008A03C2 /* MTRCommissionableBrowserTests.m in Sources */,
518D3F832AA132DC008E0007 /* MTRTestPerControllerStorage.m in Sources */,
51339B1F2A0DA64D00C798C1 /* MTRCertificateValidityTests.m in Sources */,