diff --git a/examples/darwin-framework-tool/BUILD.gn b/examples/darwin-framework-tool/BUILD.gn index e0224cc6865fbe..7532ed8dd99247 100644 --- a/examples/darwin-framework-tool/BUILD.gn +++ b/examples/darwin-framework-tool/BUILD.gn @@ -218,6 +218,9 @@ executable("darwin-framework-tool") { "commands/common/PreferencesStorage.mm", "commands/common/RemoteDataModelLogger.h", "commands/common/RemoteDataModelLogger.mm", + "commands/common/xpc/DeviceControllerServer.mm", + "commands/common/xpc/XPCServer.mm", + "commands/common/xpc/XPCServerRegistry.mm", "commands/configuration/Commands.h", "commands/configuration/ResetMRPParametersCommand.h", "commands/configuration/ResetMRPParametersCommand.mm", diff --git a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h index 7f9f3f9b6efc2c..e1cc97e20e8123 100644 --- a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h +++ b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.h @@ -52,6 +52,7 @@ class CHIPCommandBridge : public Command { AddArgument("commissioner-vendor-id", 0, UINT16_MAX, &mCommissionerVendorId, "The vendor id to use for darwin-framework-tool. If not provided, chip::VendorId::TestVendor1 (65521, 0xFFF1) will be " "used."); + AddArgument("use-xpc", &mUseXPC, "Use a controller that will connect to an XPC endpoint instead of talking to devices directly. If a string argument is provided, it must identify a Mach service name that can be used to connect to a remote endpoint. If no argument is provided, a local endpoint will be used."); } /////////// Command Interface ///////// @@ -168,4 +169,5 @@ class CHIPCommandBridge : public Command { chip::Optional mPaaTrustStorePath; chip::Optional mCommissionerVendorId; std::string mCurrentIdentity; + chip::Optional> mUseXPC; }; diff --git a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm index 0eb7d60a89eb24..d4154de024a988 100644 --- a/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm +++ b/examples/darwin-framework-tool/commands/common/CHIPCommandBridge.mm @@ -31,6 +31,8 @@ #import "DeviceDelegate.h" #include "MTRError_Utils.h" +#include "xpc/XPCServerRegistry.h" + #include #include @@ -45,6 +47,17 @@ bool CHIPCommandBridge::sUseSharedStorage = true; constexpr char kTrustStorePathVariable[] = "PAA_TRUST_STORE_PATH"; +namespace { +NSString * ToNSString(const chip::Optional> & string) +{ + if (!string.HasValue() && string.Value().IsNull()) { + return nil; + } + + return @(string.Value().Value()); +} +} + CHIP_ERROR CHIPCommandBridge::Run() { // In interactive mode, we want to avoid memory accumulating in the main autorelease pool, @@ -143,6 +156,8 @@ productAttestationAuthorityCertificates = nil; } + [[XPCServerRegistry sharedInstance] start]; + sUseSharedStorage = mCommissionerSharedStorage.ValueOr(false); if (sUseSharedStorage) { return SetUpStackWithSharedStorage(productAttestationAuthorityCertificates); @@ -202,7 +217,13 @@ params.productAttestationAuthorityCertificates = productAttestationAuthorityCertificates; - __auto_type * controller = [[MTRDeviceController alloc] initWithParameters:params error:&error]; + MTRDeviceController * controller = nil; + if (mUseXPC.HasValue()) { + __auto_type * identifier = uuidString; + controller = [[XPCServerRegistry sharedInstance] createController:identifier serviceName:ToNSString(mUseXPC) params:params error:&error]; + } else { + controller = [[MTRDeviceController alloc] initWithParameters:params error:&error]; + } VerifyOrReturnError(nil != controller, MTRErrorToCHIPErrorCode(error), ChipLogError(chipTool, "Controller startup failure: %@", error)); mControllers[identities[i]] = controller; } @@ -237,12 +258,18 @@ params.nodeId = @(mCommissionerNodeId.Value()); } - // We're not sure whether we're creating a new fabric or using an existing one, so just try both. - auto controller = [factory createControllerOnExistingFabric:params error:&error]; - if (controller == nil) { - // Maybe we didn't have this fabric yet. - params.vendorID = @(mCommissionerVendorId.ValueOr(chip::VendorId::TestVendor1)); - controller = [factory createControllerOnNewFabric:params error:&error]; + MTRDeviceController * controller = nil; + if (mUseXPC.HasValue()) { + __auto_type * identifier = @(identities[i]); + controller = [[XPCServerRegistry sharedInstance] createController:identifier serviceName:ToNSString(mUseXPC) params:params error:&error]; + } else { + // We're not sure whether we're creating a new fabric or using an existing one, so just try both. + controller = [factory createControllerOnExistingFabric:params error:&error]; + if (controller == nil) { + // Maybe we didn't have this fabric yet. + params.vendorID = @(mCommissionerVendorId.ValueOr(chip::VendorId::TestVendor1)); + controller = [factory createControllerOnNewFabric:params error:&error]; + } } VerifyOrReturnError(nil != controller, MTRErrorToCHIPErrorCode(error), ChipLogError(chipTool, "Controller startup failure: %@", error)); mControllers[identities[i]] = controller; @@ -256,6 +283,8 @@ if (IsInteractive()) { return; } + + [[XPCServerRegistry sharedInstance] stop]; ShutdownCommissioner(); } diff --git a/examples/darwin-framework-tool/commands/common/xpc/DeviceControllerServer.mm b/examples/darwin-framework-tool/commands/common/xpc/DeviceControllerServer.mm new file mode 100644 index 00000000000000..7b0f9ac99c1e79 --- /dev/null +++ b/examples/darwin-framework-tool/commands/common/xpc/DeviceControllerServer.mm @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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 "XPCServerProtocols.h" + +#import + +@interface DeviceControllerXPCServerImpl : NSObject +@property (nonatomic, strong) id clientProxy; +@property (nonatomic, strong) NSArray * controllers; +@property (nonatomic, strong) dispatch_queue_t callbackQueue; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)initWithClientProxy:(id)proxy controllers:(NSArray *)controllers; +@end + +@implementation DeviceControllerXPCServerImpl +- (instancetype)initWithClientProxy:(id)proxy controllers:(NSArray *)controllers +{ + if ([super init]) { + _clientProxy = proxy; + _controllers = controllers; + _callbackQueue = dispatch_queue_create("com.chip.xpc.command", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); + } + return self; +} + +- (MTRBaseDevice *)createDevice:(id)uniqueIdentifierString nodeID:(uint64_t)nodeID +{ + if (![uniqueIdentifierString isKindOfClass:[NSString class]]) { + ChipLogError(chipTool, "The controller identifier should be a NSString."); + return nil; + } + + __auto_type * uniqueIdentifier = [[NSUUID alloc] initWithUUIDString:uniqueIdentifierString]; + for (MTRDeviceController * controller in _controllers) { + if ([controller.uniqueIdentifier isEqual:uniqueIdentifier]) { + return [MTRBaseDevice deviceWithNodeID:@(nodeID) controller:controller]; + } + } + + ChipLogError(chipTool, "The controller '%s' could not be found.", [uniqueIdentifierString UTF8String]); + return nil; +} + +- (void)getAnyDeviceControllerWithCompletion:(MTRDeviceControllerGetterHandler)completion +{ + ChipLogError(chipTool, "XPC (NotImplemented): %s", __func__); + + __auto_type * error = [NSError errorWithDomain:MTRErrorDomain + code:MTRErrorCodeGeneralError + userInfo:nil]; + completion(nil, error); +} + +- (void)readAttributeWithController:(id _Nullable)controller + nodeId:(uint64_t)nodeId + endpointId:(NSNumber * _Nullable)endpointId + clusterId:(NSNumber * _Nullable)clusterId + attributeId:(NSNumber * _Nullable)attributeId + params:(NSDictionary * _Nullable)params + completion:(MTRValuesHandler)completion +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controller nodeID:nodeId]; + [device readAttributesWithEndpointID:endpointId + clusterID:clusterId + attributeID:attributeId + params:[MTRDeviceController decodeXPCReadParams:params] + queue:_callbackQueue + completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + completion([MTRDeviceController encodeXPCResponseValues:values], error); + }]; +} + +- (void)writeAttributeWithController:(id _Nullable)controller + nodeId:(uint64_t)nodeId + endpointId:(NSNumber *)endpointId + clusterId:(NSNumber *)clusterId + attributeId:(NSNumber *)attributeId + value:(id)value + timedWriteTimeout:(NSNumber * _Nullable)timeoutMs + completion:(MTRValuesHandler)completion +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controller nodeID:nodeId]; + [device + writeAttributeWithEndpointID:endpointId + clusterID:clusterId + attributeID:attributeId + value:value + timedWriteTimeout:timeoutMs + queue:_callbackQueue + completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + completion([MTRDeviceController encodeXPCResponseValues:values], error); + }]; +} + +- (void)invokeCommandWithController:(id _Nullable)controller + nodeId:(uint64_t)nodeId + endpointId:(NSNumber *)endpointId + clusterId:(NSNumber *)clusterId + commandId:(NSNumber *)commandId + fields:(id)fields + timedInvokeTimeout:(NSNumber * _Nullable)timeoutMs + completion:(MTRValuesHandler)completion +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controller nodeID:nodeId]; + [device + invokeCommandWithEndpointID:endpointId + clusterID:clusterId + commandID:commandId + commandFields:fields + timedInvokeTimeout:timeoutMs + queue:_callbackQueue + completion:^(NSArray *> * _Nullable values, NSError * _Nullable error) { + completion([MTRDeviceController encodeXPCResponseValues:values], error); + }]; +} + +- (void)subscribeAttributeWithController:(id _Nullable)controller + nodeId:(uint64_t)nodeId + endpointId:(NSNumber * _Nullable)endpointId + clusterId:(NSNumber * _Nullable)clusterId + attributeId:(NSNumber * _Nullable)attributeId + minInterval:(NSNumber *)minInterval + maxInterval:(NSNumber *)maxInterval + params:(NSDictionary * _Nullable)params + establishedHandler:(dispatch_block_t)establishedHandler +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controller nodeID:nodeId]; + if (nil == device) { + establishedHandler(); + // Send an error report so that the client knows of the failure + [self.clientProxy handleReportWithController:controller + nodeId:nodeId + values:nil + error:[NSError errorWithDomain:MTRErrorDomain + code:MTRErrorCodeGeneralError + userInfo:nil]]; + return; + } + + auto subscriptionParams = [MTRDeviceController decodeXPCSubscribeParams:params]; + if (subscriptionParams == nil) { + subscriptionParams = [[MTRSubscribeParams alloc] initWithMinInterval:minInterval maxInterval:maxInterval]; + } else { + subscriptionParams.minInterval = minInterval; + subscriptionParams.maxInterval = maxInterval; + } + + [device subscribeToAttributesWithEndpointID:endpointId + clusterID:clusterId + attributeID:attributeId + params:subscriptionParams + queue:_callbackQueue + reportHandler:^( + NSArray *> * _Nullable values, NSError * _Nullable error) { + [self.clientProxy + handleReportWithController:controller + nodeId:nodeId + values:[MTRDeviceController encodeXPCResponseValues:values] + error:error]; + } + subscriptionEstablished:establishedHandler]; +} + +- (void)stopReportsWithController:(id _Nullable)controller nodeId:(uint64_t)nodeId completion:(dispatch_block_t)completion +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controller nodeID:nodeId]; + VerifyOrReturn(nil != device, completion()); + + [device deregisterReportHandlersWithQueue:_callbackQueue completion:completion]; +} + +- (void)subscribeWithController:(id _Nullable)controller + nodeId:(uint64_t)nodeId + minInterval:(NSNumber *)minInterval + maxInterval:(NSNumber *)maxInterval + params:(NSDictionary * _Nullable)params + shouldCache:(BOOL)shouldCache + completion:(MTRStatusCompletion)completion +{ + ChipLogError(chipTool, "XPC (NotImplemented): %s", __func__); + + __auto_type * error = [NSError errorWithDomain:MTRErrorDomain + code:MTRErrorCodeGeneralError + userInfo:nil]; + completion(error); +} + +- (void)readAttributeCacheWithController:(id _Nullable)controller + nodeId:(uint64_t)nodeId + endpointId:(NSNumber * _Nullable)endpointId + clusterId:(NSNumber * _Nullable)clusterId + attributeId:(NSNumber * _Nullable)attributeId + completion:(MTRValuesHandler)completion +{ + ChipLogError(chipTool, "XPC (NotImplemented): %s", __func__); + + __auto_type * error = [NSError errorWithDomain:MTRErrorDomain + code:MTRErrorCodeGeneralError + userInfo:nil]; + completion(nil, error); +} + +- (void)getDeviceControllerWithFabricId:(uint64_t)fabricId + completion:(MTRDeviceControllerGetterHandler)completion +{ + ChipLogError(chipTool, "XPC (NotImplemented): %s", __func__); + + __auto_type * error = [NSError errorWithDomain:MTRErrorDomain + code:MTRErrorCodeGeneralError + userInfo:nil]; + completion(nil, error); +} + +- (void)downloadLogWithController:(id _Nullable)controller + nodeId:(NSNumber *)nodeId + type:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + completion:(void (^)(NSString * _Nullable url, NSError * _Nullable error))completion +{ + ChipLogError(chipTool, "XPC (NotImplemented): %s", __func__); + + __auto_type * error = [NSError errorWithDomain:MTRErrorDomain + code:MTRErrorCodeGeneralError + userInfo:nil]; + completion(nil, error); +} + +@end + +@interface DeviceControllerXPCServer : NSObject +@property (nonatomic, strong) NSXPCListener * listener; +@property (nonatomic, strong) NSMutableArray * controllers; +@end + +@implementation DeviceControllerXPCServer +- (instancetype)init +{ + if ((self = [super init])) { + _controllers = [NSMutableArray new]; + } + + return self; +} + +- (void)start +{ + _listener = [NSXPCListener anonymousListener]; + [_listener setDelegate:self]; + [_listener resume]; +} + +- (void)stop +{ + [_listener suspend]; + _listener = nil; + _controllers = nil; +} + +- (MTRDeviceController *)createController:(MTRDeviceControllerStartupParams *)params error:(NSError * __autoreleasing *)error +{ + __auto_type * factory = [MTRDeviceControllerFactory sharedInstance]; + __auto_type * local = [factory createControllerOnExistingFabric:params error:error]; + if (nil == local) { + return nil; + } + [_controllers addObject:local]; + + __auto_type connectBlock = ^NSXPCConnection * + { + return [[NSXPCConnection alloc] initWithListenerEndpoint:self.listener.endpoint]; + }; + return [MTRDeviceController sharedControllerWithID:[local.uniqueIdentifier UUIDString] xpcConnectBlock:connectBlock]; +} + +- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection +{ + newConnection.exportedInterface = [MTRDeviceController xpcInterfaceForServerProtocol]; + newConnection.remoteObjectInterface = [MTRDeviceController xpcInterfaceForClientProtocol]; + + auto server = [[DeviceControllerXPCServerImpl alloc] initWithClientProxy:[newConnection remoteObjectProxy] controllers:_controllers]; + newConnection.exportedObject = server; + + newConnection.interruptionHandler = ^{ + ChipLogProgress(chipTool, "XPC connection interrupted"); + }; + + newConnection.invalidationHandler = ^{ + ChipLogProgress(chipTool, "XPC connection invalidated"); + }; + + [newConnection resume]; + return YES; +} +@end + +@interface DeviceControllerXPCServerWithServiceName : NSObject +@end + +@implementation DeviceControllerXPCServerWithServiceName +- (void)start +{ +} + +- (void)stop +{ +} + +- (MTRDeviceController *)createController:(NSString *)controllerID serviceName:(NSString *)serviceName error:(NSError * __autoreleasing *)error +{ + __auto_type connectBlock = ^NSXPCConnection * + { +#if TARGET_OS_OSX + return [[NSXPCConnection alloc] initWithMachServiceName:serviceName options:0]; +#else + ChipLogError(chipTool, "NSXPCConnection::initWithMachServiceName is not supported on this platform."); + return nil; +#endif // TARGET_OS_OSX + }; + return [MTRDeviceController sharedControllerWithID:controllerID xpcConnectBlock:connectBlock]; +} +@end diff --git a/examples/darwin-framework-tool/commands/common/xpc/XPCServer.mm b/examples/darwin-framework-tool/commands/common/xpc/XPCServer.mm new file mode 100644 index 00000000000000..9aa7e77ec37177 --- /dev/null +++ b/examples/darwin-framework-tool/commands/common/xpc/XPCServer.mm @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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 "XPCServerProtocols.h" + +#import + +@interface XPCServerImpl : NSObject +@property (nonatomic, strong) id clientProxy; +@property (nonatomic, strong) NSArray * controllers; +@property (nonatomic, strong) dispatch_queue_t callbackQueue; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)initWithClientProxy:(id)proxy controllers:(NSArray *)controllers; +@end + +@implementation XPCServerImpl +- (instancetype)initWithClientProxy:(id)proxy controllers:(NSArray *)controllers +{ + if ([super init]) { + _clientProxy = proxy; + _controllers = controllers; + _callbackQueue = dispatch_queue_create("com.chip.xpc.command", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); + } + return self; +} + +- (MTRDevice *)createDevice:(NSUUID *)uniqueIdentifier nodeID:(NSNumber *)nodeID +{ + for (MTRDeviceController * controller in _controllers) { + if ([controller.uniqueIdentifier isEqual:uniqueIdentifier]) { + return [MTRDevice deviceWithNodeID:nodeID controller:controller]; + } + } + + ChipLogError(chipTool, "The controller '%s' could not be found.", [[uniqueIdentifier UUIDString] UTF8String]); + return nil; +} + +// TODO this is declared optional but the framework does not do any check on it, so it just crashes. +- (oneway void)deviceController:(NSUUID *)controllerUUID updateControllerConfiguration:(NSDictionary *)controllerState +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); +} + +- (oneway void)deviceController:(NSUUID *)controllerUUID nodeID:(NSNumber *)nodeID getStateWithReply:(void (^)(MTRDeviceState state))reply +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controllerUUID nodeID:nodeID]; + reply(device.state); +} + +- (oneway void)deviceController:(NSUUID *)controllerUUID nodeID:(NSNumber *)nodeID getDeviceCachePrimedWithReply:(void (^)(BOOL primed))reply +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controllerUUID nodeID:nodeID]; + reply(device.deviceCachePrimed); +} + +- (oneway void)deviceController:(NSUUID *)controllerUUID nodeID:(NSNumber *)nodeID getEstimatedStartTimeWithReply:(void (^)(NSDate * _Nullable estimatedStartTime))reply +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controllerUUID nodeID:nodeID]; + reply(device.estimatedStartTime); +} + +- (oneway void)deviceController:(NSUUID *)controllerUUID nodeID:(NSNumber *)nodeID getEstimatedSubscriptionLatencyWithReply:(void (^)(NSNumber * _Nullable estimatedSubscriptionLatency))reply +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controllerUUID nodeID:nodeID]; + reply(device.estimatedSubscriptionLatency); +} + +- (oneway void)deviceController:(NSUUID *)controllerUUID nodeID:(NSNumber *)nodeID readAttributeWithEndpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID params:(MTRReadParams * _Nullable)params withReply:(void (^)(NSDictionary * _Nullable))reply +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controllerUUID nodeID:nodeID]; + reply([device readAttributeWithEndpointID:endpointID clusterID:clusterID attributeID:attributeID params:params]); +} + +- (oneway void)deviceController:(NSUUID *)controllerUUID nodeID:(NSNumber *)nodeID readAttributePaths:(NSArray *)attributePaths withReply:(void (^)(NSArray *> *))reply +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controllerUUID nodeID:nodeID]; + reply([device readAttributePaths:attributePaths]); +} + +- (oneway void)deviceController:(NSUUID *)controllerUUID nodeID:(NSNumber *)nodeID writeAttributeWithEndpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID attributeID:(NSNumber *)attributeID value:(id)value expectedValueInterval:(NSNumber * _Nullable)expectedValueInterval timedWriteTimeout:(NSNumber * _Nullable)timeout +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controllerUUID nodeID:nodeID]; + [device writeAttributeWithEndpointID:endpointID clusterID:clusterID attributeID:attributeID value:value expectedValueInterval:expectedValueInterval timedWriteTimeout:timeout]; +} + +- (oneway void)deviceController:(NSUUID *)controllerUUID nodeID:(NSNumber *)nodeID invokeCommandWithEndpointID:(NSNumber *)endpointID clusterID:(NSNumber *)clusterID commandID:(NSNumber *)commandID commandFields:(id)commandFields expectedValues:(NSArray *> * _Nullable)expectedValues expectedValueInterval:(NSNumber * _Nullable)expectedValueInterval timedInvokeTimeout:(NSNumber * _Nullable)timeout serverSideProcessingTimeout:(NSNumber * _Nullable)serverSideProcessingTimeout completion:(MTRDeviceResponseHandler)completion +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controllerUUID nodeID:nodeID]; + [device invokeCommandWithEndpointID:endpointID + clusterID:clusterID + commandID:commandID + commandFields:commandFields + expectedValues:expectedValues + expectedValueInterval:expectedValueInterval + timedInvokeTimeout:timeout + queue:_callbackQueue + completion:^(NSArray * values, NSError * _Nullable error) { + completion(values, error); + }]; +} + +- (oneway void)deviceController:(NSUUID *)controller nodeID:(NSNumber *)nodeID openCommissioningWindowWithSetupPasscode:(NSNumber *)setupPasscode discriminator:(NSNumber *)discriminator duration:(NSNumber *)duration completion:(MTRDeviceOpenCommissioningWindowHandler)completion +{ + ChipLogError(chipTool, "XPC (NotImplemented): %s", __func__); + + __auto_type * error = [NSError errorWithDomain:MTRErrorDomain + code:MTRErrorCodeGeneralError + userInfo:nil]; + completion(nil, error); +} + +- (oneway void)deviceController:(NSUUID *)controllerUUID nodeID:(NSNumber *)nodeID downloadLogOfType:(MTRDiagnosticLogType)type timeout:(NSTimeInterval)timeout completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion +{ + ChipLogProgress(chipTool, "XPC: %s", __func__); + + __auto_type * device = [self createDevice:controllerUUID nodeID:nodeID]; + [device downloadLogOfType:type timeout:timeout queue:_callbackQueue completion:completion]; +} + +- (oneway void)downloadLogOfType:(MTRDiagnosticLogType)type nodeID:(NSNumber *)nodeID timeout:(NSTimeInterval)timeout completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion +{ + ChipLogError(chipTool, "XPC (NotImplemented): %s", __func__); + + __auto_type * error = [NSError errorWithDomain:MTRErrorDomain + code:MTRErrorCodeGeneralError + userInfo:nil]; + completion(nil, error); +} + +@end + +@interface XPCServer : NSObject +@property (nonatomic, strong) NSXPCListener * listener; +@property (nonatomic, strong) NSMutableArray * controllers; +@end + +@implementation XPCServer +- (instancetype)init +{ + if ((self = [super init])) { + _controllers = [NSMutableArray new]; + } + + return self; +} + +- (void)start +{ + _listener = [NSXPCListener anonymousListener]; + [_listener setDelegate:self]; + [_listener resume]; +} + +- (void)stop +{ + [_listener suspend]; + _listener = nil; + _controllers = nil; +} + +- (MTRDeviceController *)createController:(MTRDeviceControllerExternalCertificateParameters *)params error:(NSError * __autoreleasing *)error +{ + __auto_type * local = [[MTRDeviceController alloc] initWithParameters:params error:error]; + if (nil == local) { + return nil; + } + [_controllers addObject:local]; + + __auto_type connectBlock = ^NSXPCConnection * + { + return [[NSXPCConnection alloc] initWithListenerEndpoint:self.listener.endpoint]; + }; + __auto_type * xpcParams = [[MTRXPCDeviceControllerParameters alloc] initWithXPConnectionBlock:connectBlock uniqueIdentifier:local.uniqueIdentifier]; + return [[MTRDeviceController alloc] initWithParameters:xpcParams error:error]; +} + +- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection +{ + newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MTRXPCServerProtocol)]; + newConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MTRXPCClientProtocol)]; + + auto server = [[XPCServerImpl alloc] initWithClientProxy:[newConnection remoteObjectProxy] controllers:_controllers]; + newConnection.exportedObject = server; + + newConnection.interruptionHandler = ^{ + ChipLogProgress(chipTool, "XPC connection interrupted"); + }; + + newConnection.invalidationHandler = ^{ + ChipLogProgress(chipTool, "XPC connection invalidated"); + }; + + [newConnection resume]; + return YES; +} +@end + +@interface XPCServerWithServiceName : NSObject +@end + +@implementation XPCServerWithServiceName +- (void)start +{ +} + +- (void)stop +{ +} + +- (MTRDeviceController *)createController:(NSString *)controllerID serviceName:(NSString *)serviceName error:(NSError * __autoreleasing *)error +{ + __auto_type connectBlock = ^NSXPCConnection * + { +#if TARGET_OS_OSX + return [[NSXPCConnection alloc] initWithMachServiceName:serviceName options:0]; +#else + ChipLogError(chipTool, "NSXPCConnection::initWithMachServiceName is not supported on this platform."); + return nil; +#endif // TARGET_OS_OSX + }; + __auto_type * uniqueIdentifier = [[NSUUID alloc] initWithUUIDString:controllerID]; + __auto_type * xpcParams = [[MTRXPCDeviceControllerParameters alloc] initWithXPConnectionBlock:connectBlock uniqueIdentifier:uniqueIdentifier]; + return [[MTRDeviceController alloc] initWithParameters:xpcParams error:error]; +} +@end diff --git a/examples/darwin-framework-tool/commands/common/xpc/XPCServerProtocols.h b/examples/darwin-framework-tool/commands/common/xpc/XPCServerProtocols.h new file mode 100644 index 00000000000000..e713bdb4804b69 --- /dev/null +++ b/examples/darwin-framework-tool/commands/common/xpc/XPCServerProtocols.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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 + +NS_ASSUME_NONNULL_BEGIN + +@protocol XPCServerBaseProtocol +- (void)start; +- (void)stop; +@end + +@protocol XPCServerExternalCertificateParametersProtocol +- (MTRDeviceController *)createController:(MTRDeviceControllerExternalCertificateParameters *)params error:(NSError * __autoreleasing *)error; +@end + +@protocol XPCServerStartupParametersProtocol +- (MTRDeviceController *)createController:(MTRDeviceControllerStartupParams *)params error:(NSError * __autoreleasing *)error; +@end + +@protocol XPCServerExternalCertificateParametersWithServiceNameProtocol +- (MTRDeviceController *)createController:(NSString *)controllerID serviceName:(NSString *)serviceName error:(NSError * __autoreleasing *)error; +@end + +@protocol XPCServerStartupParametersWithServiceNameProtocol +- (MTRDeviceController *)createController:(NSString *)controllerID serviceName:(NSString *)serviceName error:(NSError * __autoreleasing *)error; +@end + +NS_ASSUME_NONNULL_END diff --git a/examples/darwin-framework-tool/commands/common/xpc/XPCServerRegistry.h b/examples/darwin-framework-tool/commands/common/xpc/XPCServerRegistry.h new file mode 100644 index 00000000000000..1b77728836ff37 --- /dev/null +++ b/examples/darwin-framework-tool/commands/common/xpc/XPCServerRegistry.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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 + +NS_ASSUME_NONNULL_BEGIN + +@interface XPCServerRegistry : NSObject +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; ++ (XPCServerRegistry *)sharedInstance; + +- (void)start; +- (void)stop; +- (MTRDeviceController *)createController:(NSString * _Nullable)controllerId serviceName:(NSString * _Nullable)serviceName params:(id)params error:(NSError * __autoreleasing *)error; +@end + +NS_ASSUME_NONNULL_END diff --git a/examples/darwin-framework-tool/commands/common/xpc/XPCServerRegistry.mm b/examples/darwin-framework-tool/commands/common/xpc/XPCServerRegistry.mm new file mode 100644 index 00000000000000..3e873f3e5fffe6 --- /dev/null +++ b/examples/darwin-framework-tool/commands/common/xpc/XPCServerRegistry.mm @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * 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 "XPCServerRegistry.h" + +#import "XPCServerProtocols.h" +#import + +@interface XPCServerRegistry () +@property (strong, nonatomic) NSMutableArray> * servers; +@end + +@implementation XPCServerRegistry +- (instancetype)init +{ + if ((self = [super init])) { + _servers = [NSMutableArray new]; + } + + return self; +} + ++ (XPCServerRegistry *)sharedInstance +{ + static XPCServerRegistry * registry = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + registry = [[XPCServerRegistry alloc] init]; + }); + return registry; +} + +- (void)start +{ + if (@available(macOS 13.0, *)) { + objc_enumerateClasses(nil, nil, @protocol(XPCServerBaseProtocol), nil, ^(Class cls, BOOL * stop) { + id server = [[cls alloc] init]; + [_servers addObject:server]; + }); + } else { + int numClasses = objc_getClassList(NULL, 0); + if (numClasses > 0) { + __auto_type * classes = (__unsafe_unretained Class *) malloc(sizeof(Class) * static_cast(numClasses)); + numClasses = objc_getClassList(classes, numClasses); + + for (int i = 0; i < numClasses; i++) { + id cls = classes[i]; + if (class_conformsToProtocol(cls, @protocol(XPCServerBaseProtocol))) { + id server = [[cls alloc] init]; + [_servers addObject:server]; + } + } + free(classes); // Free the allocated memory + } + } + + for (id server in _servers) { + [server start]; + } +} + +- (void)stop +{ + for (id server in _servers) { + [server stop]; + } +} + +- (MTRDeviceController *)createController:(NSString * _Nullable)controllerId serviceName:(NSString * _Nullable)serviceName params:(id)params error:(NSError * __autoreleasing *)error +{ + BOOL isExternalCertificateParameters = [params isKindOfClass:[MTRDeviceControllerExternalCertificateParameters class]]; + BOOL isStartupParameters = [params isKindOfClass:[MTRDeviceControllerStartupParams class]]; + for (id server in _servers) { + if (controllerId && serviceName) { + if ([server conformsToProtocol:@protocol(XPCServerExternalCertificateParametersWithServiceNameProtocol)] && isExternalCertificateParameters) { + return [server createController:controllerId serviceName:serviceName error:error]; + } + + if ([server conformsToProtocol:@protocol(XPCServerStartupParametersWithServiceNameProtocol)] && isStartupParameters) { + return [server createController:controllerId serviceName:serviceName error:error]; + } + } else { + if ([server conformsToProtocol:@protocol(XPCServerExternalCertificateParametersProtocol)] && isExternalCertificateParameters) { + return [server createController:params error:error]; + } + + if ([server conformsToProtocol:@protocol(XPCServerStartupParametersProtocol)] && isStartupParameters) { + return [server createController:params error:error]; + } + } + } + + __auto_type * userInfo = @{ NSLocalizedDescriptionKey : @"No XPC servers support this configuration." }; + *error = [NSError errorWithDomain:@"Error" code:0 userInfo:userInfo]; + return nil; +} + +@end diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 16ed12a0322224..702f853fdda5c1 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -436,12 +436,17 @@ B45374002A9FEC4F00807602 /* unix-init.c in Sources */ = {isa = PBXBuildFile; fileRef = B45373F92A9FEC4F00807602 /* unix-init.c */; settings = {COMPILER_FLAGS = "-Wno-error -Wno-unreachable-code -Wno-conversion -Wno-format-nonliteral"; }; }; B45374012A9FEC4F00807602 /* unix-sockets.c in Sources */ = {isa = PBXBuildFile; fileRef = B45373FA2A9FEC4F00807602 /* unix-sockets.c */; settings = {COMPILER_FLAGS = "-Wno-error -Wno-unreachable-code -Wno-conversion -Wno-format-nonliteral"; }; }; B4C8E6B72B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4C8E6B42B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.mm */; }; + B4D67A3B2D00DAB700C49965 /* XPCServerRegistry.h in Headers */ = {isa = PBXBuildFile; fileRef = B4D67A382D00DAB700C49965 /* XPCServerRegistry.h */; }; + B4D67A3C2D00DAB700C49965 /* XPCServerRegistry.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4D67A392D00DAB700C49965 /* XPCServerRegistry.mm */; }; + B4D67A3D2D00DAB700C49965 /* DeviceControllerServer.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4D67A362D00DAB700C49965 /* DeviceControllerServer.mm */; }; + B4D67A3E2D00DAB700C49965 /* XPCServer.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4D67A372D00DAB700C49965 /* XPCServer.mm */; }; B4D67A412D00DD3D00C49965 /* DFTKeypair.h in Headers */ = {isa = PBXBuildFile; fileRef = B4D67A3F2D00DD3D00C49965 /* DFTKeypair.h */; }; B4D67A422D00DD3D00C49965 /* DFTKeypair.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4D67A402D00DD3D00C49965 /* DFTKeypair.mm */; }; B4D67A922D527F4A00C49965 /* DCLClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B4D67A8E2D527F4A00C49965 /* DCLClient.cpp */; }; B4D67A932D527F4A00C49965 /* DisplayTermsAndConditions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B4D67A8F2D527F4A00C49965 /* DisplayTermsAndConditions.cpp */; }; B4D67A952D527F4A00C49965 /* JsonSchemaMacros.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B4D67A912D527F4A00C49965 /* JsonSchemaMacros.cpp */; }; B4D67A9B2D538E9700C49965 /* HTTPSRequest.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4D67A992D538E9700C49965 /* HTTPSRequest.mm */; }; + B4D67A462D07021700C49965 /* XPCServerProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = B4D67A452D07021700C49965 /* XPCServerProtocols.h */; }; B4E262162AA0CF1C00DBA5BC /* RemoteDataModelLogger.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4E262122AA0C7A300DBA5BC /* RemoteDataModelLogger.mm */; }; B4E262172AA0CF2000DBA5BC /* RemoteDataModelLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = B4E262132AA0C7A300DBA5BC /* RemoteDataModelLogger.h */; }; B4E2621B2AA0D02000DBA5BC /* SleepCommand.mm in Sources */ = {isa = PBXBuildFile; fileRef = B4E262192AA0D01D00DBA5BC /* SleepCommand.mm */; }; @@ -998,12 +1003,17 @@ B45373FA2A9FEC4F00807602 /* unix-sockets.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "unix-sockets.c"; path = "repo/lib/plat/unix/unix-sockets.c"; sourceTree = ""; }; B4C8E6B32B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDiagnosticLogsDownloader.h; sourceTree = ""; }; B4C8E6B42B3453AD00FCD54D /* MTRDiagnosticLogsDownloader.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDiagnosticLogsDownloader.mm; sourceTree = ""; }; + B4D67A362D00DAB700C49965 /* DeviceControllerServer.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = DeviceControllerServer.mm; sourceTree = ""; }; + B4D67A372D00DAB700C49965 /* XPCServer.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = XPCServer.mm; sourceTree = ""; }; + B4D67A382D00DAB700C49965 /* XPCServerRegistry.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPCServerRegistry.h; sourceTree = ""; }; + B4D67A392D00DAB700C49965 /* XPCServerRegistry.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = XPCServerRegistry.mm; sourceTree = ""; }; B4D67A3F2D00DD3D00C49965 /* DFTKeypair.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DFTKeypair.h; sourceTree = ""; }; B4D67A402D00DD3D00C49965 /* DFTKeypair.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = DFTKeypair.mm; sourceTree = ""; }; B4D67A8E2D527F4A00C49965 /* DCLClient.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = DCLClient.cpp; path = commands/dcl/DCLClient.cpp; sourceTree = ""; }; B4D67A8F2D527F4A00C49965 /* DisplayTermsAndConditions.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = DisplayTermsAndConditions.cpp; path = commands/dcl/DisplayTermsAndConditions.cpp; sourceTree = ""; }; B4D67A912D527F4A00C49965 /* JsonSchemaMacros.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = JsonSchemaMacros.cpp; path = commands/dcl/JsonSchemaMacros.cpp; sourceTree = ""; }; B4D67A992D538E9700C49965 /* HTTPSRequest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = HTTPSRequest.mm; sourceTree = ""; }; + B4D67A452D07021700C49965 /* XPCServerProtocols.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XPCServerProtocols.h; sourceTree = ""; }; B4E262122AA0C7A300DBA5BC /* RemoteDataModelLogger.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RemoteDataModelLogger.mm; sourceTree = ""; }; B4E262132AA0C7A300DBA5BC /* RemoteDataModelLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RemoteDataModelLogger.h; sourceTree = ""; }; B4E262192AA0D01D00DBA5BC /* SleepCommand.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SleepCommand.mm; sourceTree = ""; }; @@ -1171,6 +1181,7 @@ children = ( B4D67A3F2D00DD3D00C49965 /* DFTKeypair.h */, B4D67A402D00DD3D00C49965 /* DFTKeypair.mm */, + B4D67A3A2D00DAB700C49965 /* xpc */, B409D0AC2CCFB89600A7ED5A /* DeviceDelegate.h */, B409D0AD2CCFB89600A7ED5A /* DeviceDelegate.mm */, B43B39EF2CB99090006AA284 /* CertificateIssuer.h */, @@ -1889,6 +1900,18 @@ path = dcl; sourceTree = ""; }; + B4D67A3A2D00DAB700C49965 /* xpc */ = { + isa = PBXGroup; + children = ( + B4D67A452D07021700C49965 /* XPCServerProtocols.h */, + B4D67A362D00DAB700C49965 /* DeviceControllerServer.mm */, + B4D67A372D00DAB700C49965 /* XPCServer.mm */, + B4D67A382D00DAB700C49965 /* XPCServerRegistry.h */, + B4D67A392D00DAB700C49965 /* XPCServerRegistry.mm */, + ); + path = xpc; + sourceTree = ""; + }; B4E262182AA0CFFE00DBA5BC /* delay */ = { isa = PBXGroup; children = ( @@ -1976,6 +1999,7 @@ B43B39EC2CB859A5006AA284 /* DumpMemoryGraphCommand.h in Headers */, B43B39ED2CB859A5006AA284 /* Commands.h in Headers */, B43B39EE2CB859A5006AA284 /* LeaksTool.h in Headers */, + B4D67A462D07021700C49965 /* XPCServerProtocols.h in Headers */, 7534D17E2CF8CE2000F64654 /* DefaultAttributePersistenceProvider.h in Headers */, 037C3DBD2991BD5000B7EEE2 /* OTAProviderDelegate.h in Headers */, B4FCD5702B603A6300832859 /* Commands.h in Headers */, @@ -1985,6 +2009,7 @@ 037C3DCB2991BD5100B7EEE2 /* CHIPCommandStorageDelegate.h in Headers */, 037C3DD32991BD5200B7EEE2 /* logging.h in Headers */, 037C3DB72991BD5000B7EEE2 /* ModelCommandBridge.h in Headers */, + B4D67A3B2D00DAB700C49965 /* XPCServerRegistry.h in Headers */, 037C3DC52991BD5100B7EEE2 /* StorageManagementCommand.h in Headers */, 037C3DCC2991BD5100B7EEE2 /* MTRError_Utils.h in Headers */, 7592BD002CBEE98C00EB74A0 /* Instance.h in Headers */, @@ -2379,6 +2404,9 @@ 75A202E62BA8DBAC00A771DD /* reporting.cpp in Sources */, 039145E82993179300257B3E /* GetCommissionerNodeIdCommand.mm in Sources */, 0395469F2991DFC5006D42A8 /* json_reader.cpp in Sources */, + B4D67A3C2D00DAB700C49965 /* XPCServerRegistry.mm in Sources */, + B4D67A3D2D00DAB700C49965 /* DeviceControllerServer.mm in Sources */, + B4D67A3E2D00DAB700C49965 /* XPCServer.mm in Sources */, 514C79F42B62ED5500DD6D7B /* attribute-storage.cpp in Sources */, 0395469E2991DFC5006D42A8 /* json_writer.cpp in Sources */, B4D67A422D00DD3D00C49965 /* DFTKeypair.mm in Sources */,