Skip to content

Commit 04dd945

Browse files
Expose Network Commissioning FeatureMap values on MTRCommissioneeInfo. (#40778)
* Expose Network Commissioning FeatureMap values on MTRCommissioneeInfo. This is easier for API consumers than dealing with the "attributes" data structures. * Address review comments. * Address more review comments.
1 parent d1d4399 commit 04dd945

File tree

8 files changed

+285
-2
lines changed

8 files changed

+285
-2
lines changed

src/darwin/Framework/CHIP/MTRCommissioneeInfo.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
@class MTRProductIdentity;
2121
@class MTREndpointInfo;
22+
@class MTRNetworkInterfaceInfo;
2223

2324
NS_ASSUME_NONNULL_BEGIN
2425

@@ -57,6 +58,12 @@ MTR_AVAILABLE(ios(18.4), macos(15.4), watchos(11.4), tvos(18.4))
5758
*/
5859
@property (nonatomic, copy, readonly, nullable) NSDictionary<MTRAttributePath *, NSDictionary<NSString *, id> *> * attributes MTR_AVAILABLE(ios(26.2), macos(26.2), watchos(26.2), tvos(26.2));
5960

61+
/**
62+
* Network interfaces the commissionee has. The array will be empty if there
63+
* are no network interfaces exposed on the commissionee.
64+
*/
65+
@property (nonatomic, copy, readonly) NSArray<MTRNetworkInterfaceInfo *> * networkInterfaces MTR_UNSTABLE_API;
66+
6067
@end
6168

6269
NS_ASSUME_NONNULL_END

src/darwin/Framework/CHIP/MTRCommissioneeInfo.mm

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#import "MTRDeviceDataValidation.h"
2525
#import "MTREndpointInfo_Internal.h"
2626
#import "MTRLogging_Internal.h"
27+
#import "MTRNetworkInterfaceInfo_Internal.h"
2728
#import "MTRProductIdentity.h"
2829
#import "MTRUtilities.h"
2930

@@ -37,6 +38,8 @@ @implementation MTRCommissioneeInfo
3738

3839
- (instancetype)initWithCommissioningInfo:(const chip::Controller::ReadCommissioningInfo &)info commissioningParameters:(MTRCommissioningParameters *)commissioningParameters
3940
{
41+
using namespace chip::app;
42+
4043
self = [super init];
4144
_productIdentity = [[MTRProductIdentity alloc] initWithVendorID:@(info.basic.vendorId) productID:@(info.basic.productId)];
4245

@@ -47,13 +50,15 @@ - (instancetype)initWithCommissioningInfo:(const chip::Controller::ReadCommissio
4750
}
4851
}
4952

53+
NSMutableArray<MTRNetworkInterfaceInfo *> * networkInterfaces = [[NSMutableArray alloc] init];
54+
5055
if (info.attributes != nullptr) {
5156
NSMutableDictionary<MTRAttributePath *, NSDictionary<NSString *, id> *> * attributes = [[NSMutableDictionary alloc] init];
5257

5358
// Only expose attributes that match pathFilters, so that API consumers
5459
// don't start relying on undocumented internal details of which paths
5560
// we read from the device in which circumstances.
56-
std::vector<chip::app::AttributePathParams> pathFilters;
61+
std::vector<AttributePathParams> pathFilters;
5762
if (commissioningParameters.extraAttributesToRead != nil) {
5863
for (MTRAttributeRequestPath * requestPath in commissioningParameters.extraAttributesToRead) {
5964
[requestPath convertToAttributePathParams:pathFilters.emplace_back()];
@@ -64,7 +69,7 @@ - (instancetype)initWithCommissioningInfo:(const chip::Controller::ReadCommissio
6469
// attributes, using a wildcard-endpoint path.
6570
pathFilters.emplace_back(MTRClusterIDTypeNetworkCommissioningID, MTRAttributeIDTypeGlobalAttributeFeatureMapID);
6671

67-
info.attributes->ForEachAttribute([&](const chip::app::ConcreteAttributePath & path) -> CHIP_ERROR {
72+
info.attributes->ForEachAttribute([&](const ConcreteAttributePath & path) -> CHIP_ERROR {
6873
// Only grab paths that are included in extraAttributesToRead so that
6974
// API consumers don't develop dependencies on implementation details
7075
// (like which other attributes we happen to read).
@@ -111,14 +116,42 @@ - (instancetype)initWithCommissioningInfo:(const chip::Controller::ReadCommissio
111116
});
112117

113118
_attributes = attributes;
119+
120+
// Now grab the Network Commissioning information in a nicer form.
121+
info.attributes->ForEachAttribute(MTRClusterIDTypeNetworkCommissioningID, [&](const ConcreteAttributePath & path) -> CHIP_ERROR {
122+
if (path.mAttributeId != MTRAttributeIDTypeGlobalAttributeFeatureMapID) {
123+
return CHIP_NO_ERROR;
124+
}
125+
126+
uint32_t value;
127+
CHIP_ERROR err = info.attributes->Get<Clusters::NetworkCommissioning::Attributes::FeatureMap::TypeInfo>(path, value);
128+
if (err != CHIP_NO_ERROR) {
129+
// Keep iterating no matter what.
130+
return CHIP_NO_ERROR;
131+
}
132+
133+
MTRNetworkInterfaceInfo * _Nullable interfaceInfo = [[MTRNetworkInterfaceInfo alloc] initWithEndpointID:@(path.mEndpointId) featureMap:@(value)];
134+
if (!interfaceInfo) {
135+
// Invalid feature map. Just keep looking for other ones.
136+
return CHIP_NO_ERROR;
137+
}
138+
139+
[networkInterfaces addObject:interfaceInfo];
140+
return CHIP_NO_ERROR;
141+
});
114142
}
115143

144+
auto * endpointDescriptor = [[NSSortDescriptor alloc] initWithKey:@"endpointID" ascending:YES];
145+
[networkInterfaces sortUsingDescriptors:@[ endpointDescriptor ]];
146+
_networkInterfaces = networkInterfaces;
147+
116148
return self;
117149
}
118150

119151
static NSString * const sProductIdentityCodingKey = @"pi";
120152
static NSString * const sEndpointsCodingKey = @"ep";
121153
static NSString * const sAttributesCodingKey = @"at";
154+
static NSString * const sNetworkInterfacesCodingKey = @"ni";
122155

123156
- (nullable instancetype)initWithCoder:(NSCoder *)coder
124157
{
@@ -156,6 +189,15 @@ - (nullable instancetype)initWithCoder:(NSCoder *)coder
156189
}
157190
}
158191

192+
// Decode network interface array (may be nil if decoding something encoded
193+
// before we added these properties).
194+
_networkInterfaces = [coder decodeArrayOfObjectsOfClass:MTRNetworkInterfaceInfo.class forKey:sNetworkInterfacesCodingKey];
195+
196+
// Provide empty array for backward compatibility if not present in encoded data
197+
if (_networkInterfaces == nil) {
198+
_networkInterfaces = @[];
199+
}
200+
159201
return self;
160202
}
161203

@@ -164,6 +206,7 @@ - (void)encodeWithCoder:(NSCoder *)coder
164206
[coder encodeObject:_productIdentity forKey:sProductIdentityCodingKey];
165207
[coder encodeObject:_endpointsById forKey:sEndpointsCodingKey];
166208
[coder encodeObject:_attributes forKey:sAttributesCodingKey];
209+
[coder encodeObject:_networkInterfaces forKey:sNetworkInterfacesCodingKey];
167210
}
168211

169212
+ (BOOL)supportsSecureCoding
@@ -183,6 +226,7 @@ - (BOOL)isEqual:(id)object
183226
VerifyOrReturnValue(MTREqualObjects(_productIdentity, other->_productIdentity), NO);
184227
VerifyOrReturnValue(MTREqualObjects(_endpointsById, other->_endpointsById), NO);
185228
VerifyOrReturnValue(MTREqualObjects(_attributes, other->_attributes), NO);
229+
VerifyOrReturnValue(MTREqualObjects(_networkInterfaces, other->_networkInterfaces), NO);
186230

187231
return YES;
188232
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Copyright (c) 2025 Project CHIP Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import <Foundation/Foundation.h>
18+
#import <Matter/MTRBaseClusters.h>
19+
#import <Matter/MTRDefines.h>
20+
21+
NS_ASSUME_NONNULL_BEGIN
22+
23+
/**
24+
* Information about a network interface on a Matter node.
25+
*/
26+
27+
NS_SWIFT_SENDABLE
28+
MTR_UNSTABLE_API
29+
@interface MTRNetworkInterfaceInfo : NSObject <NSCopying, NSSecureCoding>
30+
31+
- (instancetype)init NS_UNAVAILABLE;
32+
+ (instancetype)new NS_UNAVAILABLE;
33+
34+
/**
35+
* The endpoint this network interface is exposed on (i.e. the endpoint its
36+
* corresponding Network Commissioning server cluster instance is on).
37+
*/
38+
@property (nonatomic, copy, readonly) NSNumber * endpointID;
39+
40+
/**
41+
* The value of the FeatureMap attribute of the corresponding Network
42+
* Commissioning cluster instance.
43+
*/
44+
@property (nonatomic, copy, readonly) NSNumber * featureMap;
45+
46+
/**
47+
* The network technology used by the interface. This will be one of the
48+
* MTRNetworkCommissioningFeature*NetworkInterface values (exactly one bit).
49+
*/
50+
@property (nonatomic, assign, readonly) MTRNetworkCommissioningFeature type;
51+
52+
@end
53+
54+
NS_ASSUME_NONNULL_END
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/**
2+
* Copyright (c) 2025 Project CHIP Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import "MTRNetworkInterfaceInfo_Internal.h"
18+
19+
#import <Matter/MTRBaseClusters.h>
20+
21+
#import "MTRDefines_Internal.h"
22+
#import "MTRLogging_Internal.h"
23+
#import "MTRUtilities.h"
24+
25+
#include <lib/support/CodeUtils.h>
26+
27+
NS_ASSUME_NONNULL_BEGIN
28+
29+
MTR_DIRECT_MEMBERS
30+
@implementation MTRNetworkInterfaceInfo
31+
32+
- (nullable instancetype)initWithEndpointID:(NSNumber *)endpointID featureMap:(NSNumber *)featureMap
33+
{
34+
constexpr MTRNetworkCommissioningFeature typeBits = (MTRNetworkCommissioningFeatureWiFiNetworkInterface | MTRNetworkCommissioningFeatureThreadNetworkInterface | MTRNetworkCommissioningFeatureEthernetNetworkInterface);
35+
auto ourTypeBits = featureMap.unsignedLongLongValue & typeBits;
36+
37+
if (ourTypeBits == 0) {
38+
MTR_LOG_ERROR("MTRNetworkInterfaceInfo for endpoint %@ has no network technology bits set", endpointID);
39+
return nil;
40+
}
41+
42+
if ((ourTypeBits & (ourTypeBits - 1)) != 0) {
43+
MTR_LOG_ERROR("MTRNetworkInterfaceInfo for endpoint %@ has multiple network technology bits set: 0x%llx", endpointID, ourTypeBits);
44+
return nil;
45+
}
46+
47+
if (!(self = [super init])) {
48+
return nil;
49+
}
50+
51+
_endpointID = [endpointID copy];
52+
_featureMap = [featureMap copy];
53+
_type = static_cast<MTRNetworkCommissioningFeature>(ourTypeBits);
54+
55+
return self;
56+
}
57+
58+
#pragma mark - NSCopying implementation
59+
60+
- (id)copyWithZone:(nullable NSZone *)zone
61+
{
62+
return self; // immutable
63+
}
64+
65+
#pragma mark - NSSecureCoding implementation
66+
67+
+ (BOOL)supportsSecureCoding
68+
{
69+
return YES;
70+
}
71+
72+
static NSString * const sEndpointIDCodingKey = @"endpointID";
73+
static NSString * const sFeatureMapCodingKey = @"featureMap";
74+
75+
- (void)encodeWithCoder:(NSCoder *)coder
76+
{
77+
[coder encodeObject:self.endpointID forKey:sEndpointIDCodingKey];
78+
[coder encodeObject:self.featureMap forKey:sFeatureMapCodingKey];
79+
}
80+
81+
- (nullable instancetype)initWithCoder:(NSCoder *)coder
82+
{
83+
NSNumber * endpointID = [coder decodeObjectOfClass:NSNumber.class forKey:sEndpointIDCodingKey];
84+
NSNumber * featureMap = [coder decodeObjectOfClass:NSNumber.class forKey:sFeatureMapCodingKey];
85+
86+
if (endpointID == nil || featureMap == nil) {
87+
return nil;
88+
}
89+
90+
return [self initWithEndpointID:endpointID featureMap:featureMap];
91+
}
92+
93+
#pragma mark - NSObject implementation
94+
95+
- (NSUInteger)hash
96+
{
97+
return self.endpointID.hash ^ self.featureMap.hash;
98+
}
99+
100+
- (BOOL)isEqual:(id)object
101+
{
102+
VerifyOrReturnValue([object class] == [self class], NO);
103+
104+
MTRNetworkInterfaceInfo * other = object;
105+
106+
VerifyOrReturnValue(MTREqualObjects(_endpointID, other->_endpointID), NO);
107+
VerifyOrReturnValue(MTREqualObjects(_featureMap, other->_featureMap), NO);
108+
109+
return YES;
110+
}
111+
112+
- (NSString *)description
113+
{
114+
return [NSString stringWithFormat:@"<MTRNetworkInterfaceInfo: endpointID=%@, featureMap=0x%llx>",
115+
self.endpointID, self.featureMap.unsignedLongLongValue];
116+
}
117+
118+
@end
119+
120+
NS_ASSUME_NONNULL_END
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Copyright (c) 2025 Project CHIP Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import <Matter/MTRNetworkInterfaceInfo.h>
18+
19+
#import "MTRDefines_Internal.h"
20+
21+
NS_ASSUME_NONNULL_BEGIN
22+
23+
MTR_DIRECT_MEMBERS
24+
@interface MTRNetworkInterfaceInfo ()
25+
26+
// Will fail init if the featureMap claims multiple interface types.
27+
- (nullable instancetype)initWithEndpointID:(NSNumber *)endpointID featureMap:(NSNumber *)featureMap;
28+
29+
@end
30+
31+
NS_ASSUME_NONNULL_END

src/darwin/Framework/CHIP/Matter.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
#import <Matter/MTRLogging.h>
6464
#import <Matter/MTRManualSetupPayloadParser.h>
6565
#import <Matter/MTRMetrics.h>
66+
#import <Matter/MTRNetworkInterfaceInfo.h>
6667
#import <Matter/MTROTAHeader.h>
6768
#import <Matter/MTROTAProviderDelegate.h>
6869
#import <Matter/MTROnboardingPayloadParser.h>

src/darwin/Framework/CHIPTests/MTRPairingTests.m

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,8 @@ - (void)controller:(MTRDeviceController *)controller readCommissioneeInfo:(MTRCo
300300
XCTAssertEqualObjects(decoded.productIdentity, info.productIdentity);
301301
XCTAssertEqualObjects(decoded.endpointsById, info.endpointsById);
302302
XCTAssertEqualObjects(decoded.rootEndpoint.children, info.rootEndpoint.children);
303+
XCTAssertEqualObjects(decoded.attributes, info.attributes);
304+
XCTAssertEqualObjects(decoded.networkInterfaces, info.networkInterfaces);
303305
} else {
304306
XCTAssertNil(info.endpointsById);
305307
XCTAssertNil(info.rootEndpoint);
@@ -311,14 +313,26 @@ - (void)controller:(MTRDeviceController *)controller readCommissioneeInfo:(MTRCo
311313
return [path.cluster isEqual:@(MTRClusterIDTypeNetworkCommissioningID)] &&
312314
[path.attribute isEqual:@(MTRAttributeIDTypeGlobalAttributeFeatureMapID)];
313315
};
316+
__auto_type endpointInList = ^(NSNumber * endpoint, NSArray<MTRNetworkInterfaceInfo *> * list) {
317+
for (MTRNetworkInterfaceInfo * info in list) {
318+
if ([info.endpointID isEqual:endpoint]) {
319+
return YES;
320+
}
321+
}
322+
323+
return NO;
324+
};
314325
NSUInteger networkCommissioningFeatureMapCount = 0;
315326
for (MTRAttributePath * path in info.attributes) {
316327
if (isNetworkCommissioningFeatureMap(path)) {
317328
++networkCommissioningFeatureMapCount;
329+
XCTAssertTrue(endpointInList(path.endpoint, info.networkInterfaces));
318330
}
319331
}
320332
XCTAssertGreaterThan(networkCommissioningFeatureMapCount, 0);
321333

334+
XCTAssertEqual(networkCommissioningFeatureMapCount, info.networkInterfaces.count);
335+
322336
if (self.extraAttributesToRead) {
323337
// The attributes we tried to read should really have worked.
324338
XCTAssertNotNil(info.attributes);

0 commit comments

Comments
 (0)