Skip to content

Commit

Permalink
Add a wildcard attribute read API on MTRDevice. (#35562)
Browse files Browse the repository at this point in the history
* Add a wildcard attribute read API on MTRDevice.

* Adding new XPC protocol

* Restyled by whitespace

* Restyled by clang-format

* Add unit test for NSCoding bits.

* Fix up XPC protocol bits.

---------

Co-authored-by: Justin Wood <[email protected]>
Co-authored-by: Restyled.io <[email protected]>
3 people authored Sep 13, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 16e4e9b commit f78dad4
Showing 9 changed files with 362 additions and 47 deletions.
4 changes: 2 additions & 2 deletions src/darwin/Framework/CHIP/MTRBaseDevice.h
Original file line number Diff line number Diff line change
@@ -166,7 +166,7 @@ typedef NS_ENUM(uint8_t, MTRTransportType) {
*/
NS_SWIFT_SENDABLE
MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0))
@interface MTRAttributeRequestPath : NSObject <NSCopying>
@interface MTRAttributeRequestPath : NSObject <NSCopying, NSSecureCoding>
@property (nonatomic, readonly, copy, nullable) NSNumber * endpoint MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0));
@property (nonatomic, readonly, copy, nullable) NSNumber * cluster MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0));
@property (nonatomic, readonly, copy, nullable)
@@ -185,7 +185,7 @@ MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0))
*/
NS_SWIFT_SENDABLE
MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0))
@interface MTREventRequestPath : NSObject <NSCopying>
@interface MTREventRequestPath : NSObject <NSCopying, NSSecureCoding>
@property (nonatomic, readonly, copy, nullable) NSNumber * endpoint MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0));
@property (nonatomic, readonly, copy, nullable) NSNumber * cluster MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0));
@property (nonatomic, readonly, copy, nullable) NSNumber * event MTR_AVAILABLE(ios(17.0), macos(14.0), watchos(10.0), tvos(17.0));
101 changes: 101 additions & 0 deletions src/darwin/Framework/CHIP/MTRBaseDevice.mm
Original file line number Diff line number Diff line change
@@ -2333,6 +2333,57 @@ - (void)convertToAttributePathParams:(chip::app::AttributePathParams &)params
params.SetWildcardAttributeId();
}
}

static NSString * const sEndpointIDKey = @"endpointIDKey";
static NSString * const sClusterIDKey = @"clusterIDKey";
static NSString * const sAttributeIDKey = @"attributeIDKey";

+ (BOOL)supportsSecureCoding
{
return YES;
}

- (nullable instancetype)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if (self == nil) {
return nil;
}

_endpoint = [decoder decodeObjectOfClass:[NSNumber class] forKey:sEndpointIDKey];
if (_endpoint && ![_endpoint isKindOfClass:[NSNumber class]]) {
MTR_LOG_ERROR("MTRAttributeRequestPath decoded %@ for endpoint, not NSNumber.", _attribute);
return nil;
}

_cluster = [decoder decodeObjectOfClass:[NSNumber class] forKey:sClusterIDKey];
if (_cluster && ![_cluster isKindOfClass:[NSNumber class]]) {
MTR_LOG_ERROR("MTRAttributeRequestPath decoded %@ for cluster, not NSNumber.", _attribute);
return nil;
}

_attribute = [decoder decodeObjectOfClass:[NSNumber class] forKey:sAttributeIDKey];
if (_attribute && ![_attribute isKindOfClass:[NSNumber class]]) {
MTR_LOG_ERROR("MTRAttributeRequestPath decoded %@ for attribute, not NSNumber.", _attribute);
return nil;
}

return self;
}

- (void)encodeWithCoder:(NSCoder *)coder
{
if (_endpoint) {
[coder encodeObject:_endpoint forKey:sEndpointIDKey];
}
if (_cluster) {
[coder encodeObject:_cluster forKey:sClusterIDKey];
}
if (_attribute) {
[coder encodeObject:_attribute forKey:sAttributeIDKey];
}
}

@end

@implementation MTREventRequestPath
@@ -2406,6 +2457,56 @@ - (void)convertToEventPathParams:(chip::app::EventPathParams &)params
params.SetWildcardEventId();
}
}
static NSString * const sEventEndpointIDKey = @"endpointIDKey";
static NSString * const sEventClusterIDKey = @"clusterIDKey";
static NSString * const sEventAttributeIDKey = @"attributeIDKey";

+ (BOOL)supportsSecureCoding
{
return YES;
}

- (nullable instancetype)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if (self == nil) {
return nil;
}

_endpoint = [decoder decodeObjectOfClass:[NSNumber class] forKey:sEventEndpointIDKey];
if (_endpoint && ![_endpoint isKindOfClass:[NSNumber class]]) {
MTR_LOG_ERROR("MTREventRequestPath decoded %@ for endpoint, not NSNumber.", _endpoint);
return nil;
}

_cluster = [decoder decodeObjectOfClass:[NSNumber class] forKey:sEventClusterIDKey];
if (_cluster && ![_cluster isKindOfClass:[NSNumber class]]) {
MTR_LOG_ERROR("MTREventRequestPath decoded %@ for cluster, not NSNumber.", _cluster);
return nil;
}

_event = [decoder decodeObjectOfClass:[NSNumber class] forKey:sEventAttributeIDKey];
if (_event && ![_event isKindOfClass:[NSNumber class]]) {
MTR_LOG_ERROR("MTREventRequestPath decoded %@ for event, not NSNumber.", _event);
return nil;
}

return self;
}

- (void)encodeWithCoder:(NSCoder *)coder
{
if (_endpoint) {
[coder encodeObject:_endpoint forKey:sEventEndpointIDKey];
}
if (_cluster) {
[coder encodeObject:_cluster forKey:sEventClusterIDKey];
}
if (_event) {
[coder encodeObject:_event forKey:sEventAttributeIDKey];
}
}

@end

@implementation MTRClusterPath
14 changes: 14 additions & 0 deletions src/darwin/Framework/CHIP/MTRDevice.h
Original file line number Diff line number Diff line change
@@ -186,6 +186,20 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1))
expectedValueInterval:(NSNumber *)expectedValueInterval
timedWriteTimeout:(NSNumber * _Nullable)timeout;

/**
* Read the attributes identified by the provided attribute paths. The paths
* can include wildcards.
*
* Paths that do not correspond to any existing attributes, or that the
* MTRDevice does not have attribute values for, will not be present in the
* return value from this function.
*
* @return an array of response-value dictionaries as described in the
* documentation for MTRDeviceResponseHandler. Each one will have an
* MTRAttributePathKey and an MTRDataKey.
*/
- (NSArray<NSDictionary<NSString *, id> *> *)readAttributePaths:(NSArray<MTRAttributeRequestPath *> *)attributePaths MTR_NEWLY_AVAILABLE;

/**
* Invoke a command with a designated command path
*
11 changes: 11 additions & 0 deletions src/darwin/Framework/CHIP/MTRDevice.mm
Original file line number Diff line number Diff line change
@@ -1272,6 +1272,17 @@ - (void)writeAttributeWithEndpointID:(NSNumber *)endpointID
#undef MTRDeviceErrorStr
}

- (NSArray<NSDictionary<NSString *, id> *> *)readAttributePaths:(NSArray<MTRAttributeRequestPath *> *)attributePaths
{
#define MTRDeviceErrorStr "MTRDevice readAttributePaths: must be handled by subclasses"
MTR_LOG_ERROR(MTRDeviceErrorStr);
#ifdef DEBUG
NSAssert(NO, @MTRDeviceErrorStr);
#endif // DEBUG
#undef MTRDeviceErrorStr
return [NSArray array];
}

- (void)invokeCommandWithEndpointID:(NSNumber *)endpointID
clusterID:(NSNumber *)clusterID
commandID:(NSNumber *)commandID
27 changes: 24 additions & 3 deletions src/darwin/Framework/CHIP/MTRDeviceController_XPC.mm
Original file line number Diff line number Diff line change
@@ -66,14 +66,35 @@ - (NSXPCInterface *)_interfaceForServerProtocol
NSMutableSet * allowedClasses = [MTRDeviceController_XPC _allowedClasses];
[allowedClasses addObjectsFromArray:@[
[MTRCommandPath class],
[MTRAttributePath class]
[MTRAttributePath class],
]];

[interface setClasses:allowedClasses
forSelector:@selector(deviceController:nodeID:invokeCommandWithEndpointID:clusterID:commandID:commandFields:expectedValues:expectedValueInterval:timedInvokeTimeout:serverSideProcessingTimeout:completion:)
argumentIndex:0
ofReply:YES];

// readAttributePaths: gets handed an array of MTRAttributeRequestPath.
allowedClasses = [MTRDeviceController_XPC _allowedClasses];
[allowedClasses addObjectsFromArray:@[
[MTRAttributeRequestPath class],
]];
[interface setClasses:allowedClasses
forSelector:@selector(deviceController:nodeID:readAttributePaths:withReply:)
argumentIndex:2
ofReply:NO];

// readAttributePaths: returns response-value dictionaries that have
// attribute paths and values.
allowedClasses = [MTRDeviceController_XPC _allowedClasses];
[allowedClasses addObjectsFromArray:@[
[MTRAttributePath class],
]];
[interface setClasses:allowedClasses
forSelector:@selector(deviceController:nodeID:readAttributePaths:withReply:)
argumentIndex:0
ofReply:YES];

return interface;
}

@@ -82,7 +103,7 @@ - (NSXPCInterface *)_interfaceForClientProtocol
NSXPCInterface * interface = [NSXPCInterface interfaceWithProtocol:@protocol(MTRXPCClientProtocol)];
NSMutableSet * allowedClasses = [MTRDeviceController_XPC _allowedClasses];
[allowedClasses addObjectsFromArray:@[
[MTRAttributePath class]
[MTRAttributePath class],
]];

[interface setClasses:allowedClasses
@@ -92,7 +113,7 @@ - (NSXPCInterface *)_interfaceForClientProtocol

allowedClasses = [MTRDeviceController_XPC _allowedClasses];
[allowedClasses addObjectsFromArray:@[
[MTREventPath class]
[MTREventPath class],
]];

[interface setClasses:allowedClasses
129 changes: 119 additions & 10 deletions src/darwin/Framework/CHIP/MTRDevice_Concrete.mm
Original file line number Diff line number Diff line change
@@ -2963,6 +2963,79 @@ - (void)writeAttributeWithEndpointID:(NSNumber *)endpointID
[_asyncWorkQueue enqueueWorkItem:workItem descriptionWithFormat:@"write %@ 0x%llx 0x%llx", endpointID, clusterID.unsignedLongLongValue, attributeID.unsignedLongLongValue];
}

- (NSArray<NSDictionary<NSString *, id> *> *)readAttributePaths:(NSArray<MTRAttributeRequestPath *> *)attributePaths
{
// Determine the set of what the spec calls "existent paths" that correspond
// to the request paths. Building the whole set in-memory is OK, because
// we're going to need all those paths for our return value anyway.
NSMutableSet<MTRAttributePath *> * existentPaths = [[NSMutableSet alloc] init];
{
std::lock_guard lock(_lock);
for (MTRAttributeRequestPath * path in attributePaths) {
[self _addExistentPathsFor:path to:existentPaths];
}
}

NSMutableArray<NSDictionary<NSString *, id> *> * result = [NSMutableArray arrayWithCapacity:existentPaths.count];
for (MTRAttributePath * path in existentPaths) {
auto * value = [self readAttributeWithEndpointID:path.endpoint clusterID:path.cluster attributeID:path.attribute params:nil];
if (!value) {
continue;
}
[result addObject:@{
MTRAttributePathKey : path,
MTRDataKey : value,
}];
}

return result;
}

- (void)_addExistentPathsFor:(MTRAttributeRequestPath *)path to:(NSMutableSet<MTRAttributePath *> *)set
{
os_unfair_lock_assert_owner(&_lock);

if (path.endpoint != nil) {
[self _addExistentPathsForEndpoint:path.endpoint path:path to:set];
return;
}

NSArray<NSNumber *> * endpointList = [self _endpointList];
for (NSNumber * endpoint in endpointList) {
[self _addExistentPathsForEndpoint:endpoint path:path to:set];
}
}

- (void)_addExistentPathsForEndpoint:(NSNumber *)endpoint path:(MTRAttributeRequestPath *)path to:(NSMutableSet<MTRAttributePath *> *)set
{
os_unfair_lock_assert_owner(&_lock);

if (path.cluster != nil) {
[self _addExistentPathsForEndpoint:endpoint cluster:path.cluster attribute:path.attribute to:set];
return;
}

auto * clusterList = [self _serverListForEndpointID:endpoint];
for (NSNumber * cluster in clusterList) {
[self _addExistentPathsForEndpoint:endpoint cluster:cluster attribute:path.attribute to:set];
}
}

- (void)_addExistentPathsForEndpoint:(NSNumber *)endpoint cluster:(NSNumber *)cluster attribute:(NSNumber * _Nullable)attribute to:(NSMutableSet<MTRAttributePath *> *)set
{
os_unfair_lock_assert_owner(&_lock);

if (attribute != nil) {
[set addObject:[MTRAttributePath attributePathWithEndpointID:endpoint clusterID:cluster attributeID:attribute]];
return;
}

auto * attributeList = [self _attributeListForEndpointID:endpoint clusterID:cluster];
for (NSNumber * existentAttribute in attributeList) {
[set addObject:[MTRAttributePath attributePathWithEndpointID:endpoint clusterID:cluster attributeID:existentAttribute]];
}
}

- (void)_invokeCommandWithEndpointID:(NSNumber *)endpointID
clusterID:(NSNumber *)clusterID
commandID:(NSNumber *)commandID
@@ -3968,19 +4041,55 @@ - (void)_updateAttributeDependentDescriptionData
{
os_unfair_lock_assert_owner(&_lock);

auto * partsListPath = [MTRAttributePath attributePathWithEndpointID:@(kRootEndpointId)
clusterID:@(MTRClusterIDTypeDescriptorID)
attributeID:@(MTRAttributeIDTypeClusterDescriptorAttributePartsListID)];
auto * partsList = [self _cachedAttributeValueForPath:partsListPath];
NSMutableArray<NSNumber *> * endpointsOnDevice = [self arrayOfNumbersFromAttributeValue:partsList];
if (!endpointsOnDevice) {
endpointsOnDevice = [[NSMutableArray<NSNumber *> alloc] init];
}
// Add Root node!
[endpointsOnDevice addObject:@(0)];
auto * partsList = [self _cachedListOfNumbersValueForEndpointID:@(kRootEndpointId)
clusterID:@(MTRClusterIDTypeDescriptorID)
attributeID:@(MTRAttributeIDTypeClusterDescriptorAttributePartsListID)];
NSMutableArray<NSNumber *> * endpointsOnDevice = [partsList mutableCopy];
// Add Root Node endpoint.
[endpointsOnDevice addObject:@(kRootEndpointId)];
return endpointsOnDevice;
}

/**
* Returns the cached value of the relevant attribute as a list of numbers.
* Returns an empty list if the value does not exist or can't be converted to a
* list of numbers.
*/
- (NSArray<NSNumber *> *)_cachedListOfNumbersValueForEndpointID:(NSNumber *)endpointID
clusterID:(NSNumber *)clusterID
attributeID:(NSNumber *)attributeID
{
os_unfair_lock_assert_owner(&_lock);

auto * path = [MTRAttributePath attributePathWithEndpointID:endpointID
clusterID:clusterID
attributeID:attributeID];
auto * value = [self _cachedAttributeValueForPath:path];
NSArray<NSNumber *> * arrayValue = [self arrayOfNumbersFromAttributeValue:value];
if (arrayValue) {
return arrayValue;
}
return [NSArray array];
}

- (NSArray<NSNumber *> *)_serverListForEndpointID:(NSNumber *)endpointID
{
os_unfair_lock_assert_owner(&_lock);

return [self _cachedListOfNumbersValueForEndpointID:endpointID
clusterID:@(MTRClusterIDTypeDescriptorID)
attributeID:@(MTRAttributeIDTypeClusterDescriptorAttributeServerListID)];
}

- (NSArray<NSNumber *> *)_attributeListForEndpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID
{
os_unfair_lock_assert_owner(&_lock);

return [self _cachedListOfNumbersValueForEndpointID:endpointID
clusterID:clusterID
attributeID:@(MTRAttributeIDTypeGlobalAttributeAttributeListID)];
}

- (NSNumber * _Nullable)_networkFeatures
{
NSNumber * _Nullable result = nil;
Loading

0 comments on commit f78dad4

Please sign in to comment.