From 8bd15973ac89af4a875b1f38a047c906b1095a93 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Fri, 1 May 2026 12:02:10 -0400 Subject: [PATCH 01/10] Add support for libxpc --- Headers/GNUstepBase/GSConfig.h.in | 1 + Source/NSXPCConnection.m | 199 ++++++++++++++++++++++++++---- config.mak.in | 1 + configure | 85 +++++++++++++ configure.ac | 24 ++++ 5 files changed, 284 insertions(+), 26 deletions(-) diff --git a/Headers/GNUstepBase/GSConfig.h.in b/Headers/GNUstepBase/GSConfig.h.in index dba527211b..ebe58f8bab 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 680a91c402..545a6c14f6 100644 --- a/Source/NSXPCConnection.m +++ b/Source/NSXPCConnection.m @@ -21,139 +21,286 @@ Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA. */ +#import "common.h" #import "Foundation/NSXPCConnection.h" #import "GNUstepBase/NSObject+GNUstepBase.h" +#import "GNUstepBase/GSConfig.h" + +#if GS_USE_LIBXPC +#include +#endif + +@interface NSXPCConnection () +{ + NSString *_serviceName; + NSXPCListenerEndpoint *_endpoint; + NSXPCInterface *_exportedInterface; + NSXPCInterface *_remoteObjectInterface; + id _remoteObjectProxy; + GSXPCInterruptionHandler _interruptionHandler; + GSXPCInvalidationHandler _invalidationHandler; + NSXPCConnectionOptions _options; + BOOL _resumed; + BOOL _invalidated; +#if GS_USE_LIBXPC + xpc_connection_t _xpcConnection; +#endif +} + +- (void) _setupLibXPCConnectionIfPossible; +@end @implementation NSXPCConnection +- (instancetype) init +{ + return [self initWithServiceName: nil]; +} + +- (void) dealloc +{ + [self invalidate]; + DESTROY(_serviceName); + DESTROY(_endpoint); + DESTROY(_exportedInterface); + DESTROY(_remoteObjectInterface); + DESTROY(_remoteObjectProxy); + DESTROY(_interruptionHandler); + DESTROY(_invalidationHandler); + [super dealloc]; +} + +- (void) _setupLibXPCConnectionIfPossible +{ +#if GS_USE_LIBXPC + uint64_t flags = 0; + NSXPCConnection *connection = self; + + if (_xpcConnection != NULL || _serviceName == nil || _invalidated == YES) + { + return; + } +#ifdef XPC_CONNECTION_MACH_SERVICE_PRIVILEGED + if ((_options & NSXPCConnectionPrivileged) == NSXPCConnectionPrivileged) + { + flags |= XPC_CONNECTION_MACH_SERVICE_PRIVILEGED; + } +#endif + _xpcConnection = xpc_connection_create_mach_service([_serviceName UTF8String], + NULL, flags); + if (_xpcConnection == NULL) + { + return; + } + + xpc_connection_set_event_handler(_xpcConnection, ^(xpc_object_t event) { + if (event == XPC_ERROR_CONNECTION_INTERRUPTED) + { + if (connection->_interruptionHandler != NULL) + { + connection->_interruptionHandler(); + } + } + else if (event == XPC_ERROR_CONNECTION_INVALID) + { + connection->_invalidated = YES; + if (connection->_invalidationHandler != NULL) + { + connection->_invalidationHandler(); + } + } + }); + + if (_resumed == YES) + { + xpc_connection_resume(_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 setServiceName: name]; + } + return self; } - (instancetype) initWithListenerEndpoint: (NSXPCListenerEndpoint *)endpoint { - return [self notImplemented: _cmd]; + if ((self = [super init]) != nil) + { + ASSIGN(_endpoint, endpoint); + } + 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) setExportInterface: (NSXPCInterface *)exportedInterface { - [self notImplemented: _cmd]; + ASSIGN(_exportedInterface, exportedInterface); } - (NSXPCInterface *) remoteObjectInterface { - return [self notImplemented: _cmd]; + return _remoteObjectInterface; } - (void) setRemoteObjectInterface: (NSXPCInterface *)remoteObjectInterface { - [self notImplemented: _cmd]; + ASSIGN(_remoteObjectInterface, remoteObjectInterface); } - (id) remoteObjectProxy { - return [self notImplemented: _cmd]; + return _remoteObjectProxy; } - (void) setRemoteObjectProxy: (id)remoteObjectProxy { - [self notImplemented: _cmd]; + ASSIGN(_remoteObjectProxy, remoteObjectProxy); } - (id) remoteObjectProxyWithErrorHandler:(GSXPCProxyErrorHandler)handler { - return [self notImplemented: _cmd]; + return [self remoteObjectProxy]; } - (id) synchronousRemoteObjectProxyWithErrorHandler: (GSXPCProxyErrorHandler)handler { - return [self notImplemented: _cmd]; + return [self remoteObjectProxy]; } - (GSXPCInterruptionHandler) interruptionHandler { - return NULL; + return _interruptionHandler; } - (void) setInterruptionHandler: (GSXPCInterruptionHandler)handler { - [self notImplemented: _cmd]; + ASSIGNCOPY(_interruptionHandler, handler); } - (GSXPCInvalidationHandler) invalidationHandler { - return NULL; + return _invalidationHandler; } - (void) setInvalidationHandler: (GSXPCInvalidationHandler)handler { - [self notImplemented: _cmd]; + ASSIGNCOPY(_invalidationHandler, handler); } - (void) resume { - [self notImplemented: _cmd]; + _resumed = YES; + [self _setupLibXPCConnectionIfPossible]; +#if GS_USE_LIBXPC + if (_xpcConnection != NULL) + { + xpc_connection_resume(_xpcConnection); + } +#endif } - (void) suspend { - [self notImplemented: _cmd]; + _resumed = NO; +#if GS_USE_LIBXPC + if (_xpcConnection != NULL) + { + xpc_connection_suspend(_xpcConnection); + } +#endif } - (void) invalidate { - [self notImplemented: _cmd]; + BOOL wasInvalidated = _invalidated; + + _invalidated = YES; +#if GS_USE_LIBXPC + if (_xpcConnection != NULL) + { + xpc_connection_cancel(_xpcConnection); + xpc_release(_xpcConnection); + _xpcConnection = NULL; + } +#endif + if (wasInvalidated == NO && _invalidationHandler != NULL) + { + _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 != NULL) + { + return xpc_connection_get_pid(_xpcConnection); + } +#endif + return 0; } - (uid_t) effectiveUserIdentifier { - return (uid_t)(uintptr_t)[self notImplemented: _cmd]; +#if GS_USE_LIBXPC + if (_xpcConnection != NULL) + { + return xpc_connection_get_euid(_xpcConnection); + } +#endif + return (uid_t)0; } - (gid_t) effectiveGroupIdentifier { - return (gid_t)(uintptr_t)[self notImplemented: _cmd]; +#if GS_USE_LIBXPC + if (_xpcConnection != NULL) + { + return xpc_connection_get_egid(_xpcConnection); + } +#endif + return (gid_t)0; } @end diff --git a/config.mak.in b/config.mak.in index 18217b1d50..0e459b7af6 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 59cd1e6469..20dcc748e5 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 055fb78c2d..ae91af35ec 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) From 95acb088153f5d91fed9430e9c93c1fc16e62a25 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Fri, 1 May 2026 12:44:09 -0400 Subject: [PATCH 02/10] Update to fix gcc issues --- Headers/Foundation/NSXPCConnection.h | 40 +++++ Source/NSXPCConnection.m | 247 ++++++++++++++++++++------- 2 files changed, 230 insertions(+), 57 deletions(-) diff --git a/Headers/Foundation/NSXPCConnection.h b/Headers/Foundation/NSXPCConnection.h index d888bc79d1..d5b6c4c2e6 100644 --- a/Headers/Foundation/NSXPCConnection.h +++ b/Headers/Foundation/NSXPCConnection.h @@ -70,6 +70,22 @@ typedef NSUInteger NSXPCConnectionOptions; GS_EXPORT_CLASS @interface NSXPCConnection : NSObject +{ +#if GS_EXPOSE(NSXPCConnection) + @private + NSString *_serviceName; + NSXPCListenerEndpoint *_endpoint; + NSXPCInterface *_exportedInterface; + NSXPCInterface *_remoteObjectInterface; + id _remoteObjectProxy; + GSXPCInterruptionHandler _interruptionHandler; + GSXPCInvalidationHandler _invalidationHandler; + NSXPCConnectionOptions _options; + BOOL _resumed; + BOOL _invalidated; + void *_xpcConnection; +#endif +} - (instancetype) initWithListenerEndpoint: (NSXPCListenerEndpoint *)endpoint; @@ -120,6 +136,16 @@ GS_EXPORT_CLASS @interface NSXPCListener : NSObject +{ +#if GS_EXPOSE(NSXPCListener) + @private + id _delegate; + NSXPCListenerEndpoint *_endpoint; + NSString *_machServiceName; + BOOL _resumed; + BOOL _invalidated; +#endif +} + (NSXPCListener *) serviceListener; @@ -149,6 +175,14 @@ GS_EXPORT_CLASS @end @interface NSXPCInterface : NSObject +{ +#if GS_EXPOSE(NSXPCInterface) + @private + Protocol *_protocol; + NSMutableDictionary *_classes; + NSMutableDictionary *_interfaces; +#endif +} + (NSXPCInterface *) interfaceWithProtocol: (Protocol *)protocol; @@ -177,6 +211,12 @@ GS_EXPORT_CLASS GS_EXPORT_CLASS @interface NSXPCListenerEndpoint : NSObject // NSSecureCoding +{ +#if GS_EXPOSE(NSXPCListenerEndpoint) + @private + NSString *_serviceName; +#endif +} @end #if defined(__cplusplus) diff --git a/Source/NSXPCConnection.m b/Source/NSXPCConnection.m index 545a6c14f6..14b27b08d3 100644 --- a/Source/NSXPCConnection.m +++ b/Source/NSXPCConnection.m @@ -22,7 +22,15 @@ */ #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 "GNUstepBase/NSObject+GNUstepBase.h" #import "GNUstepBase/GSConfig.h" @@ -30,26 +38,24 @@ #include #endif -@interface NSXPCConnection () -{ - NSString *_serviceName; - NSXPCListenerEndpoint *_endpoint; - NSXPCInterface *_exportedInterface; - NSXPCInterface *_remoteObjectInterface; - id _remoteObjectProxy; - GSXPCInterruptionHandler _interruptionHandler; - GSXPCInvalidationHandler _invalidationHandler; - NSXPCConnectionOptions _options; - BOOL _resumed; - BOOL _invalidated; -#if GS_USE_LIBXPC - xpc_connection_t _xpcConnection; -#endif -} - +@interface NSXPCConnection (Private) - (void) _setupLibXPCConnectionIfPossible; @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)]; +} + @implementation NSXPCConnection - (instancetype) init @@ -76,7 +82,7 @@ - (void) _setupLibXPCConnectionIfPossible uint64_t flags = 0; NSXPCConnection *connection = self; - if (_xpcConnection != NULL || _serviceName == nil || _invalidated == YES) + if (_xpcConnection != 0 || _serviceName == nil || _invalidated == YES) { return; } @@ -86,14 +92,15 @@ - (void) _setupLibXPCConnectionIfPossible flags |= XPC_CONNECTION_MACH_SERVICE_PRIVILEGED; } #endif - _xpcConnection = xpc_connection_create_mach_service([_serviceName UTF8String], - NULL, flags); - if (_xpcConnection == NULL) + _xpcConnection = (void *)xpc_connection_create_mach_service( + [_serviceName UTF8String], NULL, flags); + if (_xpcConnection == 0) { return; } - xpc_connection_set_event_handler(_xpcConnection, ^(xpc_object_t event) { + xpc_connection_set_event_handler((xpc_connection_t)_xpcConnection, + ^(xpc_object_t event) { if (event == XPC_ERROR_CONNECTION_INTERRUPTED) { if (connection->_interruptionHandler != NULL) @@ -113,7 +120,7 @@ - (void) _setupLibXPCConnectionIfPossible if (_resumed == YES) { - xpc_connection_resume(_xpcConnection); + xpc_connection_resume((xpc_connection_t)_xpcConnection); } #endif } @@ -149,7 +156,17 @@ - (instancetype) initWithListenerEndpoint: (NSXPCListenerEndpoint *)endpoint { 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]; + } } return self; } @@ -231,9 +248,9 @@ - (void) resume _resumed = YES; [self _setupLibXPCConnectionIfPossible]; #if GS_USE_LIBXPC - if (_xpcConnection != NULL) + if (_xpcConnection != 0) { - xpc_connection_resume(_xpcConnection); + xpc_connection_resume((xpc_connection_t)_xpcConnection); } #endif } @@ -242,9 +259,9 @@ - (void) suspend { _resumed = NO; #if GS_USE_LIBXPC - if (_xpcConnection != NULL) + if (_xpcConnection != 0) { - xpc_connection_suspend(_xpcConnection); + xpc_connection_suspend((xpc_connection_t)_xpcConnection); } #endif } @@ -255,11 +272,11 @@ - (void) invalidate _invalidated = YES; #if GS_USE_LIBXPC - if (_xpcConnection != NULL) + if (_xpcConnection != 0) { - xpc_connection_cancel(_xpcConnection); - xpc_release(_xpcConnection); - _xpcConnection = NULL; + xpc_connection_cancel((xpc_connection_t)_xpcConnection); + xpc_release((xpc_connection_t)_xpcConnection); + _xpcConnection = 0; } #endif if (wasInvalidated == NO && _invalidationHandler != NULL) @@ -275,9 +292,9 @@ - (NSUInteger) auditSessionIdentifier - (pid_t) processIdentifier { #if GS_USE_LIBXPC - if (_xpcConnection != NULL) + if (_xpcConnection != 0) { - return xpc_connection_get_pid(_xpcConnection); + return xpc_connection_get_pid((xpc_connection_t)_xpcConnection); } #endif return 0; @@ -285,9 +302,9 @@ - (pid_t) processIdentifier - (uid_t) effectiveUserIdentifier { #if GS_USE_LIBXPC - if (_xpcConnection != NULL) + if (_xpcConnection != 0) { - return xpc_connection_get_euid(_xpcConnection); + return xpc_connection_get_euid((xpc_connection_t)_xpcConnection); } #endif return (uid_t)0; @@ -295,9 +312,9 @@ - (uid_t) effectiveUserIdentifier - (gid_t) effectiveGroupIdentifier { #if GS_USE_LIBXPC - if (_xpcConnection != NULL) + if (_xpcConnection != 0) { - return xpc_connection_get_egid(_xpcConnection); + return xpc_connection_get_egid((xpc_connection_t)_xpcConnection); } #endif return (gid_t)0; @@ -308,52 +325,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]; + ASSIGN(_delegate, delegate); } - (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 @@ -362,17 +407,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 @@ -380,14 +446,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 @@ -395,29 +472,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: @"serviceName"]; + } + 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: @"serviceName"]; + } + else + { + [coder encodeObject: _serviceName]; + } } @end - From b917605ffbf3c680306ac88d2993a1f3530011af Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Fri, 1 May 2026 13:27:36 -0400 Subject: [PATCH 03/10] Fix GCC issues --- Source/NSXPCConnection.m | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/Source/NSXPCConnection.m b/Source/NSXPCConnection.m index 14b27b08d3..3939180f34 100644 --- a/Source/NSXPCConnection.m +++ b/Source/NSXPCConnection.m @@ -56,6 +56,20 @@ - (NSString *) _serviceName; (unsigned int)(ofReply ? 1 : 0)]; } +#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) + @implementation NSXPCConnection - (instancetype) init @@ -71,8 +85,8 @@ - (void) dealloc DESTROY(_exportedInterface); DESTROY(_remoteObjectInterface); DESTROY(_remoteObjectProxy); - DESTROY(_interruptionHandler); - DESTROY(_invalidationHandler); + GS_DESTROY_BLOCK(_interruptionHandler); + GS_DESTROY_BLOCK(_invalidationHandler); [super dealloc]; } @@ -105,7 +119,7 @@ - (void) _setupLibXPCConnectionIfPossible { if (connection->_interruptionHandler != NULL) { - connection->_interruptionHandler(); + CALL_BLOCK_NO_ARGS(connection->_interruptionHandler); } } else if (event == XPC_ERROR_CONNECTION_INVALID) @@ -113,7 +127,7 @@ - (void) _setupLibXPCConnectionIfPossible connection->_invalidated = YES; if (connection->_invalidationHandler != NULL) { - connection->_invalidationHandler(); + CALL_BLOCK_NO_ARGS(connection->_invalidationHandler); } } }); @@ -230,7 +244,7 @@ - (GSXPCInterruptionHandler) interruptionHandler - (void) setInterruptionHandler: (GSXPCInterruptionHandler)handler { - ASSIGNCOPY(_interruptionHandler, handler); + GS_ASSIGN_BLOCK(_interruptionHandler, handler); } - (GSXPCInvalidationHandler) invalidationHandler @@ -240,7 +254,7 @@ - (GSXPCInvalidationHandler) invalidationHandler - (void) setInvalidationHandler: (GSXPCInvalidationHandler)handler { - ASSIGNCOPY(_invalidationHandler, handler); + GS_ASSIGN_BLOCK(_invalidationHandler, handler); } - (void) resume @@ -281,7 +295,7 @@ - (void) invalidate #endif if (wasInvalidated == NO && _invalidationHandler != NULL) { - _invalidationHandler(); + CALL_BLOCK_NO_ARGS(_invalidationHandler); } } From 39d03acef1a00d44972624e75155cca82bb175ff Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Fri, 1 May 2026 17:36:50 -0400 Subject: [PATCH 04/10] Add documentation --- Headers/Foundation/NSXPCConnection.h | 211 +++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) diff --git a/Headers/Foundation/NSXPCConnection.h b/Headers/Foundation/NSXPCConnection.h index d5b6c4c2e6..ad0e9bd82d 100644 --- a/Headers/Foundation/NSXPCConnection.h +++ b/Headers/Foundation/NSXPCConnection.h @@ -47,27 +47,65 @@ 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; +/** + * Represents a bidirectional communication channel to an XPC service or + * listener endpoint. + */ GS_EXPORT_CLASS @interface NSXPCConnection : NSObject { @@ -87,54 +125,152 @@ GS_EXPORT_CLASS #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; + +/** + * Sets the interface that describes methods this process exports to the + * remote side. + */ - (void) setExportInterface: (NSXPCInterface *)exportedInterface; +/** + * 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) @@ -147,33 +283,78 @@ GS_EXPORT_CLASS #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) @@ -184,31 +365,61 @@ GS_EXPORT_CLASS #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 { From 021d6a6162f2caced3c9ec6f6958692f07eda403 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sat, 2 May 2026 07:24:21 -0400 Subject: [PATCH 05/10] Normalize key name, this class is not well documented on macOS, so using GS* key --- Source/NSXPCConnection.m | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Source/NSXPCConnection.m b/Source/NSXPCConnection.m index 3939180f34..13ae579e56 100644 --- a/Source/NSXPCConnection.m +++ b/Source/NSXPCConnection.m @@ -303,6 +303,7 @@ - (NSUInteger) auditSessionIdentifier { return 0; } + - (pid_t) processIdentifier { #if GS_USE_LIBXPC @@ -313,6 +314,7 @@ - (pid_t) processIdentifier #endif return 0; } + - (uid_t) effectiveUserIdentifier { #if GS_USE_LIBXPC @@ -323,6 +325,7 @@ - (uid_t) effectiveUserIdentifier #endif return (uid_t)0; } + - (gid_t) effectiveGroupIdentifier { #if GS_USE_LIBXPC @@ -383,7 +386,7 @@ - (void) dealloc - (void) setDelegate: (id ) delegate { - ASSIGN(_delegate, delegate); + _delegate = delegate; // weak reference... } - (NSXPCListenerEndpoint *) endpoint @@ -544,7 +547,7 @@ - (instancetype) initWithCoder: (NSCoder *)coder { if ([coder respondsToSelector: @selector(decodeObjectForKey:)]) { - serviceName = [coder decodeObjectForKey: @"serviceName"]; + serviceName = [coder decodeObjectForKey: @"GSServiceName"]; } else { @@ -559,7 +562,7 @@ - (void) encodeWithCoder: (NSCoder *)coder { if ([coder respondsToSelector: @selector(encodeObject:forKey:)]) { - [coder encodeObject: _serviceName forKey: @"serviceName"]; + [coder encodeObject: _serviceName forKey: @"GSServiceName"]; } else { From 7b4ae13fd39a82521dbcc1c0bf9ce9802625a8d5 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sat, 2 May 2026 08:30:27 -0400 Subject: [PATCH 06/10] Add plumbing to export an object proxy --- Headers/Foundation/NSXPCConnection.h | 11 ++ Source/NSXPCConnection.m | 283 ++++++++++++++++++++++++++- 2 files changed, 292 insertions(+), 2 deletions(-) diff --git a/Headers/Foundation/NSXPCConnection.h b/Headers/Foundation/NSXPCConnection.h index ad0e9bd82d..f76eba8105 100644 --- a/Headers/Foundation/NSXPCConnection.h +++ b/Headers/Foundation/NSXPCConnection.h @@ -114,6 +114,7 @@ GS_EXPORT_CLASS NSString *_serviceName; NSXPCListenerEndpoint *_endpoint; NSXPCInterface *_exportedInterface; + id _exportedObject; NSXPCInterface *_remoteObjectInterface; id _remoteObjectProxy; GSXPCInterruptionHandler _interruptionHandler; @@ -162,6 +163,16 @@ GS_EXPORT_CLASS * remote side. */ - (void) setExportInterface: (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 diff --git a/Source/NSXPCConnection.m b/Source/NSXPCConnection.m index 13ae579e56..7f2e08a50d 100644 --- a/Source/NSXPCConnection.m +++ b/Source/NSXPCConnection.m @@ -30,16 +30,25 @@ #import "Foundation/NSXPCConnection.h" #import "Foundation/NSDictionary.h" #import "Foundation/NSArchiver.h" +#import "Foundation/NSInvocation.h" +#import "Foundation/NSMethodSignature.h" +#import "Foundation/NSProxy.h" #import "GNUstepBase/NSObject+GNUstepBase.h" #import "GNUstepBase/GSConfig.h" +#import + #if GS_USE_LIBXPC #include #endif @interface NSXPCConnection (Private) - (void) _setupLibXPCConnectionIfPossible; +- (void) _sendInvocation: (NSInvocation *)invocation + errorHandler: (GSXPCProxyErrorHandler)errorHandler + synchronous: (BOOL)synchronous; +- (NSMethodSignature *) _remoteMethodSignatureForSelector: (SEL)sel; @end @interface NSXPCListenerEndpoint (Private) @@ -70,6 +79,76 @@ - (NSString *) _serviceName; } \ } 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]; +} + +@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 @@ -83,6 +162,7 @@ - (void) dealloc DESTROY(_serviceName); DESTROY(_endpoint); DESTROY(_exportedInterface); + DESTROY(_exportedObject); DESTROY(_remoteObjectInterface); DESTROY(_remoteObjectProxy); GS_DESTROY_BLOCK(_interruptionHandler); @@ -206,6 +286,16 @@ - (void) setExportInterface: (NSXPCInterface *)exportedInterface ASSIGN(_exportedInterface, exportedInterface); } +- (id) exportedObject +{ + return _exportedObject; +} + +- (void) setExportedObject: (id)exportedObject +{ + ASSIGN(_exportedObject, exportedObject); +} + - (NSXPCInterface *) remoteObjectInterface { return _remoteObjectInterface; @@ -218,6 +308,15 @@ - (void) setRemoteObjectInterface: (NSXPCInterface *)remoteObjectInterface - (id) remoteObjectProxy { + if (_remoteObjectProxy == nil) + { + id proxy = [[GSXPCRemoteProxy alloc] initWithConnection: self + errorHandler: NULL + synchronous: NO]; + + [self setRemoteObjectProxy: proxy]; + RELEASE(proxy); + } return _remoteObjectProxy; } @@ -228,13 +327,193 @@ - (void) setRemoteObjectProxy: (id)remoteObjectProxy - (id) remoteObjectProxyWithErrorHandler:(GSXPCProxyErrorHandler)handler { - return [self remoteObjectProxy]; + if (handler == NULL) + { + return [self remoteObjectProxy]; + } + return AUTORELEASE([[GSXPCRemoteProxy alloc] initWithConnection: self + errorHandler: handler + synchronous: NO]); } - (id) synchronousRemoteObjectProxyWithErrorHandler: (GSXPCProxyErrorHandler)handler { - return [self remoteObjectProxy]; + 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; + + 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; + } + + if (synchronous == YES) + { + if (errorHandler != NULL) + { + CALL_BLOCK(errorHandler, + GSXPCProxyError(@"Synchronous proxy messaging is not implemented yet.")); + } + return; + } + + if (strcmp([signature methodReturnType], "v") != 0) + { + if (errorHandler != NULL) + { + CALL_BLOCK(errorHandler, + GSXPCProxyError(@"Only void-returning methods are currently supported.")); + } + return; + } + + [self _setupLibXPCConnectionIfPossible]; + + if (_invalidated == YES) + { + if (errorHandler != NULL) + { + CALL_BLOCK(errorHandler, GSXPCProxyError(@"Connection is invalidated.")); + } + return; + } + +#if GS_USE_LIBXPC + if (_xpcConnection == 0) + { + if (errorHandler != NULL) + { + CALL_BLOCK(errorHandler, + GSXPCProxyError(@"XPC transport is unavailable for this connection.")); + } + return; + } + + { + 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.selector", selectorName); + + count = [signature numberOfArguments]; + xpc_dictionary_set_uint64(message, "gsxpc.argumentCount", (uint64_t)(count - 2)); + + for (index = 2; index < count; index++) + { + const char *argType; + + argType = [signature getArgumentTypeAtIndex: index]; + if (argType[0] != '@') + { + if (errorHandler != NULL) + { + NSString *reason; + + reason = [NSString stringWithFormat: + @"Only object arguments are currently supported (argument %lu).", + (unsigned long)(index - 2)]; + CALL_BLOCK(errorHandler, GSXPCProxyError(reason)); + } + xpc_release(message); + return; + } + + { + id value = nil; + NSData *encoded = nil; + NSString *key; + + [invocation getArgument: &value atIndex: index]; + encoded = [NSArchiver 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)); + } + xpc_release(message); + return; + } + + key = [NSString stringWithFormat: @"gsxpc.arg.%lu", + (unsigned long)(index - 2)]; + xpc_dictionary_set_data(message, + [key UTF8String], + [encoded bytes], + (size_t)[encoded length]); + } + } + + xpc_connection_send_message((xpc_connection_t)_xpcConnection, message); + xpc_release(message); + } +#else + if (errorHandler != NULL) + { + CALL_BLOCK(errorHandler, + GSXPCProxyError(@"This build does not include libxpc support.")); + } +#endif } - (GSXPCInterruptionHandler) interruptionHandler From f03d43974f4b6e387d1e825f07146de76005bd88 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sat, 2 May 2026 08:36:35 -0400 Subject: [PATCH 07/10] Add proxy reply capability to XPC --- Headers/Foundation/NSXPCConnection.h | 3 + Source/NSXPCConnection.m | 604 ++++++++++++++++++++++++++- 2 files changed, 602 insertions(+), 5 deletions(-) diff --git a/Headers/Foundation/NSXPCConnection.h b/Headers/Foundation/NSXPCConnection.h index f76eba8105..0aa74a430c 100644 --- a/Headers/Foundation/NSXPCConnection.h +++ b/Headers/Foundation/NSXPCConnection.h @@ -119,6 +119,9 @@ GS_EXPORT_CLASS id _remoteObjectProxy; GSXPCInterruptionHandler _interruptionHandler; GSXPCInvalidationHandler _invalidationHandler; + NSMutableDictionary *_pendingReplies; + NSLock *_pendingRepliesLock; + unsigned long long _nextMessageIdentifier; NSXPCConnectionOptions _options; BOOL _resumed; BOOL _invalidated; diff --git a/Source/NSXPCConnection.m b/Source/NSXPCConnection.m index 7f2e08a50d..d8a7b58c59 100644 --- a/Source/NSXPCConnection.m +++ b/Source/NSXPCConnection.m @@ -45,10 +45,21 @@ @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 + 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) @@ -65,6 +76,18 @@ - (NSString *) _serviceName; (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); } \ @@ -104,6 +127,90 @@ - (instancetype) initWithConnection: (NSXPCConnection *)connection userInfo: userInfo]; } +@interface GSXPCPendingReply : NSObject +{ + NSCondition *_condition; + BOOL _resolved; + id _returnObject; + NSError *_error; +} + +- (void) resolveWithReturnObject: (id)returnObject error: (NSError *)error; +- (BOOL) waitForResolutionUntilDate: (NSDate *)limitDate + returnObject: (id *)returnObject + error: (NSError **)error; + +@end + +@implementation GSXPCPendingReply + +- (instancetype) init +{ + if ((self = [super init]) != nil) + { + _condition = [NSCondition new]; + _resolved = NO; + } + return self; +} + +- (void) dealloc +{ + DESTROY(_condition); + DESTROY(_returnObject); + DESTROY(_error); + [super dealloc]; +} + +- (void) resolveWithReturnObject: (id)returnObject error: (NSError *)error +{ + [_condition lock]; + if (_resolved == NO) + { + ASSIGN(_returnObject, returnObject); + ASSIGN(_error, error); + _resolved = YES; + [_condition broadcast]; + } + [_condition unlock]; +} + +- (BOOL) waitForResolutionUntilDate: (NSDate *)limitDate + returnObject: (id *)returnObject + 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 (error != 0) + { + *error = [[_error retain] autorelease]; + } + } + [_condition unlock]; + return resolved; +} + +@end + @implementation GSXPCRemoteProxy - (instancetype) initWithConnection: (NSXPCConnection *)connection @@ -165,11 +272,423 @@ - (void) dealloc 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 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; + 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; + errorText = xpc_dictionary_get_string(event, "gsxpc.error"); + if (errorText != NULL) + { + NSString *reason; + + reason = [NSString stringWithUTF8String: errorText]; + error = GSXPCProxyError(reason); + } + + returnData = xpc_dictionary_get_data(event, + "gsxpc.return", + &returnDataLength); + if (returnData != NULL && returnDataLength > 0) + { + NSData *encoded; + + encoded = [NSData dataWithBytes: returnData + length: (NSUInteger)returnDataLength]; + returnObject = [NSUnarchiver unarchiveObjectWithData: encoded]; + } + + [pending resolveWithReturnObject: returnObject 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 + 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 (returnObject != nil) + { + encoded = [NSArchiver archivedDataWithRootObject: returnObject]; + if (encoded != nil) + { + xpc_dictionary_set_data(reply, + "gsxpc.return", + [encoded bytes], + (size_t)[encoded length]); + } + else + { + xpc_dictionary_set_string(reply, + "gsxpc.error", + "Unable to encode return object."); + } + } + + xpc_connection_send_message((xpc_connection_t)_xpcConnection, reply); + xpc_release(reply); +#else + (void)eventPtr; + (void)returnObject; + (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; + const char *returnType; + + event = (xpc_object_t)eventPtr; + expectsReply = xpc_dictionary_get_bool(event, "gsxpc.expectsReply") ? YES : NO; + error = nil; + returnObject = nil; + + if (_exportedObject == nil) + { + error = GSXPCProxyError(@"No exported object is configured."); + if (expectsReply == YES) + { + [self _sendInvokeReplyForEvent: eventPtr + withReturnObject: 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 + 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 + 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 + 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 + error: error]; + } + return; + } + + returnType = GSXPCStrippedTypeEncoding([signature methodReturnType]); + if (returnType[0] != 'v' && returnType[0] != '@') + { + error = GSXPCProxyError(@"Only object and void return types are supported for exported methods."); + if (expectsReply == YES) + { + [self _sendInvokeReplyForEvent: eventPtr + withReturnObject: 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; + id decoded; + + argType = GSXPCStrippedTypeEncoding([signature getArgumentTypeAtIndex: index + 2]); + if (argType[0] != '@') + { + error = GSXPCProxyError(@"Only object arguments are supported for exported methods."); + if (expectsReply == YES) + { + [self _sendInvokeReplyForEvent: eventPtr + withReturnObject: nil + error: error]; + } + return; + } + + key = [NSString stringWithFormat: @"gsxpc.arg.%lu", (unsigned long)index]; + argData = xpc_dictionary_get_data(event, [key UTF8String], &argDataLength); + decoded = nil; + if (argData != NULL && argDataLength > 0) + { + NSData *encoded; + + encoded = [NSData dataWithBytes: argData length: (NSUInteger)argDataLength]; + decoded = [NSUnarchiver unarchiveObjectWithData: encoded]; + } + [invocation setArgument: &decoded atIndex: index + 2]; + } + + @try + { + [invocation invoke]; + if (returnType[0] == '@') + { + id returned; + + returned = nil; + [invocation getReturnValue: &returned]; + returnObject = returned; + } + } + @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 + error: error]; + } +#else + (void)eventPtr; +#endif +} + - (void) _setupLibXPCConnectionIfPossible { #if GS_USE_LIBXPC @@ -195,6 +714,7 @@ - (void) _setupLibXPCConnectionIfPossible 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) @@ -205,6 +725,8 @@ - (void) _setupLibXPCConnectionIfPossible 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); @@ -241,6 +763,7 @@ - (instancetype) initWithMachServiceName: (NSString *)name if ((self = [super init]) != nil) { _options = options; + [self _initializeReplyTracking]; [self setServiceName: name]; } return self; @@ -261,6 +784,7 @@ - (instancetype) initWithListenerEndpoint: (NSXPCListenerEndpoint *)endpoint { [self setServiceName: serviceName]; } + [self _initializeReplyTracking]; } return self; } @@ -378,6 +902,13 @@ - (void) _sendInvocation: (NSInvocation *)invocation synchronous: (BOOL)synchronous { NSMethodSignature *signature; + const char *returnType; + BOOL expectsReply; + BOOL supportsObjectReturn; + NSNumber *messageID; + GSXPCPendingReply *pending; + id returnObject = nil; + NSError *replyError = nil; if (invocation == nil) { @@ -398,22 +929,26 @@ - (void) _sendInvocation: (NSInvocation *)invocation return; } - if (synchronous == YES) + returnType = GSXPCStrippedTypeEncoding([signature methodReturnType]); + expectsReply = (synchronous == YES || returnType[0] != 'v'); + supportsObjectReturn = (returnType[0] == '@'); + if (returnType[0] != 'v' + && supportsObjectReturn == NO) { if (errorHandler != NULL) { CALL_BLOCK(errorHandler, - GSXPCProxyError(@"Synchronous proxy messaging is not implemented yet.")); + GSXPCProxyError(@"Only object and void return types are currently supported.")); } return; } - if (strcmp([signature methodReturnType], "v") != 0) + if (synchronous == NO && returnType[0] != 'v') { if (errorHandler != NULL) { CALL_BLOCK(errorHandler, - GSXPCProxyError(@"Only void-returning methods are currently supported.")); + GSXPCProxyError(@"Non-void methods require a synchronous proxy.")); } return; } @@ -440,6 +975,14 @@ - (void) _sendInvocation: (NSInvocation *)invocation return; } + messageID = [self _nextMessageIdentifierObject]; + pending = nil; + if (expectsReply == YES) + { + pending = [GSXPCPendingReply new]; + [self _registerPendingReply: pending forMessageID: messageID]; + } + { xpc_object_t message; const char *selectorName; @@ -448,7 +991,12 @@ - (void) _sendInvocation: (NSInvocation *)invocation 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)); @@ -457,7 +1005,7 @@ - (void) _sendInvocation: (NSInvocation *)invocation { const char *argType; - argType = [signature getArgumentTypeAtIndex: index]; + argType = GSXPCStrippedTypeEncoding([signature getArgumentTypeAtIndex: index]); if (argType[0] != '@') { if (errorHandler != NULL) @@ -469,6 +1017,11 @@ - (void) _sendInvocation: (NSInvocation *)invocation (unsigned long)(index - 2)]; CALL_BLOCK(errorHandler, GSXPCProxyError(reason)); } + if (pending != nil) + { + [self _takePendingReplyForMessageID: messageID]; + RELEASE(pending); + } xpc_release(message); return; } @@ -491,6 +1044,11 @@ - (void) _sendInvocation: (NSInvocation *)invocation (unsigned long)(index - 2)]; CALL_BLOCK(errorHandler, GSXPCProxyError(reason)); } + if (pending != nil) + { + [self _takePendingReplyForMessageID: messageID]; + RELEASE(pending); + } xpc_release(message); return; } @@ -507,6 +1065,42 @@ - (void) _sendInvocation: (NSInvocation *)invocation 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 + 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 && supportsObjectReturn == YES) + { + id returned; + + returned = returnObject; + [invocation setReturnValue: &returned]; + } + + RELEASE(pending); + return; + } #else if (errorHandler != NULL) { From 1463b26d5f807554e6b6e3c26df185a818632da7 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sat, 2 May 2026 08:57:17 -0400 Subject: [PATCH 08/10] Eliminate warnings from the code by moving declarations into the block where XPC is used --- Headers/Foundation/NSXPCConnection.h | 34 ++++++ Source/NSXPCConnection.m | 166 +++++++++++++++++++++++++-- 2 files changed, 188 insertions(+), 12 deletions(-) diff --git a/Headers/Foundation/NSXPCConnection.h b/Headers/Foundation/NSXPCConnection.h index 0aa74a430c..85d052310b 100644 --- a/Headers/Foundation/NSXPCConnection.h +++ b/Headers/Foundation/NSXPCConnection.h @@ -101,6 +101,40 @@ enum * 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 diff --git a/Source/NSXPCConnection.m b/Source/NSXPCConnection.m index d8a7b58c59..c3c266d413 100644 --- a/Source/NSXPCConnection.m +++ b/Source/NSXPCConnection.m @@ -30,9 +30,12 @@ #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" @@ -127,14 +130,104 @@ - (instancetype) initWithConnection: (NSXPCConnection *)connection 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; +} + +@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 (GSXPCObjectMatchesAllowedClasses(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; NSError *_error; } +- (instancetype) initWithAllowedClasses: (NSSet *)allowedClasses; +- (NSSet *) allowedClasses; + - (void) resolveWithReturnObject: (id)returnObject error: (NSError *)error; - (BOOL) waitForResolutionUntilDate: (NSDate *)limitDate returnObject: (id *)returnObject @@ -144,11 +237,12 @@ - (BOOL) waitForResolutionUntilDate: (NSDate *)limitDate @implementation GSXPCPendingReply -- (instancetype) init +- (instancetype) initWithAllowedClasses: (NSSet *)allowedClasses { if ((self = [super init]) != nil) { _condition = [NSCondition new]; + ASSIGN(_allowedClasses, allowedClasses); _resolved = NO; } return self; @@ -157,11 +251,17 @@ - (instancetype) init - (void) dealloc { DESTROY(_condition); + DESTROY(_allowedClasses); DESTROY(_returnObject); DESTROY(_error); [super dealloc]; } +- (NSSet *) allowedClasses +{ + return _allowedClasses; +} + - (void) resolveWithReturnObject: (id)returnObject error: (NSError *)error { [_condition lock]; @@ -404,10 +504,18 @@ - (void) _handleIncomingXPCEvent: (void *)eventPtr if (returnData != NULL && returnDataLength > 0) { NSData *encoded; + NSError *decodeError; encoded = [NSData dataWithBytes: returnData length: (NSUInteger)returnDataLength]; - returnObject = [NSUnarchiver unarchiveObjectWithData: encoded]; + decodeError = nil; + returnObject = [NSXPCCoder unarchivedObjectWithData: encoded + allowedClasses: [pending allowedClasses] + error: &decodeError]; + if (decodeError != nil && error == nil) + { + error = decodeError; + } } [pending resolveWithReturnObject: returnObject error: error]; @@ -452,7 +560,7 @@ - (void) _sendInvokeReplyForEvent: (void *)eventPtr } else if (returnObject != nil) { - encoded = [NSArchiver archivedDataWithRootObject: returnObject]; + encoded = [NSXPCCoder archivedDataWithRootObject: returnObject]; if (encoded != nil) { xpc_dictionary_set_data(reply, @@ -648,9 +756,31 @@ - (void) _handleIncomingInvokeEvent: (void *)eventPtr if (argData != NULL && argDataLength > 0) { NSData *encoded; + NSSet *allowedClasses; + NSError *decodeError; encoded = [NSData dataWithBytes: argData length: (NSUInteger)argDataLength]; - decoded = [NSUnarchiver unarchiveObjectWithData: encoded]; + 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 + error: decodeError]; + } + return; + } } [invocation setArgument: &decoded atIndex: index + 2]; } @@ -903,12 +1033,7 @@ - (void) _sendInvocation: (NSInvocation *)invocation { NSMethodSignature *signature; const char *returnType; - BOOL expectsReply; BOOL supportsObjectReturn; - NSNumber *messageID; - GSXPCPendingReply *pending; - id returnObject = nil; - NSError *replyError = nil; if (invocation == nil) { @@ -930,7 +1055,6 @@ - (void) _sendInvocation: (NSInvocation *)invocation } returnType = GSXPCStrippedTypeEncoding([signature methodReturnType]); - expectsReply = (synchronous == YES || returnType[0] != 'v'); supportsObjectReturn = (returnType[0] == '@'); if (returnType[0] != 'v' && supportsObjectReturn == NO) @@ -965,6 +1089,12 @@ - (void) _sendInvocation: (NSInvocation *)invocation } #if GS_USE_LIBXPC + BOOL expectsReply; + NSNumber *messageID; + GSXPCPendingReply *pending; + id returnObject; + NSError *replyError; + if (_xpcConnection == 0) { if (errorHandler != NULL) @@ -975,11 +1105,23 @@ - (void) _sendInvocation: (NSInvocation *)invocation return; } + expectsReply = (synchronous == YES || returnType[0] != 'v'); + returnObject = nil; + replyError = nil; messageID = [self _nextMessageIdentifierObject]; pending = nil; if (expectsReply == YES) { - pending = [GSXPCPendingReply new]; + NSSet *allowedClasses; + + allowedClasses = nil; + if (supportsObjectReturn == YES && _remoteObjectInterface != nil) + { + allowedClasses = [_remoteObjectInterface classesForSelector: [invocation selector] + argumentIndex: 0 + ofReply: YES]; + } + pending = [[GSXPCPendingReply alloc] initWithAllowedClasses: allowedClasses]; [self _registerPendingReply: pending forMessageID: messageID]; } @@ -1032,7 +1174,7 @@ - (void) _sendInvocation: (NSInvocation *)invocation NSString *key; [invocation getArgument: &value atIndex: index]; - encoded = [NSArchiver archivedDataWithRootObject: value]; + encoded = [NSXPCCoder archivedDataWithRootObject: value]; if (encoded == nil) { if (errorHandler != NULL) From ceaaf2e353800a4adc5ebada75915818f4607472 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sat, 2 May 2026 09:49:22 -0400 Subject: [PATCH 09/10] Fix unused variable, make implementation closer to macOS compatibility --- Source/NSXPCConnection.m | 599 ++++++++++++++++++++++++++++++++------- 1 file changed, 493 insertions(+), 106 deletions(-) diff --git a/Source/NSXPCConnection.m b/Source/NSXPCConnection.m index c3c266d413..bac79adb7b 100644 --- a/Source/NSXPCConnection.m +++ b/Source/NSXPCConnection.m @@ -56,7 +56,9 @@ - (void) _failAllPendingRepliesWithError: (NSError *)error; - (void) _handleIncomingXPCEvent: (void *)event; - (void) _handleIncomingInvokeEvent: (void *)event; - (void) _sendInvokeReplyForEvent: (void *)event - withReturnObject: (id)returnObject + withReturnObject: (id)returnObject + returnData: (NSData *)returnData + returnType: (const char *)returnType error: (NSError *)error; - (void) _sendInvocation: (NSInvocation *)invocation errorHandler: (GSXPCProxyErrorHandler)errorHandler @@ -153,6 +155,86 @@ - (instancetype) initWithConnection: (NSXPCConnection *)connection 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 @@ -196,7 +278,7 @@ + (id) unarchivedObjectWithData: (NSData *)data return nil; } - if (GSXPCObjectMatchesAllowedClasses(value, allowedClasses) == NO) + if (GSXPCValidateDecodedObjectGraph(value, allowedClasses) == NO) { if (error != NULL) { @@ -222,15 +304,22 @@ @interface GSXPCPendingReply : NSObject BOOL _resolved; NSSet *_allowedClasses; id _returnObject; + NSData *_returnData; + NSString *_returnType; NSError *_error; } - (instancetype) initWithAllowedClasses: (NSSet *)allowedClasses; - (NSSet *) allowedClasses; -- (void) resolveWithReturnObject: (id)returnObject error: (NSError *)error; +- (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 @@ -253,6 +342,8 @@ - (void) dealloc DESTROY(_condition); DESTROY(_allowedClasses); DESTROY(_returnObject); + DESTROY(_returnData); + DESTROY(_returnType); DESTROY(_error); [super dealloc]; } @@ -262,12 +353,17 @@ - (NSSet *) allowedClasses return _allowedClasses; } -- (void) resolveWithReturnObject: (id)returnObject error: (NSError *)error +- (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]; @@ -277,6 +373,8 @@ - (void) resolveWithReturnObject: (id)returnObject error: (NSError *)error - (BOOL) waitForResolutionUntilDate: (NSDate *)limitDate returnObject: (id *)returnObject + returnData: (NSData **)returnData + returnType: (NSString **)returnType error: (NSError **)error { BOOL resolved; @@ -300,6 +398,14 @@ - (BOOL) waitForResolutionUntilDate: (NSDate *)limitDate { *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]; @@ -453,7 +559,10 @@ - (void) _failAllPendingRepliesWithError: (NSError *)error GSXPCPendingReply *entry; entry = [pending objectAtIndex: index]; - [entry resolveWithReturnObject: nil error: error]; + [entry resolveWithReturnObject: nil + returnData: nil + returnType: nil + error: error]; } RELEASE(pending); } @@ -476,6 +585,9 @@ - (void) _handleIncomingXPCEvent: (void *)eventPtr const char *errorText; NSError *error; id returnObject; + NSData *returnValueData; + NSString *returnValueType; + const char *returnTypeCString; const void *returnData; size_t returnDataLength; @@ -489,6 +601,8 @@ - (void) _handleIncomingXPCEvent: (void *)eventPtr error = nil; returnObject = nil; + returnValueData = nil; + returnValueType = nil; errorText = xpc_dictionary_get_string(event, "gsxpc.error"); if (errorText != NULL) { @@ -498,27 +612,45 @@ - (void) _handleIncomingXPCEvent: (void *)eventPtr 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) + if (returnData != NULL && returnDataLength > 0 && error == nil) { - NSData *encoded; - NSError *decodeError; + NSData *payload; - encoded = [NSData dataWithBytes: returnData - length: (NSUInteger)returnDataLength]; - decodeError = nil; - returnObject = [NSXPCCoder unarchivedObjectWithData: encoded - allowedClasses: [pending allowedClasses] - error: &decodeError]; - if (decodeError != nil && error == nil) + payload = [NSData dataWithBytes: returnData + length: (NSUInteger)returnDataLength]; + if (returnTypeCString != NULL + && GSXPCStrippedTypeEncoding(returnTypeCString)[0] != '@') { - error = decodeError; + 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 error: error]; + [pending resolveWithReturnObject: returnObject + returnData: returnValueData + returnType: returnValueType + error: error]; return; } if (kind != NULL && strcmp(kind, "invoke") == 0) @@ -534,6 +666,8 @@ - (void) _handleIncomingXPCEvent: (void *)eventPtr - (void) _sendInvokeReplyForEvent: (void *)eventPtr withReturnObject: (id)returnObject + returnData: (NSData *)returnData + returnType: (const char *)returnType error: (NSError *)error { #if GS_USE_LIBXPC @@ -558,21 +692,32 @@ - (void) _sendInvokeReplyForEvent: (void *)eventPtr "gsxpc.error", [[error localizedDescription] UTF8String]); } - else if (returnObject != nil) + else if (returnType != NULL) { - encoded = [NSXPCCoder archivedDataWithRootObject: returnObject]; - if (encoded != nil) + xpc_dictionary_set_string(reply, "gsxpc.returnType", returnType); + if (GSXPCStrippedTypeEncoding(returnType)[0] == '@') { - xpc_dictionary_set_data(reply, - "gsxpc.return", - [encoded bytes], - (size_t)[encoded length]); + 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 + else if (returnData != nil) { - xpc_dictionary_set_string(reply, - "gsxpc.error", - "Unable to encode return object."); + xpc_dictionary_set_data(reply, + "gsxpc.return", + [returnData bytes], + (size_t)[returnData length]); } } @@ -581,6 +726,8 @@ - (void) _sendInvokeReplyForEvent: (void *)eventPtr #else (void)eventPtr; (void)returnObject; + (void)returnData; + (void)returnType; (void)error; #endif } @@ -629,12 +776,14 @@ - (void) _handleIncomingInvokeEvent: (void *)eventPtr 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) { @@ -643,6 +792,8 @@ - (void) _handleIncomingInvokeEvent: (void *)eventPtr { [self _sendInvokeReplyForEvent: eventPtr withReturnObject: nil + returnData: nil + returnType: nil error: error]; } return; @@ -656,6 +807,8 @@ - (void) _handleIncomingInvokeEvent: (void *)eventPtr { [self _sendInvokeReplyForEvent: eventPtr withReturnObject: nil + returnData: nil + returnType: nil error: error]; } return; @@ -675,6 +828,8 @@ - (void) _handleIncomingInvokeEvent: (void *)eventPtr { [self _sendInvokeReplyForEvent: eventPtr withReturnObject: nil + returnData: nil + returnType: nil error: error]; } return; @@ -692,6 +847,8 @@ - (void) _handleIncomingInvokeEvent: (void *)eventPtr { [self _sendInvokeReplyForEvent: eventPtr withReturnObject: nil + returnData: nil + returnType: nil error: error]; } return; @@ -707,19 +864,23 @@ - (void) _handleIncomingInvokeEvent: (void *)eventPtr { [self _sendInvokeReplyForEvent: eventPtr withReturnObject: nil + returnData: nil + returnType: nil error: error]; } return; } returnType = GSXPCStrippedTypeEncoding([signature methodReturnType]); - if (returnType[0] != 'v' && returnType[0] != '@') + if (returnType[0] != 'v' && GSXPCTypeSize(returnType, NULL) == NO) { - error = GSXPCProxyError(@"Only object and void return types are supported for exported methods."); + error = GSXPCProxyError(@"Unsupported return type for exported method."); if (expectsReply == YES) { [self _sendInvokeReplyForEvent: eventPtr withReturnObject: nil + returnData: nil + returnType: nil error: error]; } return; @@ -735,54 +896,127 @@ - (void) _handleIncomingInvokeEvent: (void *)eventPtr const void *argData; size_t argDataLength; const char *argType; - id decoded; + const char *argTypeFromMessage; + NSString *typeKey; + BOOL hasNilObject; + NSString *nilKey; argType = GSXPCStrippedTypeEncoding([signature getArgumentTypeAtIndex: index + 2]); - if (argType[0] != '@') + typeKey = [NSString stringWithFormat: @"gsxpc.argtype.%lu", (unsigned long)index]; + argTypeFromMessage = xpc_dictionary_get_string(event, [typeKey UTF8String]); + if (argTypeFromMessage != NULL) { - error = GSXPCProxyError(@"Only object arguments are supported for exported methods."); - if (expectsReply == YES) + const char *normalized; + + normalized = GSXPCStrippedTypeEncoding(argTypeFromMessage); + if (normalized[0] != argType[0]) { - [self _sendInvokeReplyForEvent: eventPtr - withReturnObject: nil + 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; } - return; } key = [NSString stringWithFormat: @"gsxpc.arg.%lu", (unsigned long)index]; argData = xpc_dictionary_get_data(event, [key UTF8String], &argDataLength); - decoded = nil; - if (argData != NULL && argDataLength > 0) + 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 { - NSData *encoded; - NSSet *allowedClasses; - NSError *decodeError; + NSUInteger expectedSize; + void *buffer; - encoded = [NSData dataWithBytes: argData length: (NSUInteger)argDataLength]; - allowedClasses = nil; - if (_exportedInterface != nil) + if (GSXPCTypeSize(argType, &expectedSize) == NO) { - allowedClasses = [_exportedInterface classesForSelector: selector - argumentIndex: index - ofReply: NO]; + error = GSXPCProxyError(@"Unsupported argument type in exported selector."); + if (expectsReply == YES) + { + [self _sendInvokeReplyForEvent: eventPtr + withReturnObject: nil + returnData: nil + returnType: nil + error: error]; + } + return; } - decodeError = nil; - decoded = [NSXPCCoder unarchivedObjectWithData: encoded - allowedClasses: allowedClasses - error: &decodeError]; - if (decodeError != nil) + if (argData == NULL || argDataLength != expectedSize) { + error = GSXPCProxyError(@"Incoming argument payload size does not match selector signature."); if (expectsReply == YES) { [self _sendInvokeReplyForEvent: eventPtr withReturnObject: nil - error: decodeError]; + 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); } - [invocation setArgument: &decoded atIndex: index + 2]; } @try @@ -796,6 +1030,32 @@ - (void) _handleIncomingInvokeEvent: (void *)eventPtr [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) { @@ -812,6 +1072,8 @@ - (void) _handleIncomingInvokeEvent: (void *)eventPtr { [self _sendInvokeReplyForEvent: eventPtr withReturnObject: returnObject + returnData: returnData + returnType: returnType error: error]; } #else @@ -1033,7 +1295,6 @@ - (void) _sendInvocation: (NSInvocation *)invocation { NSMethodSignature *signature; const char *returnType; - BOOL supportsObjectReturn; if (invocation == nil) { @@ -1055,14 +1316,12 @@ - (void) _sendInvocation: (NSInvocation *)invocation } returnType = GSXPCStrippedTypeEncoding([signature methodReturnType]); - supportsObjectReturn = (returnType[0] == '@'); - if (returnType[0] != 'v' - && supportsObjectReturn == NO) + if (returnType[0] != 'v' && GSXPCTypeSize(returnType, NULL) == NO) { if (errorHandler != NULL) { CALL_BLOCK(errorHandler, - GSXPCProxyError(@"Only object and void return types are currently supported.")); + GSXPCProxyError(@"Unsupported method return type.")); } return; } @@ -1093,6 +1352,8 @@ - (void) _sendInvocation: (NSInvocation *)invocation NSNumber *messageID; GSXPCPendingReply *pending; id returnObject; + NSData *returnValueData; + NSString *returnValueType; NSError *replyError; if (_xpcConnection == 0) @@ -1107,6 +1368,8 @@ - (void) _sendInvocation: (NSInvocation *)invocation expectsReply = (synchronous == YES || returnType[0] != 'v'); returnObject = nil; + returnValueData = nil; + returnValueType = nil; replyError = nil; messageID = [self _nextMessageIdentifierObject]; pending = nil; @@ -1115,7 +1378,7 @@ - (void) _sendInvocation: (NSInvocation *)invocation NSSet *allowedClasses; allowedClasses = nil; - if (supportsObjectReturn == YES && _remoteObjectInterface != nil) + if (returnType[0] == '@' && _remoteObjectInterface != nil) { allowedClasses = [_remoteObjectInterface classesForSelector: [invocation selector] argumentIndex: 0 @@ -1146,62 +1409,125 @@ - (void) _sendInvocation: (NSInvocation *)invocation for (index = 2; index < count; index++) { const char *argType; + NSString *typeKey; + NSString *nilKey; + NSString *dataKey; argType = GSXPCStrippedTypeEncoding([signature getArgumentTypeAtIndex: index]); - if (argType[0] != '@') + 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] == '@') { - if (errorHandler != NULL) - { - NSString *reason; + id value; + NSData *encoded; - reason = [NSString stringWithFormat: - @"Only object arguments are currently supported (argument %lu).", - (unsigned long)(index - 2)]; - CALL_BLOCK(errorHandler, GSXPCProxyError(reason)); + value = nil; + [invocation getArgument: &value atIndex: index]; + if (value == nil) + { + xpc_dictionary_set_bool(message, [nilKey UTF8String], true); + continue; } - if (pending != nil) + + encoded = [NSXPCCoder archivedDataWithRootObject: value]; + if (encoded == nil) { - [self _takePendingReplyForMessageID: messageID]; - RELEASE(pending); + 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_release(message); - return; + + xpc_dictionary_set_data(message, + [dataKey UTF8String], + [encoded bytes], + (size_t)[encoded length]); } + else + { + NSUInteger argSize; + void *buffer; + NSData *encoded; - { - id value = nil; - NSData *encoded = nil; - NSString *key; + 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; + } - [invocation getArgument: &value atIndex: index]; - encoded = [NSXPCCoder archivedDataWithRootObject: value]; - if (encoded == nil) - { - if (errorHandler != NULL) - { - NSString *reason; + 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); - 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; - } + 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; + } - key = [NSString stringWithFormat: @"gsxpc.arg.%lu", - (unsigned long)(index - 2)]; - xpc_dictionary_set_data(message, - [key UTF8String], - [encoded bytes], - (size_t)[encoded length]); - } + xpc_dictionary_set_data(message, + [dataKey UTF8String], + [encoded bytes], + (size_t)[encoded length]); + } } xpc_connection_send_message((xpc_connection_t)_xpcConnection, message); @@ -1215,6 +1541,8 @@ - (void) _sendInvocation: (NSInvocation *)invocation didResolve = [pending waitForResolutionUntilDate: [NSDate dateWithTimeIntervalSinceNow: 30.0] returnObject: &returnObject + returnData: &returnValueData + returnType: &returnValueType error: &replyError]; if (didResolve == NO) { @@ -1232,13 +1560,72 @@ - (void) _sendInvocation: (NSInvocation *)invocation return; } - if (synchronous == YES && supportsObjectReturn == YES) + 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; From f7d8016158351e3fb49b1d37238ac26ea189a73e Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sat, 2 May 2026 17:00:25 -0400 Subject: [PATCH 10/10] Latest updates for Apple compatibility --- Headers/Foundation/NSXPCConnection.h | 2 +- Source/NSXPCConnection.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Headers/Foundation/NSXPCConnection.h b/Headers/Foundation/NSXPCConnection.h index 85d052310b..0e3dae90df 100644 --- a/Headers/Foundation/NSXPCConnection.h +++ b/Headers/Foundation/NSXPCConnection.h @@ -199,7 +199,7 @@ GS_EXPORT_CLASS * Sets the interface that describes methods this process exports to the * remote side. */ -- (void) setExportInterface: (NSXPCInterface *)exportedInterface; +- (void) setExportedInterface: (NSXPCInterface *)exportedInterface; /** * Returns the object exported to the remote side for incoming invocations. diff --git a/Source/NSXPCConnection.m b/Source/NSXPCConnection.m index bac79adb7b..e85c134413 100644 --- a/Source/NSXPCConnection.m +++ b/Source/NSXPCConnection.m @@ -1197,7 +1197,7 @@ - (NSXPCInterface *) exportedInterface return _exportedInterface; } -- (void) setExportInterface: (NSXPCInterface *)exportedInterface +- (void) setExportedInterface: (NSXPCInterface *)exportedInterface { ASSIGN(_exportedInterface, exportedInterface); }