blob: 02f1e2c0ac134b982e36487e695ff623c0eca8e8 [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 "MTRDeviceControllerXPCConnection.h"
#import "MTRDeviceControllerOverXPC.h"
#import "MTRLogging_Internal.h"
#import <Foundation/Foundation.h>
@interface MTRDeviceControllerXPCProxyHandle ()
@property (weak, nonatomic, readonly) NSXPCConnection * xpcConnection;
- (instancetype)initWithXPCConnection:(NSXPCConnection *)xpcConnection;
@end
@implementation MTRDeviceControllerXPCProxyHandle
- (instancetype)initWithXPCConnection:(NSXPCConnection *)xpcConnection
{
if ([super init]) {
_xpcConnection = xpcConnection;
}
return self;
}
- (id<MTRDeviceControllerServerProtocol>)proxy
{
return [_xpcConnection remoteObjectProxy];
}
- (void)dealloc
{
[_xpcConnection invalidate];
}
@end
@interface MTRDeviceControllerXPCConnection ()
@property (strong, nonatomic, readonly) NSXPCInterface * remoteDeviceServerProtocol;
@property (strong, nonatomic, readonly) NSXPCInterface * remoteDeviceClientProtocol;
@property (strong, nonatomic, readonly) NSXPCConnection * (^connectBlock)(void);
@property (weak, nonatomic, readwrite) MTRDeviceControllerXPCProxyHandle * proxyHandle;
@property (strong, nonatomic, readwrite) MTRDeviceControllerXPCProxyHandle * proxyRetainerForReports;
@property (strong, atomic, readonly) dispatch_queue_t workQueue;
@property (strong, nonatomic, readonly) NSMutableDictionary<id, NSMutableDictionary *> * reportRegistry;
@end
@implementation MTRDeviceControllerXPCConnection
- (instancetype)initWithWorkQueue:(dispatch_queue_t)workQueue connectBlock:(NSXPCConnection * (^)(void) )connectBlock
{
if ([super init]) {
_remoteDeviceServerProtocol = [MTRDeviceController xpcInterfaceForServerProtocol];
_remoteDeviceClientProtocol = [MTRDeviceController xpcInterfaceForClientProtocol];
_connectBlock = connectBlock;
_workQueue = workQueue;
_reportRegistry = [[NSMutableDictionary alloc] init];
}
return self;
}
// This class method is for unit testing
+ (MTRDeviceControllerXPCConnection *)connectionWithWorkQueue:(dispatch_queue_t)workQueue
connectBlock:(NSXPCConnection * (^)(void) )connectBlock
{
return [[MTRDeviceControllerXPCConnection alloc] initWithWorkQueue:workQueue connectBlock:connectBlock];
}
- (void)getProxyHandleWithCompletion:(MTRGetProxyHandleHandler)completion
{
dispatch_async(_workQueue, ^{
MTRDeviceControllerXPCProxyHandle * container = self.proxyHandle;
if (!container) {
NSXPCConnection * xpcConnection = self.connectBlock();
if (!xpcConnection) {
MTR_LOG_ERROR("Cannot connect to XPC server for remote controller");
completion(self.workQueue, nil);
return;
}
xpcConnection.remoteObjectInterface = self.remoteDeviceServerProtocol;
xpcConnection.exportedInterface = self.remoteDeviceClientProtocol;
xpcConnection.exportedObject = self;
[xpcConnection resume];
container = [[MTRDeviceControllerXPCProxyHandle alloc] initWithXPCConnection:xpcConnection];
self.proxyHandle = container;
__weak typeof(self) weakSelf = self;
xpcConnection.invalidationHandler = ^{
typeof(self) strongSelf = weakSelf;
if (strongSelf) {
dispatch_async(strongSelf.workQueue, ^{
strongSelf.proxyHandle = nil;
strongSelf.proxyRetainerForReports = nil;
[strongSelf.reportRegistry removeAllObjects];
MTR_LOG_DEBUG("CHIP XPC connection disconnected");
});
}
};
MTR_LOG_DEBUG("CHIP XPC connection established");
}
completion(self.workQueue, container);
});
}
- (void)registerReportHandlerWithController:(id<NSCopying>)controller nodeID:(NSNumber *)nodeID handler:(MTRXPCReportHandler)handler
{
dispatch_async(_workQueue, ^{
BOOL shouldRetainProxyForReport = ([self.reportRegistry count] == 0);
NSMutableDictionary * controllerDictionary = self.reportRegistry[controller];
if (!controllerDictionary) {
controllerDictionary = [[NSMutableDictionary alloc] init];
[self.reportRegistry setObject:controllerDictionary forKey:controller];
}
NSMutableArray * nodeArray = controllerDictionary[nodeID];
if (!nodeArray) {
nodeArray = [[NSMutableArray alloc] init];
[controllerDictionary setObject:nodeArray forKey:nodeID];
}
[nodeArray addObject:handler];
if (shouldRetainProxyForReport) {
self.proxyRetainerForReports = self.proxyHandle;
}
});
}
- (void)deregisterReportHandlersWithController:(id<NSCopying>)controller
nodeID:(NSNumber *)nodeID
completion:(dispatch_block_t)completion
{
dispatch_async(_workQueue, ^{
__auto_type clearRegistry = ^{
NSMutableDictionary * controllerDictionary = self.reportRegistry[controller];
if (!controllerDictionary) {
completion();
return;
}
NSMutableArray * nodeArray = controllerDictionary[nodeID];
if (!nodeArray) {
completion();
return;
}
[controllerDictionary removeObjectForKey:nodeID];
if ([controllerDictionary count] == 0) {
// Dereference proxy retainer for reports so that XPC connection may be invalidated if no longer used.
self.proxyRetainerForReports = nil;
}
completion();
};
[self
getProxyHandleWithCompletion:^(dispatch_queue_t _Nonnull queue, MTRDeviceControllerXPCProxyHandle * _Nullable handle) {
if (handle) {
MTR_LOG_DEBUG("CHIP XPC connection requests to stop reports");
[handle.proxy stopReportsWithController:controller
nodeId:nodeID.unsignedLongLongValue
completion:^{
// Make sure handle stays alive until we get to this
// completion (in particular while we are working
// with handle.proxy).
__auto_type handleRetainer = handle;
(void) handleRetainer;
dispatch_async(queue, clearRegistry);
}];
} else {
MTR_LOG_ERROR("CHIP XPC connection failed to stop reporting");
clearRegistry();
}
}];
});
}
- (void)handleReportWithController:(id)controller
nodeId:(uint64_t)nodeId
values:(id _Nullable)values
error:(NSError * _Nullable)error
{
dispatch_async(_workQueue, ^{
NSMutableDictionary * controllerDictionary = self.reportRegistry[controller];
if (!controllerDictionary) {
return;
}
NSNumber * nodeIdKey = [NSNumber numberWithUnsignedInteger:nodeId];
NSMutableArray * nodeArray = controllerDictionary[nodeIdKey];
if (!nodeArray) {
return;
}
for (void (^handler)(id _Nullable values, NSError * _Nullable error) in nodeArray) {
handler(values, error);
}
});
}
- (void)callSubscriptionEstablishedHandler:(dispatch_block_t)handler
{
// Call the handler from our _workQueue, so that we guarantee the same
// number of queue hops as for handleReportWithController.
dispatch_async(_workQueue, handler);
}
@end