diff --git a/Headers/Foundation/NSXPCConnection.h b/Headers/Foundation/NSXPCConnection.h index d888bc79d..0e3dae90d 100644 --- a/Headers/Foundation/NSXPCConnection.h +++ b/Headers/Foundation/NSXPCConnection.h @@ -47,136 +47,435 @@ extern "C" { @class NSXPCConnection, NSXPCListener, NSXPCInterface, NSXPCListenerEndpoint; @protocol NSXPCListenerDelegate; +/** + * Handles asynchronous errors that occur while sending a message through + * a remote object proxy. + */ DEFINE_BLOCK_TYPE(GSXPCProxyErrorHandler, void, NSError *); + +/** + * Invoked when a connection is interrupted and may recover later. + */ DEFINE_BLOCK_TYPE_NO_ARGS(GSXPCInterruptionHandler, void); + +/** + * Invoked when a connection is invalidated and can no longer be used. + */ DEFINE_BLOCK_TYPE_NO_ARGS(GSXPCInvalidationHandler, void); +/** + * Defines methods that create proxy objects for messaging a remote process. + */ @protocol NSXPCProxyCreating +/** + * Returns a proxy object used to invoke methods on the remote object + * asynchronously. + */ - (id) remoteObjectProxy; +/** + * Returns an asynchronous remote proxy and installs an error handler that + * receives communication failures for invocations sent through that proxy. + */ - (id) remoteObjectProxyWithErrorHandler: (GSXPCProxyErrorHandler)handler; +/** + * Returns a proxy that performs synchronous round trips and reports failures + * through the supplied error handler. + */ - (id) synchronousRemoteObjectProxyWithErrorHandler: (GSXPCProxyErrorHandler)handler; @end +/** + * Option flag used to request connection to a privileged service instance. + */ enum { NSXPCConnectionPrivileged = (1 << 12UL) }; + +/** + * Bitmask of options controlling how an XPC connection is created. + */ typedef NSUInteger NSXPCConnectionOptions; + +/** + * NSXPCCoder provides object encoding and decoding helpers used by + * NSXPCConnection message transport. + */ +GS_EXPORT_CLASS +@interface NSXPCCoder : NSObject + +/** + * Archives an object graph into transport data. + */ ++ (NSData *) archivedDataWithRootObject: (id)object; + +/** + * Unarchives an object graph from transport data. + */ ++ (id) unarchivedObjectWithData: (NSData *)data; + +/** + * Unarchives an object graph from transport data and returns details + * via error if decoding fails. + */ ++ (id) unarchivedObjectWithData: (NSData *)data + error: (NSError **)error; + +/** + * Unarchives an object graph and verifies the top-level object belongs to + * one of the allowed classes when a class set is supplied. + */ ++ (id) unarchivedObjectWithData: (NSData *)data + allowedClasses: (NSSet *)allowedClasses + error: (NSError **)error; + +@end +/** + * Represents a bidirectional communication channel to an XPC service or + * listener endpoint. + */ GS_EXPORT_CLASS @interface NSXPCConnection : NSObject +{ +#if GS_EXPOSE(NSXPCConnection) + @private + NSString *_serviceName; + NSXPCListenerEndpoint *_endpoint; + NSXPCInterface *_exportedInterface; + id _exportedObject; + NSXPCInterface *_remoteObjectInterface; + id _remoteObjectProxy; + GSXPCInterruptionHandler _interruptionHandler; + GSXPCInvalidationHandler _invalidationHandler; + NSMutableDictionary *_pendingReplies; + NSLock *_pendingRepliesLock; + unsigned long long _nextMessageIdentifier; + NSXPCConnectionOptions _options; + BOOL _resumed; + BOOL _invalidated; + void *_xpcConnection; +#endif +} +/** + * Initializes a connection that targets an existing listener endpoint. + */ - (instancetype) initWithListenerEndpoint: (NSXPCListenerEndpoint *)endpoint; +/** + * Initializes a connection to a Mach service name using the supplied + * connection options. + */ - (instancetype) initWithMachServiceName: (NSString *)name options: (NSXPCConnectionOptions)options; +/** + * Initializes a connection to a named service. + */ - (instancetype) initWithServiceName:(NSString *)serviceName; +/** + * Returns the listener endpoint currently associated with the connection. + */ - (NSXPCListenerEndpoint *) endpoint; + +/** + * Sets the listener endpoint used by the connection. + */ - (void) setEndpoint: (NSXPCListenerEndpoint *) endpoint; +/** + * Returns the interface that describes objects exported by this process. + */ - (NSXPCInterface *) exportedInterface; -- (void) setExportInterface: (NSXPCInterface *)exportedInterface; + +/** + * Sets the interface that describes methods this process exports to the + * remote side. + */ +- (void) setExportedInterface: (NSXPCInterface *)exportedInterface; + +/** + * Returns the object exported to the remote side for incoming invocations. + */ +- (id) exportedObject; + +/** + * Sets the object exported to the remote side for incoming invocations. + */ +- (void) setExportedObject: (id)exportedObject; +/** + * Returns the interface that describes methods available on the remote + * object. + */ - (NSXPCInterface *) remoteObjectInterface; + +/** + * Sets the interface used to validate and encode messages sent to the remote + * object. + */ - (void) setRemoteObjectInterface: (NSXPCInterface *)remoteObjectInterface; +/** + * Returns an asynchronous proxy for invoking methods on the remote object. + */ - (id) remoteObjectProxy; + +/** + * Sets the underlying proxy object used for remote invocations. + */ - (void) setRemoteObjectProxy: (id)remoteObjectProxy; +/** + * Returns an asynchronous remote proxy that uses the supplied handler to + * report transport or encoding failures. + */ - (id) remoteObjectProxyWithErrorHandler:(GSXPCProxyErrorHandler)handler; +/** + * Returns the service name currently associated with the connection. + */ - (NSString *) serviceName; + +/** + * Sets the service name used when establishing the connection. + */ - (void) setServiceName: (NSString *)serviceName; +/** + * Returns a proxy that blocks for replies and reports failures through the + * supplied handler. + */ - (id) synchronousRemoteObjectProxyWithErrorHandler: (GSXPCProxyErrorHandler)handler; +/** + * Returns the block invoked when the connection is interrupted. + */ - (GSXPCInterruptionHandler) interruptionHandler; + +/** + * Sets the block invoked when the connection is interrupted. + */ - (void) setInterruptionHandler: (GSXPCInterruptionHandler)handler; +/** + * Returns the block invoked after the connection becomes invalid. + */ - (GSXPCInvalidationHandler) invalidationHandler; + +/** + * Sets the block invoked when the connection is invalidated permanently. + */ - (void) setInvalidationHandler: (GSXPCInvalidationHandler)handler; +/** + * Activates the connection so it can begin receiving and sending messages. + */ - (void) resume; +/** + * Temporarily stops message delivery on the connection. + */ - (void) suspend; +/** + * Permanently tears down the connection and releases underlying resources. + */ - (void) invalidate; +/** + * Returns the audit session identifier associated with the remote process. + */ - (NSUInteger) auditSessionIdentifier; + +/** + * Returns the process identifier of the connected peer. + */ - (pid_t) processIdentifier; + +/** + * Returns the effective user identifier of the connected peer. + */ - (uid_t) effectiveUserIdentifier; + +/** + * Returns the effective group identifier of the connected peer. + */ - (gid_t) effectiveGroupIdentifier; @end +/** + * Accepts incoming XPC connections for a service and dispatches them to + * a delegate for validation and configuration. + */ @interface NSXPCListener : NSObject +{ +#if GS_EXPOSE(NSXPCListener) + @private + id _delegate; + NSXPCListenerEndpoint *_endpoint; + NSString *_machServiceName; + BOOL _resumed; + BOOL _invalidated; +#endif +} +/** + * Returns the listener for the current XPC service process. + */ + (NSXPCListener *) serviceListener; +/** + * Creates and returns a listener with an endpoint that can be shared + * manually with another process. + */ + (NSXPCListener *) anonymousListener; +/** + * Initializes a listener that receives incoming connections for the named + * Mach service. + */ - (instancetype) initWithMachServiceName:(NSString *)name; +/** + * Returns the delegate that decides whether new incoming connections are + * accepted. + */ - (id ) delegate; + +/** + * Sets the delegate used to inspect and accept new incoming connections. + */ - (void) setDelegate: (id ) delegate; +/** + * Returns the endpoint representing this listener. + */ - (NSXPCListenerEndpoint *) endpoint; + +/** + * Sets the endpoint associated with this listener. + */ - (void) setEndpoint: (NSXPCListenerEndpoint *)endpoint; +/** + * Starts the listener so it can begin accepting incoming connections. + */ - (void) resume; +/** + * Temporarily stops the listener from accepting incoming connections. + */ - (void) suspend; +/** + * Permanently stops the listener and invalidates its endpoint. + */ - (void) invalidate; @end +/** + * Receives connection acceptance decisions for an [NSXPCListener]. + */ @protocol NSXPCListenerDelegate +/** + * Asks the delegate whether a newly arrived connection should be accepted. + */ - (BOOL) listener: (NSXPCListener *)listener shouldAcceptNewConnection: (NSXPCConnection *)newConnection; @end +/** + * Describes the allowed methods and object classes that can be exchanged + * over an XPC connection. + */ @interface NSXPCInterface : NSObject +{ +#if GS_EXPOSE(NSXPCInterface) + @private + Protocol *_protocol; + NSMutableDictionary *_classes; + NSMutableDictionary *_interfaces; +#endif +} +/** + * Creates an interface description from an Objective-C protocol. + */ + (NSXPCInterface *) interfaceWithProtocol: (Protocol *)protocol; +/** + * Returns the protocol used to define this interface. + */ - (Protocol *) protocol; + +/** + * Sets the protocol used to define this interface. + */ - (void) setProtocol: (Protocol *)protocol; +/** + * Records which classes are permitted for a specific selector argument or + * reply position. + */ - (void) setClasses: (NSSet *)classes forSelector: (SEL)sel argumentIndex: (NSUInteger)arg ofReply: (BOOL)ofReply; +/** + * Returns the classes currently permitted for a selector argument or reply + * position. + */ - (NSSet *) classesForSelector: (SEL)sel argumentIndex: (NSUInteger)arg ofReply: (BOOL)ofReply; +/** + * Associates a nested XPC interface with a selector argument or reply + * position for proxying complex object graphs. + */ - (void) setInterface: (NSXPCInterface *)ifc forSelector: (SEL)sel argumentIndex: (NSUInteger)arg ofReply: (BOOL)ofReply; +/** + * Returns the nested XPC interface associated with a selector argument or + * reply position. + */ - (NSXPCInterface *) interfaceForSelector: (SEL)sel argumentIndex: (NSUInteger)arg ofReply: (BOOL)ofReply; @end +/** + * Serializable object that represents a listener endpoint which can be passed + * to another process and used to create a connection back to a listener. + */ GS_EXPORT_CLASS @interface NSXPCListenerEndpoint : NSObject // NSSecureCoding +{ +#if GS_EXPOSE(NSXPCListenerEndpoint) + @private + NSString *_serviceName; +#endif +} @end #if defined(__cplusplus) diff --git a/Headers/GNUstepBase/GSConfig.h.in b/Headers/GNUstepBase/GSConfig.h.in index dba527211..ebe58f8ba 100644 --- a/Headers/GNUstepBase/GSConfig.h.in +++ b/Headers/GNUstepBase/GSConfig.h.in @@ -272,6 +272,7 @@ typedef struct { #define GS_USE_LIBCURL @HAVE_LIBCURL@ #define GS_USE_LIBDISPATCH @HAVE_LIBDISPATCH@ #define GS_USE_LIBDISPATCH_RUNLOOP @HAVE_LIBDISPATCH_RUNLOOP@ +#define GS_USE_LIBXPC @HAVE_LIBXPC@ #define GS_HAVE_FAST_ENUMERATION @OBJCFASTENUMERATION@ #define GS_HAVE_FAST_ENUMERATION_SETTER @OBJCSETFASTENUMERATION@ #define GS_HAVE_NSURLSESSION @GS_HAVE_NSURLSESSION@ diff --git a/Source/NSXPCConnection.m b/Source/NSXPCConnection.m index 680a91c40..e85c13441 100644 --- a/Source/NSXPCConnection.m +++ b/Source/NSXPCConnection.m @@ -21,139 +21,1722 @@ Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA. */ +#import "common.h" +#define EXPOSE_NSXPCConnection_IVARS 1 +#define EXPOSE_NSXPCListener_IVARS 1 +#define EXPOSE_NSXPCInterface_IVARS 1 +#define EXPOSE_NSXPCListenerEndpoint_IVARS 1 + #import "Foundation/NSXPCConnection.h" +#import "Foundation/NSDictionary.h" +#import "Foundation/NSArchiver.h" +#import "Foundation/NSArray.h" +#import "Foundation/NSInvocation.h" +#import "Foundation/NSMethodSignature.h" +#import "Foundation/NSProxy.h" +#import "Foundation/NSValue.h" +#import "Foundation/NSLock.h" + #import "GNUstepBase/NSObject+GNUstepBase.h" +#import "GNUstepBase/GSConfig.h" + +#import + +#if GS_USE_LIBXPC +#include +#endif + +@interface NSXPCConnection (Private) +- (void) _setupLibXPCConnectionIfPossible; +- (void) _initializeReplyTracking; +- (NSNumber *) _nextMessageIdentifierObject; +- (void) _registerPendingReply: (id)pending forMessageID: (NSNumber *)messageID; +- (id) _takePendingReplyForMessageID: (NSNumber *)messageID; +- (void) _failAllPendingRepliesWithError: (NSError *)error; +- (void) _handleIncomingXPCEvent: (void *)event; +- (void) _handleIncomingInvokeEvent: (void *)event; +- (void) _sendInvokeReplyForEvent: (void *)event + withReturnObject: (id)returnObject + returnData: (NSData *)returnData + returnType: (const char *)returnType + error: (NSError *)error; +- (void) _sendInvocation: (NSInvocation *)invocation + errorHandler: (GSXPCProxyErrorHandler)errorHandler + synchronous: (BOOL)synchronous; +- (NSMethodSignature *) _remoteMethodSignatureForSelector: (SEL)sel; +- (NSMethodSignature *) _exportedMethodSignatureForSelector: (SEL)sel; +@end + +@interface NSXPCListenerEndpoint (Private) +- (instancetype) initWithServiceName: (NSString *)serviceName; +- (NSString *) _serviceName; +@end + +static NSString * +GSXPCSignatureKey(SEL sel, NSUInteger arg, BOOL ofReply) +{ + return [NSString stringWithFormat: @"%s:%lu:%u", + (sel == 0 ? "" : sel_getName(sel)), + (unsigned long)arg, + (unsigned int)(ofReply ? 1 : 0)]; +} + +static const char * +GSXPCStrippedTypeEncoding(const char *type) +{ + while (*type == 'r' || *type == 'n' || *type == 'N' + || *type == 'o' || *type == 'O' || *type == 'R' + || *type == 'V') + { + type++; + } + return type; +} + +#define GS_ASSIGN_BLOCK(var, val) do { \ + if ((var) != (val)) { \ + if ((var) != 0) { Block_release(var); } \ + (var) = ((val) != 0) ? Block_copy(val) : 0; \ + } \ +} while (0) + +#define GS_DESTROY_BLOCK(var) do { \ + if ((var) != 0) { \ + Block_release(var); \ + (var) = 0; \ + } \ +} while (0) + +@interface GSXPCRemoteProxy : NSProxy +{ + NSXPCConnection *_connection; + GSXPCProxyErrorHandler _errorHandler; + BOOL _synchronous; +} + +- (instancetype) initWithConnection: (NSXPCConnection *)connection + errorHandler: (GSXPCProxyErrorHandler)errorHandler + synchronous: (BOOL)synchronous; + +@end + +static NSError * +GSXPCProxyError(NSString *description) +{ + NSDictionary *userInfo; + + userInfo = [NSDictionary dictionaryWithObject: description + forKey: NSLocalizedDescriptionKey]; + return [NSError errorWithDomain: @"NSXPCConnectionErrorDomain" + code: 1 + userInfo: userInfo]; +} + +static BOOL +GSXPCObjectMatchesAllowedClasses(id object, NSSet *allowedClasses) +{ + NSEnumerator *enumerator; + id candidate; + + if (object == nil || allowedClasses == nil || [allowedClasses count] == 0) + { + return YES; + } + + enumerator = [allowedClasses objectEnumerator]; + while ((candidate = [enumerator nextObject]) != nil) + { + if (class_isMetaClass(object_getClass(candidate)) + && [object isKindOfClass: candidate]) + { + return YES; + } + } + return NO; +} + +static BOOL +GSXPCValidateDecodedObjectGraph(id object, NSSet *allowedClasses) +{ + NSEnumerator *enumerator; + id child; + + if (object == nil || allowedClasses == nil || [allowedClasses count] == 0) + { + return YES; + } + + if (GSXPCObjectMatchesAllowedClasses(object, allowedClasses) == NO) + { + return NO; + } + + if ([object isKindOfClass: [NSArray class]]) + { + enumerator = [object objectEnumerator]; + while ((child = [enumerator nextObject]) != nil) + { + if (GSXPCValidateDecodedObjectGraph(child, allowedClasses) == NO) + { + return NO; + } + } + } + else if ([object isKindOfClass: [NSSet class]]) + { + enumerator = [object objectEnumerator]; + while ((child = [enumerator nextObject]) != nil) + { + if (GSXPCValidateDecodedObjectGraph(child, allowedClasses) == NO) + { + return NO; + } + } + } + else if ([object isKindOfClass: [NSDictionary class]]) + { + id key; + id value; + + enumerator = [object keyEnumerator]; + while ((key = [enumerator nextObject]) != nil) + { + value = [object objectForKey: key]; + if (GSXPCValidateDecodedObjectGraph(key, allowedClasses) == NO + || GSXPCValidateDecodedObjectGraph(value, allowedClasses) == NO) + { + return NO; + } + } + } + + return YES; +} + +static BOOL +GSXPCTypeSize(const char *type, NSUInteger *size) +{ + NSUInteger localSize; + + if (type == 0 || type[0] == '\0') + { + return NO; + } + localSize = 0; + NSGetSizeAndAlignment(type, &localSize, NULL); + if (localSize == 0) + { + return NO; + } + if (size != 0) + { + *size = localSize; + } + return YES; +} + +@implementation NSXPCCoder + ++ (NSData *) archivedDataWithRootObject: (id)object +{ + return [NSArchiver archivedDataWithRootObject: object]; +} + ++ (id) unarchivedObjectWithData: (NSData *)data +{ + return [self unarchivedObjectWithData: data error: NULL]; +} + ++ (id) unarchivedObjectWithData: (NSData *)data + error: (NSError **)error +{ + return [self unarchivedObjectWithData: data + allowedClasses: nil + error: error]; +} + ++ (id) unarchivedObjectWithData: (NSData *)data + allowedClasses: (NSSet *)allowedClasses + error: (NSError **)error +{ + id value; + + value = [NSUnarchiver unarchiveObjectWithData: data]; + if (value == nil && data != nil && [data length] > 0) + { + if (error != NULL) + { + NSDictionary *userInfo; + + userInfo = [NSDictionary dictionaryWithObject: + @"Unable to decode XPC payload data." + forKey: NSLocalizedDescriptionKey]; + *error = [NSError errorWithDomain: @"NSXPCConnectionErrorDomain" + code: 2 + userInfo: userInfo]; + } + return nil; + } + + if (GSXPCValidateDecodedObjectGraph(value, allowedClasses) == NO) + { + if (error != NULL) + { + NSDictionary *userInfo; + + userInfo = [NSDictionary dictionaryWithObject: + @"Decoded XPC object is not in the allowed class set." + forKey: NSLocalizedDescriptionKey]; + *error = [NSError errorWithDomain: @"NSXPCConnectionErrorDomain" + code: 3 + userInfo: userInfo]; + } + return nil; + } + return value; +} + +@end + +@interface GSXPCPendingReply : NSObject +{ + NSCondition *_condition; + BOOL _resolved; + NSSet *_allowedClasses; + id _returnObject; + NSData *_returnData; + NSString *_returnType; + NSError *_error; +} + +- (instancetype) initWithAllowedClasses: (NSSet *)allowedClasses; +- (NSSet *) allowedClasses; + +- (void) resolveWithReturnObject: (id)returnObject + returnData: (NSData *)returnData + returnType: (NSString *)returnType + error: (NSError *)error; +- (BOOL) waitForResolutionUntilDate: (NSDate *)limitDate + returnObject: (id *)returnObject + returnData: (NSData **)returnData + returnType: (NSString **)returnType + error: (NSError **)error; + +@end + +@implementation GSXPCPendingReply + +- (instancetype) initWithAllowedClasses: (NSSet *)allowedClasses +{ + if ((self = [super init]) != nil) + { + _condition = [NSCondition new]; + ASSIGN(_allowedClasses, allowedClasses); + _resolved = NO; + } + return self; +} + +- (void) dealloc +{ + DESTROY(_condition); + DESTROY(_allowedClasses); + DESTROY(_returnObject); + DESTROY(_returnData); + DESTROY(_returnType); + DESTROY(_error); + [super dealloc]; +} + +- (NSSet *) allowedClasses +{ + return _allowedClasses; +} + +- (void) resolveWithReturnObject: (id)returnObject + returnData: (NSData *)returnData + returnType: (NSString *)returnType + error: (NSError *)error +{ + [_condition lock]; + if (_resolved == NO) + { + ASSIGN(_returnObject, returnObject); + ASSIGN(_returnData, returnData); + ASSIGN(_returnType, returnType); + ASSIGN(_error, error); + _resolved = YES; + [_condition broadcast]; + } + [_condition unlock]; +} + +- (BOOL) waitForResolutionUntilDate: (NSDate *)limitDate + returnObject: (id *)returnObject + returnData: (NSData **)returnData + returnType: (NSString **)returnType + error: (NSError **)error +{ + BOOL resolved; + + [_condition lock]; + while (_resolved == NO) + { + if (limitDate == nil) + { + [_condition wait]; + } + else if ([_condition waitUntilDate: limitDate] == NO) + { + break; + } + } + resolved = _resolved; + if (resolved == YES) + { + if (returnObject != 0) + { + *returnObject = [[_returnObject retain] autorelease]; + } + if (returnData != 0) + { + *returnData = [[_returnData retain] autorelease]; + } + if (returnType != 0) + { + *returnType = [[_returnType retain] autorelease]; + } + if (error != 0) + { + *error = [[_error retain] autorelease]; + } + } + [_condition unlock]; + return resolved; +} + +@end + +@implementation GSXPCRemoteProxy + +- (instancetype) initWithConnection: (NSXPCConnection *)connection + errorHandler: (GSXPCProxyErrorHandler)errorHandler + synchronous: (BOOL)synchronous +{ + _connection = RETAIN(connection); + GS_ASSIGN_BLOCK(_errorHandler, errorHandler); + _synchronous = synchronous; + return self; +} + +- (void) dealloc +{ + DESTROY(_connection); + GS_DESTROY_BLOCK(_errorHandler); + [super dealloc]; +} + +- (NSMethodSignature *) methodSignatureForSelector: (SEL)sel +{ + NSMethodSignature *sig; + + sig = [_connection _remoteMethodSignatureForSelector: sel]; + if (sig == nil) + { + sig = [NSMethodSignature signatureWithObjCTypes: "v@:"]; + } + return sig; +} + +- (void) forwardInvocation: (NSInvocation *)invocation +{ + [_connection _sendInvocation: invocation + errorHandler: _errorHandler + synchronous: _synchronous]; +} + +- (BOOL) respondsToSelector: (SEL)aSelector +{ + return ([_connection _remoteMethodSignatureForSelector: aSelector] != nil); +} + +@end @implementation NSXPCConnection +- (instancetype) init +{ + return [self initWithServiceName: nil]; +} + +- (void) dealloc +{ + [self invalidate]; + DESTROY(_serviceName); + DESTROY(_endpoint); + DESTROY(_exportedInterface); + DESTROY(_exportedObject); + DESTROY(_remoteObjectInterface); + DESTROY(_remoteObjectProxy); + DESTROY(_pendingReplies); + DESTROY(_pendingRepliesLock); + GS_DESTROY_BLOCK(_interruptionHandler); + GS_DESTROY_BLOCK(_invalidationHandler); + [super dealloc]; +} + +- (void) _initializeReplyTracking +{ + if (_pendingRepliesLock == nil) + { + _pendingRepliesLock = [NSLock new]; + } + if (_pendingReplies == nil) + { + _pendingReplies = [NSMutableDictionary new]; + } + if (_nextMessageIdentifier == 0) + { + _nextMessageIdentifier = 1; + } +} + +- (NSNumber *) _nextMessageIdentifierObject +{ + NSNumber *value; + + [self _initializeReplyTracking]; + [_pendingRepliesLock lock]; + value = [NSNumber numberWithUnsignedLongLong: _nextMessageIdentifier++]; + [_pendingRepliesLock unlock]; + return value; +} + +- (void) _registerPendingReply: (id)pending forMessageID: (NSNumber *)messageID +{ + if (pending == nil || messageID == nil) + { + return; + } + [self _initializeReplyTracking]; + [_pendingRepliesLock lock]; + [_pendingReplies setObject: pending forKey: messageID]; + [_pendingRepliesLock unlock]; +} + +- (id) _takePendingReplyForMessageID: (NSNumber *)messageID +{ + id pending; + + if (messageID == nil) + { + return nil; + } + [self _initializeReplyTracking]; + [_pendingRepliesLock lock]; + pending = [[[_pendingReplies objectForKey: messageID] retain] autorelease]; + if (pending != nil) + { + [_pendingReplies removeObjectForKey: messageID]; + } + [_pendingRepliesLock unlock]; + return pending; +} + +- (void) _failAllPendingRepliesWithError: (NSError *)error +{ + NSArray *pending; + NSUInteger index; + + [self _initializeReplyTracking]; + [_pendingRepliesLock lock]; + pending = [[_pendingReplies allValues] retain]; + [_pendingReplies removeAllObjects]; + [_pendingRepliesLock unlock]; + + for (index = 0; index < [pending count]; index++) + { + GSXPCPendingReply *entry; + + entry = [pending objectAtIndex: index]; + [entry resolveWithReturnObject: nil + returnData: nil + returnType: nil + error: error]; + } + RELEASE(pending); +} + +- (void) _handleIncomingXPCEvent: (void *)eventPtr +{ +#if GS_USE_LIBXPC + xpc_object_t event; + + event = (xpc_object_t)eventPtr; + if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) + { + const char *kind; + + kind = xpc_dictionary_get_string(event, "gsxpc.kind"); + if (kind != NULL && strcmp(kind, "reply") == 0) + { + NSNumber *messageID; + GSXPCPendingReply *pending; + const char *errorText; + NSError *error; + id returnObject; + NSData *returnValueData; + NSString *returnValueType; + const char *returnTypeCString; + const void *returnData; + size_t returnDataLength; + + messageID = [NSNumber numberWithUnsignedLongLong: + xpc_dictionary_get_uint64(event, "gsxpc.messageID")]; + pending = [self _takePendingReplyForMessageID: messageID]; + if (pending == nil) + { + return; + } + + error = nil; + returnObject = nil; + returnValueData = nil; + returnValueType = nil; + errorText = xpc_dictionary_get_string(event, "gsxpc.error"); + if (errorText != NULL) + { + NSString *reason; + + reason = [NSString stringWithUTF8String: errorText]; + error = GSXPCProxyError(reason); + } + + returnTypeCString = xpc_dictionary_get_string(event, "gsxpc.returnType"); + if (returnTypeCString != NULL) + { + returnValueType = [NSString stringWithUTF8String: returnTypeCString]; + } + + returnData = xpc_dictionary_get_data(event, + "gsxpc.return", + &returnDataLength); + if (returnData != NULL && returnDataLength > 0 && error == nil) + { + NSData *payload; + + payload = [NSData dataWithBytes: returnData + length: (NSUInteger)returnDataLength]; + if (returnTypeCString != NULL + && GSXPCStrippedTypeEncoding(returnTypeCString)[0] != '@') + { + returnValueData = payload; + } + else + { + NSError *decodeError; + + decodeError = nil; + returnObject = [NSXPCCoder unarchivedObjectWithData: payload + allowedClasses: [pending allowedClasses] + error: &decodeError]; + if (decodeError != nil && error == nil) + { + error = decodeError; + } + } + } + + [pending resolveWithReturnObject: returnObject + returnData: returnValueData + returnType: returnValueType + error: error]; + return; + } + if (kind != NULL && strcmp(kind, "invoke") == 0) + { + [self _handleIncomingInvokeEvent: eventPtr]; + return; + } + } +#else + (void)eventPtr; +#endif +} + +- (void) _sendInvokeReplyForEvent: (void *)eventPtr + withReturnObject: (id)returnObject + returnData: (NSData *)returnData + returnType: (const char *)returnType + error: (NSError *)error +{ +#if GS_USE_LIBXPC + xpc_object_t event; + xpc_object_t reply; + NSData *encoded; + + if (_xpcConnection == 0) + { + return; + } + + event = (xpc_object_t)eventPtr; + reply = xpc_dictionary_create(NULL, NULL, 0); + xpc_dictionary_set_string(reply, "gsxpc.kind", "reply"); + xpc_dictionary_set_uint64(reply, "gsxpc.messageID", + xpc_dictionary_get_uint64(event, "gsxpc.messageID")); + + if (error != nil) + { + xpc_dictionary_set_string(reply, + "gsxpc.error", + [[error localizedDescription] UTF8String]); + } + else if (returnType != NULL) + { + xpc_dictionary_set_string(reply, "gsxpc.returnType", returnType); + if (GSXPCStrippedTypeEncoding(returnType)[0] == '@') + { + encoded = [NSXPCCoder archivedDataWithRootObject: returnObject]; + if (encoded != nil) + { + xpc_dictionary_set_data(reply, + "gsxpc.return", + [encoded bytes], + (size_t)[encoded length]); + } + else if (returnObject != nil) + { + xpc_dictionary_set_string(reply, + "gsxpc.error", + "Unable to encode return object."); + } + } + else if (returnData != nil) + { + xpc_dictionary_set_data(reply, + "gsxpc.return", + [returnData bytes], + (size_t)[returnData length]); + } + } + + xpc_connection_send_message((xpc_connection_t)_xpcConnection, reply); + xpc_release(reply); +#else + (void)eventPtr; + (void)returnObject; + (void)returnData; + (void)returnType; + (void)error; +#endif +} + +- (NSMethodSignature *) _exportedMethodSignatureForSelector: (SEL)sel +{ + Protocol *protocol; + struct objc_method_description desc; + + if (_exportedInterface != nil) + { + protocol = [_exportedInterface protocol]; + if (protocol != NULL) + { + desc = protocol_getMethodDescription(protocol, sel, YES, YES); + if (desc.name == NULL) + { + desc = protocol_getMethodDescription(protocol, sel, NO, YES); + } + if (desc.name != NULL && desc.types != NULL) + { + return [NSMethodSignature signatureWithObjCTypes: desc.types]; + } + return nil; + } + } + + if (_exportedObject != nil && [_exportedObject respondsToSelector: sel]) + { + return [_exportedObject methodSignatureForSelector: sel]; + } + return nil; +} + +- (void) _handleIncomingInvokeEvent: (void *)eventPtr +{ +#if GS_USE_LIBXPC + xpc_object_t event; + const char *selectorName; + SEL selector; + NSMethodSignature *signature; + NSInvocation *invocation; + NSUInteger argumentCount; + NSUInteger messageArgumentCount; + NSUInteger index; + NSError *error; + BOOL expectsReply; + id returnObject; + NSData *returnData; + const char *returnType; + + event = (xpc_object_t)eventPtr; + expectsReply = xpc_dictionary_get_bool(event, "gsxpc.expectsReply") ? YES : NO; + error = nil; + returnObject = nil; + returnData = nil; + + if (_exportedObject == nil) + { + error = GSXPCProxyError(@"No exported object is configured."); + if (expectsReply == YES) + { + [self _sendInvokeReplyForEvent: eventPtr + withReturnObject: nil + returnData: nil + returnType: nil + error: error]; + } + return; + } + + selectorName = xpc_dictionary_get_string(event, "gsxpc.selector"); + if (selectorName == NULL) + { + error = GSXPCProxyError(@"Missing selector in incoming invoke message."); + if (expectsReply == YES) + { + [self _sendInvokeReplyForEvent: eventPtr + withReturnObject: nil + returnData: nil + returnType: nil + error: error]; + } + return; + } + + selector = sel_getUid(selectorName); + signature = [self _exportedMethodSignatureForSelector: selector]; + if (signature == nil) + { + NSString *reason; + + reason = [NSString stringWithFormat: + @"Exported interface does not allow selector '%s'.", + selectorName]; + error = GSXPCProxyError(reason); + if (expectsReply == YES) + { + [self _sendInvokeReplyForEvent: eventPtr + withReturnObject: nil + returnData: nil + returnType: nil + error: error]; + } + return; + } + + if ([_exportedObject respondsToSelector: selector] == NO) + { + NSString *reason; + + reason = [NSString stringWithFormat: + @"Exported object does not respond to selector '%s'.", + selectorName]; + error = GSXPCProxyError(reason); + if (expectsReply == YES) + { + [self _sendInvokeReplyForEvent: eventPtr + withReturnObject: nil + returnData: nil + returnType: nil + error: error]; + } + return; + } + + argumentCount = [signature numberOfArguments] - 2; + messageArgumentCount = (NSUInteger)xpc_dictionary_get_uint64(event, + "gsxpc.argumentCount"); + if (argumentCount != messageArgumentCount) + { + error = GSXPCProxyError(@"Incoming argument count does not match exported selector."); + if (expectsReply == YES) + { + [self _sendInvokeReplyForEvent: eventPtr + withReturnObject: nil + returnData: nil + returnType: nil + error: error]; + } + return; + } + + returnType = GSXPCStrippedTypeEncoding([signature methodReturnType]); + if (returnType[0] != 'v' && GSXPCTypeSize(returnType, NULL) == NO) + { + error = GSXPCProxyError(@"Unsupported return type for exported method."); + if (expectsReply == YES) + { + [self _sendInvokeReplyForEvent: eventPtr + withReturnObject: nil + returnData: nil + returnType: nil + error: error]; + } + return; + } + + invocation = [NSInvocation invocationWithMethodSignature: signature]; + [invocation setTarget: _exportedObject]; + [invocation setSelector: selector]; + + for (index = 0; index < argumentCount; index++) + { + NSString *key; + const void *argData; + size_t argDataLength; + const char *argType; + const char *argTypeFromMessage; + NSString *typeKey; + BOOL hasNilObject; + NSString *nilKey; + + argType = GSXPCStrippedTypeEncoding([signature getArgumentTypeAtIndex: index + 2]); + typeKey = [NSString stringWithFormat: @"gsxpc.argtype.%lu", (unsigned long)index]; + argTypeFromMessage = xpc_dictionary_get_string(event, [typeKey UTF8String]); + if (argTypeFromMessage != NULL) + { + const char *normalized; + + normalized = GSXPCStrippedTypeEncoding(argTypeFromMessage); + if (normalized[0] != argType[0]) + { + error = GSXPCProxyError(@"Incoming argument type does not match exported selector signature."); + if (expectsReply == YES) + { + [self _sendInvokeReplyForEvent: eventPtr + withReturnObject: nil + returnData: nil + returnType: nil + error: error]; + } + return; + } + } + + key = [NSString stringWithFormat: @"gsxpc.arg.%lu", (unsigned long)index]; + argData = xpc_dictionary_get_data(event, [key UTF8String], &argDataLength); + nilKey = [NSString stringWithFormat: @"gsxpc.argnil.%lu", (unsigned long)index]; + hasNilObject = xpc_dictionary_get_bool(event, [nilKey UTF8String]) ? YES : NO; + + if (argType[0] == '@') + { + id decoded; + + decoded = nil; + if (hasNilObject == NO && argData != NULL && argDataLength > 0) + { + NSData *encoded; + NSSet *allowedClasses; + NSError *decodeError; + + encoded = [NSData dataWithBytes: argData length: (NSUInteger)argDataLength]; + allowedClasses = nil; + if (_exportedInterface != nil) + { + allowedClasses = [_exportedInterface classesForSelector: selector + argumentIndex: index + ofReply: NO]; + } + decodeError = nil; + decoded = [NSXPCCoder unarchivedObjectWithData: encoded + allowedClasses: allowedClasses + error: &decodeError]; + if (decodeError != nil) + { + if (expectsReply == YES) + { + [self _sendInvokeReplyForEvent: eventPtr + withReturnObject: nil + returnData: nil + returnType: nil + error: decodeError]; + } + return; + } + } + [invocation setArgument: &decoded atIndex: index + 2]; + } + else + { + NSUInteger expectedSize; + void *buffer; + + if (GSXPCTypeSize(argType, &expectedSize) == NO) + { + error = GSXPCProxyError(@"Unsupported argument type in exported selector."); + if (expectsReply == YES) + { + [self _sendInvokeReplyForEvent: eventPtr + withReturnObject: nil + returnData: nil + returnType: nil + error: error]; + } + return; + } + if (argData == NULL || argDataLength != expectedSize) + { + error = GSXPCProxyError(@"Incoming argument payload size does not match selector signature."); + if (expectsReply == YES) + { + [self _sendInvokeReplyForEvent: eventPtr + withReturnObject: nil + returnData: nil + returnType: nil + error: error]; + } + return; + } + + buffer = malloc(expectedSize); + if (buffer == NULL) + { + error = GSXPCProxyError(@"Unable to allocate memory for incoming argument decode."); + if (expectsReply == YES) + { + [self _sendInvokeReplyForEvent: eventPtr + withReturnObject: nil + returnData: nil + returnType: nil + error: error]; + } + return; + } + memcpy(buffer, argData, expectedSize); + [invocation setArgument: buffer atIndex: index + 2]; + free(buffer); + } + } + + @try + { + [invocation invoke]; + if (returnType[0] == '@') + { + id returned; + + returned = nil; + [invocation getReturnValue: &returned]; + returnObject = returned; + } + else if (returnType[0] != 'v') + { + NSUInteger returnSize; + + if (GSXPCTypeSize(returnType, &returnSize) == YES) + { + void *buffer; + + buffer = malloc(returnSize); + if (buffer != NULL) + { + [invocation getReturnValue: buffer]; + returnData = [NSData dataWithBytes: buffer + length: returnSize]; + free(buffer); + } + else + { + error = GSXPCProxyError(@"Unable to allocate memory for return value encoding."); + } + } + else + { + error = GSXPCProxyError(@"Unsupported return type for exported method."); + } + } + } + @catch (id exception) + { + NSString *reason; + + reason = [NSString stringWithFormat: + @"Exception while invoking exported selector '%s': %@", + selectorName, + [exception description]]; + error = GSXPCProxyError(reason); + } + + if (expectsReply == YES) + { + [self _sendInvokeReplyForEvent: eventPtr + withReturnObject: returnObject + returnData: returnData + returnType: returnType + error: error]; + } +#else + (void)eventPtr; +#endif +} + +- (void) _setupLibXPCConnectionIfPossible +{ +#if GS_USE_LIBXPC + uint64_t flags = 0; + NSXPCConnection *connection = self; + + if (_xpcConnection != 0 || _serviceName == nil || _invalidated == YES) + { + return; + } +#ifdef XPC_CONNECTION_MACH_SERVICE_PRIVILEGED + if ((_options & NSXPCConnectionPrivileged) == NSXPCConnectionPrivileged) + { + flags |= XPC_CONNECTION_MACH_SERVICE_PRIVILEGED; + } +#endif + _xpcConnection = (void *)xpc_connection_create_mach_service( + [_serviceName UTF8String], NULL, flags); + if (_xpcConnection == 0) + { + return; + } + + xpc_connection_set_event_handler((xpc_connection_t)_xpcConnection, + ^(xpc_object_t event) { + [connection _handleIncomingXPCEvent: (void *)event]; + if (event == XPC_ERROR_CONNECTION_INTERRUPTED) + { + if (connection->_interruptionHandler != NULL) + { + CALL_BLOCK_NO_ARGS(connection->_interruptionHandler); + } + } + else if (event == XPC_ERROR_CONNECTION_INVALID) + { + connection->_invalidated = YES; + [connection _failAllPendingRepliesWithError: + GSXPCProxyError(@"Connection was invalidated.")]; + if (connection->_invalidationHandler != NULL) + { + CALL_BLOCK_NO_ARGS(connection->_invalidationHandler); + } + } + }); + + if (_resumed == YES) + { + xpc_connection_resume((xpc_connection_t)_xpcConnection); + } +#endif +} + - (instancetype) initWithServiceName:(NSString *)serviceName { - return [self notImplemented: _cmd]; + return [self initWithMachServiceName: serviceName options: 0]; } - (NSString *) serviceName { - return [self notImplemented: _cmd]; + return _serviceName; } - (void) setServiceName: (NSString *)serviceName { - [self notImplemented: _cmd]; + ASSIGNCOPY(_serviceName, serviceName); + [self _setupLibXPCConnectionIfPossible]; } - (instancetype) initWithMachServiceName: (NSString *)name options: (NSXPCConnectionOptions)options { - return [self notImplemented: _cmd]; + if ((self = [super init]) != nil) + { + _options = options; + [self _initializeReplyTracking]; + [self setServiceName: name]; + } + return self; } - (instancetype) initWithListenerEndpoint: (NSXPCListenerEndpoint *)endpoint { - return [self notImplemented: _cmd]; + if ((self = [super init]) != nil) + { + NSString *serviceName = nil; + + ASSIGN(_endpoint, endpoint); + if ([_endpoint respondsToSelector: @selector(_serviceName)]) + { + serviceName = [_endpoint performSelector: @selector(_serviceName)]; + } + if (serviceName != nil) + { + [self setServiceName: serviceName]; + } + [self _initializeReplyTracking]; + } + return self; } - (NSXPCListenerEndpoint *) endpoint { - return [self notImplemented: _cmd]; + return _endpoint; } - (void) setEndpoint: (NSXPCListenerEndpoint *) endpoint { - [self notImplemented: _cmd]; + ASSIGN(_endpoint, endpoint); } - (NSXPCInterface *) exportedInterface { - return [self notImplemented: _cmd]; + return _exportedInterface; +} + +- (void) setExportedInterface: (NSXPCInterface *)exportedInterface +{ + ASSIGN(_exportedInterface, exportedInterface); } -- (void) setExportInterface: (NSXPCInterface *)exportedInterface +- (id) exportedObject { - [self notImplemented: _cmd]; + return _exportedObject; +} + +- (void) setExportedObject: (id)exportedObject +{ + ASSIGN(_exportedObject, exportedObject); } - (NSXPCInterface *) remoteObjectInterface { - return [self notImplemented: _cmd]; + return _remoteObjectInterface; } - (void) setRemoteObjectInterface: (NSXPCInterface *)remoteObjectInterface { - [self notImplemented: _cmd]; + ASSIGN(_remoteObjectInterface, remoteObjectInterface); } - (id) remoteObjectProxy { - return [self notImplemented: _cmd]; + if (_remoteObjectProxy == nil) + { + id proxy = [[GSXPCRemoteProxy alloc] initWithConnection: self + errorHandler: NULL + synchronous: NO]; + + [self setRemoteObjectProxy: proxy]; + RELEASE(proxy); + } + return _remoteObjectProxy; } - (void) setRemoteObjectProxy: (id)remoteObjectProxy { - [self notImplemented: _cmd]; + ASSIGN(_remoteObjectProxy, remoteObjectProxy); } - (id) remoteObjectProxyWithErrorHandler:(GSXPCProxyErrorHandler)handler { - return [self notImplemented: _cmd]; + if (handler == NULL) + { + return [self remoteObjectProxy]; + } + return AUTORELEASE([[GSXPCRemoteProxy alloc] initWithConnection: self + errorHandler: handler + synchronous: NO]); } - (id) synchronousRemoteObjectProxyWithErrorHandler: (GSXPCProxyErrorHandler)handler { - return [self notImplemented: _cmd]; + return AUTORELEASE([[GSXPCRemoteProxy alloc] initWithConnection: self + errorHandler: handler + synchronous: YES]); +} + +- (NSMethodSignature *) _remoteMethodSignatureForSelector: (SEL)sel +{ + Protocol *protocol; + struct objc_method_description desc; + + if (_remoteObjectInterface == nil) + { + return nil; + } + + protocol = [_remoteObjectInterface protocol]; + if (protocol == NULL) + { + return nil; + } + + desc = protocol_getMethodDescription(protocol, sel, YES, YES); + if (desc.name == NULL) + { + desc = protocol_getMethodDescription(protocol, sel, NO, YES); + } + if (desc.name == NULL || desc.types == NULL) + { + return nil; + } + + return [NSMethodSignature signatureWithObjCTypes: desc.types]; +} + +- (void) _sendInvocation: (NSInvocation *)invocation + errorHandler: (GSXPCProxyErrorHandler)errorHandler + synchronous: (BOOL)synchronous +{ + NSMethodSignature *signature; + const char *returnType; + + if (invocation == nil) + { + if (errorHandler != NULL) + { + CALL_BLOCK(errorHandler, GSXPCProxyError(@"Missing invocation.")); + } + return; + } + + signature = [invocation methodSignature]; + if (signature == nil) + { + if (errorHandler != NULL) + { + CALL_BLOCK(errorHandler, GSXPCProxyError(@"Missing method signature.")); + } + return; + } + + returnType = GSXPCStrippedTypeEncoding([signature methodReturnType]); + if (returnType[0] != 'v' && GSXPCTypeSize(returnType, NULL) == NO) + { + if (errorHandler != NULL) + { + CALL_BLOCK(errorHandler, + GSXPCProxyError(@"Unsupported method return type.")); + } + return; + } + + if (synchronous == NO && returnType[0] != 'v') + { + if (errorHandler != NULL) + { + CALL_BLOCK(errorHandler, + GSXPCProxyError(@"Non-void methods require a synchronous proxy.")); + } + return; + } + + [self _setupLibXPCConnectionIfPossible]; + + if (_invalidated == YES) + { + if (errorHandler != NULL) + { + CALL_BLOCK(errorHandler, GSXPCProxyError(@"Connection is invalidated.")); + } + return; + } + +#if GS_USE_LIBXPC + BOOL expectsReply; + NSNumber *messageID; + GSXPCPendingReply *pending; + id returnObject; + NSData *returnValueData; + NSString *returnValueType; + NSError *replyError; + + if (_xpcConnection == 0) + { + if (errorHandler != NULL) + { + CALL_BLOCK(errorHandler, + GSXPCProxyError(@"XPC transport is unavailable for this connection.")); + } + return; + } + + expectsReply = (synchronous == YES || returnType[0] != 'v'); + returnObject = nil; + returnValueData = nil; + returnValueType = nil; + replyError = nil; + messageID = [self _nextMessageIdentifierObject]; + pending = nil; + if (expectsReply == YES) + { + NSSet *allowedClasses; + + allowedClasses = nil; + if (returnType[0] == '@' && _remoteObjectInterface != nil) + { + allowedClasses = [_remoteObjectInterface classesForSelector: [invocation selector] + argumentIndex: 0 + ofReply: YES]; + } + pending = [[GSXPCPendingReply alloc] initWithAllowedClasses: allowedClasses]; + [self _registerPendingReply: pending forMessageID: messageID]; + } + + { + xpc_object_t message; + const char *selectorName; + NSUInteger count; + NSUInteger index; + + message = xpc_dictionary_create(NULL, NULL, 0); + selectorName = sel_getName([invocation selector]); + xpc_dictionary_set_string(message, "gsxpc.kind", "invoke"); + xpc_dictionary_set_uint64(message, "gsxpc.messageID", + [messageID unsignedLongLongValue]); + xpc_dictionary_set_string(message, "gsxpc.selector", selectorName); + xpc_dictionary_set_bool(message, "gsxpc.expectsReply", + expectsReply ? true : false); + + count = [signature numberOfArguments]; + xpc_dictionary_set_uint64(message, "gsxpc.argumentCount", (uint64_t)(count - 2)); + + for (index = 2; index < count; index++) + { + const char *argType; + NSString *typeKey; + NSString *nilKey; + NSString *dataKey; + + argType = GSXPCStrippedTypeEncoding([signature getArgumentTypeAtIndex: index]); + typeKey = [NSString stringWithFormat: @"gsxpc.argtype.%lu", + (unsigned long)(index - 2)]; + xpc_dictionary_set_string(message, [typeKey UTF8String], argType); + dataKey = [NSString stringWithFormat: @"gsxpc.arg.%lu", + (unsigned long)(index - 2)]; + nilKey = [NSString stringWithFormat: @"gsxpc.argnil.%lu", + (unsigned long)(index - 2)]; + + if (argType[0] == '@') + { + id value; + NSData *encoded; + + value = nil; + [invocation getArgument: &value atIndex: index]; + if (value == nil) + { + xpc_dictionary_set_bool(message, [nilKey UTF8String], true); + continue; + } + + encoded = [NSXPCCoder archivedDataWithRootObject: value]; + if (encoded == nil) + { + if (errorHandler != NULL) + { + NSString *reason; + + reason = [NSString stringWithFormat: + @"Unable to encode object argument %lu.", + (unsigned long)(index - 2)]; + CALL_BLOCK(errorHandler, GSXPCProxyError(reason)); + } + if (pending != nil) + { + [self _takePendingReplyForMessageID: messageID]; + RELEASE(pending); + } + xpc_release(message); + return; + } + + xpc_dictionary_set_data(message, + [dataKey UTF8String], + [encoded bytes], + (size_t)[encoded length]); + } + else + { + NSUInteger argSize; + void *buffer; + NSData *encoded; + + if (GSXPCTypeSize(argType, &argSize) == NO) + { + if (errorHandler != NULL) + { + NSString *reason; + + reason = [NSString stringWithFormat: + @"Unsupported argument type at index %lu.", + (unsigned long)(index - 2)]; + CALL_BLOCK(errorHandler, GSXPCProxyError(reason)); + } + if (pending != nil) + { + [self _takePendingReplyForMessageID: messageID]; + RELEASE(pending); + } + xpc_release(message); + return; + } + + buffer = malloc(argSize); + if (buffer == NULL) + { + if (errorHandler != NULL) + { + CALL_BLOCK(errorHandler, + GSXPCProxyError(@"Unable to allocate memory for argument encoding.")); + } + if (pending != nil) + { + [self _takePendingReplyForMessageID: messageID]; + RELEASE(pending); + } + xpc_release(message); + return; + } + [invocation getArgument: buffer atIndex: index]; + encoded = [NSData dataWithBytes: buffer length: argSize]; + free(buffer); + + if (encoded == nil) + { + if (errorHandler != NULL) + { + CALL_BLOCK(errorHandler, + GSXPCProxyError(@"Unable to encode non-object argument payload.")); + } + if (pending != nil) + { + [self _takePendingReplyForMessageID: messageID]; + RELEASE(pending); + } + xpc_release(message); + return; + } + + xpc_dictionary_set_data(message, + [dataKey UTF8String], + [encoded bytes], + (size_t)[encoded length]); + } + } + + xpc_connection_send_message((xpc_connection_t)_xpcConnection, message); + xpc_release(message); + } + + if (expectsReply == YES) + { + BOOL didResolve; + + didResolve = [pending waitForResolutionUntilDate: + [NSDate dateWithTimeIntervalSinceNow: 30.0] + returnObject: &returnObject + returnData: &returnValueData + returnType: &returnValueType + error: &replyError]; + if (didResolve == NO) + { + [self _takePendingReplyForMessageID: messageID]; + replyError = GSXPCProxyError(@"Timed out waiting for reply."); + } + + if (replyError != nil) + { + if (errorHandler != NULL) + { + CALL_BLOCK(errorHandler, replyError); + } + RELEASE(pending); + return; + } + + if (synchronous == YES && returnType[0] == '@') + { + id returned; + + returned = returnObject; + [invocation setReturnValue: &returned]; + } + else if (synchronous == YES && returnType[0] != 'v') + { + const char *resolvedType; + NSUInteger expectedSize; + + resolvedType = returnType; + if (returnValueType != nil) + { + resolvedType = GSXPCStrippedTypeEncoding([returnValueType UTF8String]); + } + + if (resolvedType[0] != returnType[0]) + { + if (errorHandler != NULL) + { + CALL_BLOCK(errorHandler, + GSXPCProxyError(@"Reply type does not match method return type.")); + } + RELEASE(pending); + return; + } + + if (GSXPCTypeSize(returnType, &expectedSize) == NO) + { + if (errorHandler != NULL) + { + CALL_BLOCK(errorHandler, + GSXPCProxyError(@"Unsupported method return type.")); + } + RELEASE(pending); + return; + } + + if (returnValueData == nil) + { + void *empty; + + empty = calloc(1, expectedSize); + if (empty != NULL) + { + [invocation setReturnValue: empty]; + free(empty); + } + } + else if ([returnValueData length] != expectedSize) + { + if (errorHandler != NULL) + { + CALL_BLOCK(errorHandler, + GSXPCProxyError(@"Reply payload size does not match method return type.")); + } + RELEASE(pending); + return; + } + else + { + [invocation setReturnValue: (void *)[returnValueData bytes]]; + } + } + + RELEASE(pending); + return; + } +#else + if (errorHandler != NULL) + { + CALL_BLOCK(errorHandler, + GSXPCProxyError(@"This build does not include libxpc support.")); + } +#endif } - (GSXPCInterruptionHandler) interruptionHandler { - return NULL; + return _interruptionHandler; } - (void) setInterruptionHandler: (GSXPCInterruptionHandler)handler { - [self notImplemented: _cmd]; + GS_ASSIGN_BLOCK(_interruptionHandler, handler); } - (GSXPCInvalidationHandler) invalidationHandler { - return NULL; + return _invalidationHandler; } - (void) setInvalidationHandler: (GSXPCInvalidationHandler)handler { - [self notImplemented: _cmd]; + GS_ASSIGN_BLOCK(_invalidationHandler, handler); } - (void) resume { - [self notImplemented: _cmd]; + _resumed = YES; + [self _setupLibXPCConnectionIfPossible]; +#if GS_USE_LIBXPC + if (_xpcConnection != 0) + { + xpc_connection_resume((xpc_connection_t)_xpcConnection); + } +#endif } - (void) suspend { - [self notImplemented: _cmd]; + _resumed = NO; +#if GS_USE_LIBXPC + if (_xpcConnection != 0) + { + xpc_connection_suspend((xpc_connection_t)_xpcConnection); + } +#endif } - (void) invalidate { - [self notImplemented: _cmd]; + BOOL wasInvalidated = _invalidated; + + _invalidated = YES; +#if GS_USE_LIBXPC + if (_xpcConnection != 0) + { + xpc_connection_cancel((xpc_connection_t)_xpcConnection); + xpc_release((xpc_connection_t)_xpcConnection); + _xpcConnection = 0; + } +#endif + if (wasInvalidated == NO && _invalidationHandler != NULL) + { + CALL_BLOCK_NO_ARGS(_invalidationHandler); + } } - (NSUInteger) auditSessionIdentifier { - return (NSUInteger)[self notImplemented: _cmd]; + return 0; } + - (pid_t) processIdentifier { - return (pid_t)(uintptr_t)[self notImplemented: _cmd]; +#if GS_USE_LIBXPC + if (_xpcConnection != 0) + { + return xpc_connection_get_pid((xpc_connection_t)_xpcConnection); + } +#endif + return 0; } + - (uid_t) effectiveUserIdentifier { - return (uid_t)(uintptr_t)[self notImplemented: _cmd]; +#if GS_USE_LIBXPC + if (_xpcConnection != 0) + { + return xpc_connection_get_euid((xpc_connection_t)_xpcConnection); + } +#endif + return (uid_t)0; } + - (gid_t) effectiveGroupIdentifier { - return (gid_t)(uintptr_t)[self notImplemented: _cmd]; +#if GS_USE_LIBXPC + if (_xpcConnection != 0) + { + return xpc_connection_get_egid((xpc_connection_t)_xpcConnection); + } +#endif + return (gid_t)0; } @end @@ -161,52 +1744,80 @@ @implementation NSXPCListener + (NSXPCListener *) serviceListener { - return [self notImplemented: _cmd]; + return AUTORELEASE([[self alloc] initWithMachServiceName: nil]); } + (NSXPCListener *) anonymousListener { - return [self notImplemented: _cmd]; + return AUTORELEASE([[self alloc] initWithMachServiceName: nil]); } - (instancetype) initWithMachServiceName:(NSString *)name { - return [self notImplemented: _cmd]; + if ((self = [super init]) != nil) + { + NSXPCListenerEndpoint *ep; + + ASSIGNCOPY(_machServiceName, name); + ep = [[NSXPCListenerEndpoint alloc] initWithServiceName: _machServiceName]; + ASSIGN(_endpoint, ep); + RELEASE(ep); + _resumed = NO; + _invalidated = NO; + } + return self; +} + +- (instancetype) init +{ + return [self initWithMachServiceName: nil]; +} + +- (void) dealloc +{ + DESTROY(_delegate); + DESTROY(_endpoint); + DESTROY(_machServiceName); + [super dealloc]; } - (id ) delegate { - return [self notImplemented: _cmd]; + return _delegate; } - (void) setDelegate: (id ) delegate { - [self notImplemented: _cmd]; + _delegate = delegate; // weak reference... } - (NSXPCListenerEndpoint *) endpoint { - return [self notImplemented: _cmd]; + return _endpoint; } - (void) setEndpoint: (NSXPCListenerEndpoint *)endpoint { - [self notImplemented: _cmd]; + ASSIGN(_endpoint, endpoint); } - (void) resume { - [self notImplemented: _cmd]; + if (_invalidated == NO) + { + _resumed = YES; + } } - (void) suspend { - [self notImplemented: _cmd]; + _resumed = NO; } - (void) invalidate { - [self notImplemented: _cmd]; + _resumed = NO; + _invalidated = YES; } @end @@ -215,17 +1826,38 @@ @implementation NSXPCInterface + (NSXPCInterface *) interfaceWithProtocol: (Protocol *)protocol { - return [self notImplemented: _cmd]; + NSXPCInterface *ifc; + + ifc = AUTORELEASE([[self alloc] init]); + [ifc setProtocol: protocol]; + return ifc; +} + +- (instancetype) init +{ + if ((self = [super init]) != nil) + { + _classes = [NSMutableDictionary new]; + _interfaces = [NSMutableDictionary new]; + } + return self; +} + +- (void) dealloc +{ + DESTROY(_classes); + DESTROY(_interfaces); + [super dealloc]; } - (Protocol *) protocol { - return [self notImplemented: _cmd]; + return _protocol; } - (void) setProtocol: (Protocol *)protocol { - [self notImplemented: _cmd]; + _protocol = protocol; } - (void) setClasses: (NSSet *)classes @@ -233,14 +1865,25 @@ - (void) setClasses: (NSSet *)classes argumentIndex: (NSUInteger)arg ofReply: (BOOL)ofReply { - [self notImplemented: _cmd]; + NSString *key = GSXPCSignatureKey(sel, arg, ofReply); + + if (classes == nil) + { + [_classes removeObjectForKey: key]; + } + else + { + [_classes setObject: [[classes copy] autorelease] forKey: key]; + } } - (NSSet *) classesForSelector: (SEL)sel argumentIndex: (NSUInteger)arg ofReply: (BOOL)ofReply { - return [self notImplemented: _cmd]; + NSString *key = GSXPCSignatureKey(sel, arg, ofReply); + + return [_classes objectForKey: key]; } - (void) setInterface: (NSXPCInterface *)ifc @@ -248,29 +1891,85 @@ - (void) setInterface: (NSXPCInterface *)ifc argumentIndex: (NSUInteger)arg ofReply: (BOOL)ofReply { - [self notImplemented: _cmd]; + NSString *key = GSXPCSignatureKey(sel, arg, ofReply); + + if (ifc == nil) + { + [_interfaces removeObjectForKey: key]; + } + else + { + [_interfaces setObject: ifc forKey: key]; + } } - (NSXPCInterface *) interfaceForSelector: (SEL)sel argumentIndex: (NSUInteger)arg ofReply: (BOOL)ofReply { - return [self notImplemented: _cmd]; + NSString *key = GSXPCSignatureKey(sel, arg, ofReply); + + return [_interfaces objectForKey: key]; } @end @implementation NSXPCListenerEndpoint +- (instancetype) initWithServiceName: (NSString *)serviceName +{ + if ((self = [super init]) != nil) + { + ASSIGNCOPY(_serviceName, serviceName); + } + return self; +} + +- (instancetype) init +{ + return [self initWithServiceName: nil]; +} + +- (void) dealloc +{ + DESTROY(_serviceName); + [super dealloc]; +} + +- (NSString *) _serviceName +{ + return _serviceName; +} + - (instancetype) initWithCoder: (NSCoder *)coder { - return [self notImplemented: _cmd]; + NSString *serviceName = nil; + + if ((self = [super init]) != nil) + { + if ([coder respondsToSelector: @selector(decodeObjectForKey:)]) + { + serviceName = [coder decodeObjectForKey: @"GSServiceName"]; + } + else + { + serviceName = [coder decodeObject]; + } + ASSIGNCOPY(_serviceName, serviceName); + } + return self; } - (void) encodeWithCoder: (NSCoder *)coder { - [self notImplemented: _cmd]; + if ([coder respondsToSelector: @selector(encodeObject:forKey:)]) + { + [coder encodeObject: _serviceName forKey: @"GSServiceName"]; + } + else + { + [coder encodeObject: _serviceName]; + } } @end - diff --git a/config.mak.in b/config.mak.in index 18217b1d5..0e459b7af 100644 --- a/config.mak.in +++ b/config.mak.in @@ -42,6 +42,7 @@ GNUSTEP_BASE_HAVE_ICU=@HAVE_ICU@ GNUSTEP_BASE_HAVE_LIBCURL=@HAVE_LIBCURL@ GNUSTEP_BASE_HAVE_LIBDISPATCH=@HAVE_LIBDISPATCH@ GNUSTEP_BASE_HAVE_LIBDISPATCH_RUNLOOP=@HAVE_LIBDISPATCH_RUNLOOP@ +GNUSTEP_BASE_HAVE_LIBXPC=@HAVE_LIBXPC@ GNUSTEP_BASE_HAVE_LIBXML=@HAVE_LIBXML@ GNUSTEP_BASE_HAVE_MDNS=@HAVE_MDNS@ GNUSTEP_BASE_WITH_NSURLSESSION=@GS_HAVE_NSURLSESSION@ diff --git a/configure b/configure index 59cd1e646..20dcc748e 100755 --- a/configure +++ b/configure @@ -664,6 +664,7 @@ USE_GMP GS_HAVE_NSURLSESSION HAVE_LIBCURL HAVE_NEWKVO +HAVE_LIBXPC HAVE_LIBDISPATCH_RUNLOOP HAVE_LIBDISPATCH HAVE_ICU @@ -867,6 +868,7 @@ enable_icu enable_libdispatch with_dispatch_include with_dispatch_library +enable_libxpc enable_newkvo with_curl enable_nsurlsession @@ -1564,6 +1566,8 @@ Optional Features: --disable-zeroconf Disable NSNetServices support --disable-icu Disable International Components for Unicode --disable-libdispatch Disable dispatching blocks via libdispatch + --disable-libxpc Disable use of libxpc library for XPC inter-process + communication --disable-newkvo Disable new KVO implementation --disable-nsurlsession Disable support for NSURLSession --enable-setuid-gdomap Enable installing gdomap as a setuid executable. By @@ -14980,6 +14984,87 @@ fi +#-------------------------------------------------------------------- +# Check for libxpc (XPC inter-process communication) +#-------------------------------------------------------------------- +HAVE_LIBXPC=0 +# Check whether --enable-libxpc was given. +if test ${enable_libxpc+y} +then : + enableval=$enable_libxpc; enable_libxpc=$enableval +else $as_nop + enable_libxpc=yes +fi + + +if test $enable_libxpc = yes; then + for ac_header in xpc/xpc.h +do : + ac_fn_c_check_header_compile "$LINENO" "xpc/xpc.h" "ac_cv_header_xpc_xpc_h" "$ac_includes_default" +if test "x$ac_cv_header_xpc_xpc_h" = xyes +then : + printf "%s\n" "#define HAVE_XPC_XPC_H 1" >>confdefs.h + have_libxpc=yes +else $as_nop + have_libxpc=no +fi + +done + if test "$have_libxpc" = "yes"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for xpc_connection_create_mach_service in -lxpc" >&5 +printf %s "checking for xpc_connection_create_mach_service in -lxpc... " >&6; } +if test ${ac_cv_lib_xpc_xpc_connection_create_mach_service+y} +then : + printf %s "(cached) " >&6 +else $as_nop + ac_check_lib_save_LIBS=$LIBS +LIBS="-lxpc $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +char xpc_connection_create_mach_service (); +int +main (void) +{ +return xpc_connection_create_mach_service (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + ac_cv_lib_xpc_xpc_connection_create_mach_service=yes +else $as_nop + ac_cv_lib_xpc_xpc_connection_create_mach_service=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_xpc_xpc_connection_create_mach_service" >&5 +printf "%s\n" "$ac_cv_lib_xpc_xpc_connection_create_mach_service" >&6; } +if test "x$ac_cv_lib_xpc_xpc_connection_create_mach_service" = xyes +then : + have_libxpc=yes +else $as_nop + have_libxpc=no +fi + + if test "$have_libxpc" = "yes"; then + HAVE_LIBXPC=1 + LIBS="$LIBS -lxpc" + +printf "%s\n" "#define HAVE_LIBXPC 1" >>confdefs.h + + fi + fi +fi + + + #-------------------------------------------------------------------- # Check for whether the new KVO implementation is enabled (only with # the NG runtime) diff --git a/configure.ac b/configure.ac index 055fb78c2..ae91af35e 100644 --- a/configure.ac +++ b/configure.ac @@ -3805,6 +3805,30 @@ fi AC_SUBST(HAVE_LIBDISPATCH_RUNLOOP) +#-------------------------------------------------------------------- +# Check for libxpc (XPC inter-process communication) +#-------------------------------------------------------------------- +HAVE_LIBXPC=0 +AC_ARG_ENABLE(libxpc, + [AS_HELP_STRING([--disable-libxpc], + [Disable use of libxpc library for XPC inter-process communication])], + enable_libxpc=$enableval, + enable_libxpc=yes) + +if test $enable_libxpc = yes; then + AC_CHECK_HEADERS(xpc/xpc.h, have_libxpc=yes, have_libxpc=no) + if test "$have_libxpc" = "yes"; then + AC_CHECK_LIB(xpc, xpc_connection_create_mach_service, have_libxpc=yes, have_libxpc=no) + if test "$have_libxpc" = "yes"; then + HAVE_LIBXPC=1 + LIBS="$LIBS -lxpc" + AC_DEFINE(HAVE_LIBXPC,1,[Define if libxpc is available]) + fi + fi +fi +AC_SUBST(HAVE_LIBXPC) + + #-------------------------------------------------------------------- # Check for whether the new KVO implementation is enabled (only with # the NG runtime)