blob: d6008fdf603799fb6bf8926de3776b32e7dfcba9 [file] [log] [blame]
/**
*
* Copyright (c) 2022 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 "MTRDeviceOverXPC.h"
#import "MTRAttributeCacheContainer+XPC.h"
#import "MTRCluster.h"
#import "MTRDeviceController+XPC.h"
#import "MTRDeviceControllerXPCConnection.h"
#import "MTRError.h"
#import "MTRLogging.h"
NS_ASSUME_NONNULL_BEGIN
@interface MTRDeviceOverXPC ()
@property (nonatomic, strong, readonly) id<NSCopying> controller;
@property (nonatomic, readonly) uint64_t nodeId;
@property (nonatomic, strong, readonly) MTRDeviceControllerXPCConnection * xpcConnection;
@end
@implementation MTRDeviceOverXPC
- (instancetype)initWithController:(id<NSCopying>)controller
deviceId:(uint64_t)deviceId
xpcConnection:(MTRDeviceControllerXPCConnection *)xpcConnection
{
_controller = controller;
_nodeId = deviceId;
_xpcConnection = xpcConnection;
return self;
}
- (void)subscribeWithQueue:(dispatch_queue_t)queue
minInterval:(uint16_t)minInterval
maxInterval:(uint16_t)maxInterval
params:(nullable MTRSubscribeParams *)params
cacheContainer:(MTRAttributeCacheContainer * _Nullable)attributeCacheContainer
attributeReportHandler:(nullable void (^)(NSArray * value))attributeReportHandler
eventReportHandler:(nullable void (^)(NSArray * value))eventReportHandler
errorHandler:(void (^)(NSError * error))errorHandler
subscriptionEstablished:(nullable void (^)(void))subscriptionEstablishedHandler;
{
MTR_LOG_DEBUG("Subscribing all attributes... Note that reportHandler is not supported.");
if (attributeCacheContainer) {
[attributeCacheContainer setXPCConnection:_xpcConnection controllerId:self.controller deviceId:self.nodeId];
}
[_xpcConnection
getProxyHandleWithCompletion:^(dispatch_queue_t _Nonnull proxyQueue, MTRDeviceControllerXPCProxyHandle * _Nullable handle) {
if (handle) {
[handle.proxy subscribeWithController:self.controller
nodeId:self.nodeId
minInterval:@(minInterval)
maxInterval:@(maxInterval)
params:[MTRDeviceController encodeXPCSubscribeParams:params]
shouldCache:(attributeCacheContainer != nil)
completion:^(NSError * _Nullable error) {
dispatch_async(queue, ^{
if (error) {
errorHandler(error);
} else {
subscriptionEstablishedHandler();
}
});
__auto_type handleRetainer = handle;
(void) handleRetainer;
}];
} else {
MTR_LOG_ERROR("Failed to obtain XPC connection to write attribute");
dispatch_async(queue, ^{
errorHandler([NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeGeneralError userInfo:nil]);
});
}
}];
}
- (void)readAttributeWithEndpointId:(NSNumber * _Nullable)endpointId
clusterId:(NSNumber * _Nullable)clusterId
attributeId:(NSNumber * _Nullable)attributeId
params:(MTRReadParams * _Nullable)params
clientQueue:(dispatch_queue_t)clientQueue
completion:(MTRDeviceResponseHandler)completion
{
MTR_LOG_DEBUG("Reading attribute ...");
[_xpcConnection
getProxyHandleWithCompletion:^(dispatch_queue_t _Nonnull queue, MTRDeviceControllerXPCProxyHandle * _Nullable handle) {
if (handle) {
[handle.proxy readAttributeWithController:self.controller
nodeId:self.nodeId
endpointId:endpointId
clusterId:clusterId
attributeId:attributeId
params:[MTRDeviceController encodeXPCReadParams:params]
completion:^(id _Nullable values, NSError * _Nullable error) {
dispatch_async(clientQueue, ^{
MTR_LOG_DEBUG("Attribute read");
completion([MTRDeviceController decodeXPCResponseValues:values], error);
// The following captures the proxy handle in the closure so that the
// handle won't be released prior to block call.
__auto_type handleRetainer = handle;
(void) handleRetainer;
});
}];
} else {
dispatch_async(clientQueue, ^{
MTR_LOG_ERROR("Failed to obtain XPC connection to read attribute");
completion(nil, [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeGeneralError userInfo:nil]);
});
}
}];
}
- (void)writeAttributeWithEndpointId:(NSNumber *)endpointId
clusterId:(NSNumber *)clusterId
attributeId:(NSNumber *)attributeId
value:(id)value
timedWriteTimeout:(NSNumber * _Nullable)timeoutMs
clientQueue:(dispatch_queue_t)clientQueue
completion:(MTRDeviceResponseHandler)completion
{
MTR_LOG_DEBUG("Writing attribute ...");
[_xpcConnection
getProxyHandleWithCompletion:^(dispatch_queue_t _Nonnull queue, MTRDeviceControllerXPCProxyHandle * _Nullable handle) {
if (handle) {
[handle.proxy writeAttributeWithController:self.controller
nodeId:self.nodeId
endpointId:endpointId
clusterId:clusterId
attributeId:attributeId
value:value
timedWriteTimeout:timeoutMs
completion:^(id _Nullable values, NSError * _Nullable error) {
dispatch_async(clientQueue, ^{
MTR_LOG_DEBUG("Attribute written");
completion([MTRDeviceController decodeXPCResponseValues:values], error);
// The following captures the proxy handle in the closure so that the
// handle won't be released prior to block call.
__auto_type handleRetainer = handle;
(void) handleRetainer;
});
}];
} else {
dispatch_async(clientQueue, ^{
MTR_LOG_ERROR("Failed to obtain XPC connection to write attribute");
completion(nil, [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeGeneralError userInfo:nil]);
});
}
}];
}
- (void)invokeCommandWithEndpointId:(NSNumber *)endpointId
clusterId:(NSNumber *)clusterId
commandId:(NSNumber *)commandId
commandFields:(id)commandFields
timedInvokeTimeout:(NSNumber * _Nullable)timeoutMs
clientQueue:(dispatch_queue_t)clientQueue
completion:(MTRDeviceResponseHandler)completion
{
MTR_LOG_DEBUG("Invoking command ...");
[_xpcConnection
getProxyHandleWithCompletion:^(dispatch_queue_t _Nonnull queue, MTRDeviceControllerXPCProxyHandle * _Nullable handle) {
if (handle) {
[handle.proxy invokeCommandWithController:self.controller
nodeId:self.nodeId
endpointId:endpointId
clusterId:clusterId
commandId:commandId
fields:commandFields
timedInvokeTimeout:timeoutMs
completion:^(id _Nullable values, NSError * _Nullable error) {
dispatch_async(clientQueue, ^{
MTR_LOG_DEBUG("Command invoked");
completion([MTRDeviceController decodeXPCResponseValues:values], error);
// The following captures the proxy handle in the closure so that the
// handle won't be released prior to block call.
__auto_type handleRetainer = handle;
(void) handleRetainer;
});
}];
} else {
dispatch_async(clientQueue, ^{
MTR_LOG_ERROR("Failed to obtain XPC connection to invoke command");
completion(nil, [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeGeneralError userInfo:nil]);
});
}
}];
}
- (void)subscribeAttributeWithEndpointId:(NSNumber * _Nullable)endpointId
clusterId:(NSNumber * _Nullable)clusterId
attributeId:(NSNumber * _Nullable)attributeId
minInterval:(NSNumber *)minInterval
maxInterval:(NSNumber *)maxInterval
params:(MTRSubscribeParams * _Nullable)params
clientQueue:(dispatch_queue_t)clientQueue
reportHandler:(MTRDeviceResponseHandler)reportHandler
subscriptionEstablished:(void (^_Nullable)(void))subscriptionEstablishedHandler
{
MTR_LOG_DEBUG("Subscribing attribute ...");
[_xpcConnection getProxyHandleWithCompletion:^(
dispatch_queue_t _Nonnull queue, MTRDeviceControllerXPCProxyHandle * _Nullable handle) {
if (handle) {
MTR_LOG_DEBUG("Setup report handler");
[self.xpcConnection
registerReportHandlerWithController:self.controller
nodeId:self.nodeId
handler:^(id _Nullable values, NSError * _Nullable error) {
if (values && ![values isKindOfClass:[NSArray class]]) {
MTR_LOG_ERROR("Unsupported report format");
return;
}
if (!values) {
MTR_LOG_DEBUG("Error report received");
dispatch_async(clientQueue, ^{
reportHandler(values, error);
});
return;
}
__auto_type decodedValues = [MTRDeviceController decodeXPCResponseValues:values];
NSMutableArray<NSDictionary<NSString *, id> *> * filteredValues =
[NSMutableArray arrayWithCapacity:[decodedValues count]];
for (NSDictionary<NSString *, id> * decodedValue in decodedValues) {
MTRAttributePath * attributePath = decodedValue[MTRAttributePathKey];
if ((endpointId == nil || [attributePath.endpoint isEqualToNumber:endpointId])
&& (clusterId == nil || [attributePath.cluster isEqualToNumber:clusterId])
&& (attributeId == nil ||
[attributePath.attribute isEqualToNumber:attributeId])) {
[filteredValues addObject:decodedValue];
}
}
if ([filteredValues count] > 0) {
MTR_LOG_DEBUG("Report received");
dispatch_async(clientQueue, ^{
reportHandler(filteredValues, error);
});
}
}];
[handle.proxy subscribeAttributeWithController:self.controller
nodeId:self.nodeId
endpointId:endpointId
clusterId:clusterId
attributeId:attributeId
minInterval:minInterval
maxInterval:maxInterval
params:[MTRDeviceController encodeXPCSubscribeParams:params]
establishedHandler:^{
dispatch_async(clientQueue, ^{
MTR_LOG_DEBUG("Subscription established");
subscriptionEstablishedHandler();
// The following captures the proxy handle in the closure so that the handle
// won't be released prior to block call.
__auto_type handleRetainer = handle;
(void) handleRetainer;
});
}];
} else {
dispatch_async(clientQueue, ^{
MTR_LOG_ERROR("Failed to obtain XPC connection to subscribe to attribute");
subscriptionEstablishedHandler();
reportHandler(nil, [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeGeneralError userInfo:nil]);
});
}
}];
}
- (void)deregisterReportHandlersWithClientQueue:(dispatch_queue_t)clientQueue completion:(void (^)(void))completion
{
MTR_LOG_DEBUG("Deregistering report handlers");
[_xpcConnection deregisterReportHandlersWithController:self.controller
nodeId:self.nodeId
completion:^{
dispatch_async(clientQueue, completion);
}];
}
@end
NS_ASSUME_NONNULL_END