|  | /** | 
|  | * | 
|  | *    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 "MTRCluster.h" | 
|  | #import "MTRClusterStateCacheContainer+XPC.h" | 
|  | #import "MTRDeviceController+XPC.h" | 
|  | #import "MTRDeviceControllerOverXPC_Internal.h" | 
|  | #import "MTRDeviceControllerXPCConnection.h" | 
|  | #import "MTRError.h" | 
|  | #import "MTRLogging_Internal.h" | 
|  |  | 
|  | NS_ASSUME_NONNULL_BEGIN | 
|  |  | 
|  | typedef void (^MTRFetchProxyHandleCompletion)(MTRDeviceControllerXPCProxyHandle * _Nullable handle, NSError * _Nullable error); | 
|  |  | 
|  | @interface MTRDeviceOverXPC () | 
|  |  | 
|  | @property (nonatomic, strong, readonly, nullable) id<NSCopying> controllerID; | 
|  | @property (nonatomic, strong, readonly) MTRDeviceControllerOverXPC * controller; | 
|  | @property (nonatomic, readonly) NSNumber * nodeID; | 
|  | @property (nonatomic, strong, readonly) MTRDeviceControllerXPCConnection * xpcConnection; | 
|  |  | 
|  | - (void)fetchProxyHandleWithQueue:(dispatch_queue_t)queue completion:(MTRFetchProxyHandleCompletion)completion; | 
|  |  | 
|  | @end | 
|  |  | 
|  | @implementation MTRDeviceOverXPC | 
|  |  | 
|  | - (instancetype)initWithControllerOverXPC:(MTRDeviceControllerOverXPC *)controllerOverXPC | 
|  | deviceID:(NSNumber *)deviceID | 
|  | xpcConnection:(MTRDeviceControllerXPCConnection *)xpcConnection | 
|  | { | 
|  | _controllerID = controllerOverXPC.controllerID; | 
|  | _controller = controllerOverXPC; | 
|  | _nodeID = deviceID; | 
|  | _xpcConnection = xpcConnection; | 
|  | return self; | 
|  | } | 
|  |  | 
|  | - (void)subscribeWithQueue:(dispatch_queue_t)queue | 
|  | params:(MTRSubscribeParams *)params | 
|  | clusterStateCacheContainer:(MTRClusterStateCacheContainer * _Nullable)clusterStateCacheContainer | 
|  | attributeReportHandler:(void (^_Nullable)(NSArray * value))attributeReportHandler | 
|  | eventReportHandler:(void (^_Nullable)(NSArray * value))eventReportHandler | 
|  | errorHandler:(void (^)(NSError * error))errorHandler | 
|  | subscriptionEstablished:(void (^_Nullable)(void))subscriptionEstablishedHandler | 
|  | resubscriptionScheduled:(MTRDeviceResubscriptionScheduledHandler _Nullable)resubscriptionScheduledHandler | 
|  | { | 
|  | MTR_LOG_DEBUG("Subscribing all attributes... Note that attributeReportHandler, eventReportHandler, and resubscriptionScheduled " | 
|  | "are not supported."); | 
|  |  | 
|  | __auto_type workBlock = ^(MTRDeviceControllerXPCProxyHandle * _Nullable handle, NSError * _Nullable error) { | 
|  | if (error != nil) { | 
|  | errorHandler(error); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (clusterStateCacheContainer) { | 
|  | [clusterStateCacheContainer setXPCConnection:self->_xpcConnection controllerID:self.controllerID deviceID:self.nodeID]; | 
|  | } | 
|  |  | 
|  | [handle.proxy subscribeWithController:self.controllerID | 
|  | nodeId:self.nodeID.unsignedLongLongValue | 
|  | minInterval:params.minInterval | 
|  | maxInterval:params.maxInterval | 
|  | params:[MTRDeviceController encodeXPCSubscribeParams:params] | 
|  | shouldCache:(clusterStateCacheContainer != nil) | 
|  | completion:^(NSError * _Nullable error) { | 
|  | dispatch_async(queue, ^{ | 
|  | if (error) { | 
|  | errorHandler(error); | 
|  | } else { | 
|  | subscriptionEstablishedHandler(); | 
|  | } | 
|  | }); | 
|  | __auto_type handleRetainer = handle; | 
|  | (void) handleRetainer; | 
|  | }]; | 
|  | }; | 
|  |  | 
|  | [self fetchProxyHandleWithQueue:queue completion:workBlock]; | 
|  | } | 
|  |  | 
|  | - (void)readAttributesWithEndpointID:(NSNumber * _Nullable)endpointID | 
|  | clusterID:(NSNumber * _Nullable)clusterID | 
|  | attributeID:(NSNumber * _Nullable)attributeID | 
|  | params:(MTRReadParams * _Nullable)params | 
|  | queue:(dispatch_queue_t)queue | 
|  | completion:(MTRDeviceResponseHandler)completion | 
|  | { | 
|  | MTR_LOG_DEBUG("Reading attribute ..."); | 
|  |  | 
|  | __auto_type workBlock = ^(MTRDeviceControllerXPCProxyHandle * _Nullable handle, NSError * _Nullable error) { | 
|  | if (error != nil) { | 
|  | completion(nil, error); | 
|  | return; | 
|  | } | 
|  |  | 
|  | [handle.proxy readAttributeWithController:self.controllerID | 
|  | nodeId:self.nodeID.unsignedLongLongValue | 
|  | endpointId:endpointID | 
|  | clusterId:clusterID | 
|  | attributeId:attributeID | 
|  | params:[MTRDeviceController encodeXPCReadParams:params] | 
|  | completion:^(id _Nullable values, NSError * _Nullable error) { | 
|  | dispatch_async(queue, ^{ | 
|  | 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; | 
|  | }); | 
|  | }]; | 
|  | }; | 
|  |  | 
|  | [self fetchProxyHandleWithQueue:queue completion:workBlock]; | 
|  | } | 
|  |  | 
|  | - (void)writeAttributeWithEndpointID:(NSNumber *)endpointID | 
|  | clusterID:(NSNumber *)clusterID | 
|  | attributeID:(NSNumber *)attributeID | 
|  | value:(id)value | 
|  | timedWriteTimeout:(NSNumber * _Nullable)timeoutMs | 
|  | queue:(dispatch_queue_t)queue | 
|  | completion:(MTRDeviceResponseHandler)completion | 
|  | { | 
|  | MTR_LOG_DEBUG("Writing attribute ..."); | 
|  |  | 
|  | __auto_type workBlock = ^(MTRDeviceControllerXPCProxyHandle * _Nullable handle, NSError * _Nullable error) { | 
|  | if (error != nil) { | 
|  | completion(nil, error); | 
|  | return; | 
|  | } | 
|  |  | 
|  | [handle.proxy writeAttributeWithController:self.controllerID | 
|  | nodeId:self.nodeID.unsignedLongLongValue | 
|  | endpointId:endpointID | 
|  | clusterId:clusterID | 
|  | attributeId:attributeID | 
|  | value:value | 
|  | timedWriteTimeout:timeoutMs | 
|  | completion:^(id _Nullable values, NSError * _Nullable error) { | 
|  | dispatch_async(queue, ^{ | 
|  | 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; | 
|  | }); | 
|  | }]; | 
|  | }; | 
|  |  | 
|  | [self fetchProxyHandleWithQueue:queue completion:workBlock]; | 
|  | } | 
|  |  | 
|  | - (void)invokeCommandWithEndpointID:(NSNumber *)endpointID | 
|  | clusterID:(NSNumber *)clusterID | 
|  | commandID:(NSNumber *)commandID | 
|  | commandFields:(id)commandFields | 
|  | timedInvokeTimeout:(NSNumber * _Nullable)timeoutMs | 
|  | queue:(dispatch_queue_t)queue | 
|  | completion:(MTRDeviceResponseHandler)completion | 
|  | { | 
|  | MTR_LOG_DEBUG("Invoking command ..."); | 
|  |  | 
|  | __auto_type workBlock = ^(MTRDeviceControllerXPCProxyHandle * _Nullable handle, NSError * _Nullable error) { | 
|  | if (error != nil) { | 
|  | completion(nil, error); | 
|  | return; | 
|  | } | 
|  |  | 
|  | [handle.proxy invokeCommandWithController:self.controllerID | 
|  | nodeId:self.nodeID.unsignedLongLongValue | 
|  | endpointId:endpointID | 
|  | clusterId:clusterID | 
|  | commandId:commandID | 
|  | fields:commandFields | 
|  | timedInvokeTimeout:timeoutMs | 
|  | completion:^(id _Nullable values, NSError * _Nullable error) { | 
|  | dispatch_async(queue, ^{ | 
|  | 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; | 
|  | }); | 
|  | }]; | 
|  | }; | 
|  |  | 
|  | [self fetchProxyHandleWithQueue:queue completion:workBlock]; | 
|  | } | 
|  |  | 
|  | - (void)subscribeToAttributesWithEndpointID:(NSNumber * _Nullable)endpointID | 
|  | clusterID:(NSNumber * _Nullable)clusterID | 
|  | attributeID:(NSNumber * _Nullable)attributeID | 
|  | params:(MTRSubscribeParams * _Nullable)params | 
|  | queue:(dispatch_queue_t)queue | 
|  | reportHandler:(MTRDeviceResponseHandler)reportHandler | 
|  | subscriptionEstablished:(void (^_Nullable)(void))subscriptionEstablishedHandler | 
|  | { | 
|  | MTR_LOG_DEBUG("Subscribing attribute ..."); | 
|  |  | 
|  | __auto_type workBlock = ^(MTRDeviceControllerXPCProxyHandle * _Nullable handle, NSError * _Nullable error) { | 
|  | MTR_LOG_DEBUG("Setup report handler"); | 
|  |  | 
|  | if (error != nil) { | 
|  | subscriptionEstablishedHandler(); | 
|  | reportHandler(nil, error); | 
|  | return; | 
|  | } | 
|  |  | 
|  | [self.xpcConnection | 
|  | registerReportHandlerWithController:self.controllerID | 
|  | 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(queue, ^{ | 
|  | 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(queue, ^{ | 
|  | reportHandler(filteredValues, error); | 
|  | }); | 
|  | } | 
|  | }]; | 
|  |  | 
|  | [handle.proxy subscribeAttributeWithController:self.controllerID | 
|  | nodeId:self.nodeID.unsignedLongLongValue | 
|  | endpointId:endpointID | 
|  | clusterId:clusterID | 
|  | attributeId:attributeID | 
|  | minInterval:params.minInterval | 
|  | maxInterval:params.maxInterval | 
|  | params:[MTRDeviceController encodeXPCSubscribeParams:params] | 
|  | establishedHandler:^{ | 
|  | dispatch_async(queue, ^{ | 
|  | 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; | 
|  | }); | 
|  | }]; | 
|  | }; | 
|  |  | 
|  | [self fetchProxyHandleWithQueue:queue completion:workBlock]; | 
|  | } | 
|  |  | 
|  | - (void)deregisterReportHandlersWithQueue:(dispatch_queue_t)queue completion:(void (^)(void))completion | 
|  | { | 
|  | MTR_LOG_DEBUG("Deregistering report handlers"); | 
|  | __auto_type workBlock = ^{ | 
|  | [self->_xpcConnection deregisterReportHandlersWithController:self.controllerID | 
|  | nodeID:self.nodeID | 
|  | completion:^{ | 
|  | dispatch_async(queue, completion); | 
|  | }]; | 
|  | }; | 
|  |  | 
|  | if (self.controllerID != nil) { | 
|  | workBlock(); | 
|  | } else { | 
|  | [self.controller fetchControllerIdWithQueue:queue | 
|  | completion:^(id _Nullable controllerID, | 
|  | MTRDeviceControllerXPCProxyHandle * _Nullable handle, NSError * _Nullable error) { | 
|  | if (error != nil) { | 
|  | // We're already running on the right queue. | 
|  | completion(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | self->_controllerID = controllerID; | 
|  | workBlock(); | 
|  | }]; | 
|  | } | 
|  | } | 
|  |  | 
|  | - (void)openCommissioningWindowWithSetupPasscode:(NSNumber *)setupPasscode | 
|  | discriminator:(NSNumber *)discriminator | 
|  | duration:(NSNumber *)duration | 
|  | queue:(dispatch_queue_t)queue | 
|  | completion:(MTRDeviceOpenCommissioningWindowHandler)completion | 
|  | { | 
|  | MTR_LOG_ERROR("MTRDevice doesn't support openCommissioningWindowWithSetupPasscode over XPC"); | 
|  | dispatch_async(queue, ^{ | 
|  | completion(nil, [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeInvalidState userInfo:nil]); | 
|  | }); | 
|  | } | 
|  |  | 
|  | - (void)fetchProxyHandleWithQueue:(dispatch_queue_t)queue completion:(MTRFetchProxyHandleCompletion)completion | 
|  | { | 
|  | if (self.controllerID != nil) { | 
|  | [self->_xpcConnection getProxyHandleWithCompletion:^( | 
|  | dispatch_queue_t _Nonnull proxyQueue, MTRDeviceControllerXPCProxyHandle * _Nullable handle) { | 
|  | dispatch_async(queue, ^{ | 
|  | if (handle == nil) { | 
|  | MTR_LOG_ERROR("Failed to obtain XPC connection"); | 
|  | completion(nil, [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeGeneralError userInfo:nil]); | 
|  | } else { | 
|  | completion(handle, nil); | 
|  | } | 
|  | }); | 
|  | }]; | 
|  | } else { | 
|  | [self.controller fetchControllerIdWithQueue:queue | 
|  | completion:^(id _Nullable controllerID, | 
|  | MTRDeviceControllerXPCProxyHandle * _Nullable handle, NSError * _Nullable error) { | 
|  | // We're already running on the right queue. | 
|  | if (error != nil) { | 
|  | completion(nil, error); | 
|  | } else { | 
|  | self->_controllerID = controllerID; | 
|  | completion(handle, nil); | 
|  | } | 
|  | }]; | 
|  | } | 
|  | } | 
|  |  | 
|  | @end | 
|  |  | 
|  | NS_ASSUME_NONNULL_END |