mirror of
https://github.com/darlinghq/darling-libxpc.git
synced 2024-11-23 03:39:40 +00:00
Implement XPC connections on top of dispatch_mach
Also includes a serialization overhaul to be more object-oriented (each XPC object class worries about its own (de)serialization). The new serialization code also includes Mach port serialization (which we didn't have before). The "test" service is not really a test; it's more of a sample. You have to test it out manually.
This commit is contained in:
parent
675120a197
commit
07ae92b98b
@ -18,6 +18,7 @@ add_compile_definitions(
|
||||
|
||||
add_compile_options(
|
||||
-Wno-extern-initializer
|
||||
-Werror
|
||||
)
|
||||
|
||||
add_compile_options(
|
||||
@ -28,7 +29,6 @@ set(xpc_sources
|
||||
src/bsm_wrappers.c
|
||||
src/init.c
|
||||
src/reboot3.c
|
||||
src/serialization.c
|
||||
|
||||
src/activity.m
|
||||
src/array.m
|
||||
@ -55,7 +55,7 @@ set(xpc_sources
|
||||
src/pipe.m
|
||||
src/pointer.m
|
||||
src/runtime.m
|
||||
src/serializer.m
|
||||
src/serialization.m
|
||||
src/service_instance.m
|
||||
src/service.m
|
||||
src/shmem.m
|
||||
@ -86,6 +86,7 @@ add_circular(xpc
|
||||
system_dyld
|
||||
unwind
|
||||
compiler_rt
|
||||
system_pthread
|
||||
UPWARD
|
||||
# break an upward dependency on libobjc
|
||||
# ---
|
||||
|
@ -206,7 +206,9 @@ XPC_TYPE(_xpc_type_activity);
|
||||
* eligible to run, its execution state will be updated and an invocation of
|
||||
* its handler block will be made.
|
||||
*/
|
||||
XPC_IGNORE_DUPLICATE_PROTOCOL_PUSH;
|
||||
XPC_DECL(xpc_activity);
|
||||
XPC_IGNORE_DUPLICATE_PROTOCOL_POP;
|
||||
|
||||
/*!
|
||||
* @typedef xpc_activity_handler_t
|
||||
|
@ -162,6 +162,17 @@ __BEGIN_DECLS
|
||||
#define XPC_UNAVAILABLE(m)
|
||||
#endif // __GNUC__
|
||||
|
||||
#if __STDC_VERSION__ >= 199901
|
||||
#define XPC_IGNORE_DUPLICATE_PROTOCOL_PUSH \
|
||||
_Pragma("GCC diagnostic push") \
|
||||
_Pragma("GCC diagnostic ignored \"-Wduplicate-protocol\"")
|
||||
#define XPC_IGNORE_DUPLICATE_PROTOCOL_POP \
|
||||
_Pragma("GCC diagnostic pop")
|
||||
#else
|
||||
#define XPC_IGNORE_DUPLICATE_PROTOCOL_PUSH
|
||||
#define XPC_IGNORE_DUPLICATE_PROTOCOL_POP
|
||||
#endif
|
||||
|
||||
__END_DECLS
|
||||
|
||||
#endif // __XPC_BASE_H__
|
||||
|
@ -14,7 +14,9 @@ extern "C" {
|
||||
#define EXSRCH 3
|
||||
#define EXMAX EXSRCH
|
||||
|
||||
XPC_IGNORE_DUPLICATE_PROTOCOL_PUSH;
|
||||
XPC_DECL(xpc_pipe);
|
||||
XPC_IGNORE_DUPLICATE_PROTOCOL_POP;
|
||||
|
||||
const char *xpc_strerror(int error);
|
||||
xpc_object_t xpc_copy_entitlement_for_token(const char *, audit_token_t *);
|
||||
|
@ -46,10 +46,6 @@ typedef enum {
|
||||
#define XPC_SERVICE_TYPE_APP 0xDEADCAFE
|
||||
#define XPC_SERVICE_TYPE_ENTITLEMENT_ATTACH 0x00DEAD00
|
||||
|
||||
|
||||
#define XPC_SERVICE_ENV_ATTACHED "XPC_SERVICE_ENV_ATTACHED"
|
||||
#define XPC_SERVICE_RENDEZVOUS_TOKEN "XPC_SERVICE_RENDEZVOUS_TOKEN"
|
||||
|
||||
#define XPC_EVENT_ROUTINE_KEY_STREAM "XPC key stream"
|
||||
#define XPC_EVENT_ROUTINE_KEY_TOKEN "XPC key token"
|
||||
#define XPC_EVENT_ROUTINE_KEY_NAME "XPC key name"
|
||||
|
@ -11,6 +11,39 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
XPC_EXPORT
|
||||
XPC_TYPE(_xpc_type_mach_send);
|
||||
#define XPC_TYPE_MACH_SEND (&_xpc_type_mach_send)
|
||||
|
||||
XPC_EXPORT
|
||||
XPC_TYPE(_xpc_type_mach_recv);
|
||||
#define XPC_TYPE_MACH_RECV (&_xpc_type_mach_recv)
|
||||
|
||||
XPC_EXPORT
|
||||
XPC_TYPE(_xpc_type_serializer);
|
||||
#define XPC_TYPE_SERIALIZER (&_xpc_type_serializer)
|
||||
|
||||
XPC_EXPORT
|
||||
XPC_TYPE(_xpc_type_pipe);
|
||||
#define XPC_TYPE_PIPE (&_xpc_type_pipe)
|
||||
|
||||
XPC_EXPORT
|
||||
XPC_TYPE(_xpc_type_bundle);
|
||||
#define XPC_TYPE_BUNDLE (&_xpc_type_bundle)
|
||||
|
||||
XPC_EXPORT
|
||||
XPC_TYPE(_xpc_type_service);
|
||||
#define XPC_TYPE_SERVICE (&_xpc_type_service)
|
||||
|
||||
XPC_EXPORT
|
||||
XPC_TYPE(_xpc_type_service_instance);
|
||||
#define XPC_TYPE_SERVICE_INSTANCE (&_xpc_type_service_instance)
|
||||
|
||||
XPC_EXPORT
|
||||
XPC_TYPE(_xpc_type_file_transfer);
|
||||
#define XPC_TYPE_FILE_TRANSFER (&_xpc_type_file_transfer)
|
||||
|
||||
int _xpc_runtime_is_app_sandboxed();
|
||||
|
||||
void xpc_pipe_invalidate(xpc_pipe_t pipe);
|
||||
@ -106,6 +139,8 @@ void _xpc_bool_set_value(xpc_object_t xbool, bool value);
|
||||
|
||||
const char* xpc_type_get_name(xpc_type_t xtype);
|
||||
|
||||
xpc_connection_t xpc_connection_create_listener(const char* name, dispatch_queue_t queue);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -59,7 +59,9 @@ typedef const struct _xpc_type_s * xpc_type_t;
|
||||
*
|
||||
* See <os/object.h> for details.
|
||||
*/
|
||||
XPC_IGNORE_DUPLICATE_PROTOCOL_PUSH;
|
||||
OS_OBJECT_DECL(xpc_object);
|
||||
XPC_IGNORE_DUPLICATE_PROTOCOL_POP;
|
||||
#ifndef __XPC_PROJECT_BUILD__
|
||||
#define XPC_DECL(name) typedef xpc_object_t name##_t
|
||||
#endif // __XPC_PROJECT_BUILD__
|
||||
@ -74,6 +76,7 @@ _xpc_object_validate(xpc_object_t object) {
|
||||
}
|
||||
#else // OS_OBJECT_USE_OBJC
|
||||
typedef void * xpc_object_t;
|
||||
#undef XPC_DECL
|
||||
#define XPC_DECL(name) typedef struct _##name##_s * name##_t
|
||||
#define XPC_GLOBAL_OBJECT(object) (&(object))
|
||||
#define XPC_RETURNS_RETAINED
|
||||
|
207
internal-include/xpc/generic_array.h
Normal file
207
internal-include/xpc/generic_array.h
Normal file
@ -0,0 +1,207 @@
|
||||
#ifndef _XPC_GENERIC_ARRAY_H_
|
||||
#define _XPC_GENERIC_ARRAY_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <pthread/pthread.h>
|
||||
|
||||
#define XPC_GENARR_APPEND SIZE_MAX
|
||||
|
||||
/**
|
||||
* Type-generic array.
|
||||
*/
|
||||
|
||||
#define XPC_GENARR_STRUCT(name, type) \
|
||||
struct xpc_genarr_ ## name ## _s { \
|
||||
bool resize_by_factor; \
|
||||
size_t size; \
|
||||
size_t length; \
|
||||
type* array; \
|
||||
xpc_genarr_ ## name ## _item_destructor_f item_dtor; \
|
||||
}
|
||||
|
||||
#define XPC_GENARR_INITIALIZER(_resize_by_factor, _item_dtor) { \
|
||||
.resize_by_factor = _resize_by_factor, \
|
||||
.size = 0, \
|
||||
.length = 0, \
|
||||
.array = NULL, \
|
||||
.item_dtor = _item_dtor, \
|
||||
}
|
||||
|
||||
#define XPC_GENARR_DECL(name, type, static) \
|
||||
typedef struct xpc_genarr_ ## name ## _s xpc_genarr_ ## name ## _t; \
|
||||
typedef void (*xpc_genarr_ ## name ## _item_destructor_f)(type* item); \
|
||||
typedef bool (*xpc_genarr_ ## name ## _iterator_f)(void* context, size_t index, type* value); \
|
||||
static void xpc_genarr_ ## name ## _init(xpc_genarr_ ## name ## _t* genarr, bool resize_by_factor, xpc_genarr_ ## name ## _item_destructor_f item_dtor); \
|
||||
static void xpc_genarr_ ## name ## _destroy(xpc_genarr_ ## name ## _t* genarr); \
|
||||
static bool xpc_genarr_ ## name ## _get(xpc_genarr_ ## name ## _t* genarr, size_t index, type* value); \
|
||||
static bool xpc_genarr_ ## name ## _set(xpc_genarr_ ## name ## _t* genarr, size_t index, type const* value); \
|
||||
static bool xpc_genarr_ ## name ## _append(xpc_genarr_ ## name ## _t* genarr, type const* value); \
|
||||
static bool xpc_genarr_ ## name ## _remove(xpc_genarr_ ## name ## _t* genarr, size_t index); \
|
||||
static bool xpc_genarr_ ## name ## _insert(xpc_genarr_ ## name ## _t* genarr, size_t index, type const* value); \
|
||||
static size_t xpc_genarr_ ## name ## _length(xpc_genarr_ ## name ## _t* genarr); \
|
||||
static type* xpc_genarr_ ## name ## _data(xpc_genarr_ ## name ## _t* genarr); \
|
||||
static void xpc_genarr_ ## name ## _iterate(xpc_genarr_ ## name ## _t* genarr, void* context, xpc_genarr_ ## name ## _iterator_f iterator);
|
||||
|
||||
#define XPC_GENARR_SEARCH_DECL(name, type, static) \
|
||||
typedef struct xpc_genarr_ ## name ## _search_context_s { \
|
||||
size_t index; \
|
||||
type const* target; \
|
||||
} xpc_genarr_ ## name ## _search_context_t; \
|
||||
static bool xpc_genarr_ ## name ## _search_iterator(void* context, size_t index, type* value); \
|
||||
static size_t xpc_genarr_ ## name ## _find(xpc_genarr_ ## name ## _t* genarr, type const* target); \
|
||||
|
||||
#define XPC_GENARR_BLOCKS_DECL(name, type, static) \
|
||||
typedef bool (^xpc_genarr_ ## name ## _block_iterator_t)(size_t index, type* value); \
|
||||
static void xpc_genarr_ ## name ## _block_iterate(xpc_genarr_ ## name ## _t* genarr, xpc_genarr_ ## name ## _block_iterator_t block_iterator);
|
||||
|
||||
|
||||
#define XPC_GENARR_DEF(name, type, static) \
|
||||
static void xpc_genarr_ ## name ## _init(xpc_genarr_ ## name ## _t* genarr, bool resize_by_factor, xpc_genarr_ ## name ## _item_destructor_f item_dtor) { \
|
||||
genarr->resize_by_factor = resize_by_factor; \
|
||||
genarr->size = 0; \
|
||||
genarr->length = 0; \
|
||||
genarr->array = NULL; \
|
||||
genarr->item_dtor = item_dtor; \
|
||||
}; \
|
||||
static void xpc_genarr_ ## name ## _destroy(xpc_genarr_ ## name ## _t* genarr) { \
|
||||
for (size_t i = 0; i < genarr->length; ++i) { \
|
||||
if (genarr->item_dtor) { \
|
||||
genarr->item_dtor(&genarr->array[i]); \
|
||||
} \
|
||||
} \
|
||||
if (genarr->array) {\
|
||||
free(genarr->array); \
|
||||
} \
|
||||
genarr->array = NULL; \
|
||||
genarr->length = 0; \
|
||||
genarr->size = 0; \
|
||||
}; \
|
||||
static bool xpc_genarr_ ## name ## _get(xpc_genarr_ ## name ## _t* genarr, size_t index, type* value) { \
|
||||
bool result = false; \
|
||||
if (index < genarr->length) { \
|
||||
if (value) { \
|
||||
memcpy(value, &genarr->array[index], sizeof(type)); \
|
||||
} \
|
||||
result = true; \
|
||||
} \
|
||||
return result; \
|
||||
}; \
|
||||
static bool xpc_genarr_ ## name ## _set(xpc_genarr_ ## name ## _t* genarr, size_t index, type const* value) { \
|
||||
bool result = false; \
|
||||
if (index < genarr->length) { \
|
||||
if (genarr->item_dtor) { \
|
||||
genarr->item_dtor(&genarr->array[index]); \
|
||||
} \
|
||||
memcpy(&genarr->array[index], value, sizeof(type)); \
|
||||
result = true; \
|
||||
} \
|
||||
return result; \
|
||||
}; \
|
||||
static bool xpc_genarr_ ## name ## _append(xpc_genarr_ ## name ## _t* genarr, type const* value) { \
|
||||
bool result = false; \
|
||||
if (genarr->size < genarr->length + 1) { \
|
||||
size_t new_size = 0; \
|
||||
type* new_array = NULL; \
|
||||
if (genarr->resize_by_factor) { \
|
||||
new_size = (genarr->size == 0) ? 1 : genarr->size * 2; \
|
||||
} else { \
|
||||
new_size = genarr->length + 1; \
|
||||
} \
|
||||
new_array = realloc(genarr->array, sizeof(type) * new_size); \
|
||||
if (new_array) { \
|
||||
genarr->array = new_array; \
|
||||
genarr->size = new_size; \
|
||||
} \
|
||||
} \
|
||||
if (genarr->size >= genarr->length + 1) { \
|
||||
if (value) { \
|
||||
memcpy(&genarr->array[genarr->length++], value, sizeof(type)); \
|
||||
} \
|
||||
result = true; \
|
||||
} \
|
||||
return result; \
|
||||
}; \
|
||||
static bool xpc_genarr_ ## name ## _remove(xpc_genarr_ ## name ## _t* genarr, size_t index) { \
|
||||
bool result = false; \
|
||||
size_t new_size = genarr->size; \
|
||||
if (index < genarr->length) { \
|
||||
if (genarr->item_dtor) { \
|
||||
genarr->item_dtor(&genarr->array[index]); \
|
||||
} \
|
||||
memmove(&genarr->array[index], &genarr->array[index + 1], (genarr->length - index - 1) * sizeof(type)); \
|
||||
--genarr->length; \
|
||||
result = true; \
|
||||
} \
|
||||
if (genarr->resize_by_factor && genarr->length <= genarr->size / 2) { \
|
||||
new_size = genarr->size / 2; \
|
||||
} else if (!genarr->resize_by_factor && genarr->length < genarr->size) { \
|
||||
new_size = genarr->length; \
|
||||
} \
|
||||
if (new_size != genarr->size) { \
|
||||
type* new_array = realloc(genarr->array, sizeof(type) * new_size); \
|
||||
if (new_array) { \
|
||||
genarr->array = new_array; \
|
||||
genarr->size = new_size; \
|
||||
} \
|
||||
} \
|
||||
return result; \
|
||||
}; \
|
||||
static bool xpc_genarr_ ## name ## _insert(xpc_genarr_ ## name ## _t* genarr, size_t index, type const* value) { \
|
||||
bool result = false; \
|
||||
if (index == genarr->length || index == XPC_GENARR_APPEND) { \
|
||||
result = xpc_genarr_ ## name ## _append(genarr, value); \
|
||||
} else if (index < genarr->length) { \
|
||||
result = xpc_genarr_ ## name ## _append(genarr, NULL); \
|
||||
if (result) { \
|
||||
memmove(&genarr->array[index + 1], &genarr->array[index], (genarr->length - index - 2) * sizeof(type)); \
|
||||
if (value) { \
|
||||
memcpy(&genarr->array[index], value, sizeof(type)); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
return result; \
|
||||
}; \
|
||||
static size_t xpc_genarr_ ## name ## _length(xpc_genarr_ ## name ## _t* genarr) { \
|
||||
return genarr->length; \
|
||||
}; \
|
||||
static type* xpc_genarr_ ## name ## _data(xpc_genarr_ ## name ## _t* genarr) { \
|
||||
return genarr->array; \
|
||||
}; \
|
||||
static void xpc_genarr_ ## name ## _iterate(xpc_genarr_ ## name ## _t* genarr, void* context, xpc_genarr_ ## name ## _iterator_f iterator) { \
|
||||
for (size_t i = 0; i < genarr->length; ++i) { \
|
||||
if (!iterator(context, i, &genarr->array[i])) { \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
};
|
||||
|
||||
#define XPC_GENARR_SEARCH_DEF(name, type, static) \
|
||||
static bool xpc_genarr_ ## name ## _search_iterator(void* context, size_t index, type* value) { \
|
||||
xpc_genarr_ ## name ## _search_context_t* search_context = context; \
|
||||
if (memcmp(value, search_context->target, sizeof(type)) == 0) { \
|
||||
search_context->index = index; \
|
||||
return false; \
|
||||
} \
|
||||
return true; \
|
||||
}; \
|
||||
static size_t xpc_genarr_ ## name ## _find(xpc_genarr_ ## name ## _t* genarr, type const* target) { \
|
||||
xpc_genarr_ ## name ## _search_context_t search_context = { \
|
||||
.index = SIZE_MAX, \
|
||||
.target = target, \
|
||||
}; \
|
||||
xpc_genarr_ ## name ## _iterate(genarr, &search_context, xpc_genarr_ ## name ## _search_iterator); \
|
||||
return search_context.index; \
|
||||
}; \
|
||||
|
||||
#define XPC_GENARR_BLOCKS_DEF(name, type, static) \
|
||||
static void xpc_genarr_ ## name ## _block_iterate(xpc_genarr_ ## name ## _t* genarr, xpc_genarr_ ## name ## _block_iterator_t block_iterator) { \
|
||||
for (size_t i = 0; i < genarr->length; ++i) { \
|
||||
if (!block_iterator(i, &genarr->array[i])) { \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
};
|
||||
|
||||
#endif // _XPC_GENERIC_ARRAY_H_
|
@ -28,163 +28,38 @@
|
||||
#ifndef _LIBXPC_XPC_INTERNAL_H
|
||||
#define _LIBXPC_XPC_INTERNAL_H
|
||||
|
||||
#include <sys/queue.h>
|
||||
#include <xpc/xpc.h>
|
||||
#include <xpc/activity.h>
|
||||
#include <darling/emulation/simple.h>
|
||||
#include <xpc/private.h>
|
||||
|
||||
#ifdef XPC_DEBUG
|
||||
#define debugf(...) \
|
||||
do { \
|
||||
fprintf(stderr, "%s: ", __func__); \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
fprintf(stderr, "\n"); \
|
||||
} while(0);
|
||||
#else
|
||||
#define debugf(...) __simple_kprintf("libxpc: " __VA_ARGS__)
|
||||
#endif
|
||||
#include <xpc/util.h>
|
||||
|
||||
#define _XPC_TYPE_INVALID 0
|
||||
#define _XPC_TYPE_DICTIONARY 1
|
||||
#define _XPC_TYPE_ARRAY 2
|
||||
#define _XPC_TYPE_BOOL 3
|
||||
#define _XPC_TYPE_CONNECTION 4
|
||||
#define _XPC_TYPE_ENDPOINT 5
|
||||
#define _XPC_TYPE_NULL 6
|
||||
#define _XPC_TYPE_ACTIVITY 7
|
||||
#define _XPC_TYPE_INT64 8
|
||||
#define _XPC_TYPE_UINT64 9
|
||||
#define _XPC_TYPE_DATE 10
|
||||
#define _XPC_TYPE_DATA 11
|
||||
#define _XPC_TYPE_STRING 12
|
||||
#define _XPC_TYPE_UUID 13
|
||||
#define _XPC_TYPE_FD 14
|
||||
#define _XPC_TYPE_SHMEM 15
|
||||
#define _XPC_TYPE_ERROR 16
|
||||
#define _XPC_TYPE_DOUBLE 17
|
||||
#define _XPC_TYPE_POINTER 18
|
||||
#define _XPC_TYPE_MAX _XPC_TYPE_POINTER
|
||||
#import <xpc/objects/base.h>
|
||||
|
||||
#define XPC_SEQID "XPC sequence number"
|
||||
|
||||
struct xpc_object;
|
||||
struct xpc_dict_pair;
|
||||
|
||||
TAILQ_HEAD(xpc_dict_head, xpc_dict_pair);
|
||||
TAILQ_HEAD(xpc_array_head, xpc_object);
|
||||
|
||||
struct xpc_connection {
|
||||
const char * xc_name;
|
||||
mach_port_t xc_remote_port;
|
||||
mach_port_t xc_local_port;
|
||||
xpc_handler_t xc_handler;
|
||||
dispatch_source_t xc_recv_source;
|
||||
dispatch_queue_t xc_send_queue;
|
||||
dispatch_queue_t xc_recv_queue;
|
||||
dispatch_queue_t xc_target_queue;
|
||||
int xc_suspend_count;
|
||||
int xc_transaction_count;
|
||||
int xc_flags;
|
||||
void * xc_context;
|
||||
struct xpc_connection * xc_parent;
|
||||
uid_t xc_remote_euid;
|
||||
gid_t xc_remote_guid;
|
||||
pid_t xc_remote_pid;
|
||||
au_asid_t xc_remote_asid;
|
||||
TAILQ_HEAD(, xpc_pending_call) xc_pending;
|
||||
TAILQ_HEAD(, xpc_connection) xc_peers;
|
||||
TAILQ_ENTRY(xpc_connection) xc_link;
|
||||
};
|
||||
|
||||
typedef union {
|
||||
struct xpc_dict_head dict;
|
||||
struct xpc_array_head array;
|
||||
uint64_t ui;
|
||||
int64_t i;
|
||||
const char *str;
|
||||
bool b;
|
||||
double d;
|
||||
uintptr_t ptr;
|
||||
int fd;
|
||||
uuid_t uuid;
|
||||
struct {
|
||||
struct xpc_object * criteria;
|
||||
xpc_activity_state_t state;
|
||||
} activity;
|
||||
mach_port_t port;
|
||||
struct xpc_connection connection;
|
||||
} xpc_u;
|
||||
|
||||
|
||||
// indicates that the object should never be released
|
||||
#define _XPC_KEEP_ALIVE UINT32_MAX
|
||||
#define _XPC_FROM_WIRE 0x1
|
||||
struct xpc_object {
|
||||
uint8_t xo_xpc_type;
|
||||
uint16_t xo_flags;
|
||||
volatile uint32_t xo_refcnt;
|
||||
size_t xo_size;
|
||||
xpc_u xo_u;
|
||||
audit_token_t * xo_audit_token;
|
||||
TAILQ_ENTRY(xpc_object) xo_link;
|
||||
};
|
||||
|
||||
struct xpc_dict_pair {
|
||||
const char * key;
|
||||
struct xpc_object * value;
|
||||
TAILQ_ENTRY(xpc_dict_pair) xo_link;
|
||||
};
|
||||
|
||||
struct xpc_pending_call {
|
||||
uint64_t xp_id;
|
||||
xpc_object_t xp_response;
|
||||
dispatch_queue_t xp_queue;
|
||||
xpc_handler_t xp_handler;
|
||||
TAILQ_ENTRY(xpc_pending_call) xp_link;
|
||||
};
|
||||
|
||||
struct xpc_service {
|
||||
mach_port_t xs_remote_port;
|
||||
TAILQ_HEAD(, xpc_connection) xs_connections;
|
||||
};
|
||||
|
||||
#define xo_str xo_u.str
|
||||
#define xo_bool xo_u.b
|
||||
#define xo_uint xo_u.ui
|
||||
#define xo_int xo_u.i
|
||||
#define xo_ptr xo_u.ptr
|
||||
#define xo_d xo_u.d
|
||||
#define xo_fd xo_u.fd
|
||||
#define xo_uuid xo_u.uuid
|
||||
#define xo_port xo_u.port
|
||||
#define xo_array xo_u.array
|
||||
#define xo_dict xo_u.dict
|
||||
#define xo_activity xo_u.activity
|
||||
#define xo_connection xo_u.connection
|
||||
|
||||
#define _XPC_GLOBAL_OBJECT_INITIALIZER(type, extra_size, union_value) { \
|
||||
.xo_xpc_type = type, \
|
||||
.xo_flags = 0, \
|
||||
.xo_refcnt = _XPC_KEEP_ALIVE, \
|
||||
.xo_size = extra_size, \
|
||||
.xo_u = { \
|
||||
union_value, \
|
||||
}, \
|
||||
.xo_audit_token = NULL, \
|
||||
.xo_link = NULL, \
|
||||
}
|
||||
|
||||
__private_extern__ struct xpc_object *_xpc_prim_create(int type, xpc_u value,
|
||||
size_t size);
|
||||
__private_extern__ struct xpc_object *_xpc_prim_create_flags(int type,
|
||||
xpc_u value, size_t size, uint16_t flags);
|
||||
__private_extern__ const char *_xpc_get_type_name(xpc_object_t obj);
|
||||
__private_extern__ void xpc_object_destroy(struct xpc_object *xo);
|
||||
__private_extern__ int xpc_pipe_send(xpc_object_t obj, mach_port_t dst,
|
||||
mach_port_t local, uint64_t id);
|
||||
__private_extern__ int xpc_pipe_receive(mach_port_t local, mach_port_t *remote,
|
||||
xpc_object_t *result, uint64_t *id);
|
||||
|
||||
void fail_log(const char *) __dead2;
|
||||
#import <xpc/objects/activity.h>
|
||||
#import <xpc/objects/array.h>
|
||||
#import <xpc/objects/bool.h>
|
||||
#import <xpc/objects/bundle.h>
|
||||
#import <xpc/objects/connection.h>
|
||||
#import <xpc/objects/data.h>
|
||||
#import <xpc/objects/date.h>
|
||||
#import <xpc/objects/deserializer.h>
|
||||
#import <xpc/objects/dictionary.h>
|
||||
#import <xpc/objects/double.h>
|
||||
#import <xpc/objects/endpoint.h>
|
||||
#import <xpc/objects/error.h>
|
||||
#import <xpc/objects/fd.h>
|
||||
#import <xpc/objects/file_transfer.h>
|
||||
#import <xpc/objects/int64.h>
|
||||
#import <xpc/objects/mach_recv.h>
|
||||
#import <xpc/objects/mach_send.h>
|
||||
#import <xpc/objects/null.h>
|
||||
#import <xpc/objects/pipe.h>
|
||||
#import <xpc/objects/pointer.h>
|
||||
#import <xpc/objects/serializer.h>
|
||||
#import <xpc/objects/service_instance.h>
|
||||
#import <xpc/objects/service.h>
|
||||
#import <xpc/objects/shmem.h>
|
||||
#import <xpc/objects/string.h>
|
||||
#import <xpc/objects/uint64.h>
|
||||
#import <xpc/objects/uuid.h>
|
||||
|
||||
#endif /* _LIBXPC_XPC_INTERNAL_H */
|
||||
|
10
internal-include/xpc/internal_base.h
Normal file
10
internal-include/xpc/internal_base.h
Normal file
@ -0,0 +1,10 @@
|
||||
#ifndef _XPC_INTERNAL_BASE_H_
|
||||
#define _XPC_INTERNAL_BASE_H_
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define XPC_DEBUG 1
|
||||
#else
|
||||
#define XPC_DEBUG 0
|
||||
#endif
|
||||
|
||||
#endif // _XPC_INTERNAL_BASE_H_
|
@ -3,7 +3,9 @@
|
||||
|
||||
#import <xpc/objects/base.h>
|
||||
|
||||
XPC_IGNORE_DUPLICATE_PROTOCOL_PUSH;
|
||||
XPC_CLASS_DECL(activity);
|
||||
XPC_IGNORE_DUPLICATE_PROTOCOL_POP;
|
||||
|
||||
struct xpc_activity_s {
|
||||
struct xpc_object_s base;
|
||||
|
@ -8,7 +8,7 @@ XPC_CLASS_DECL(array);
|
||||
|
||||
struct xpc_array_s {
|
||||
struct xpc_object_s base;
|
||||
NSUInteger size;
|
||||
unsigned long size; // not NSUInteger or size_t because it needs to be `unsigned long` in 32-bit builds as well
|
||||
XPC_CLASS(object)** array;
|
||||
};
|
||||
|
||||
|
@ -3,14 +3,15 @@
|
||||
|
||||
#import <os/object_private.h>
|
||||
#import <objc/NSObject.h>
|
||||
|
||||
#ifndef __XPC_INDIRECT__
|
||||
OS_OBJECT_DECL(xpc_object);
|
||||
#endif
|
||||
#import <xpc/internal_base.h>
|
||||
|
||||
#define __XPC_INDIRECT__
|
||||
#import <xpc/base.h>
|
||||
|
||||
XPC_IGNORE_DUPLICATE_PROTOCOL_PUSH;
|
||||
OS_OBJECT_DECL(xpc_object);
|
||||
XPC_IGNORE_DUPLICATE_PROTOCOL_POP;
|
||||
|
||||
#define XPC_CLASS(name) OS_OBJECT_CLASS(xpc_ ## name)
|
||||
|
||||
#define XPC_CLASS_DECL(name) OS_OBJECT_DECL_SUBCLASS(xpc_ ## name, xpc_object)
|
||||
@ -21,7 +22,7 @@ OS_OBJECT_DECL(xpc_object);
|
||||
|
||||
#define XPC_CLASS_HEADER(name) \
|
||||
OS_OBJECT_NONLAZY_CLASS_LOAD \
|
||||
+ (NSUInteger)_instanceSize \
|
||||
+ (NSUInteger)instanceSize \
|
||||
{ \
|
||||
return sizeof(struct xpc_ ## name ## _s); \
|
||||
}
|
||||
@ -76,6 +77,53 @@ OS_OBJECT_DECL(xpc_object);
|
||||
} \
|
||||
@end
|
||||
|
||||
#define XPC_WRAPPER_CLASS_SERIAL_IMPL(name, type, serial_type, serial_U32_or_U64, serial_uint32_t_or_uint64_t) \
|
||||
@implementation XPC_CLASS(name) (XPCSerialization) \
|
||||
- (BOOL)serializable \
|
||||
{ \
|
||||
return YES; \
|
||||
} \
|
||||
- (NSUInteger)serializationLength \
|
||||
{ \
|
||||
return xpc_serial_padded_length(sizeof(xpc_serial_type_t)) + xpc_serial_padded_length(sizeof(serial_uint32_t_or_uint64_t)); \
|
||||
} \
|
||||
+ (instancetype)deserialize: (XPC_CLASS(deserializer)*)deserializer \
|
||||
{ \
|
||||
XPC_CLASS(name)* result = nil; \
|
||||
xpc_serial_type_t inputType = XPC_SERIAL_TYPE_INVALID; \
|
||||
serial_uint32_t_or_uint64_t value = 0; \
|
||||
if (![deserializer readU32: &inputType]) { \
|
||||
goto error_out; \
|
||||
} \
|
||||
if (inputType != XPC_SERIAL_TYPE_ ## serial_type) { \
|
||||
goto error_out; \
|
||||
} \
|
||||
if (![deserializer read ## serial_U32_or_U64: &value]) { \
|
||||
goto error_out; \
|
||||
} \
|
||||
result = [[[self class] alloc] initWithValue: (type)value]; \
|
||||
return result; \
|
||||
error_out: \
|
||||
if (result != nil) { \
|
||||
[result release]; \
|
||||
} \
|
||||
return nil; \
|
||||
} \
|
||||
- (BOOL)serialize: (XPC_CLASS(serializer)*)serializer \
|
||||
{ \
|
||||
XPC_THIS_DECL(name); \
|
||||
if (![serializer writeU32: XPC_SERIAL_TYPE_ ## serial_type]) { \
|
||||
goto error_out; \
|
||||
} \
|
||||
if (![serializer write ## serial_U32_or_U64: (serial_uint32_t_or_uint64_t)this->value]) { \
|
||||
goto error_out; \
|
||||
} \
|
||||
return YES; \
|
||||
error_out: \
|
||||
return NO; \
|
||||
} \
|
||||
@end
|
||||
|
||||
// hack to create symbol aliases programmatically, because the `alias` attribute isn't supported on Darwin platforms
|
||||
#define _CREATE_ALIAS(original, alias) __asm__(".globl " original "; .globl " alias "; .equiv " alias ", " original)
|
||||
|
||||
@ -110,11 +158,28 @@ struct xpc_object_s {
|
||||
XPC_EXPORT
|
||||
@interface XPC_CLASS(object) : OS_OBJECT_CLASS(object) <XPC_CLASS(object)>
|
||||
|
||||
+ (NSUInteger)_instanceSize;
|
||||
@property(readonly, class) NSUInteger instanceSize;
|
||||
|
||||
// note that this method returns a string that must be freed
|
||||
- (char*)xpcDescription;
|
||||
|
||||
@end
|
||||
|
||||
@class XPC_CLASS(serializer);
|
||||
@class XPC_CLASS(deserializer);
|
||||
|
||||
@interface XPC_CLASS(object) (XPCSerialization)
|
||||
|
||||
@property(readonly) BOOL serializable;
|
||||
|
||||
// NOTE for implementations: this length MUST include any padding that might be added.
|
||||
// use `xpc_serial_padded_length` for each component of the serialization.
|
||||
@property(readonly) NSUInteger serializationLength;
|
||||
|
||||
+ (instancetype)deserialize: (XPC_CLASS(deserializer)*)deserializer;
|
||||
|
||||
- (BOOL)serialize: (XPC_CLASS(serializer)*)serializer;
|
||||
|
||||
@end
|
||||
|
||||
#endif // _XPC_OBJECTS_BASE_H_
|
||||
|
@ -1,16 +1,144 @@
|
||||
#ifndef _XPC_OBJECTS_CONNECTION_H_
|
||||
#define _XPC_OBJECTS_CONNECTION_H_
|
||||
|
||||
#import <xpc/objects/base.h>
|
||||
// ensure libdispatch exposes libxpc SPI
|
||||
#define DISPATCH_MACH_SPI 1
|
||||
#include <dispatch/dispatch.h>
|
||||
#ifndef __DISPATCH_INDIRECT__
|
||||
#define __DISPATCH_INDIRECT__ 1
|
||||
#endif
|
||||
#include <dispatch/mach_private.h>
|
||||
|
||||
#import <xpc/objects/base.h>
|
||||
#import <xpc/xpc.h>
|
||||
#import <xpc/connection.h>
|
||||
#import <xpc/generic_array.h>
|
||||
|
||||
#include <stdatomic.h>
|
||||
|
||||
XPC_IGNORE_DUPLICATE_PROTOCOL_PUSH;
|
||||
XPC_CLASS_DECL(connection);
|
||||
XPC_IGNORE_DUPLICATE_PROTOCOL_POP;
|
||||
|
||||
@class XPC_CLASS(connection);
|
||||
@class XPC_CLASS(dictionary);
|
||||
@class XPC_CLASS(endpoint);
|
||||
|
||||
typedef struct xpc_connection_reply_context_s {
|
||||
xpc_handler_t handler;
|
||||
dispatch_queue_t queue;
|
||||
} xpc_connection_reply_context_t;
|
||||
|
||||
XPC_GENARR_DECL(connection, XPC_CLASS(connection)*, /* non-static */);
|
||||
XPC_GENARR_SEARCH_DECL(connection, XPC_CLASS(connection)*, /* non-static */);
|
||||
XPC_GENARR_BLOCKS_DECL(connection, XPC_CLASS(connection)*, /* non-static */);
|
||||
XPC_GENARR_STRUCT(connection, XPC_CLASS(connection)*);
|
||||
|
||||
struct xpc_connection_s {
|
||||
struct xpc_object_s base;
|
||||
|
||||
//
|
||||
// immutable after init
|
||||
//
|
||||
bool is_listener;
|
||||
bool is_server_peer;
|
||||
const char* service_name;
|
||||
XPC_CLASS(connection)* parent_server;
|
||||
|
||||
//
|
||||
// immutable after activation
|
||||
//
|
||||
mach_port_t recv_port;
|
||||
mach_port_t checkin_port;
|
||||
|
||||
//
|
||||
// mutable only when locked
|
||||
//
|
||||
pthread_rwlock_t activated_lock;
|
||||
bool activated;
|
||||
pthread_rwlock_t server_peers_lock;
|
||||
xpc_genarr_connection_t server_peers;
|
||||
|
||||
//
|
||||
// mutable and lock-free
|
||||
//
|
||||
dispatch_mach_t mach_ctx;
|
||||
void* user_context;
|
||||
xpc_handler_t event_handler;
|
||||
xpc_finalizer_t finalizer;
|
||||
atomic_intmax_t suspension_count;
|
||||
bool is_cancelled;
|
||||
|
||||
//
|
||||
// other
|
||||
// (each one has it's own explaination)
|
||||
//
|
||||
|
||||
// immutable most of the time and can be treated as such.
|
||||
// only mutated when the connection is (re)activated
|
||||
mach_port_t send_port;
|
||||
};
|
||||
|
||||
@interface XPC_CLASS_INTERFACE(connection)
|
||||
|
||||
@property(assign) void* userContext;
|
||||
@property(readonly) const char* serviceName;
|
||||
@property(copy) xpc_handler_t eventHandler;
|
||||
@property(assign) xpc_finalizer_t finalizer;
|
||||
@property(readonly) mach_port_t sendPort;
|
||||
@property(readonly) mach_port_t receivePort;
|
||||
@property(strong) dispatch_queue_t targetQueue;
|
||||
@property(assign /* actually weak */) XPC_CLASS(connection)* parentServer;
|
||||
|
||||
- (instancetype)initAsClientForService: (const char*)serviceName queue: (dispatch_queue_t)queue;
|
||||
- (instancetype)initAsServerForService: (const char*)serviceName queue: (dispatch_queue_t)queue;
|
||||
- (instancetype)initWithEndpoint: (XPC_CLASS(endpoint)*)endpoint;
|
||||
- (instancetype)initAsAnonymousServerWithQueue: (dispatch_queue_t)queue;
|
||||
// NOTE: this initializer will take ownership of the ports references passed in
|
||||
// (i.e. the caller loses a reference on both the send port and the receive port)
|
||||
- (instancetype)initAsServerPeerForServer: (XPC_CLASS(connection)*)server sendPort: (mach_port_t)sendPort receivePort: (mach_port_t)receivePort;
|
||||
|
||||
- (void)resume;
|
||||
- (void)suspend;
|
||||
- (void)cancel;
|
||||
|
||||
/**
|
||||
* Takes ownership of the given server peer connection.
|
||||
*/
|
||||
- (void)addServerPeer: (XPC_CLASS(connection)*)serverPeer;
|
||||
|
||||
/**
|
||||
* Disowns the given server peer connection.
|
||||
*/
|
||||
- (void)removeServerPeer: (XPC_CLASS(connection)*)serverPeer;
|
||||
|
||||
/**
|
||||
* Increments the connection's suspension count. Does not actually suspend the connection.
|
||||
*
|
||||
* @returns `YES` if the connection is now suspended as a result of this call, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)incrementSuspensionCount;
|
||||
|
||||
/**
|
||||
* Decrements the connection's suspension count. Does not actually resume the connection.
|
||||
*
|
||||
* @returns `YES` if the connection is now resumed as a result of this call, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)decrementSuspensionCount;
|
||||
|
||||
/**
|
||||
* Activates the connection, if it has not been activated already.
|
||||
*
|
||||
* @returns `YES` if the connection was activated as a result of this call, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)activate;
|
||||
|
||||
- (void)enqueueSendBarrier: (dispatch_block_t)barrier;
|
||||
|
||||
- (void)sendMessage: (XPC_CLASS(dictionary)*)message;
|
||||
- (void)sendMessage: (XPC_CLASS(dictionary)*)message queue: (dispatch_queue_t)queue withReply: (xpc_handler_t)handler;
|
||||
- (xpc_object_t)sendMessageWithSynchronousReply: (XPC_CLASS(dictionary)*)message;
|
||||
|
||||
@end
|
||||
|
||||
#endif // _XPC_OBJECTS_CONNECTION_H_
|
||||
|
103
internal-include/xpc/objects/deserializer.h
Normal file
103
internal-include/xpc/objects/deserializer.h
Normal file
@ -0,0 +1,103 @@
|
||||
#ifndef _XPC_OBJECTS_DESERIALIZER_H_
|
||||
#define _XPC_OBJECTS_DESERIALIZER_H_
|
||||
|
||||
#import <xpc/objects/base.h>
|
||||
|
||||
#include <dispatch/dispatch.h>
|
||||
#ifndef __DISPATCH_INDIRECT__
|
||||
#define __DISPATCH_INDIRECT__ 1
|
||||
#endif
|
||||
#include <dispatch/mach_private.h>
|
||||
|
||||
#define MACH_MSG_RECV_DISPOSITION_FIRST MACH_MSG_TYPE_PORT_NAME
|
||||
#define MACH_MSG_RECV_DISPOSITION_LAST MACH_MSG_TYPE_PORT_SEND_ONCE
|
||||
#define MACH_MSG_RECV_DISPOSITION_COUNT (MACH_MSG_RECV_DISPOSITION_LAST - MACH_MSG_RECV_DISPOSITION_FIRST + 1)
|
||||
|
||||
// NOTE: this class is not present in the official libxpc
|
||||
|
||||
XPC_CLASS_DECL(deserializer);
|
||||
|
||||
typedef struct xpc_deserial_port_array {
|
||||
mach_port_t* array;
|
||||
size_t length;
|
||||
size_t offset;
|
||||
} xpc_deserial_port_array_t;
|
||||
|
||||
struct xpc_deserializer_s {
|
||||
struct xpc_object_s base;
|
||||
dispatch_mach_msg_t mach_msg;
|
||||
size_t length;
|
||||
size_t offset;
|
||||
const void* buffer;
|
||||
xpc_deserial_port_array_t port_arrays[MACH_MSG_RECV_DISPOSITION_COUNT];
|
||||
};
|
||||
|
||||
@class XPC_CLASS(dictionary);
|
||||
|
||||
@interface XPC_CLASS_INTERFACE(deserializer)
|
||||
|
||||
@property(readonly) NSUInteger offset;
|
||||
|
||||
/**
|
||||
* The send(-once) port that can be used to reply to the remote peer that sent the message being deserialized.
|
||||
*/
|
||||
@property(readonly) mach_port_t remotePort;
|
||||
|
||||
/**
|
||||
* Creates a new, autoreleased deserializer for the given message.
|
||||
*/
|
||||
+ (instancetype)deserializerWithMessage: (dispatch_mach_msg_t)message;
|
||||
|
||||
/**
|
||||
* Transforms the given Mach message directly into an XPC dictionary.
|
||||
* Returns `nil` if the given message does not contain a dictionary as the root object or it is not a valid XPC message.
|
||||
*
|
||||
* This method will automatically populate remote peer information in the returned dictionary.
|
||||
*
|
||||
* The returned dictionary is autoreleased.
|
||||
*
|
||||
* @note This method consumes the message passed in (regardless of whether it succeeds or not),
|
||||
* so you should not use it anymore after passing it to this method.
|
||||
*/
|
||||
+ (XPC_CLASS(dictionary)*)process: (dispatch_mach_msg_t)message;
|
||||
|
||||
/**
|
||||
* Initializes this deserializer using the given Mach message.
|
||||
* Returns `nil` if the given message is not a valid XPC message.
|
||||
*
|
||||
* @note This initializer consumes the message passed in (regardless of whether it succeeds or not),
|
||||
* so you should not use it anymore after passing it to this initializer.
|
||||
*/
|
||||
- (instancetype)initWithMessage: (dispatch_mach_msg_t)message;
|
||||
|
||||
/**
|
||||
* Initializes this deserializer using the given Mach message, without expecting an XPC header (i.e. XPC magic and version).
|
||||
*
|
||||
* @note This initializer consumes the message passed in (regardless of whether it succeeds or not),
|
||||
* so you should not use it anymore after passing it to this initializer.
|
||||
*/
|
||||
- (instancetype)initWithoutHeaderWithMessage: (dispatch_mach_msg_t)message;
|
||||
|
||||
// NOTE: all reads from the internal buffer are subject to padding,
|
||||
// so the number of bytes passed in might not be the same as number of bytes actually read.
|
||||
|
||||
- (BOOL)read: (void*)data length: (NSUInteger)length;
|
||||
- (BOOL)consume: (NSUInteger)length region: (const void**)region;
|
||||
- (BOOL)readString: (const char**)string;
|
||||
- (BOOL)readU32: (uint32_t*)value;
|
||||
- (BOOL)readU64: (uint64_t*)value;
|
||||
- (BOOL)readPort: (mach_port_t*)port type: (mach_msg_type_name_t)type;
|
||||
- (BOOL)readObject: (XPC_CLASS(object)**)object;
|
||||
|
||||
- (BOOL)peek: (void*)data length: (NSUInteger)length;
|
||||
- (BOOL)peekNoCopy: (NSUInteger)length region: (const void**)region;
|
||||
- (BOOL)peekString: (const char**)string;
|
||||
- (BOOL)peekU32: (uint32_t*)value;
|
||||
- (BOOL)peekU64: (uint64_t*)value;
|
||||
|
||||
// ports are non-trivial to read, so they don't have a peeking method.
|
||||
// objects might need to read ports, so they also don't have a peeking method.
|
||||
|
||||
@end
|
||||
|
||||
#endif // _XPC_OBJECTS_DESERIALIZER_H_
|
@ -2,11 +2,12 @@
|
||||
#define _XPC_OBJECTS_DICTIONARY_H_
|
||||
|
||||
#import <xpc/objects/base.h>
|
||||
#import <xpc/objects/connection.h>
|
||||
|
||||
#include <sys/queue.h>
|
||||
#include <mach/mach.h>
|
||||
|
||||
@class XPC_CLASS(string);
|
||||
@class XPC_CLASS(connection);
|
||||
|
||||
XPC_CLASS_DECL(dictionary);
|
||||
|
||||
@ -22,6 +23,8 @@ struct xpc_dictionary_s {
|
||||
NSUInteger size;
|
||||
LIST_HEAD(, xpc_dictionary_entry_s) head;
|
||||
XPC_CLASS(connection)* associatedConnection;
|
||||
mach_port_t incoming_port;
|
||||
mach_port_t outgoing_port;
|
||||
};
|
||||
|
||||
@interface XPC_CLASS_INTERFACE(dictionary)
|
||||
@ -29,8 +32,27 @@ struct xpc_dictionary_s {
|
||||
// this API is modeled after NSMutableDictionary
|
||||
|
||||
@property(readonly) NSUInteger count;
|
||||
// NOTE: i'm not sure if the associated connection should be strongly or weakly associated
|
||||
@property(strong) XPC_CLASS(connection)* associatedConnection;
|
||||
@property(assign) XPC_CLASS(connection)* associatedConnection;
|
||||
|
||||
/**
|
||||
* The send(-once) port that can be used to reply to the remote peer that sent this dictionary.
|
||||
*/
|
||||
@property(assign) mach_port_t incomingPort;
|
||||
|
||||
/**
|
||||
* The send(-once) port that this dictionary is going to.
|
||||
*/
|
||||
@property(assign) mach_port_t outgoingPort;
|
||||
|
||||
/**
|
||||
* `YES` if this dictionary expects a reply from a remote peer, `NO` otherwise.
|
||||
*/
|
||||
@property(readonly) BOOL expectsReply;
|
||||
|
||||
/**
|
||||
* `YES` if this dictionary is a reply to an earlier message from a remote peer, `NO` otherwise.
|
||||
*/
|
||||
@property(readonly) BOOL isReply;
|
||||
|
||||
- (instancetype)initWithObjects: (XPC_CLASS(object)* const*)objects forKeys: (const char* const*)keys count: (NSUInteger)count;
|
||||
|
||||
|
@ -4,15 +4,29 @@
|
||||
#import <xpc/objects/base.h>
|
||||
#import <xpc/objects/connection.h>
|
||||
|
||||
XPC_IGNORE_DUPLICATE_PROTOCOL_PUSH;
|
||||
XPC_CLASS_DECL(endpoint);
|
||||
XPC_IGNORE_DUPLICATE_PROTOCOL_POP;
|
||||
|
||||
struct xpc_endpoint_s {
|
||||
struct xpc_object_s base;
|
||||
mach_port_t port;
|
||||
};
|
||||
|
||||
@interface XPC_CLASS_INTERFACE(endpoint)
|
||||
|
||||
@property(assign) mach_port_t port;
|
||||
|
||||
- (instancetype)initWithConnection: (XPC_CLASS(connection)*)connection;
|
||||
- (instancetype)initWithPort: (mach_port_t)port;
|
||||
- (instancetype)initWithPortNoCopy: (mach_port_t)port;
|
||||
|
||||
/**
|
||||
* Compares `self` with the given endpoint with `self` as the left-hand side and the given endpoint as the right-hand side.
|
||||
*
|
||||
* @returns 0 if the endpoints are equal, -1 if `self` comes before `rhs`, or 1 if `rhs` comes before `self`.
|
||||
*/
|
||||
- (int)compare: (XPC_CLASS(endpoint)*)rhs;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -8,10 +8,17 @@ XPC_CLASS_DECL(mach_recv);
|
||||
|
||||
struct xpc_mach_recv_s {
|
||||
struct xpc_object_s base;
|
||||
mach_port_t port;
|
||||
};
|
||||
|
||||
@interface XPC_CLASS_INTERFACE(mach_recv)
|
||||
|
||||
@property(readonly) mach_port_t port;
|
||||
|
||||
- (instancetype)initWithPort: (mach_port_t)port;
|
||||
|
||||
- (mach_port_t)extractPort;
|
||||
|
||||
@end
|
||||
|
||||
//
|
||||
|
@ -8,10 +8,20 @@ XPC_CLASS_DECL(mach_send);
|
||||
|
||||
struct xpc_mach_send_s {
|
||||
struct xpc_object_s base;
|
||||
mach_port_t port;
|
||||
};
|
||||
|
||||
@interface XPC_CLASS_INTERFACE(mach_send)
|
||||
|
||||
@property(readonly) mach_port_t port;
|
||||
|
||||
- (instancetype)initWithPort: (mach_port_t)port;
|
||||
- (instancetype)initWithPortNoCopy: (mach_port_t)port;
|
||||
- (instancetype)initWithPort: (mach_port_t)port disposition: (mach_msg_type_name_t)disposition;
|
||||
|
||||
- (mach_port_t)extractPort;
|
||||
- (mach_port_t)copyPort;
|
||||
|
||||
@end
|
||||
|
||||
//
|
||||
|
@ -3,7 +3,9 @@
|
||||
|
||||
#import <xpc/objects/base.h>
|
||||
|
||||
XPC_IGNORE_DUPLICATE_PROTOCOL_PUSH;
|
||||
XPC_CLASS_DECL(pipe);
|
||||
XPC_IGNORE_DUPLICATE_PROTOCOL_POP;
|
||||
|
||||
struct xpc_pipe_s {
|
||||
struct xpc_object_s base;
|
||||
|
@ -3,14 +3,93 @@
|
||||
|
||||
#import <xpc/objects/base.h>
|
||||
|
||||
#include <dispatch/dispatch.h>
|
||||
#ifndef __DISPATCH_INDIRECT__
|
||||
#define __DISPATCH_INDIRECT__ 1
|
||||
#endif
|
||||
#include <dispatch/mach_private.h>
|
||||
|
||||
#define MACH_MSG_SEND_DISPOSITION_FIRST MACH_MSG_TYPE_MOVE_RECEIVE
|
||||
#define MACH_MSG_SEND_DISPOSITION_LAST MACH_MSG_TYPE_MAKE_SEND_ONCE
|
||||
#define MACH_MSG_SEND_DISPOSITION_COUNT (MACH_MSG_SEND_DISPOSITION_LAST - MACH_MSG_SEND_DISPOSITION_FIRST + 1)
|
||||
|
||||
XPC_CLASS_DECL(serializer);
|
||||
|
||||
typedef struct xpc_serial_port_array {
|
||||
mach_port_t* array;
|
||||
size_t length;
|
||||
} xpc_serial_port_array_t;
|
||||
|
||||
struct xpc_serializer_s {
|
||||
struct xpc_object_s base;
|
||||
dispatch_mach_msg_t finalized_message;
|
||||
size_t length;
|
||||
size_t offset;
|
||||
void* buffer;
|
||||
xpc_serial_port_array_t port_arrays[MACH_MSG_SEND_DISPOSITION_COUNT];
|
||||
};
|
||||
|
||||
@class XPC_CLASS(dictionary);
|
||||
|
||||
@interface XPC_CLASS_INTERFACE(serializer)
|
||||
|
||||
@property(readonly) NSUInteger offset;
|
||||
@property(readonly) BOOL isFinalized;
|
||||
|
||||
/**
|
||||
* Creates a new, autoreleased serializer.
|
||||
*/
|
||||
+ (instancetype)serializer;
|
||||
|
||||
/**
|
||||
* Initializes the serializer without automatically writing in the XPC serial message header (i.e. XPC magic and version).
|
||||
*/
|
||||
- (instancetype)initWithoutHeader;
|
||||
|
||||
/**
|
||||
* Finalizes the serializer and packs all the content written to it into a Mach message.
|
||||
*
|
||||
* After this method is called, you can no longer write new content.
|
||||
* Subsequent calls to this method simply return the same message.
|
||||
*
|
||||
* The finalized message is retained by the serializer and remains valid for as long as the serializer does.
|
||||
*
|
||||
* The returned message is not automatically retained. Therefore, if you want the message to live past the serializer,
|
||||
* make sure to retain it yourself.
|
||||
*/
|
||||
- (dispatch_mach_msg_t)finalizeWithRemotePort: (mach_port_t)remotePort localPort: (mach_port_t)localPort asReply: (BOOL)asReply expectingReply: (BOOL)expectingReply messageID: (uint32_t)messageID;
|
||||
|
||||
/**
|
||||
* Like the other `finalizeWithRemotePort:...`, but automatically determines the correct XPC message ID.
|
||||
*
|
||||
* @see finalizeWithRemotePort:localPort:asReply:expectingReply:messageID:
|
||||
*/
|
||||
- (dispatch_mach_msg_t)finalizeWithRemotePort: (mach_port_t)remotePort localPort: (mach_port_t)localPort asReply: (BOOL)asReply expectingReply: (BOOL)expectingReply;
|
||||
|
||||
/**
|
||||
* Determines whether the internal buffer would have to be expanded to append content of the given size.
|
||||
*/
|
||||
- (BOOL)needsToResizeToWrite: (NSUInteger)extraSize;
|
||||
|
||||
/**
|
||||
* Determines if the internal buffer has enough extra space to append content of the given size,
|
||||
* and if not, automatically resizes the internal buffer.
|
||||
*
|
||||
* This method only returns `NO` when the internal buffer failed to resize.
|
||||
*/
|
||||
- (BOOL)ensure: (NSUInteger)extraSize;
|
||||
|
||||
// NOTE: all writes to the internal buffer are subject to padding,
|
||||
// so the number of bytes passed in might not be the same as number of bytes actually written.
|
||||
|
||||
- (BOOL)write: (const void*)data length: (NSUInteger)length;
|
||||
- (BOOL)reserve: (NSUInteger)length region: (void**)region;
|
||||
- (BOOL)writeString: (const char*)string;
|
||||
- (BOOL)writeU32: (uint32_t)value;
|
||||
- (BOOL)writeU64: (uint64_t)value;
|
||||
- (BOOL)writePort: (mach_port_t)port type: (mach_msg_type_name_t)type;
|
||||
- (BOOL)writeObject: (XPC_CLASS(object)*)object;
|
||||
|
||||
@end
|
||||
|
||||
#endif // _XPC_OBJECTS_SERIALIZER_H_
|
||||
|
@ -3,6 +3,7 @@
|
||||
#if __OBJC2__
|
||||
// Objective-C 2.0 marks `struct objc_class` as unavailable by default.
|
||||
// we know what we're doing; let's prevent the compiler from complaining.
|
||||
#undef OBJC2_UNAVAILABLE
|
||||
#define OBJC2_UNAVAILABLE
|
||||
#endif
|
||||
|
||||
|
@ -46,231 +46,107 @@ extern "C" {
|
||||
|
||||
#define XPC_SERIAL_CURRENT_VERSION 5
|
||||
|
||||
#define XPC_SERIAL_TYPE_NULL 0x1000
|
||||
#define XPC_SERIAL_TYPE_BOOL 0x2000
|
||||
#define XPC_SERIAL_TYPE_INT64 0x3000
|
||||
#define XPC_SERIAL_TYPE_UINT64 0x4000
|
||||
#define XPC_SERIAL_TYPE_DOUBLE 0x5000
|
||||
#define XPC_SERIAL_TYPE_DATA 0x8000
|
||||
#define XPC_SERIAL_TYPE_STRING 0x9000
|
||||
#define XPC_SERIAL_TYPE_UUID 0xa000
|
||||
#define XPC_SERIAL_TYPE_FD 0xb000
|
||||
#define XPC_SERIAL_TYPE_SHMEM 0xc000
|
||||
#define XPC_SERIAL_TYPE_SEND_PORT 0xd000
|
||||
#define XPC_SERIAL_TYPE_ARRAY 0xe000
|
||||
#define XPC_SERIAL_TYPE_DICT 0xf000
|
||||
#define XPC_SERIAL_TYPE_RECV_PORT 0x15000
|
||||
#define XPC_SERIAL_TYPE_NULL 0x01000
|
||||
#define XPC_SERIAL_TYPE_BOOL 0x02000
|
||||
#define XPC_SERIAL_TYPE_INT64 0x03000
|
||||
#define XPC_SERIAL_TYPE_UINT64 0x04000
|
||||
#define XPC_SERIAL_TYPE_DOUBLE 0x05000
|
||||
#define XPC_SERIAL_TYPE_POINTER 0x06000
|
||||
#define XPC_SERIAL_TYPE_DATE 0x07000
|
||||
#define XPC_SERIAL_TYPE_DATA 0x08000
|
||||
#define XPC_SERIAL_TYPE_STRING 0x09000
|
||||
#define XPC_SERIAL_TYPE_UUID 0x0a000
|
||||
#define XPC_SERIAL_TYPE_FD 0x0b000
|
||||
#define XPC_SERIAL_TYPE_SHMEM 0x0c000
|
||||
#define XPC_SERIAL_TYPE_MACH_SEND 0x0d000
|
||||
#define XPC_SERIAL_TYPE_ARRAY 0x0e000
|
||||
#define XPC_SERIAL_TYPE_DICT 0x0f000
|
||||
#define XPC_SERIAL_TYPE_ERROR 0x10000
|
||||
#define XPC_SERIAL_TYPE_CONNECTION 0x11000
|
||||
#define XPC_SERIAL_TYPE_ENDPOINT 0x12000
|
||||
#define XPC_SERIAL_TYPE_SERIALIZER 0x13000
|
||||
#define XPC_SERIAL_TYPE_PIPE 0x14000
|
||||
#define XPC_SERIAL_TYPE_MACH_RECV 0x15000
|
||||
#define XPC_SERIAL_TYPE_BUNDLE 0x16000
|
||||
#define XPC_SERIAL_TYPE_SERVICE 0x17000
|
||||
#define XPC_SERIAL_TYPE_SERVICE_INSTANCE 0x18000
|
||||
#define XPC_SERIAL_TYPE_ACTIVITY 0x19000
|
||||
#define XPC_SERIAL_TYPE_FILE_TRANSFER 0x1a000
|
||||
|
||||
#define XPC_SERIAL_TYPE_INVALID 0
|
||||
#define XPC_SERIAL_TYPE_MIN XPC_SERIAL_TYPE_NULL
|
||||
#define XPC_SERIAL_TYPE_MAX XPC_SERIAL_TYPE_RECV_PORT
|
||||
#define XPC_SERIAL_TYPE_MAX XPC_SERIAL_TYPE_FILE_TRANSFER
|
||||
|
||||
// "w00t"
|
||||
#define XPC_MSGH_ID_CHECKIN 0x77303074
|
||||
#define XPC_MSGH_ID_MESSAGE 0x10000000
|
||||
#define XPC_MSGH_ID_ASYNC_REPLY 0x20000000
|
||||
#define XPC_MSGH_ID_NOTIFICATION 0x30000000
|
||||
// NOTE: i'm unsure as to exact the purpose of this msgh_id, but this is a good guess
|
||||
#define XPC_MSGH_ID_SYNC_MESSAGE 0x40000000
|
||||
|
||||
typedef uint32_t xpc_serial_type_t;
|
||||
typedef uint32_t xpc_serial_message_id_t;
|
||||
|
||||
// common to all XPC object serial representations
|
||||
typedef struct XPC_PACKED xpc_serial_base {
|
||||
xpc_serial_type_t type;
|
||||
} xpc_serial_base_t;
|
||||
|
||||
// for types like dictionary and array.
|
||||
typedef struct __attribute__((packed)) xpc_serial_container {
|
||||
uint32_t type;
|
||||
typedef struct XPC_PACKED xpc_serial_container {
|
||||
xpc_serial_base_t base;
|
||||
uint32_t size;
|
||||
uint32_t entry_count;
|
||||
char content[];
|
||||
} xpc_serial_container_t;
|
||||
|
||||
// vld = variable-length data (i.e. strings and data arrays).
|
||||
typedef struct __attribute__((packed)) xpc_serial_vld {
|
||||
uint32_t type;
|
||||
typedef struct XPC_PACKED xpc_serial_vld {
|
||||
xpc_serial_base_t base;
|
||||
uint32_t size;
|
||||
char content[];
|
||||
} xpc_serial_vld_t;
|
||||
|
||||
// integral data like integers, booleans, and UUIDs.
|
||||
typedef struct __attribute__((packed)) xpc_serial_integral {
|
||||
uint32_t type;
|
||||
typedef struct XPC_PACKED xpc_serial_integral {
|
||||
xpc_serial_base_t base;
|
||||
char content[];
|
||||
} xpc_serial_integral_t;
|
||||
|
||||
// types that only need to be present; they don't actually carray any value.
|
||||
// examples: fds, ports, and null.
|
||||
typedef struct __attribute__((packed)) xpc_serial_no_content {
|
||||
uint32_t type;
|
||||
typedef struct XPC_PACKED xpc_serial_no_content {
|
||||
xpc_serial_base_t base;
|
||||
} xpc_serial_no_content_t;
|
||||
|
||||
// header for a single XPC message.
|
||||
typedef struct __attribute__((packed)) xpc_serial_header {
|
||||
typedef struct XPC_PACKED xpc_serial_header {
|
||||
uint32_t magic;
|
||||
uint32_t version;
|
||||
xpc_serial_container_t container;
|
||||
xpc_serial_base_t contents[];
|
||||
} xpc_serial_header_t;
|
||||
|
||||
//
|
||||
// misc structures for our implementation (irrelevant to the actual serialized format)
|
||||
//
|
||||
// message format used for the checkin message.
|
||||
typedef struct XPC_PACKED xpc_checkin_message {
|
||||
mach_msg_header_t header;
|
||||
mach_msg_body_t body;
|
||||
mach_msg_port_descriptor_t server_receive_port;
|
||||
mach_msg_port_descriptor_t server_send_port;
|
||||
} xpc_checkin_message_t;
|
||||
|
||||
typedef struct xpc_serial_port_descriptor {
|
||||
mach_port_t port;
|
||||
mach_msg_type_name_t type;
|
||||
} xpc_serial_port_descriptor_t;
|
||||
|
||||
typedef struct xpc_serializer {
|
||||
char* buffer;
|
||||
const char* buffer_end;
|
||||
char* current_position;
|
||||
xpc_serial_port_descriptor_t* ports;
|
||||
size_t port_count;
|
||||
} xpc_serializer_t;
|
||||
|
||||
typedef struct xpc_deserializer {
|
||||
const char* buffer;
|
||||
const char* buffer_end;
|
||||
const char* current_position;
|
||||
} xpc_deserializer_t;
|
||||
|
||||
/**
|
||||
* Writes the given data into the output buffer.
|
||||
*
|
||||
* @param serializer The serializer context that we're operating on.
|
||||
* @param buffer Optional. The data to write.
|
||||
* @param length Number of bytes to write from the buffer.
|
||||
* @param out_length Optional. Pointer to a value that gets the actual number of bytes the write occupies in the output buffer (including padding) added to it.
|
||||
*
|
||||
* @returns 0 if it succeeded; negative error code otherwise.
|
||||
*/
|
||||
int xpc_serial_write(xpc_serializer_t* serializer, const void* buffer, size_t length, size_t* out_length);
|
||||
|
||||
/**
|
||||
* Writes the given string into the output buffer.
|
||||
*
|
||||
* @param serializer The serializer context that we're operating on.
|
||||
* @param string The null-terminated string to write.
|
||||
* @param out_length Same as in `xpc_serial_write`.
|
||||
*/
|
||||
int xpc_serial_write_string(xpc_serializer_t* serializer, const char* string, size_t* out_length);
|
||||
|
||||
/**
|
||||
* Writes the given 32-bit unsigned integer into the output buffer.
|
||||
*
|
||||
* @param serializer The serializer context that we're operating on.
|
||||
* @param integer The 32-bit unsigned integer to write.
|
||||
* @param out_length Same as in `xpc_serial_write`.
|
||||
*/
|
||||
int xpc_serial_write_u32(xpc_serializer_t* serializer, uint32_t integer, size_t* out_length);
|
||||
|
||||
/**
|
||||
* Writes the given 64-bit unsigned integer into the output buffer.
|
||||
*
|
||||
* @param serializer The serializer context that we're operating on.
|
||||
* @param integer The 64-bit unsigned integer to write.
|
||||
* @param out_length Same as in `xpc_serial_write`.
|
||||
*/
|
||||
int xpc_serial_write_u64(xpc_serializer_t* serializer, uint64_t integer, size_t* out_length);
|
||||
|
||||
/**
|
||||
* Reserves a chunk of the output buffer for future use.
|
||||
*
|
||||
* @param serializer The serializer context that we're operating on.
|
||||
* @param reservation_pointer Pointer that will be set to a pointer for the reserved chunk.
|
||||
* @param reserved_length Number of bytes to reserve in the output buffer.
|
||||
* @param out_length Same as in `xpc_serial_write`.
|
||||
*
|
||||
* @returns 0 if it succeeded; negative error code otherwise.
|
||||
*
|
||||
* @note It is actually valid for this function to return `NULL` in `reservation_pointer`. This occurs when the serializer context has no buffer associated with it.
|
||||
* To determine if the function failed, rely solely on the return code.
|
||||
*/
|
||||
int xpc_serial_write_reserve(xpc_serializer_t* serializer, char** reservation_pointer, size_t reserved_length, size_t* out_length);
|
||||
|
||||
/**
|
||||
* Serializes the given XPC object into the output buffer.
|
||||
*
|
||||
* @param serializer The serializer context that we're operating on.
|
||||
* @param object The XPC object to serialize.
|
||||
* @param out_length Same as in `xpc_serial_write`.
|
||||
*
|
||||
* @note This function may modify the serializer context and then fail. If you'd like to preserve the serializer state before the call in case of failure, make sure to copy it before calling this function.
|
||||
*/
|
||||
int xpc_serial_write_object(xpc_serializer_t* serializer, xpc_object_t object, size_t* out_length);
|
||||
|
||||
/**
|
||||
* Reads the next available data from the deserialization context.
|
||||
*
|
||||
* @param deserializer The deserializer context that we're operating on.
|
||||
* @param buffer The buffer to read the data into.
|
||||
* @param length Size in bytes of the given buffer.
|
||||
* @param out_length Optional. Pointer to a value that gets the actual number of bytes the read occupies in the input buffer (including padding) added to it.
|
||||
*/
|
||||
int xpc_serial_read(xpc_deserializer_t* deserializer, void* buffer, size_t length, size_t* out_length);
|
||||
|
||||
/**
|
||||
* Consumes the a chunk of the input buffer without copying it.
|
||||
*
|
||||
* @param deserializer The deserializer context that we're operating on.
|
||||
* @param buffer Pointer that will be set to a pointer to the start of the chunk that was consumed.
|
||||
* @param length Number of bytes to consume.
|
||||
* @param out_length Same as in `xpc_serial_read`.
|
||||
*/
|
||||
int xpc_serial_read_in_place(xpc_deserializer_t* deserializer, const void** buffer, size_t length, size_t* out_length);
|
||||
|
||||
/**
|
||||
* Returns the current chunk of the input buffer as a string pointer.
|
||||
*
|
||||
* @param deserializer The deserializer context that we're operating on.
|
||||
* @param string Pointer that will be set to a pointer to the constant string in the input buffer.
|
||||
* @param out_length Same as in `xpc_serial_read`.
|
||||
*
|
||||
* @note This is **NOT** the exact inverse of `xpc_serial_write_string`, and also does not call `xpc_serial_read` internally.
|
||||
* It behaves like `xpc_serial_read_in_place`. It does so to avoid unnecessary string copying (you can do that yourself
|
||||
* after calling this method if you really want a copy of the string).
|
||||
*/
|
||||
int xpc_serial_read_string(xpc_deserializer_t* deserializer, const char** string, size_t* out_length);
|
||||
|
||||
/**
|
||||
* Reads a 32-bit unsigned integer from the input buffer.
|
||||
*
|
||||
* @param deserializer The deserializer context that we're operating on.
|
||||
* @param integer Pointer to a 32-bit unsigned integer where the value will be read into.
|
||||
* @param out_length Same as in `xpc_serial_read`.
|
||||
*/
|
||||
int xpc_serial_read_u32(xpc_deserializer_t* deserializer, uint32_t* integer, size_t* out_length);
|
||||
|
||||
/**
|
||||
* Reads a 64-bit unsigned integer from the input buffer.
|
||||
*
|
||||
* @param deserializer The deserializer context that we're operating on.
|
||||
* @param integer Pointer to a 64-bit unsigned integer where the value will be read into.
|
||||
* @param out_length Same as in `xpc_serial_read`.
|
||||
*/
|
||||
int xpc_serial_read_u64(xpc_deserializer_t* deserializer, uint64_t* integer, size_t* out_length);
|
||||
|
||||
/**
|
||||
* Deserializes an XPC object from the input buffer.
|
||||
*
|
||||
* @param deserializer The deserializer context that we're operating on.
|
||||
* @param object Pointer to an XPC object that will be set to the object created from parsing the input buffer.
|
||||
* @param out_length Same as in `xpc_serial_read`.
|
||||
*
|
||||
* @note This function may modify the deserializer context and then fail. If you'd like to preserve the deserializer state before the call in case of failure, make sure to copy it before calling this function.
|
||||
*/
|
||||
int xpc_serial_read_object(xpc_deserializer_t* deserializer, xpc_object_t* object, size_t* out_length);
|
||||
|
||||
/**
|
||||
* Serializes an XPC object as a message.
|
||||
*
|
||||
* @param object The XPC object to serialize.
|
||||
* @param buffer Buffer to output the serialized representation of the XPC object into.
|
||||
* @param size Size of the given buffer in bytes.
|
||||
* @param out_length Optional. Pointer to a value that gets the actual number of bytes written to the buffer added to it.
|
||||
*/
|
||||
int xpc_serialize(xpc_object_t object, void* buffer, size_t length, size_t* out_length);
|
||||
|
||||
/**
|
||||
* Deserializes an XPC object from a message.
|
||||
*
|
||||
* @param buffer Buffer to read the serialized representation of the XPC object from.
|
||||
* @param size Size of the given buffer in bytes.
|
||||
* @param object Pointer to an XPC object that will be set to the object created from parsing the input buffer.
|
||||
* @param out_length Optional. Pointer to a value that gets the actual number of bytes read from the buffer added to it.
|
||||
*/
|
||||
int xpc_deserialize(xpc_object_t* object, const void* buffer, size_t length, size_t* out_length);
|
||||
XPC_INLINE
|
||||
uint64_t xpc_serial_padded_length(uint64_t length) {
|
||||
return (length + 3) & -4;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#if __OBJC__
|
||||
#import <xpc/objects/serializer.h>
|
||||
#import <xpc/objects/deserializer.h>
|
||||
#endif
|
||||
|
||||
#endif // _XPC_SERIALIZATION_H_
|
||||
|
@ -1,12 +1,13 @@
|
||||
#ifndef _XPC_UTIL_H_
|
||||
#define _XPC_UTIL_H_
|
||||
|
||||
#import <xpc/internal_base.h>
|
||||
#import <xpc/objects/base.h>
|
||||
#import <xpc/objects/connection.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <mach/mach.h>
|
||||
#include <sys/syslog.h>
|
||||
|
||||
/**
|
||||
* Defines `objcName` by casting `origName`, but also checks to ensure it's not `nil`
|
||||
@ -31,7 +32,7 @@
|
||||
/**
|
||||
* Like `TO_OBJC_CHECKED`, but the condition checks for failure to pass the checks.
|
||||
*
|
||||
* @see `TO_OBJC_CHECKED`
|
||||
* @see TO_OBJC_CHECKED
|
||||
*/
|
||||
#define TO_OBJC_CHECKED_ON_FAIL(className, origName, objcName) \
|
||||
XPC_CLASS(className)* objcName = XPC_CAST(className, origName); \
|
||||
@ -50,7 +51,7 @@ XPC_CLASS(object)* xpc_retain_for_collection(XPC_CLASS(object)* object);
|
||||
/**
|
||||
* Special `release` variant for collection classes like dictionaries and arrays.
|
||||
*
|
||||
* @see `xpc_retain_for_collection`
|
||||
* @see xpc_retain_for_collection
|
||||
*
|
||||
* @returns The object passed in, possibly with a decreased reference count.
|
||||
*/
|
||||
@ -77,14 +78,185 @@ char* xpc_description_indent(const char* description, bool indentFirstLine);
|
||||
*/
|
||||
size_t xpc_raw_data_hash(const void* data, size_t data_length);
|
||||
|
||||
/**
|
||||
* Checks if the given port is dead.
|
||||
*/
|
||||
bool xpc_mach_port_is_dead(mach_port_t port);
|
||||
|
||||
/**
|
||||
* Increments the reference count on the given right for the given port.
|
||||
*
|
||||
* Works regardless of whether the port is dead or not.
|
||||
*
|
||||
* Can safely be called on `MACH_PORT_NULL` and `MACH_PORT_DEAD`, which will just result in a no-op.
|
||||
*
|
||||
* @note Certain rights (like receive rights) cannot be retained, only released. This a Mach limitation.
|
||||
*/
|
||||
kern_return_t xpc_mach_port_retain_right(mach_port_name_t port, mach_port_right_t right);
|
||||
|
||||
/**
|
||||
* Decrements the reference count on the given right for the given port.
|
||||
*
|
||||
* Works regardless of whether the port is dead or not.
|
||||
*
|
||||
* Can safely be called on `MACH_PORT_NULL` and `MACH_PORT_DEAD`, which will just result in a no-op.
|
||||
*/
|
||||
kern_return_t xpc_mach_port_release_right(mach_port_name_t port, mach_port_right_t right);
|
||||
|
||||
/**
|
||||
* Increments the reference count on the send right for the given port.
|
||||
*
|
||||
* Works regardless of whether the port is dead or not.
|
||||
*
|
||||
* Can safely be called on `MACH_PORT_NULL` and `MACH_PORT_DEAD`, which will just result in a no-op.
|
||||
*/
|
||||
kern_return_t xpc_mach_port_retain_send(mach_port_t port);
|
||||
|
||||
/**
|
||||
* Decrements the reference count on the send right for the given port.
|
||||
*
|
||||
* Works regardless of whether the port is dead or not.
|
||||
*
|
||||
* Can safely be called on `MACH_PORT_NULL` and `MACH_PORT_DEAD`, which will just result in a no-op.
|
||||
*/
|
||||
kern_return_t xpc_mach_port_release_send(mach_port_t port);
|
||||
|
||||
/**
|
||||
* Creates or retains a send right in the given port.
|
||||
*/
|
||||
kern_return_t xpc_mach_port_make_send(mach_port_t port);
|
||||
|
||||
/**
|
||||
* Increments the reference count on the send-once right for the given port.
|
||||
*
|
||||
* Works regardless of whether the port is dead or not.
|
||||
*
|
||||
* Can safely be called on `MACH_PORT_NULL` and `MACH_PORT_DEAD`, which will just result in a no-op.
|
||||
*/
|
||||
kern_return_t xpc_mach_port_retain_send_once(mach_port_t port);
|
||||
|
||||
/**
|
||||
* Decrements the reference count on the send-once right for the given port.
|
||||
*
|
||||
* Works regardless of whether the port is dead or not.
|
||||
*
|
||||
* Can safely be called on `MACH_PORT_NULL` and `MACH_PORT_DEAD`, which will just result in a no-op.
|
||||
*/
|
||||
kern_return_t xpc_mach_port_release_send_once(mach_port_t port);
|
||||
|
||||
/**
|
||||
* Increments the reference count on all types of send rights (i.e. send and send-once) for the given port.
|
||||
*
|
||||
* Works regardless of whether the port is dead or not.
|
||||
*
|
||||
* Can safely be called on `MACH_PORT_NULL` and `MACH_PORT_DEAD`, which will just result in a no-op.
|
||||
*/
|
||||
kern_return_t xpc_mach_port_retain_send_any(mach_port_t port);
|
||||
|
||||
/**
|
||||
* Decrements the reference count on all types of send rights (i.e. send and send-once) for the given port.
|
||||
*
|
||||
* Works regardless of whether the port is dead or not.
|
||||
*
|
||||
* Can safely be called on `MACH_PORT_NULL` and `MACH_PORT_DEAD`, which will just result in a no-op.
|
||||
*/
|
||||
kern_return_t xpc_mach_port_release_send_any(mach_port_t port);
|
||||
|
||||
/**
|
||||
* Decrements the reference count on the receive right for the given port.
|
||||
*
|
||||
* Works regardless of whether the port is dead or not.
|
||||
*
|
||||
* Can safely be called on `MACH_PORT_NULL` and `MACH_PORT_DEAD`, which will just result in a no-op.
|
||||
*
|
||||
* @note There is no corresponding retain operation for receive ports because Mach limits receive ports to a single reference.
|
||||
*/
|
||||
kern_return_t xpc_mach_port_release_receive(mach_port_t port);
|
||||
|
||||
/**
|
||||
* Maps the given message type name to a port right type.
|
||||
*/
|
||||
mach_port_right_t xpc_mach_msg_type_name_to_port_right(mach_msg_type_name_t type_name);
|
||||
|
||||
/**
|
||||
* Creates a new receive port.
|
||||
*/
|
||||
mach_port_t xpc_mach_port_create_receive(void);
|
||||
|
||||
/**
|
||||
* Creates a new send-receive port.
|
||||
*/
|
||||
mach_port_t xpc_mach_port_create_send_receive(void);
|
||||
|
||||
/**
|
||||
* Checks if the given port contains a send-once right.
|
||||
*/
|
||||
bool xpc_mach_port_is_send_once(mach_port_t port);
|
||||
|
||||
/**
|
||||
* Aborts the current process, with an optional reason.
|
||||
*/
|
||||
XPC_NORETURN XPC_PRINTF(4, 5)
|
||||
void _xpc_abort(const char* function, const char* file, size_t line, const char* reason_format, ...);
|
||||
|
||||
/**
|
||||
* @see _xpc_abort
|
||||
*/
|
||||
XPC_NORETURN XPC_PRINTF(4, 0)
|
||||
void _xpc_abortv(const char* function, const char* file, size_t line, const char* reason_format, va_list args);
|
||||
|
||||
/**
|
||||
* Aborts the current process, with an optional reason. This macro automatically fills in most of the arguments to `_xpc_abort`.
|
||||
*/
|
||||
#define xpc_abort(...) _xpc_abort(__PRETTY_FUNCTION__, __FILE__, __LINE__, __VA_ARGS__)
|
||||
|
||||
/**
|
||||
* @see xpc_abort
|
||||
*/
|
||||
#define xpc_abortv(...) _xpc_abortv(__PRETTY_FUNCTION__, __FILE__, __LINE__, __VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Aborts the current process due to a failed assertion.
|
||||
*/
|
||||
XPC_NORETURN
|
||||
void _xpc_assertion_failed(const char* function, const char* file, size_t line, const char* expression);
|
||||
|
||||
#if XPC_DEBUG
|
||||
#define xpc_assert(x) do { \
|
||||
if (!(x)) _xpc_assertion_failed(__PRETTY_FUNCTION__, __FILE__, __LINE__, #x);\
|
||||
} while (0)
|
||||
#else
|
||||
#define xpc_assert(x)
|
||||
#endif
|
||||
|
||||
#define XPC_LOG_DEBUG LOG_DEBUG
|
||||
#define XPC_LOG_WARNING LOG_WARNING
|
||||
#define XPC_LOG_ERROR LOG_ERR
|
||||
#define XPC_LOG_NOTICE LOG_NOTICE
|
||||
#define XPC_LOG_INFO LOG_INFO
|
||||
|
||||
typedef int xpc_log_priority_t;
|
||||
|
||||
/**
|
||||
* Logs a message with the given priority.
|
||||
*/
|
||||
XPC_PRINTF(5, 6)
|
||||
void _xpc_log(const char* function, const char* file, size_t line, xpc_log_priority_t priority, const char* format, ...);
|
||||
|
||||
/**
|
||||
* @see _xpc_log
|
||||
*/
|
||||
XPC_PRINTF(5, 0)
|
||||
void _xpc_logv(const char* function, const char* file, size_t line, xpc_log_priority_t priority, const char* format, va_list args);
|
||||
|
||||
/**
|
||||
* Logs a message with the given priority. This macro automatically fills in most of the arguments to `_xpc_log`.
|
||||
*/
|
||||
#define xpc_log(...) _xpc_log(__PRETTY_FUNCTION__, __FILE__, __LINE__, __VA_ARGS__)
|
||||
|
||||
/**
|
||||
* @see xpc_log
|
||||
*/
|
||||
#define xpc_logv(...) _xpc_logv(__PRETTY_FUNCTION__, __FILE__, __LINE__, __VA_ARGS__)
|
||||
|
||||
#endif // _XPC_UTIL_H_
|
||||
|
118
src/array.m
118
src/array.m
@ -4,6 +4,8 @@
|
||||
#import <xpc/connection.h>
|
||||
#import <xpc/objects/mach_send.h>
|
||||
#import <xpc/objects/dictionary.h>
|
||||
#import <xpc/serialization.h>
|
||||
#import <xpc/objects/null.h>
|
||||
|
||||
XPC_CLASS_SYMBOL_DECL(array);
|
||||
|
||||
@ -204,6 +206,122 @@ XPC_CLASS_HEADER(array);
|
||||
|
||||
@end
|
||||
|
||||
@implementation XPC_CLASS(array) (XPCSerialization)
|
||||
|
||||
- (BOOL)serializable
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSUInteger)serializationLength
|
||||
{
|
||||
XPC_THIS_DECL(array);
|
||||
NSUInteger total = 0;
|
||||
|
||||
total += xpc_serial_padded_length(sizeof(xpc_serial_type_t));
|
||||
total += xpc_serial_padded_length(sizeof(uint32_t));
|
||||
total += xpc_serial_padded_length(sizeof(uint32_t));
|
||||
|
||||
for (NSUInteger i = 0; i < this->size; ++i) {
|
||||
XPC_CLASS(object)* object = this->array[i];
|
||||
if (!object.serializable) {
|
||||
object = [XPC_CLASS(null) null];
|
||||
}
|
||||
total += object.serializationLength;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
+ (instancetype)deserialize: (XPC_CLASS(deserializer)*)deserializer
|
||||
{
|
||||
XPC_CLASS(array)* result = nil;
|
||||
xpc_serial_type_t type = XPC_SERIAL_TYPE_INVALID;
|
||||
uint32_t contentLength = 0;
|
||||
uint32_t entryCount = 0;
|
||||
NSUInteger contentStartOffset = 0;
|
||||
|
||||
if (![deserializer readU32: &type]) {
|
||||
goto error_out;
|
||||
}
|
||||
if (type != XPC_SERIAL_TYPE_ARRAY) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![deserializer readU32: &contentLength]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![deserializer readU32: &entryCount]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
contentStartOffset = deserializer.offset;
|
||||
|
||||
result = [[[self class] alloc] initWithObjects: NULL count: 0];
|
||||
|
||||
for (uint32_t i = 0; i < entryCount; ++i) {
|
||||
XPC_CLASS(object)* object = nil;
|
||||
if (![deserializer readObject: &object]) {
|
||||
goto error_out;
|
||||
}
|
||||
[result addObject: object];
|
||||
[object release];
|
||||
}
|
||||
|
||||
if (deserializer.offset - contentStartOffset != contentLength) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
error_out:
|
||||
if (result != nil) {
|
||||
[result release];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)serialize: (XPC_CLASS(serializer)*)serializer
|
||||
{
|
||||
XPC_THIS_DECL(array);
|
||||
void* reservedForContentLength = NULL;
|
||||
NSUInteger contentStartOffset = 0;
|
||||
|
||||
if (![serializer writeU32: XPC_SERIAL_TYPE_ARRAY]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![serializer reserve: sizeof(uint32_t) region: &reservedForContentLength]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![serializer writeU32: this->size]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
contentStartOffset = serializer.offset;
|
||||
|
||||
for (NSUInteger i = 0; i < this->size; ++i) {
|
||||
XPC_CLASS(object)* object = this->array[i];
|
||||
if (!object.serializable) {
|
||||
object = [XPC_CLASS(null) null];
|
||||
}
|
||||
if (![serializer writeObject: object]) {
|
||||
goto error_out;
|
||||
}
|
||||
}
|
||||
|
||||
OSWriteLittleInt32(reservedForContentLength, 0, serializer.offset - contentStartOffset);
|
||||
|
||||
return YES;
|
||||
|
||||
error_out:
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//
|
||||
// C API
|
||||
//
|
||||
|
30
src/base.m
30
src/base.m
@ -3,6 +3,7 @@
|
||||
#import <Foundation/NSString.h>
|
||||
#import <objc/runtime.h>
|
||||
#import <xpc/xpc.h>
|
||||
#import <xpc/serialization.h>
|
||||
|
||||
// the symbol alias for the base xpc_object class is named differently
|
||||
XPC_EXPORT struct objc_class _xpc_type_base;
|
||||
@ -15,15 +16,18 @@ XPC_CLASS_HEADER(object);
|
||||
|
||||
+ (instancetype)allocWithZone: (NSZone*)zone
|
||||
{
|
||||
return (id)_os_object_alloc_realized([self class], [self _instanceSize]);
|
||||
return (id)_os_object_alloc_realized([self class], [self instanceSize]);
|
||||
}
|
||||
|
||||
_Pragma("GCC diagnostic push");
|
||||
_Pragma("GCC diagnostic ignored \"-Wobjc-designated-initializers\"");
|
||||
- (instancetype)init
|
||||
{
|
||||
// we CANNOT call `-[super init]`.
|
||||
// libdispatch makes `init` crash on `OS_object`s.
|
||||
return self;
|
||||
}
|
||||
_Pragma("GCC diagnostic pop");
|
||||
|
||||
- (char*)xpcDescription
|
||||
{
|
||||
@ -57,6 +61,30 @@ XPC_CLASS_HEADER(object);
|
||||
|
||||
@end
|
||||
|
||||
@implementation XPC_CLASS(object) (XPCSerialization)
|
||||
|
||||
- (BOOL)serializable
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSUInteger)serializationLength
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
+ (instancetype)deserialize: (XPC_CLASS(deserializer)*)deserializer
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)serialize: (XPC_CLASS(serializer)*)serializer
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//
|
||||
// C API
|
||||
//
|
||||
|
61
src/bool.m
61
src/bool.m
@ -1,6 +1,7 @@
|
||||
#import <xpc/objects/bool.h>
|
||||
#import <xpc/util.h>
|
||||
#import <xpc/xpc.h>
|
||||
#import <xpc/serialization.h>
|
||||
|
||||
#undef bool
|
||||
|
||||
@ -87,6 +88,66 @@ XPC_CLASS_HEADER(bool);
|
||||
|
||||
@end
|
||||
|
||||
@implementation XPC_CLASS(bool) (XPCSerialization)
|
||||
|
||||
- (BOOL)serializable
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSUInteger)serializationLength
|
||||
{
|
||||
return xpc_serial_padded_length(sizeof(xpc_serial_type_t)) + xpc_serial_padded_length(sizeof(uint32_t));
|
||||
}
|
||||
|
||||
+ (instancetype)deserialize: (XPC_CLASS(deserializer)*)deserializer
|
||||
{
|
||||
XPC_CLASS(bool)* result = nil;
|
||||
xpc_serial_type_t type = XPC_SERIAL_TYPE_INVALID;
|
||||
uint32_t value = 0;
|
||||
|
||||
if (![deserializer readU32: &type]) {
|
||||
goto error_out;
|
||||
}
|
||||
if (type != XPC_SERIAL_TYPE_BOOL) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![deserializer readU32: &value]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
result = [[self class] boolForValue: !!value];
|
||||
|
||||
return result;
|
||||
|
||||
error_out:
|
||||
if (result != nil) {
|
||||
[result release];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)serialize: (XPC_CLASS(serializer)*)serializer
|
||||
{
|
||||
XPC_THIS_DECL(bool);
|
||||
|
||||
if (![serializer writeU32: XPC_SERIAL_TYPE_BOOL]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![serializer writeU32: this->value]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
return YES;
|
||||
|
||||
error_out:
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#define bool _Bool
|
||||
|
||||
//
|
||||
|
979
src/connection.m
979
src/connection.m
File diff suppressed because it is too large
Load Diff
74
src/data.m
74
src/data.m
@ -3,6 +3,7 @@
|
||||
#define __DISPATCH_INDIRECT__
|
||||
#import <dispatch/data_private.h>
|
||||
#import <xpc/xpc.h>
|
||||
#import <xpc/serialization.h>
|
||||
|
||||
XPC_CLASS_SYMBOL_DECL(data);
|
||||
|
||||
@ -84,6 +85,79 @@ XPC_CLASS_HEADER(data);
|
||||
|
||||
@end
|
||||
|
||||
@implementation XPC_CLASS(data) (XPCSerialization)
|
||||
|
||||
- (BOOL)serializable
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSUInteger)serializationLength
|
||||
{
|
||||
return xpc_serial_padded_length(sizeof(xpc_serial_type_t)) + xpc_serial_padded_length(sizeof(uint32_t)) + xpc_serial_padded_length(self.length);
|
||||
}
|
||||
|
||||
+ (instancetype)deserialize: (XPC_CLASS(deserializer)*)deserializer
|
||||
{
|
||||
XPC_CLASS(data)* result = nil;
|
||||
xpc_serial_type_t type = XPC_SERIAL_TYPE_INVALID;
|
||||
uint32_t length = 0;
|
||||
const void* region = NULL;
|
||||
|
||||
if (![deserializer readU32: &type]) {
|
||||
goto error_out;
|
||||
}
|
||||
if (type != XPC_SERIAL_TYPE_DATA) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![deserializer readU32: &length]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![deserializer consume: length region: ®ion]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
result = [[[self class] alloc] initWithBytes: region length: length];
|
||||
|
||||
return result;
|
||||
|
||||
error_out:
|
||||
if (result != nil) {
|
||||
[result release];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)serialize: (XPC_CLASS(serializer)*)serializer
|
||||
{
|
||||
XPC_THIS_DECL(data);
|
||||
uint32_t length = self.length;
|
||||
void* region = NULL;
|
||||
|
||||
if (![serializer writeU32: XPC_SERIAL_TYPE_DATA]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![serializer writeU32: length]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![serializer reserve: length region: ®ion]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
[self getBytes: region length: length];
|
||||
|
||||
return YES;
|
||||
|
||||
error_out:
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//
|
||||
// C API
|
||||
//
|
||||
|
@ -2,10 +2,12 @@
|
||||
#import <xpc/util.h>
|
||||
#import <xpc/xpc.h>
|
||||
#import <Foundation/NSDate.h>
|
||||
#import <xpc/serialization.h>
|
||||
|
||||
#include <time.h>
|
||||
|
||||
XPC_WRAPPER_CLASS_IMPL(date, int64_t, "%lld");
|
||||
XPC_WRAPPER_CLASS_SERIAL_IMPL(date, int64_t, DATE, U64, uint64_t);
|
||||
|
||||
//
|
||||
// C API
|
||||
|
200
src/dictionary.m
200
src/dictionary.m
@ -6,6 +6,10 @@
|
||||
#import <xpc/objects/mach_send.h>
|
||||
#import <xpc/objects/mach_recv.h>
|
||||
#import <xpc/objects/array.h>
|
||||
#import <xpc/objects/null.h>
|
||||
#import <xpc/objects/connection.h>
|
||||
#import <xpc/serialization.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
XPC_CLASS_SYMBOL_DECL(dictionary);
|
||||
|
||||
@ -71,6 +75,40 @@ XPC_CLASS_HEADER(dictionary);
|
||||
return output;
|
||||
}
|
||||
|
||||
- (mach_port_t)incomingPort
|
||||
{
|
||||
XPC_THIS_DECL(dictionary);
|
||||
return this->incoming_port;
|
||||
}
|
||||
|
||||
- (void)setIncomingPort: (mach_port_t)incomingPort
|
||||
{
|
||||
XPC_THIS_DECL(dictionary);
|
||||
this->incoming_port = incomingPort;
|
||||
}
|
||||
|
||||
- (mach_port_t)outgoingPort
|
||||
{
|
||||
XPC_THIS_DECL(dictionary);
|
||||
return this->outgoing_port;
|
||||
}
|
||||
|
||||
- (void)setOutgoingPort: (mach_port_t)outgoingPort
|
||||
{
|
||||
XPC_THIS_DECL(dictionary);
|
||||
this->outgoing_port = outgoingPort;
|
||||
}
|
||||
|
||||
- (BOOL)expectsReply
|
||||
{
|
||||
return xpc_mach_port_is_send_once(self.incomingPort);
|
||||
}
|
||||
|
||||
- (BOOL)isReply
|
||||
{
|
||||
return xpc_mach_port_is_send_once(self.outgoingPort);
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [super init]) {
|
||||
@ -130,15 +168,13 @@ XPC_CLASS_HEADER(dictionary);
|
||||
- (XPC_CLASS(connection)*)associatedConnection
|
||||
{
|
||||
XPC_THIS_DECL(dictionary);
|
||||
return this->associatedConnection;
|
||||
return objc_loadWeak(&this->associatedConnection);
|
||||
}
|
||||
|
||||
- (void)setAssociatedConnection: (XPC_CLASS(connection)*)associatedConnection
|
||||
{
|
||||
XPC_THIS_DECL(dictionary);
|
||||
XPC_CLASS(connection)* old = this->associatedConnection;
|
||||
this->associatedConnection = [associatedConnection retain];
|
||||
[old release];
|
||||
objc_storeWeak(&this->associatedConnection, associatedConnection);
|
||||
}
|
||||
|
||||
- (XPC_CLASS(object)*)objectForKey: (const char*)key
|
||||
@ -245,6 +281,132 @@ XPC_CLASS_HEADER(dictionary);
|
||||
|
||||
@end
|
||||
|
||||
@implementation XPC_CLASS(dictionary) (XPCSerialization)
|
||||
|
||||
- (BOOL)serializable
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSUInteger)serializationLength
|
||||
{
|
||||
XPC_THIS_DECL(dictionary);
|
||||
NSUInteger total = 0;
|
||||
xpc_dictionary_entry_t entry = NULL;
|
||||
|
||||
total += xpc_serial_padded_length(sizeof(xpc_serial_type_t));
|
||||
total += xpc_serial_padded_length(sizeof(uint32_t));
|
||||
total += xpc_serial_padded_length(sizeof(uint32_t));
|
||||
|
||||
LIST_FOREACH(entry, &this->head, link) {
|
||||
XPC_CLASS(object)* object = entry->object;
|
||||
if (!object.serializable) {
|
||||
object = [XPC_CLASS(null) null];
|
||||
}
|
||||
total += xpc_serial_padded_length(strlen(entry->name) + 1);
|
||||
total += object.serializationLength;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
+ (instancetype)deserialize: (XPC_CLASS(deserializer)*)deserializer
|
||||
{
|
||||
XPC_CLASS(dictionary)* result = nil;
|
||||
xpc_serial_type_t type = XPC_SERIAL_TYPE_INVALID;
|
||||
uint32_t contentLength = 0;
|
||||
uint32_t entryCount = 0;
|
||||
NSUInteger contentStartOffset = 0;
|
||||
|
||||
if (![deserializer readU32: &type]) {
|
||||
goto error_out;
|
||||
}
|
||||
if (type != XPC_SERIAL_TYPE_DICT) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![deserializer readU32: &contentLength]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![deserializer readU32: &entryCount]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
contentStartOffset = deserializer.offset;
|
||||
|
||||
result = [[[self class] alloc] initWithObjects: NULL forKeys: NULL count: 0];
|
||||
|
||||
for (uint32_t i = 0; i < entryCount; ++i) {
|
||||
const char* key = NULL;
|
||||
XPC_CLASS(object)* object = nil;
|
||||
if (![deserializer readString: &key]) {
|
||||
goto error_out;
|
||||
}
|
||||
if (![deserializer readObject: &object]) {
|
||||
goto error_out;
|
||||
}
|
||||
[result setObject: object forKey: key];
|
||||
[object release];
|
||||
}
|
||||
|
||||
if (deserializer.offset - contentStartOffset != contentLength) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
error_out:
|
||||
if (result != nil) {
|
||||
[result release];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)serialize: (XPC_CLASS(serializer)*)serializer
|
||||
{
|
||||
XPC_THIS_DECL(dictionary);
|
||||
void* reservedForContentLength = NULL;
|
||||
NSUInteger contentStartOffset = 0;
|
||||
xpc_dictionary_entry_t entry = NULL;
|
||||
|
||||
if (![serializer writeU32: XPC_SERIAL_TYPE_DICT]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![serializer reserve: sizeof(uint32_t) region: &reservedForContentLength]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![serializer writeU32: this->size]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
contentStartOffset = serializer.offset;
|
||||
|
||||
LIST_FOREACH(entry, &this->head, link) {
|
||||
if (![serializer writeString: entry->name]) {
|
||||
goto error_out;
|
||||
}
|
||||
XPC_CLASS(object)* object = entry->object;
|
||||
if (!object.serializable) {
|
||||
object = [XPC_CLASS(null) null];
|
||||
}
|
||||
if (![serializer writeObject: object]) {
|
||||
goto error_out;
|
||||
}
|
||||
}
|
||||
|
||||
OSWriteLittleInt32(reservedForContentLength, 0, serializer.offset - contentStartOffset);
|
||||
|
||||
return YES;
|
||||
|
||||
error_out:
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//
|
||||
// C API
|
||||
//
|
||||
@ -255,8 +417,21 @@ xpc_object_t xpc_dictionary_create(const char* const* keys, const xpc_object_t*
|
||||
};
|
||||
|
||||
XPC_EXPORT
|
||||
xpc_object_t xpc_dictionary_create_reply(xpc_object_t original) {
|
||||
// TODO
|
||||
xpc_object_t xpc_dictionary_create_reply(xpc_object_t xorig) {
|
||||
TO_OBJC_CHECKED(dictionary, xorig, orig) {
|
||||
XPC_CLASS(dictionary)* dict = NULL;
|
||||
|
||||
if (!orig.expectsReply) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dict = [XPC_CLASS(dictionary) new];
|
||||
|
||||
dict.outgoingPort = orig.incomingPort;
|
||||
orig.incomingPort = MACH_PORT_NULL;
|
||||
|
||||
return dict;
|
||||
}
|
||||
return NULL;
|
||||
};
|
||||
|
||||
@ -316,7 +491,9 @@ void xpc_dictionary_get_audit_token(xpc_object_t xdict, audit_token_t* token) {
|
||||
|
||||
XPC_EXPORT
|
||||
xpc_object_t _xpc_dictionary_create_reply_with_port(mach_port_t port) {
|
||||
return NULL;
|
||||
XPC_CLASS(dictionary)* dict = [XPC_CLASS(dictionary) new];
|
||||
dict.incomingPort = port;
|
||||
return dict;
|
||||
};
|
||||
|
||||
XPC_EXPORT
|
||||
@ -336,7 +513,11 @@ mach_msg_id_t _xpc_dictionary_get_reply_msg_id(xpc_object_t xdict) {
|
||||
|
||||
XPC_EXPORT
|
||||
void _xpc_dictionary_set_remote_connection(xpc_object_t xdict, xpc_connection_t xconn) {
|
||||
|
||||
TO_OBJC_CHECKED(dictionary, xdict, dict) {
|
||||
TO_OBJC_CHECKED(connection, xconn, conn) {
|
||||
dict.associatedConnection = conn;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
XPC_EXPORT
|
||||
@ -361,6 +542,9 @@ char* xpc_dictionary_copy_basic_description(xpc_object_t xdict) {
|
||||
|
||||
XPC_EXPORT
|
||||
bool xpc_dictionary_expects_reply(xpc_object_t xdict) {
|
||||
TO_OBJC_CHECKED(dictionary, xdict, dict) {
|
||||
return dict.expectsReply;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
68
src/double.m
68
src/double.m
@ -1,11 +1,79 @@
|
||||
#import <xpc/objects/double.h>
|
||||
#import <xpc/util.h>
|
||||
#import <xpc/xpc.h>
|
||||
#import <xpc/serialization.h>
|
||||
|
||||
#include <math.h>
|
||||
|
||||
XPC_WRAPPER_CLASS_IMPL(double, double, "%f");
|
||||
|
||||
// i don't like serializing doubles to raw bytes,
|
||||
// (because that assumes the peer uses the same binary represenation for doubles, which they might not)
|
||||
// but this is the way Apple does it, so it has to be the way we do it
|
||||
|
||||
@implementation XPC_CLASS(double) (XPCSerialization)
|
||||
|
||||
- (BOOL)serializable
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSUInteger)serializationLength
|
||||
{
|
||||
return xpc_serial_padded_length(sizeof(xpc_serial_type_t)) + xpc_serial_padded_length(sizeof(double));
|
||||
}
|
||||
|
||||
+ (instancetype)deserialize: (XPC_CLASS(deserializer)*)deserializer
|
||||
{
|
||||
XPC_CLASS(double)* result = nil;
|
||||
xpc_serial_type_t type = XPC_SERIAL_TYPE_INVALID;
|
||||
const double* data = NULL;
|
||||
|
||||
if (![deserializer readU32: &type]) {
|
||||
goto error_out;
|
||||
}
|
||||
if (type != XPC_SERIAL_TYPE_DOUBLE) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![deserializer consume: sizeof(double) region: (const void**)&data]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
result = [[[self class] alloc] initWithValue: *data];
|
||||
|
||||
return result;
|
||||
|
||||
error_out:
|
||||
if (result != nil) {
|
||||
[result release];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)serialize: (XPC_CLASS(serializer)*)serializer
|
||||
{
|
||||
XPC_THIS_DECL(double);
|
||||
double* data = NULL;
|
||||
|
||||
if (![serializer writeU32: XPC_SERIAL_TYPE_DOUBLE]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![serializer reserve: sizeof(double) region: (void**)&data]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
*data = this->value;
|
||||
|
||||
return YES;
|
||||
|
||||
error_out:
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//
|
||||
// C API
|
||||
//
|
||||
|
121
src/endpoint.m
121
src/endpoint.m
@ -1,6 +1,7 @@
|
||||
#import <xpc/objects/endpoint.h>
|
||||
#import <xpc/util.h>
|
||||
#import <xpc/xpc.h>
|
||||
#import <xpc/serialization.h>
|
||||
|
||||
XPC_CLASS_SYMBOL_DECL(endpoint);
|
||||
|
||||
@ -9,14 +10,112 @@ OS_OBJECT_NONLAZY_CLASS
|
||||
|
||||
XPC_CLASS_HEADER(endpoint);
|
||||
|
||||
- (mach_port_t)port
|
||||
{
|
||||
XPC_THIS_DECL(endpoint);
|
||||
return this->port;
|
||||
}
|
||||
|
||||
- (void)setPort: (mach_port_t)port
|
||||
{
|
||||
XPC_THIS_DECL(endpoint);
|
||||
xpc_mach_port_retain_send(port);
|
||||
xpc_mach_port_release_send(this->port);
|
||||
this->port = port;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
self.port = MACH_PORT_NULL;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (instancetype)initWithConnection: (XPC_CLASS(connection)*)connection
|
||||
{
|
||||
return [self initWithPort: connection.sendPort];
|
||||
}
|
||||
|
||||
- (instancetype)initWithPort: (mach_port_t)port
|
||||
{
|
||||
xpc_mach_port_retain_send(port);
|
||||
return [self initWithPortNoCopy: port];
|
||||
}
|
||||
|
||||
- (instancetype)initWithPortNoCopy: (mach_port_t)port
|
||||
{
|
||||
if (self = [super init]) {
|
||||
#warning TODO: -[XPC_CLASS(endpoint) initWithConnection:]
|
||||
XPC_THIS_DECL(endpoint);
|
||||
this->port = port;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (int)compare: (XPC_CLASS(endpoint)*)rhs
|
||||
{
|
||||
int diff = self.port - rhs.port;
|
||||
return (diff == 0) ? 0 : ((diff < 0) ? -1 : 1);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation XPC_CLASS(endpoint) (XPCSerialization)
|
||||
|
||||
- (BOOL)serializable
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSUInteger)serializationLength
|
||||
{
|
||||
return xpc_serial_padded_length(sizeof(xpc_serial_type_t));
|
||||
}
|
||||
|
||||
+ (instancetype)deserialize: (XPC_CLASS(deserializer)*)deserializer
|
||||
{
|
||||
XPC_CLASS(endpoint)* result = nil;
|
||||
xpc_serial_type_t type = XPC_SERIAL_TYPE_INVALID;
|
||||
mach_port_t port = MACH_PORT_NULL;
|
||||
|
||||
if (![deserializer readU32: &type]) {
|
||||
goto error_out;
|
||||
}
|
||||
if (type != XPC_SERIAL_TYPE_ENDPOINT) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![deserializer readPort: &port type: MACH_MSG_TYPE_PORT_SEND]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
result = [[[self class] alloc] initWithPortNoCopy: port];
|
||||
|
||||
return result;
|
||||
|
||||
error_out:
|
||||
if (result != nil) {
|
||||
[result release];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)serialize: (XPC_CLASS(serializer)*)serializer
|
||||
{
|
||||
XPC_THIS_DECL(endpoint);
|
||||
|
||||
if (![serializer writeU32: XPC_SERIAL_TYPE_ENDPOINT]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![serializer writePort: this->port type: MACH_MSG_TYPE_COPY_SEND]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
return YES;
|
||||
|
||||
error_out:
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//
|
||||
@ -25,7 +124,10 @@ XPC_CLASS_HEADER(endpoint);
|
||||
|
||||
XPC_EXPORT
|
||||
xpc_endpoint_t xpc_endpoint_create(xpc_connection_t xconn) {
|
||||
return [[XPC_CLASS(endpoint) alloc] initWithConnection: XPC_CAST(connection, xconn)];
|
||||
TO_OBJC_CHECKED(connection, xconn, conn) {
|
||||
return [[XPC_CLASS(endpoint) alloc] initWithConnection: conn];
|
||||
}
|
||||
return NULL;
|
||||
};
|
||||
|
||||
//
|
||||
@ -33,12 +135,23 @@ xpc_endpoint_t xpc_endpoint_create(xpc_connection_t xconn) {
|
||||
//
|
||||
|
||||
XPC_EXPORT
|
||||
int xpc_endpoint_compare(xpc_endpoint_t lhs, xpc_endpoint_t rhs) {
|
||||
int xpc_endpoint_compare(xpc_endpoint_t xlhs, xpc_endpoint_t xrhs) {
|
||||
TO_OBJC_CHECKED(endpoint, xlhs, lhs) {
|
||||
TO_OBJC_CHECKED(endpoint, xrhs, rhs) {
|
||||
return [lhs compare: rhs];
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
XPC_EXPORT
|
||||
mach_port_t xpc_endpoint_copy_listener_port_4sim(xpc_endpoint_t xendpoint) {
|
||||
TO_OBJC_CHECKED(endpoint, xendpoint, endpoint) {
|
||||
mach_port_t port = endpoint.port;
|
||||
xpc_mach_port_retain_send(port);
|
||||
return port;
|
||||
}
|
||||
return MACH_PORT_NULL;
|
||||
};
|
||||
|
||||
@ -50,5 +163,5 @@ xpc_endpoint_t xpc_endpoint_create_bs_named(const char* name, uint64_t flags, ui
|
||||
|
||||
XPC_EXPORT
|
||||
xpc_endpoint_t xpc_endpoint_create_mach_port_4sim(mach_port_t port) {
|
||||
return NULL;
|
||||
return [[XPC_CLASS(endpoint) alloc] initWithPortNoCopy: port];
|
||||
};
|
||||
|
@ -22,16 +22,16 @@ struct _xpc_dictionary_s {
|
||||
.string = _description, \
|
||||
.freeWhenDone = false \
|
||||
}; \
|
||||
extern struct _xpc_dictionary_s _xpc_error_ ## _name; \
|
||||
extern const struct _xpc_dictionary_s _xpc_error_ ## _name; \
|
||||
struct xpc_dictionary_entry_s _xpc_error_ ## _name ## _entry = { \
|
||||
.link = { \
|
||||
.le_next = NULL, \
|
||||
.le_prev = &LIST_FIRST(&_xpc_error_ ## _name.the_real_thing.base.head), \
|
||||
.le_prev = (struct xpc_dictionary_entry_s**)&LIST_FIRST(&_xpc_error_ ## _name.the_real_thing.base.head), \
|
||||
}, \
|
||||
.object = XPC_CAST(string, &_xpc_error_ ## _name ## _entry_string), \
|
||||
.name = XPCErrorDescriptionKey, \
|
||||
}; \
|
||||
XPC_EXPORT struct _xpc_dictionary_s _xpc_error_ ## _name = { \
|
||||
XPC_EXPORT const struct _xpc_dictionary_s _xpc_error_ ## _name = { \
|
||||
.the_real_thing = { \
|
||||
.base = { \
|
||||
.base = { \
|
||||
|
65
src/fd.m
65
src/fd.m
@ -1,6 +1,7 @@
|
||||
#import <xpc/objects/fd.h>
|
||||
#import <xpc/util.h>
|
||||
#import <xpc/xpc.h>
|
||||
#import <xpc/serialization.h>
|
||||
|
||||
#include <sys/fileport.h>
|
||||
#include <mach/mach.h>
|
||||
@ -16,7 +17,7 @@ XPC_CLASS_HEADER(fd);
|
||||
{
|
||||
XPC_THIS_DECL(fd);
|
||||
if (this->port != MACH_PORT_NULL) {
|
||||
xpc_mach_port_release_right(this->port, MACH_PORT_RIGHT_SEND);
|
||||
xpc_mach_port_release_send(this->port);
|
||||
}
|
||||
[super dealloc];
|
||||
}
|
||||
@ -50,7 +51,7 @@ XPC_CLASS_HEADER(fd);
|
||||
{
|
||||
if (self = [super init]) {
|
||||
XPC_THIS_DECL(fd);
|
||||
if (xpc_mach_port_retain_right(port, MACH_PORT_RIGHT_SEND) != KERN_SUCCESS) {
|
||||
if (xpc_mach_port_retain_send(port) != KERN_SUCCESS) {
|
||||
[self release];
|
||||
return nil;
|
||||
}
|
||||
@ -79,6 +80,66 @@ XPC_CLASS_HEADER(fd);
|
||||
|
||||
@end
|
||||
|
||||
@implementation XPC_CLASS(fd) (XPCSerialization)
|
||||
|
||||
- (BOOL)serializable
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSUInteger)serializationLength
|
||||
{
|
||||
return xpc_serial_padded_length(sizeof(xpc_serial_type_t));
|
||||
}
|
||||
|
||||
+ (instancetype)deserialize: (XPC_CLASS(deserializer)*)deserializer
|
||||
{
|
||||
XPC_CLASS(fd)* result = nil;
|
||||
xpc_serial_type_t type = XPC_SERIAL_TYPE_INVALID;
|
||||
mach_port_t port = MACH_PORT_NULL;
|
||||
|
||||
if (![deserializer readU32: &type]) {
|
||||
goto error_out;
|
||||
}
|
||||
if (type != XPC_SERIAL_TYPE_FD) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![deserializer readPort: &port type: MACH_MSG_TYPE_PORT_SEND]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
result = [[[self class] alloc] initWithPort: port];
|
||||
|
||||
return result;
|
||||
|
||||
error_out:
|
||||
if (result != nil) {
|
||||
[result release];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)serialize: (XPC_CLASS(serializer)*)serializer
|
||||
{
|
||||
XPC_THIS_DECL(fd);
|
||||
|
||||
if (![serializer writeU32: XPC_SERIAL_TYPE_FD]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![serializer writePort: this->port type: MACH_MSG_TYPE_COPY_SEND]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
return YES;
|
||||
|
||||
error_out:
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//
|
||||
// C API
|
||||
//
|
||||
|
@ -1,8 +1,10 @@
|
||||
#import <xpc/objects/int64.h>
|
||||
#import <xpc/util.h>
|
||||
#import <xpc/xpc.h>
|
||||
#import <xpc/serialization.h>
|
||||
|
||||
XPC_WRAPPER_CLASS_IMPL(int64, int64_t, "%lld");
|
||||
XPC_WRAPPER_CLASS_SERIAL_IMPL(int64, int64_t, INT64, U64, uint64_t);
|
||||
|
||||
//
|
||||
// C API
|
||||
|
112
src/mach_recv.m
112
src/mach_recv.m
@ -1,5 +1,7 @@
|
||||
#import <xpc/objects/mach_recv.h>
|
||||
#import <xpc/xpc.h>
|
||||
#import <xpc/util.h>
|
||||
#import <xpc/serialization.h>
|
||||
|
||||
XPC_CLASS_SYMBOL_DECL(mach_recv);
|
||||
|
||||
@ -8,6 +10,111 @@ OS_OBJECT_NONLAZY_CLASS
|
||||
|
||||
XPC_CLASS_HEADER(mach_recv);
|
||||
|
||||
- (char*)xpcDescription
|
||||
{
|
||||
char* output = NULL;
|
||||
asprintf(&output, "<%s: port = %d>", xpc_class_name(self), self.port);
|
||||
return output;
|
||||
}
|
||||
|
||||
- (mach_port_t)port
|
||||
{
|
||||
XPC_THIS_DECL(mach_recv);
|
||||
return this->port;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPort: (mach_port_t)port
|
||||
{
|
||||
if (self = [super init]) {
|
||||
XPC_THIS_DECL(mach_recv);
|
||||
this->port = port;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
XPC_THIS_DECL(mach_recv);
|
||||
return (NSUInteger)this->port;
|
||||
}
|
||||
|
||||
- (instancetype)copy
|
||||
{
|
||||
XPC_THIS_DECL(mach_recv);
|
||||
return [[XPC_CLASS(mach_recv) alloc] initWithPort: this->port];
|
||||
}
|
||||
|
||||
- (mach_port_t)extractPort
|
||||
{
|
||||
XPC_THIS_DECL(mach_recv);
|
||||
mach_port_t port = this->port;
|
||||
if (port == MACH_PORT_DEAD) {
|
||||
xpc_abort("attempt to extract port from a mach_recv object more than once");
|
||||
}
|
||||
this->port = MACH_PORT_DEAD;
|
||||
return port;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation XPC_CLASS(mach_recv) (XPCSerialization)
|
||||
|
||||
- (BOOL)serializable
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSUInteger)serializationLength
|
||||
{
|
||||
return xpc_serial_padded_length(sizeof(xpc_serial_type_t));
|
||||
}
|
||||
|
||||
+ (instancetype)deserialize: (XPC_CLASS(deserializer)*)deserializer
|
||||
{
|
||||
XPC_CLASS(mach_recv)* result = nil;
|
||||
xpc_serial_type_t type = XPC_SERIAL_TYPE_INVALID;
|
||||
mach_port_t port = MACH_PORT_NULL;
|
||||
|
||||
if (![deserializer readU32: &type]) {
|
||||
goto error_out;
|
||||
}
|
||||
if (type != XPC_SERIAL_TYPE_MACH_RECV) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![deserializer readPort: &port type: MACH_MSG_TYPE_PORT_RECEIVE]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
result = [[[self class] alloc] initWithPort: port];
|
||||
|
||||
return result;
|
||||
|
||||
error_out:
|
||||
if (result != nil) {
|
||||
[result release];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)serialize: (XPC_CLASS(serializer)*)serializer
|
||||
{
|
||||
XPC_THIS_DECL(mach_recv);
|
||||
|
||||
if (![serializer writeU32: XPC_SERIAL_TYPE_MACH_RECV]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![serializer writePort: this->port type: MACH_MSG_TYPE_MOVE_RECEIVE]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
return YES;
|
||||
|
||||
error_out:
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//
|
||||
@ -16,10 +123,13 @@ XPC_CLASS_HEADER(mach_recv);
|
||||
|
||||
XPC_EXPORT
|
||||
xpc_object_t xpc_mach_recv_create(mach_port_t recv) {
|
||||
return NULL;
|
||||
return [[XPC_CLASS(mach_recv) alloc] initWithPort: recv];
|
||||
};
|
||||
|
||||
XPC_EXPORT
|
||||
mach_port_t xpc_mach_recv_extract_right(xpc_object_t xrecv) {
|
||||
TO_OBJC_CHECKED(mach_recv, xrecv, recv) {
|
||||
return [recv extractPort];
|
||||
}
|
||||
return MACH_PORT_NULL;
|
||||
};
|
||||
|
163
src/mach_send.m
163
src/mach_send.m
@ -1,5 +1,7 @@
|
||||
#import <xpc/objects/mach_send.h>
|
||||
#import <xpc/xpc.h>
|
||||
#import <xpc/util.h>
|
||||
#import <xpc/serialization.h>
|
||||
|
||||
XPC_CLASS_SYMBOL_DECL(mach_send);
|
||||
|
||||
@ -8,6 +10,148 @@ OS_OBJECT_NONLAZY_CLASS
|
||||
|
||||
XPC_CLASS_HEADER(mach_send);
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
XPC_THIS_DECL(mach_send);
|
||||
xpc_mach_port_release_send_any(this->port);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (char*)xpcDescription
|
||||
{
|
||||
char* output = NULL;
|
||||
asprintf(&output, "<%s: port = %d>", xpc_class_name(self), self.port);
|
||||
return output;
|
||||
}
|
||||
|
||||
- (mach_port_t)port
|
||||
{
|
||||
XPC_THIS_DECL(mach_send);
|
||||
return this->port;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPort: (mach_port_t)port
|
||||
{
|
||||
return [self initWithPort: port disposition: MACH_MSG_TYPE_COPY_SEND];
|
||||
}
|
||||
|
||||
- (instancetype)initWithPortNoCopy: (mach_port_t)port
|
||||
{
|
||||
return [self initWithPort: port disposition: MACH_MSG_TYPE_MOVE_SEND];
|
||||
}
|
||||
|
||||
- (instancetype)initWithPort: (mach_port_t)port disposition: (mach_msg_type_name_t)disposition
|
||||
{
|
||||
if (self = [super init]) {
|
||||
XPC_THIS_DECL(mach_send);
|
||||
if (disposition == MACH_MSG_TYPE_COPY_SEND) {
|
||||
if (xpc_mach_port_retain_send(port) != KERN_SUCCESS) {
|
||||
[self release];
|
||||
return nil;
|
||||
}
|
||||
} else if (disposition == MACH_MSG_TYPE_MAKE_SEND) {
|
||||
if (xpc_mach_port_make_send(port) != KERN_SUCCESS) {
|
||||
[self release];
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
this->port = port;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
XPC_THIS_DECL(mach_send);
|
||||
return (NSUInteger)this->port;
|
||||
}
|
||||
|
||||
- (instancetype)copy
|
||||
{
|
||||
XPC_THIS_DECL(mach_send);
|
||||
return [[XPC_CLASS(mach_send) alloc] initWithPort: this->port];
|
||||
}
|
||||
|
||||
- (mach_port_t)extractPort
|
||||
{
|
||||
XPC_THIS_DECL(mach_send);
|
||||
mach_port_t port = this->port;
|
||||
if (port == MACH_PORT_DEAD) {
|
||||
xpc_abort("attempt to extract port from a mach_send object more than once");
|
||||
}
|
||||
this->port = MACH_PORT_DEAD;
|
||||
return port;
|
||||
}
|
||||
|
||||
- (mach_port_t)copyPort
|
||||
{
|
||||
XPC_THIS_DECL(mach_send);
|
||||
if (xpc_mach_port_retain_send(this->port) != KERN_SUCCESS) {
|
||||
return MACH_PORT_NULL;
|
||||
}
|
||||
return this->port;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation XPC_CLASS(mach_send) (XPCSerialization)
|
||||
|
||||
- (BOOL)serializable
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSUInteger)serializationLength
|
||||
{
|
||||
return xpc_serial_padded_length(sizeof(xpc_serial_type_t));
|
||||
}
|
||||
|
||||
+ (instancetype)deserialize: (XPC_CLASS(deserializer)*)deserializer
|
||||
{
|
||||
XPC_CLASS(mach_send)* result = nil;
|
||||
xpc_serial_type_t type = XPC_SERIAL_TYPE_INVALID;
|
||||
mach_port_t port = MACH_PORT_NULL;
|
||||
|
||||
if (![deserializer readU32: &type]) {
|
||||
goto error_out;
|
||||
}
|
||||
if (type != XPC_SERIAL_TYPE_MACH_SEND) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![deserializer readPort: &port type: MACH_MSG_TYPE_PORT_SEND]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
result = [[[self class] alloc] initWithPortNoCopy: port];
|
||||
|
||||
return result;
|
||||
|
||||
error_out:
|
||||
if (result != nil) {
|
||||
[result release];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)serialize: (XPC_CLASS(serializer)*)serializer
|
||||
{
|
||||
XPC_THIS_DECL(mach_send);
|
||||
|
||||
if (![serializer writeU32: XPC_SERIAL_TYPE_MACH_SEND]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![serializer writePort: this->port type: MACH_MSG_TYPE_COPY_SEND]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
return YES;
|
||||
|
||||
error_out:
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//
|
||||
@ -16,30 +160,33 @@ XPC_CLASS_HEADER(mach_send);
|
||||
|
||||
XPC_EXPORT
|
||||
mach_port_t xpc_mach_send_copy_right(xpc_object_t xsend) {
|
||||
// retains the send right
|
||||
TO_OBJC_CHECKED(mach_send, xsend, send) {
|
||||
return [send copyPort];
|
||||
}
|
||||
return MACH_PORT_NULL;
|
||||
};
|
||||
|
||||
XPC_EXPORT
|
||||
xpc_object_t xpc_mach_send_create(mach_port_t send) {
|
||||
return NULL;
|
||||
return [[XPC_CLASS(mach_send) alloc] initWithPort: send];
|
||||
};
|
||||
|
||||
XPC_EXPORT
|
||||
xpc_object_t xpc_mach_send_create_with_disposition(mach_port_t send, unsigned int disposition) {
|
||||
// values for `disposition` are either:
|
||||
// - 0x11 - no action taken with send
|
||||
// - 0x13 - send is retained
|
||||
// - 0x14 - send is `make_send`ed
|
||||
return NULL;
|
||||
return [[XPC_CLASS(mach_send) alloc] initWithPort: send disposition: disposition];
|
||||
};
|
||||
|
||||
XPC_EXPORT
|
||||
mach_port_t xpc_mach_send_get_right(xpc_object_t xsend) {
|
||||
// only returns the send right; doesn't retain it
|
||||
TO_OBJC_CHECKED(mach_send, xsend, send) {
|
||||
return send.port;
|
||||
}
|
||||
return MACH_PORT_NULL;
|
||||
};
|
||||
|
||||
mach_port_t xpc_mach_send_extract_right(xpc_object_t xsend) {
|
||||
TO_OBJC_CHECKED(mach_send, xsend, send) {
|
||||
return [send extractPort];
|
||||
}
|
||||
return MACH_PORT_NULL;
|
||||
};
|
||||
|
41
src/null.m
41
src/null.m
@ -1,6 +1,7 @@
|
||||
#import <xpc/objects/null.h>
|
||||
#import <xpc/util.h>
|
||||
#import <xpc/xpc.h>
|
||||
#import <xpc/serialization.h>
|
||||
|
||||
XPC_CLASS_SYMBOL_DECL(null);
|
||||
|
||||
@ -29,7 +30,47 @@ XPC_CLASS_HEADER(null);
|
||||
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
#if __LP64__
|
||||
return 0x804201026298ULL;
|
||||
#else
|
||||
return 0x8042010U;
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation XPC_CLASS(null) (XPCSerialization)
|
||||
|
||||
- (BOOL)serializable
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSUInteger)serializationLength
|
||||
{
|
||||
return xpc_serial_padded_length(sizeof(xpc_serial_type_t));
|
||||
}
|
||||
|
||||
+ (instancetype)deserialize: (XPC_CLASS(deserializer)*)deserializer
|
||||
{
|
||||
xpc_serial_type_t type = XPC_SERIAL_TYPE_INVALID;
|
||||
|
||||
if (![deserializer readU32: &type]) {
|
||||
goto error_out;
|
||||
}
|
||||
if (type != XPC_SERIAL_TYPE_NULL) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
return [[self class] null];
|
||||
|
||||
error_out:
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)serialize: (XPC_CLASS(serializer)*)serializer
|
||||
{
|
||||
return [serializer writeU32: XPC_SERIAL_TYPE_NULL];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -1,8 +1,10 @@
|
||||
#import <xpc/objects/pointer.h>
|
||||
#import <xpc/util.h>
|
||||
#import <xpc/xpc.h>
|
||||
#import <xpc/serialization.h>
|
||||
|
||||
XPC_WRAPPER_CLASS_IMPL(pointer, void*, "%p");
|
||||
XPC_WRAPPER_CLASS_SERIAL_IMPL(pointer, void*, POINTER, U64, uint64_t);
|
||||
|
||||
//
|
||||
// C API
|
||||
|
@ -1,482 +0,0 @@
|
||||
/**
|
||||
* This file is part of Darling.
|
||||
*
|
||||
* Copyright (C) 2021 Darling developers
|
||||
*
|
||||
* Darling is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Darling is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <xpc/xpc.h>
|
||||
#include <xpc/internal.h>
|
||||
#include <xpc/serialization.h>
|
||||
|
||||
// works only for multiples of 2
|
||||
// credit: https://stackoverflow.com/a/9194117/6620880
|
||||
XPC_INLINE int round_up_multiple_of_2(uint64_t number, uint64_t multiple) {
|
||||
return (number + multiple - 1) & -multiple;
|
||||
};
|
||||
|
||||
XPC_INLINE int xpc_serial_space_check(xpc_serializer_t* serializer, size_t space) {
|
||||
// no buffer means the user wants to know how much space is necessary to serialize the given object
|
||||
// that means we're not actually going to copy anything, so just return success
|
||||
if (!serializer->buffer)
|
||||
return 0;
|
||||
|
||||
if (serializer->current_position + space > serializer->buffer_end)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
XPC_INLINE uint32_t xpc_serial_map_type(uint8_t xpc_type) {
|
||||
static uint32_t xpc_type_map[] = {
|
||||
XPC_SERIAL_TYPE_INVALID, // invalid
|
||||
XPC_SERIAL_TYPE_DICT,
|
||||
XPC_SERIAL_TYPE_ARRAY,
|
||||
XPC_SERIAL_TYPE_BOOL,
|
||||
XPC_SERIAL_TYPE_INVALID, // connection
|
||||
XPC_SERIAL_TYPE_INVALID, // endpoint
|
||||
XPC_SERIAL_TYPE_NULL,
|
||||
XPC_SERIAL_TYPE_INVALID, // activity
|
||||
XPC_SERIAL_TYPE_INT64,
|
||||
XPC_SERIAL_TYPE_UINT64,
|
||||
XPC_SERIAL_TYPE_INVALID, // date
|
||||
XPC_SERIAL_TYPE_DATA,
|
||||
XPC_SERIAL_TYPE_STRING,
|
||||
XPC_SERIAL_TYPE_UUID,
|
||||
XPC_SERIAL_TYPE_FD,
|
||||
XPC_SERIAL_TYPE_SHMEM,
|
||||
XPC_SERIAL_TYPE_INVALID, // error
|
||||
XPC_SERIAL_TYPE_DOUBLE,
|
||||
XPC_SERIAL_TYPE_INVALID, // pointer
|
||||
};
|
||||
|
||||
return (xpc_type < (sizeof(xpc_type_map) / sizeof(*xpc_type_map))) ? xpc_type_map[xpc_type] : XPC_SERIAL_TYPE_INVALID;
|
||||
};
|
||||
|
||||
int xpc_serial_write(xpc_serializer_t* serializer, const void* buffer, size_t length, size_t* out_length) {
|
||||
size_t total_length = round_up_multiple_of_2(length, 4);
|
||||
size_t padding_length = total_length - length;
|
||||
|
||||
if (xpc_serial_space_check(serializer, total_length) < 0)
|
||||
return -1;
|
||||
|
||||
if (serializer->buffer && buffer)
|
||||
memcpy(serializer->current_position, buffer, length);
|
||||
serializer->current_position += length;
|
||||
if (serializer->buffer)
|
||||
memset(serializer->current_position, 0, padding_length);
|
||||
serializer->current_position += padding_length;
|
||||
|
||||
if (out_length)
|
||||
*out_length += total_length;
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
int xpc_serial_read(xpc_deserializer_t* deserializer, void* buffer, size_t length, size_t* out_length) {
|
||||
size_t total_length = round_up_multiple_of_2(length, 4);
|
||||
|
||||
if (deserializer->current_position + length > deserializer->buffer_end)
|
||||
return -1;
|
||||
|
||||
memcpy(buffer, deserializer->current_position, length);
|
||||
deserializer->current_position += total_length;
|
||||
|
||||
if (out_length)
|
||||
*out_length += total_length;
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
int xpc_serial_write_reserve(xpc_serializer_t* serializer, char** reservation_pointer, size_t reserved_length, size_t* out_length) {
|
||||
if (reservation_pointer)
|
||||
*reservation_pointer = serializer->buffer ? serializer->current_position : NULL;
|
||||
return xpc_serial_write(serializer, NULL, reserved_length, out_length);
|
||||
};
|
||||
|
||||
int xpc_serial_read_in_place(xpc_deserializer_t* deserializer, const void** buffer, size_t length, size_t* out_length) {
|
||||
size_t total_length = round_up_multiple_of_2(length, 4);
|
||||
|
||||
*buffer = deserializer->current_position;
|
||||
deserializer->current_position += total_length;
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
int xpc_serial_write_string(xpc_serializer_t* serializer, const char* string, size_t* out_length) {
|
||||
return xpc_serial_write(serializer, string, strlen(string) + 1, out_length);
|
||||
};
|
||||
|
||||
int xpc_serial_read_string(xpc_deserializer_t* deserializer, const char** string, size_t* out_length) {
|
||||
return xpc_serial_read_in_place(deserializer, string, strlen(deserializer->current_position) + 1, out_length);
|
||||
};
|
||||
|
||||
int xpc_serial_write_u32(xpc_serializer_t* serializer, uint32_t integer, size_t* out_length) {
|
||||
char data[sizeof(uint32_t)];
|
||||
OSWriteLittleInt32(data, 0, integer);
|
||||
return xpc_serial_write(serializer, data, sizeof(uint32_t), out_length);
|
||||
};
|
||||
|
||||
int xpc_serial_read_u32(xpc_deserializer_t* deserializer, uint32_t* integer, size_t* out_length) {
|
||||
char data[sizeof(uint32_t)];
|
||||
if (xpc_serial_read(deserializer, data, sizeof(uint32_t), out_length) < 0)
|
||||
return -1;
|
||||
*integer = OSReadLittleInt32(data, 0);
|
||||
return 0;
|
||||
};
|
||||
|
||||
int xpc_serial_write_u64(xpc_serializer_t* serializer, uint64_t integer, size_t* out_length) {
|
||||
char data[sizeof(uint64_t)];
|
||||
OSWriteLittleInt64(data, 0, integer);
|
||||
return xpc_serial_write(serializer, data, sizeof(uint64_t), out_length);
|
||||
};
|
||||
|
||||
int xpc_serial_read_u64(xpc_deserializer_t* deserializer, uint64_t* integer, size_t* out_length) {
|
||||
char data[sizeof(uint64_t)];
|
||||
if (xpc_serial_read(deserializer, data, sizeof(uint64_t), out_length) < 0)
|
||||
return -1;
|
||||
*integer = OSReadLittleInt64(data, 0);
|
||||
return 0;
|
||||
};
|
||||
|
||||
int xpc_serial_write_object(xpc_serializer_t* serializer, xpc_object_t object, size_t* out_length) {
|
||||
struct xpc_object* xo = object;
|
||||
// we don't want to modify `out_length` if we fail, so keep our value local and add it at the end
|
||||
size_t local_out_length = 0;
|
||||
|
||||
uint32_t mapped_type = xpc_serial_map_type(xo->xo_xpc_type);
|
||||
|
||||
if (mapped_type < XPC_SERIAL_TYPE_MIN || mapped_type > XPC_SERIAL_TYPE_MAX)
|
||||
return -1;
|
||||
|
||||
// write the type
|
||||
if (xpc_serial_write_u32(serializer, mapped_type, &local_out_length) < 0)
|
||||
return -1;
|
||||
|
||||
switch (xo->xo_xpc_type) {
|
||||
case _XPC_TYPE_DICTIONARY: {
|
||||
char* reserved_for_size = NULL;
|
||||
__block size_t content_size = 0;
|
||||
|
||||
// reserve space for the content size (we'll know it later)
|
||||
if (xpc_serial_write_reserve(serializer, &reserved_for_size, sizeof(uint32_t), &local_out_length) < 0)
|
||||
return -1;
|
||||
|
||||
// write the number of entries
|
||||
if (xpc_serial_write_u32(serializer, xpc_dictionary_get_count(xo), &local_out_length) < 0)
|
||||
return -1;
|
||||
|
||||
// write the entries
|
||||
if (!xpc_dictionary_apply(xo, ^bool(const char* key, xpc_object_t value) {
|
||||
// write the key
|
||||
if (xpc_serial_write_string(serializer, key, &content_size) < 0)
|
||||
return false;
|
||||
|
||||
// write the content
|
||||
if (xpc_serial_write_object(serializer, value, &content_size) < 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
})) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// now we write the content size to the space we reserved
|
||||
if (reserved_for_size) {
|
||||
OSWriteLittleInt32(reserved_for_size, 0, (uint32_t)content_size);
|
||||
}
|
||||
|
||||
local_out_length += content_size;
|
||||
} break;
|
||||
|
||||
case _XPC_TYPE_ARRAY: {
|
||||
char* reserved_for_size = NULL;
|
||||
__block size_t content_size = 0;
|
||||
|
||||
// reserve space for the content size (we'll know it later)
|
||||
if (xpc_serial_write_reserve(serializer, &reserved_for_size, sizeof(uint32_t), &local_out_length) < 0)
|
||||
return -1;
|
||||
|
||||
// write the number of entries
|
||||
if (xpc_serial_write_u32(serializer, xpc_array_get_count(xo), &local_out_length) < 0)
|
||||
return -1;
|
||||
|
||||
// write the entries
|
||||
if (!xpc_array_apply(xo, ^bool(size_t index, xpc_object_t value) {
|
||||
if (xpc_serial_write_object(serializer, value, &content_size) < 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
})) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// now we write the content size to the space we reserved
|
||||
if (reserved_for_size) {
|
||||
OSWriteLittleInt32(reserved_for_size, 0, (uint32_t)content_size);
|
||||
}
|
||||
|
||||
local_out_length += content_size;
|
||||
} break;
|
||||
|
||||
case _XPC_TYPE_BOOL: {
|
||||
if (xpc_serial_write_u32(serializer, xpc_bool_get_value(xo), &local_out_length) < 0)
|
||||
return -1;
|
||||
} break;
|
||||
|
||||
case _XPC_TYPE_INT64: {
|
||||
if (xpc_serial_write_u64(serializer, xpc_int64_get_value(xo), &local_out_length) < 0)
|
||||
return -1;
|
||||
} break;
|
||||
|
||||
case _XPC_TYPE_UINT64: {
|
||||
if (xpc_serial_write_u64(serializer, xpc_uint64_get_value(xo), &local_out_length) < 0)
|
||||
return -1;
|
||||
} break;
|
||||
|
||||
case _XPC_TYPE_NULL: {
|
||||
// null doesn't carry a value
|
||||
} break;
|
||||
|
||||
case _XPC_TYPE_DOUBLE: {
|
||||
// not entirely sure how doubles are supposed to be encoded in Apple's XPC serialization format
|
||||
double value = xpc_double_get_value(xo);
|
||||
if (xpc_serial_write(serializer, &value, sizeof(double), &local_out_length) < 0)
|
||||
return -1;
|
||||
} break;
|
||||
|
||||
case _XPC_TYPE_DATA: {
|
||||
size_t data_length = xpc_data_get_length(xo);
|
||||
|
||||
if (xpc_serial_write_u32(serializer, data_length, &local_out_length) < 0)
|
||||
return -1;
|
||||
|
||||
if (xpc_serial_write(serializer, xpc_data_get_bytes_ptr(xo), data_length, &local_out_length) < 0)
|
||||
return -1;
|
||||
} break;
|
||||
|
||||
case _XPC_TYPE_STRING: {
|
||||
size_t data_length = xpc_string_get_length(xo) + 1;
|
||||
|
||||
if (xpc_serial_write_u32(serializer, data_length, &local_out_length) < 0)
|
||||
return -1;
|
||||
|
||||
if (xpc_serial_write(serializer, xpc_string_get_string_ptr(xo), data_length, &local_out_length) < 0)
|
||||
return -1;
|
||||
} break;
|
||||
|
||||
case _XPC_TYPE_UUID: {
|
||||
if (xpc_serial_write(serializer, xpc_uuid_get_bytes(xo), sizeof(uuid_t), &local_out_length) < 0)
|
||||
return -1;
|
||||
} break;
|
||||
|
||||
case _XPC_TYPE_FD:
|
||||
case _XPC_TYPE_SHMEM:
|
||||
case _XPC_TYPE_CONNECTION:
|
||||
case _XPC_TYPE_ENDPOINT:
|
||||
default: {
|
||||
debugf("%s:%s:%s: TODO: XPC type %hhu", __FILE__, __LINE__, __FUNCTION__, xo->xo_xpc_type);
|
||||
return -1;
|
||||
} break;
|
||||
}
|
||||
|
||||
if (out_length)
|
||||
*out_length += local_out_length;
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
int xpc_serial_read_object(xpc_deserializer_t* deserializer, xpc_object_t* object, size_t* out_length) {
|
||||
size_t local_out_length = 0;
|
||||
uint32_t type = 0;
|
||||
|
||||
if (xpc_serial_read_u32(deserializer, &type, &local_out_length) < 0)
|
||||
return -1;
|
||||
|
||||
switch (type) {
|
||||
case XPC_SERIAL_TYPE_NULL: {
|
||||
*object = xpc_null_create();
|
||||
} break;
|
||||
|
||||
case XPC_SERIAL_TYPE_BOOL: {
|
||||
uint32_t value = 0;
|
||||
if (xpc_serial_read_u32(deserializer, &value, &local_out_length) < 0)
|
||||
return -1;
|
||||
*object = xpc_bool_create(value != 0);
|
||||
} break;
|
||||
|
||||
case XPC_SERIAL_TYPE_INT64: {
|
||||
int64_t value = 0;
|
||||
if (xpc_serial_read_u64(deserializer, &value, &local_out_length) < 0)
|
||||
return -1;
|
||||
*object = xpc_int64_create(value);
|
||||
} break;
|
||||
|
||||
case XPC_SERIAL_TYPE_UINT64: {
|
||||
uint64_t value = 0;
|
||||
if (xpc_serial_read_u64(deserializer, &value, &local_out_length) < 0)
|
||||
return -1;
|
||||
*object = xpc_uint64_create(value);
|
||||
} break;
|
||||
|
||||
case XPC_SERIAL_TYPE_DOUBLE: {
|
||||
// again, not sure how to properly represent doubles
|
||||
double value = 0;
|
||||
if (xpc_serial_read(deserializer, &value, sizeof(double), &local_out_length) < 0)
|
||||
return -1;
|
||||
*object = xpc_double_create(value);
|
||||
} break;
|
||||
|
||||
case XPC_SERIAL_TYPE_DATA: {
|
||||
uint32_t size = 0;
|
||||
const char* data = NULL;
|
||||
if (xpc_serial_read_u32(deserializer, &size, NULL) < 0)
|
||||
return -1;
|
||||
if (xpc_serial_read_in_place(deserializer, &data, size, &local_out_length) < 0)
|
||||
return -1;
|
||||
*object = xpc_data_create(data, size);
|
||||
} break;
|
||||
|
||||
case XPC_SERIAL_TYPE_STRING: {
|
||||
uint32_t size = 0;
|
||||
const char* data = NULL;
|
||||
if (xpc_serial_read_u32(deserializer, &size, NULL) < 0)
|
||||
return -1;
|
||||
if (xpc_serial_read_in_place(deserializer, &data, size, &local_out_length) < 0)
|
||||
return -1;
|
||||
*object = xpc_string_create(data);
|
||||
} break;
|
||||
|
||||
case XPC_SERIAL_TYPE_UUID: {
|
||||
uuid_t data;
|
||||
if (xpc_serial_read(deserializer, &data, sizeof(uuid_t), &local_out_length) < 0)
|
||||
return -1;
|
||||
*object = xpc_uuid_create(data);
|
||||
} break;
|
||||
|
||||
case XPC_SERIAL_TYPE_ARRAY: {
|
||||
uint32_t size = 0;
|
||||
uint32_t entry_count = 0;
|
||||
|
||||
if (xpc_serial_read_u32(deserializer, &size, NULL) < 0)
|
||||
return -1;
|
||||
if (xpc_serial_read_u32(deserializer, &entry_count, NULL) < 0)
|
||||
return -1;
|
||||
|
||||
// create an empty array; we'll add entries to it as we go
|
||||
*object = xpc_array_create(NULL, 0);
|
||||
|
||||
// deserialize all the entries
|
||||
for (uint32_t i = 0; i < entry_count; ++i) {
|
||||
xpc_object_t entry = NULL;
|
||||
if (xpc_serial_read_object(deserializer, &entry, &local_out_length) < 0) {
|
||||
xpc_release(*object);
|
||||
return -1;
|
||||
}
|
||||
xpc_array_append_value(*object, entry);
|
||||
// retained by the array, so release our ref on it
|
||||
xpc_release(entry);
|
||||
}
|
||||
} break;
|
||||
|
||||
case XPC_SERIAL_TYPE_DICT: {
|
||||
uint32_t size = 0;
|
||||
uint32_t entry_count = 0;
|
||||
|
||||
if (xpc_serial_read_u32(deserializer, &size, NULL) < 0)
|
||||
return -1;
|
||||
if (xpc_serial_read_u32(deserializer, &entry_count, NULL) < 0)
|
||||
return -1;
|
||||
|
||||
// create an empty dictionary that we'll populate as we go
|
||||
*object = xpc_dictionary_create(NULL, NULL, 0);
|
||||
|
||||
for (uint32_t i = 0; i < entry_count; ++i) {
|
||||
const char* key = NULL;
|
||||
xpc_object_t value = NULL;
|
||||
if (xpc_serial_read_string(deserializer, &key, &local_out_length) < 0)
|
||||
goto dict_entry_fail;
|
||||
if (xpc_serial_read_object(deserializer, &value, &local_out_length) < 0)
|
||||
goto dict_entry_fail;
|
||||
|
||||
xpc_dictionary_set_value(*object, key, value);
|
||||
// retained by the dictionary, so release our ref on it
|
||||
xpc_release(value);
|
||||
|
||||
continue;
|
||||
|
||||
dict_entry_fail:
|
||||
xpc_release(*object);
|
||||
return -1;
|
||||
}
|
||||
} break;
|
||||
|
||||
case XPC_SERIAL_TYPE_FD:
|
||||
case XPC_SERIAL_TYPE_SHMEM:
|
||||
case XPC_SERIAL_TYPE_SEND_PORT:
|
||||
case XPC_SERIAL_TYPE_RECV_PORT:
|
||||
default: {
|
||||
debugf("%s:%s:%s: TODO: XPC type (wire) %u", __FILE__, __LINE__, __FUNCTION__, type);
|
||||
return -1;
|
||||
} break;
|
||||
}
|
||||
|
||||
if (out_length)
|
||||
*out_length += local_out_length;
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
int xpc_serialize(xpc_object_t object, void* buffer, size_t length, size_t* out_length) {
|
||||
xpc_serializer_t serializer = {
|
||||
.buffer = buffer,
|
||||
.buffer_end = buffer + length,
|
||||
.current_position = buffer,
|
||||
.port_count = 0,
|
||||
.ports = NULL,
|
||||
};
|
||||
|
||||
if (xpc_serial_write_u32(&serializer, XPC_SERIAL_MAGIC, out_length) < 0)
|
||||
return -1;
|
||||
|
||||
if (xpc_serial_write_u32(&serializer, XPC_SERIAL_CURRENT_VERSION, out_length) < 0)
|
||||
return -1;
|
||||
|
||||
return xpc_serial_write_object(&serializer, object, out_length);
|
||||
};
|
||||
|
||||
int xpc_deserialize(xpc_object_t* object, const void* buffer, size_t length, size_t* out_length) {
|
||||
xpc_deserializer_t deserializer = {
|
||||
.buffer = buffer,
|
||||
.buffer_end = buffer + length,
|
||||
.current_position = buffer,
|
||||
};
|
||||
uint32_t magic = 0;
|
||||
uint32_t version = 0;
|
||||
|
||||
if (xpc_serial_read_u32(&deserializer, &magic, out_length) < 0)
|
||||
return -1;
|
||||
|
||||
if (magic != XPC_SERIAL_MAGIC)
|
||||
return -1;
|
||||
|
||||
if (xpc_serial_read_u32(&deserializer, &version, out_length) < 0)
|
||||
return -1;
|
||||
|
||||
if (version != XPC_SERIAL_CURRENT_VERSION)
|
||||
return -1;
|
||||
|
||||
return xpc_serial_read_object(&deserializer, object, out_length);
|
||||
};
|
793
src/serialization.m
Normal file
793
src/serialization.m
Normal file
@ -0,0 +1,793 @@
|
||||
/**
|
||||
* This file is part of Darling.
|
||||
*
|
||||
* Copyright (C) 2021 Darling developers
|
||||
*
|
||||
* Darling is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Darling is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <xpc/serialization.h>
|
||||
#include <xpc/internal.h>
|
||||
#include <xpc/activity.h>
|
||||
#include <mach/mach_vm.h>
|
||||
|
||||
// maximum number of ports of the same type to embed in an inline descriptor.
|
||||
// if the number of ports of the same type exceeds this number, they are all stuffed into an OOL descriptor.
|
||||
#define OOL_PORT_THRESHOLD 1
|
||||
|
||||
static Class xpc_classes[] = {
|
||||
XPC_TYPE_NULL,
|
||||
XPC_TYPE_BOOL,
|
||||
XPC_TYPE_INT64,
|
||||
XPC_TYPE_UINT64,
|
||||
XPC_TYPE_DOUBLE,
|
||||
XPC_TYPE_POINTER,
|
||||
XPC_TYPE_DATE,
|
||||
XPC_TYPE_DATA,
|
||||
XPC_TYPE_STRING,
|
||||
XPC_TYPE_UUID,
|
||||
XPC_TYPE_FD,
|
||||
XPC_TYPE_SHMEM,
|
||||
XPC_TYPE_MACH_SEND,
|
||||
XPC_TYPE_ARRAY,
|
||||
XPC_TYPE_DICTIONARY,
|
||||
XPC_TYPE_ERROR,
|
||||
XPC_TYPE_CONNECTION,
|
||||
XPC_TYPE_ENDPOINT,
|
||||
XPC_TYPE_SERIALIZER,
|
||||
XPC_TYPE_PIPE,
|
||||
XPC_TYPE_MACH_RECV,
|
||||
XPC_TYPE_BUNDLE,
|
||||
XPC_TYPE_SERVICE,
|
||||
XPC_TYPE_SERVICE_INSTANCE,
|
||||
XPC_TYPE_ACTIVITY,
|
||||
XPC_TYPE_FILE_TRANSFER,
|
||||
};
|
||||
|
||||
XPC_INLINE
|
||||
Class type_to_class(uint32_t type) {
|
||||
if ((type & 0xfff) != 0 || type < XPC_SERIAL_TYPE_MIN || type > XPC_SERIAL_TYPE_MAX) {
|
||||
return nil;
|
||||
}
|
||||
return xpc_classes[(type / 0x1000) - 1];
|
||||
};
|
||||
|
||||
XPC_INLINE
|
||||
uint32_t class_to_type(Class class) {
|
||||
for (size_t i = 0; i < (sizeof(xpc_classes) / sizeof(Class)); ++i) {
|
||||
if (xpc_classes[i] == class) {
|
||||
return (i + 1) * 0x1000;
|
||||
}
|
||||
}
|
||||
return XPC_SERIAL_TYPE_INVALID;
|
||||
};
|
||||
|
||||
static size_t descriptor_sizes[] = {
|
||||
sizeof(mach_msg_port_descriptor_t),
|
||||
sizeof(mach_msg_ool_descriptor_t),
|
||||
sizeof(mach_msg_ool_ports_descriptor_t),
|
||||
sizeof(mach_msg_ool_ports_descriptor_t),
|
||||
sizeof(mach_msg_guarded_port_descriptor_t),
|
||||
};
|
||||
|
||||
XPC_INLINE
|
||||
size_t descriptor_size(mach_msg_descriptor_type_t type) {
|
||||
if (type > sizeof(descriptor_sizes) / sizeof(*descriptor_sizes)) {
|
||||
return 0;
|
||||
}
|
||||
return descriptor_sizes[type];
|
||||
};
|
||||
|
||||
XPC_CLASS_SYMBOL_DECL(serializer);
|
||||
XPC_CLASS_SYMBOL_DECL(deserializer);
|
||||
|
||||
OS_OBJECT_NONLAZY_CLASS
|
||||
@implementation XPC_CLASS(serializer)
|
||||
|
||||
XPC_CLASS_HEADER(serializer);
|
||||
|
||||
- (NSUInteger)offset
|
||||
{
|
||||
XPC_THIS_DECL(serializer);
|
||||
return this->offset;
|
||||
}
|
||||
|
||||
- (BOOL)isFinalized
|
||||
{
|
||||
XPC_THIS_DECL(serializer);
|
||||
return this->finalized_message != NULL;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
XPC_THIS_DECL(serializer);
|
||||
|
||||
if (this->finalized_message != NULL) {
|
||||
dispatch_release(this->finalized_message);
|
||||
}
|
||||
|
||||
if (this->buffer != NULL) {
|
||||
free(this->buffer);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sizeof(this->port_arrays) / sizeof(*this->port_arrays); ++i) {
|
||||
mach_port_right_t port_right = xpc_mach_msg_type_name_to_port_right(i + MACH_MSG_SEND_DISPOSITION_FIRST);
|
||||
xpc_serial_port_array_t* port_array = &this->port_arrays[i];
|
||||
if (port_array->array != NULL) {
|
||||
for (size_t j = 0; j < port_array->length; ++j) {
|
||||
xpc_mach_port_release_right(port_array->array[j], port_right);
|
||||
}
|
||||
free(port_array->array);
|
||||
port_array->array = NULL;
|
||||
port_array->length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (self = [self initWithoutHeader]) {
|
||||
XPC_THIS_DECL(serializer);
|
||||
|
||||
if (![self ensure: sizeof(xpc_serial_header_t)]) {
|
||||
[self release];
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (![self writeU32: XPC_SERIAL_MAGIC]) {
|
||||
[self release];
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (![self writeU32: XPC_SERIAL_CURRENT_VERSION]) {
|
||||
[self release];
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithoutHeader
|
||||
{
|
||||
if (self = [super init]) {
|
||||
// nothing
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (dispatch_mach_msg_t)finalizeWithRemotePort: (mach_port_t)remotePort localPort: (mach_port_t)localPort asReply: (BOOL)asReply expectingReply: (BOOL)expectingReply messageID: (uint32_t)messageID
|
||||
{
|
||||
XPC_THIS_DECL(serializer);
|
||||
size_t messageSize = sizeof(mach_msg_base_t);
|
||||
mach_msg_base_t* base = NULL;
|
||||
mach_msg_descriptor_t* descriptors = NULL;
|
||||
void* body = NULL;
|
||||
size_t descriptorCount = 0;
|
||||
|
||||
if (this->finalized_message) {
|
||||
return this->finalized_message;
|
||||
}
|
||||
|
||||
// first, determine the total message size
|
||||
|
||||
// add in port descriptor sizes
|
||||
for (size_t i = 0; i < sizeof(this->port_arrays) / sizeof(*this->port_arrays); ++i) {
|
||||
xpc_serial_port_array_t* port_array = &this->port_arrays[i];
|
||||
if (port_array->length > OOL_PORT_THRESHOLD) {
|
||||
// for anything more than the OOL threshold, we use an OOL port array
|
||||
messageSize += sizeof(mach_msg_ool_ports_descriptor_t);
|
||||
++descriptorCount;
|
||||
} else if (port_array->length > 0) {
|
||||
// otherwise, we use an inline descriptor for each
|
||||
messageSize += port_array->length * sizeof(mach_msg_port_descriptor_t);
|
||||
descriptorCount += port_array->length;
|
||||
}
|
||||
}
|
||||
|
||||
// add in the length of the actual serialized XPC data
|
||||
messageSize += this->length;
|
||||
|
||||
// now we allocate the message
|
||||
this->finalized_message = dispatch_mach_msg_create(NULL, messageSize, DISPATCH_MACH_MSG_DESTRUCTOR_DEFAULT, (mach_msg_header_t**)&base);
|
||||
if (!this->finalized_message) {
|
||||
return NULL;
|
||||
}
|
||||
descriptors = (mach_msg_descriptor_t*)((char*)base + sizeof(*base));
|
||||
body = descriptors;
|
||||
|
||||
// next, fill in header information
|
||||
base->header.msgh_id = messageID;
|
||||
base->header.msgh_size = messageSize;
|
||||
base->header.msgh_bits = MACH_MSGH_BITS(asReply ? MACH_MSG_TYPE_MOVE_SEND_ONCE : MACH_MSG_TYPE_COPY_SEND, expectingReply ? MACH_MSG_TYPE_MAKE_SEND_ONCE : 0) | ((descriptorCount > 0) ? MACH_MSGH_BITS_COMPLEX : 0);
|
||||
base->header.msgh_remote_port = remotePort;
|
||||
base->header.msgh_local_port = expectingReply ? localPort : MACH_PORT_NULL;
|
||||
base->body.msgh_descriptor_count = descriptorCount;
|
||||
|
||||
// and transfer the port descriptors
|
||||
for (size_t i = 0; i < sizeof(this->port_arrays) / sizeof(*this->port_arrays); ++i) {
|
||||
xpc_serial_port_array_t* port_array = &this->port_arrays[i];
|
||||
mach_msg_type_name_t disposition = i + MACH_MSG_SEND_DISPOSITION_FIRST;
|
||||
|
||||
if (port_array->length > OOL_PORT_THRESHOLD) {
|
||||
mach_msg_ool_ports_descriptor_t* ool_ports_desc = body;
|
||||
body = (char*)body + sizeof(*ool_ports_desc);
|
||||
|
||||
ool_ports_desc->type = MACH_MSG_OOL_PORTS_DESCRIPTOR;
|
||||
ool_ports_desc->disposition = disposition;
|
||||
ool_ports_desc->copy = MACH_MSG_VIRTUAL_COPY;
|
||||
ool_ports_desc->deallocate = 1;
|
||||
ool_ports_desc->count = port_array->length;
|
||||
ool_ports_desc->address = NULL;
|
||||
|
||||
// it might be wasteful to be allocating whole pages just for a few mach_port_t's,
|
||||
// but it's the best way to ensure the array survives until the kernel receives it (and the kernel will take care of deallocating it)
|
||||
if (mach_vm_allocate(mach_task_self(), (mach_vm_address_t*)&ool_ports_desc->address, sizeof(mach_port_t) * port_array->length, VM_FLAGS_ANYWHERE) != KERN_SUCCESS) {
|
||||
xpc_abort("failed to allocate memory for OOL port array");
|
||||
}
|
||||
|
||||
memcpy(ool_ports_desc->address, port_array->array, sizeof(mach_port_t) * port_array->length);
|
||||
} else if (port_array->length > 0) {
|
||||
for (size_t j = 0; j < port_array->length; ++j) {
|
||||
mach_msg_port_descriptor_t* port_desc = body;
|
||||
body = (char*)body + sizeof(*port_desc);
|
||||
|
||||
port_desc->type = MACH_MSG_PORT_DESCRIPTOR;
|
||||
port_desc->disposition = disposition;
|
||||
port_desc->name = port_array->array[j];
|
||||
}
|
||||
}
|
||||
|
||||
// we've transferred ownership of the ports in this array into the message,
|
||||
// so we can release the array now (and that way our destructor won't try to release them)
|
||||
if (port_array->length > 0) {
|
||||
free(port_array->array);
|
||||
port_array->array = NULL;
|
||||
port_array->length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// finally, copy in the serialized XPC data...
|
||||
memcpy(body, this->buffer, this->length);
|
||||
|
||||
// ...and free our buffer
|
||||
free(this->buffer);
|
||||
this->buffer = NULL;
|
||||
|
||||
return this->finalized_message;
|
||||
}
|
||||
|
||||
- (dispatch_mach_msg_t)finalizeWithRemotePort: (mach_port_t)remotePort localPort: (mach_port_t)localPort asReply: (BOOL)asReply expectingReply: (BOOL)expectingReply
|
||||
{
|
||||
return [self finalizeWithRemotePort: remotePort localPort: localPort asReply: asReply expectingReply: expectingReply messageID: asReply ? XPC_MSGH_ID_ASYNC_REPLY : XPC_MSGH_ID_MESSAGE];
|
||||
}
|
||||
|
||||
- (BOOL)needsToResizeToWrite: (NSUInteger)extraSize
|
||||
{
|
||||
XPC_THIS_DECL(serializer);
|
||||
return extraSize > this->length - this->offset;
|
||||
}
|
||||
|
||||
- (BOOL)ensure: (NSUInteger)extraSize
|
||||
{
|
||||
XPC_THIS_DECL(serializer);
|
||||
if ([self needsToResizeToWrite: extraSize]) {
|
||||
size_t extra = extraSize - (this->length - this->offset);
|
||||
void* newBuffer = realloc(this->buffer, this->length + extra);
|
||||
if (this->length + extra > 0 && newBuffer == NULL) {
|
||||
return NO;
|
||||
}
|
||||
this->buffer = newBuffer;
|
||||
this->length += extra;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)write: (const void*)data length: (NSUInteger)length
|
||||
{
|
||||
XPC_THIS_DECL(serializer);
|
||||
size_t total_length = xpc_serial_padded_length(length);
|
||||
size_t padding_length = total_length - length;
|
||||
|
||||
if (![self ensure: total_length]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (data != NULL) {
|
||||
memcpy(&this->buffer[this->offset], data, length);
|
||||
}
|
||||
this->offset += length;
|
||||
memset(&this->buffer[this->offset], 0, padding_length);
|
||||
this->offset += padding_length;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)reserve: (NSUInteger)length region: (void**)region
|
||||
{
|
||||
XPC_THIS_DECL(serializer);
|
||||
if (region != NULL) {
|
||||
*region = &this->buffer[this->offset];
|
||||
}
|
||||
return [self write: NULL length: length];
|
||||
}
|
||||
|
||||
- (BOOL)writeString: (const char*)string
|
||||
{
|
||||
return [self write: string length: strlen(string) + 1];
|
||||
}
|
||||
|
||||
- (BOOL)writeU32: (uint32_t)value
|
||||
{
|
||||
char data[sizeof(uint32_t)];
|
||||
OSWriteLittleInt32(data, 0, value);
|
||||
return [self write: data length: sizeof(data)];
|
||||
}
|
||||
|
||||
- (BOOL)writeU64: (uint64_t)value
|
||||
{
|
||||
char data[sizeof(uint64_t)];
|
||||
OSWriteLittleInt64(data, 0, value);
|
||||
return [self write: data length: sizeof(data)];
|
||||
}
|
||||
|
||||
- (BOOL)writePort: (mach_port_t)port type: (mach_msg_type_name_t)type
|
||||
{
|
||||
XPC_THIS_DECL(serializer);
|
||||
xpc_serial_port_array_t* port_array = NULL;
|
||||
mach_port_t* expanded_array = NULL;
|
||||
|
||||
if (!MACH_MSG_TYPE_PORT_ANY(type)) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
port_array = &this->port_arrays[type - MACH_MSG_SEND_DISPOSITION_FIRST];
|
||||
|
||||
expanded_array = realloc(port_array->array, (port_array->length + 1) * sizeof(mach_port_t));
|
||||
if (!expanded_array) {
|
||||
return NO;
|
||||
}
|
||||
port_array->array = expanded_array;
|
||||
|
||||
port_array->array[port_array->length++] = port;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)writeObject: (XPC_CLASS(object)*)object
|
||||
{
|
||||
XPC_THIS_DECL(serializer);
|
||||
size_t savedOffset = this->offset;
|
||||
uint32_t type = class_to_type([object class]);
|
||||
if (type == XPC_SERIAL_TYPE_INVALID) {
|
||||
goto error_out;
|
||||
}
|
||||
if (![self ensure: object.serializationLength]) {
|
||||
goto error_out;
|
||||
}
|
||||
if (![object serialize: self]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
return YES;
|
||||
|
||||
error_out:
|
||||
this->offset = savedOffset;
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (instancetype)serializer
|
||||
{
|
||||
return [[[self class] new] autorelease];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
OS_OBJECT_NONLAZY_CLASS
|
||||
@implementation XPC_CLASS(deserializer)
|
||||
|
||||
XPC_CLASS_HEADER(deserializer);
|
||||
|
||||
- (NSUInteger)offset
|
||||
{
|
||||
XPC_THIS_DECL(deserializer);
|
||||
return this->offset;
|
||||
}
|
||||
|
||||
- (mach_port_t)remotePort
|
||||
{
|
||||
XPC_THIS_DECL(deserializer);
|
||||
mach_msg_header_t* header = dispatch_mach_msg_get_msg(this->mach_msg, NULL);
|
||||
return header->msgh_remote_port;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
XPC_THIS_DECL(deserializer);
|
||||
|
||||
if (this->mach_msg != NULL) {
|
||||
dispatch_release(this->mach_msg);
|
||||
}
|
||||
|
||||
// release any ports that weren't consumed, plus the arrays themselves
|
||||
for (size_t i = 0; i < sizeof(this->port_arrays) / sizeof(*this->port_arrays); ++i) {
|
||||
mach_port_right_t port_right = xpc_mach_msg_type_name_to_port_right(i + MACH_MSG_RECV_DISPOSITION_FIRST);
|
||||
xpc_deserial_port_array_t* port_array = &this->port_arrays[i];
|
||||
if (port_array->array != NULL) {
|
||||
for (size_t j = port_array->offset; j < port_array->length; ++j) {
|
||||
xpc_mach_port_release_right(port_array->array[j], port_right);
|
||||
}
|
||||
free(port_array->array);
|
||||
port_array->array = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (instancetype)initWithMessage: (dispatch_mach_msg_t)message
|
||||
{
|
||||
if (self = [self initWithoutHeaderWithMessage: message]) {
|
||||
uint32_t magic = 0;
|
||||
uint32_t version = 0;
|
||||
|
||||
// make sure this is an XPC message
|
||||
if (![self readU32: &magic]) {
|
||||
[self release];
|
||||
return nil;
|
||||
}
|
||||
if (magic != XPC_SERIAL_MAGIC) {
|
||||
[self release];
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (![self readU32: &version]) {
|
||||
[self release];
|
||||
return nil;
|
||||
}
|
||||
if (version != XPC_SERIAL_CURRENT_VERSION) {
|
||||
[self release];
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithoutHeaderWithMessage: (dispatch_mach_msg_t)message
|
||||
{
|
||||
if (self = [super init]) {
|
||||
XPC_THIS_DECL(deserializer);
|
||||
size_t messageSize = 0;
|
||||
const mach_msg_base_t* base = (const mach_msg_base_t*)dispatch_mach_msg_get_msg(message, &messageSize);
|
||||
const mach_msg_descriptor_t* descriptors = (const mach_msg_descriptor_t*)((const char*)base + sizeof(mach_msg_base_t));
|
||||
const void* body = descriptors;
|
||||
|
||||
// don't retain it; we own it now
|
||||
this->mach_msg = message;
|
||||
|
||||
if (MACH_MSGH_BITS_IS_COMPLEX(base->header.msgh_bits)) {
|
||||
// first, count how many ports we have of each
|
||||
for (size_t i = 0; i < base->body.msgh_descriptor_count; ++i) {
|
||||
const mach_msg_descriptor_t* descriptor = body;
|
||||
body = (const char*)body + descriptor_size(descriptor->type.type);
|
||||
|
||||
switch (descriptor->type.type) {
|
||||
case MACH_MSG_PORT_DESCRIPTOR: {
|
||||
const mach_msg_port_descriptor_t* port_desc = (const mach_msg_port_descriptor_t*)descriptor;
|
||||
if (!MACH_MSG_TYPE_PORT_ANY_RIGHT(port_desc->disposition)) {
|
||||
xpc_abort("unexpected port disposition in Mach message");
|
||||
}
|
||||
++this->port_arrays[port_desc->disposition - MACH_MSG_RECV_DISPOSITION_FIRST].length;
|
||||
} break;
|
||||
case MACH_MSG_OOL_PORTS_DESCRIPTOR: {
|
||||
const mach_msg_ool_ports_descriptor_t* ool_ports_desc = (const mach_msg_ool_ports_descriptor_t*)descriptor;
|
||||
if (!MACH_MSG_TYPE_PORT_ANY_RIGHT(ool_ports_desc->disposition)) {
|
||||
xpc_abort("unexpected OOL port disposition in Mach message");
|
||||
}
|
||||
this->port_arrays[ool_ports_desc->disposition - MACH_MSG_RECV_DISPOSITION_FIRST].length += ool_ports_desc->count;
|
||||
} break;
|
||||
case MACH_MSG_GUARDED_PORT_DESCRIPTOR: {
|
||||
// treat it like a normal port; we'll unguard it later
|
||||
const mach_msg_guarded_port_descriptor_t* guarded_desc = (const mach_msg_guarded_port_descriptor_t*)descriptor;
|
||||
if (!MACH_MSG_TYPE_PORT_ANY_RIGHT(guarded_desc->disposition)) {
|
||||
xpc_abort("unexpected port disposition in Mach message");
|
||||
}
|
||||
++this->port_arrays[guarded_desc->disposition - MACH_MSG_RECV_DISPOSITION_FIRST].length;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
// next, let's allocate arrays to store the ports
|
||||
for (size_t i = 0; i < sizeof(this->port_arrays) / sizeof(*this->port_arrays); ++i) {
|
||||
this->port_arrays[i].array = calloc(this->port_arrays[i].length, sizeof(mach_port_t));
|
||||
if (this->port_arrays[i].length > 0 && this->port_arrays[i].array == NULL) {
|
||||
xpc_abort("failed to allocate memory for port arrays");
|
||||
}
|
||||
}
|
||||
|
||||
// now we actually save the ports and deallocate memory passed in for OOL descriptors
|
||||
body = descriptors;
|
||||
for (size_t i = 0; i < base->body.msgh_descriptor_count; ++i) {
|
||||
const mach_msg_descriptor_t* descriptor = body;
|
||||
body = (const char*)body + descriptor_size(descriptor->type.type);
|
||||
|
||||
switch (descriptor->type.type) {
|
||||
case MACH_MSG_PORT_DESCRIPTOR: {
|
||||
const mach_msg_port_descriptor_t* port_desc = (const mach_msg_port_descriptor_t*)descriptor;
|
||||
xpc_deserial_port_array_t* port_array = &this->port_arrays[port_desc->disposition - MACH_MSG_RECV_DISPOSITION_FIRST];
|
||||
port_array->array[port_array->offset++] = port_desc->name;
|
||||
} break;
|
||||
|
||||
case MACH_MSG_OOL_PORTS_DESCRIPTOR: {
|
||||
const mach_msg_ool_ports_descriptor_t* ool_ports_desc = (const mach_msg_ool_ports_descriptor_t*)descriptor;
|
||||
xpc_deserial_port_array_t* port_array = &this->port_arrays[ool_ports_desc->disposition - MACH_MSG_RECV_DISPOSITION_FIRST];
|
||||
|
||||
memcpy(&port_array->array[port_array->offset], ool_ports_desc->address, ool_ports_desc->count * sizeof(mach_port_t));
|
||||
port_array->offset += ool_ports_desc->count;
|
||||
|
||||
if (ool_ports_desc->deallocate) {
|
||||
vm_deallocate(mach_task_self(), (vm_address_t)ool_ports_desc->address, ool_ports_desc->count * sizeof(mach_port_t));
|
||||
}
|
||||
} break;
|
||||
|
||||
case MACH_MSG_GUARDED_PORT_DESCRIPTOR: {
|
||||
const mach_msg_guarded_port_descriptor_t* guarded_desc = (const mach_msg_guarded_port_descriptor_t*)descriptor;
|
||||
xpc_deserial_port_array_t* port_array = &this->port_arrays[guarded_desc->disposition - MACH_MSG_RECV_DISPOSITION_FIRST];
|
||||
|
||||
// we should never have to deal with guarded ports, but if we ever do,
|
||||
// we should unguard them so they can be used like other ports
|
||||
if ((guarded_desc->flags & MACH_MSG_GUARD_FLAGS_UNGUARDED_ON_SEND) == 0) {
|
||||
mach_port_unguard(mach_task_self(), guarded_desc->name, guarded_desc->context);
|
||||
}
|
||||
|
||||
port_array->array[port_array->offset++] = guarded_desc->name;
|
||||
} break;
|
||||
|
||||
// we don't use these, but we have to deallocate them if we ever encountered them.
|
||||
// we should probably log a warning or something if we find these
|
||||
case MACH_MSG_OOL_DESCRIPTOR:
|
||||
case MACH_MSG_OOL_VOLATILE_DESCRIPTOR: {
|
||||
const mach_msg_ool_descriptor_t* ool_desc = (const mach_msg_ool_descriptor_t*)descriptor;
|
||||
if (ool_desc->deallocate) {
|
||||
vm_deallocate(mach_task_self(), (vm_address_t)ool_desc->address, ool_desc->size);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
// reset the port array offsets
|
||||
for (size_t i = 0; i < sizeof(this->port_arrays) / sizeof(*this->port_arrays); ++i) {
|
||||
this->port_arrays[i].offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// the `body` pointer now points to the actual serialized XPC data
|
||||
this->buffer = body;
|
||||
this->length = messageSize - ((uintptr_t)body - (uintptr_t)base);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)ensure: (NSUInteger)extraSize
|
||||
{
|
||||
XPC_THIS_DECL(deserializer);
|
||||
return extraSize <= this->length - this->offset;
|
||||
}
|
||||
|
||||
- (BOOL)read: (void*)data length: (NSUInteger)length
|
||||
{
|
||||
XPC_THIS_DECL(deserializer);
|
||||
size_t total_length = xpc_serial_padded_length(length);
|
||||
|
||||
if (![self ensure: total_length]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (data != NULL) {
|
||||
memcpy(data, &this->buffer[this->offset], length);
|
||||
}
|
||||
this->offset += total_length;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)consume: (NSUInteger)length region: (const void**)region
|
||||
{
|
||||
XPC_THIS_DECL(deserializer);
|
||||
if (region != NULL) {
|
||||
*region = &this->buffer[this->offset];
|
||||
}
|
||||
return [self read: NULL length: length];
|
||||
}
|
||||
|
||||
- (BOOL)readString: (const char**)string
|
||||
{
|
||||
XPC_THIS_DECL(deserializer);
|
||||
return [self consume: strlen(&this->buffer[this->offset]) + 1 region: (const void**)string];
|
||||
}
|
||||
|
||||
- (BOOL)readU32: (uint32_t*)value
|
||||
{
|
||||
const void* data = NULL;
|
||||
if (![self consume: sizeof(uint32_t) region: &data]) {
|
||||
return NO;
|
||||
}
|
||||
if (value != NULL) {
|
||||
*value = OSReadLittleInt32(data, 0);
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)readU64: (uint64_t*)value
|
||||
{
|
||||
const void* data = NULL;
|
||||
if (![self consume: sizeof(uint64_t) region: &data]) {
|
||||
return NO;
|
||||
}
|
||||
if (value != NULL) {
|
||||
*value = OSReadLittleInt64(data, 0);
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)readPort: (mach_port_t*)port type: (mach_msg_type_name_t)type
|
||||
{
|
||||
XPC_THIS_DECL(deserializer);
|
||||
xpc_deserial_port_array_t* port_array = NULL;
|
||||
|
||||
if (!MACH_MSG_TYPE_PORT_ANY_RIGHT(type)) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
port_array = &this->port_arrays[type - MACH_MSG_RECV_DISPOSITION_FIRST];
|
||||
|
||||
if (port_array->offset >= port_array->length) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (port == NULL) {
|
||||
// just release the right
|
||||
xpc_mach_port_release_right(port_array->array[port_array->offset], xpc_mach_msg_type_name_to_port_right(type));
|
||||
} else {
|
||||
// the caller now owns the right
|
||||
*port = port_array->array[port_array->offset];
|
||||
}
|
||||
|
||||
port_array->array[port_array->offset++] = MACH_PORT_NULL;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)readObject: (XPC_CLASS(object)**)object
|
||||
{
|
||||
XPC_THIS_DECL(deserializer);
|
||||
size_t savedOffset = this->offset;
|
||||
uint32_t type = XPC_SERIAL_TYPE_INVALID;
|
||||
Class class = nil;
|
||||
XPC_CLASS(object)* result = nil;
|
||||
|
||||
if (![self peekU32: &type]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
class = type_to_class(type);
|
||||
if (class == nil) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
result = [class deserialize: self];
|
||||
if (result == nil) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (object != NULL) {
|
||||
*object = result;
|
||||
}
|
||||
|
||||
return YES;
|
||||
|
||||
error_out:
|
||||
this->offset = savedOffset;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)peek: (void*)data length: (NSUInteger)length
|
||||
{
|
||||
XPC_THIS_DECL(deserializer);
|
||||
size_t savedOffset = this->offset;
|
||||
if (![self read: data length: length]) {
|
||||
return NO;
|
||||
}
|
||||
this->offset = savedOffset;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)peekNoCopy: (NSUInteger)length region: (const void**)region
|
||||
{
|
||||
XPC_THIS_DECL(deserializer);
|
||||
size_t savedOffset = this->offset;
|
||||
if (![self consume: length region: region]) {
|
||||
return NO;
|
||||
}
|
||||
this->offset = savedOffset;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)peekString: (const char**)string
|
||||
{
|
||||
XPC_THIS_DECL(deserializer);
|
||||
size_t savedOffset = this->offset;
|
||||
if (![self readString: string]) {
|
||||
return NO;
|
||||
}
|
||||
this->offset = savedOffset;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)peekU32: (uint32_t*)value
|
||||
{
|
||||
XPC_THIS_DECL(deserializer);
|
||||
size_t savedOffset = this->offset;
|
||||
if (![self readU32: value]) {
|
||||
return NO;
|
||||
}
|
||||
this->offset = savedOffset;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)peekU64: (uint64_t*)value
|
||||
{
|
||||
XPC_THIS_DECL(deserializer);
|
||||
size_t savedOffset = this->offset;
|
||||
if (![self readU64: value]) {
|
||||
return NO;
|
||||
}
|
||||
this->offset = savedOffset;
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (XPC_CLASS(dictionary)*)process: (dispatch_mach_msg_t)message
|
||||
{
|
||||
XPC_CLASS(deserializer)* deserializer = [[[self class] alloc] initWithMessage: message];
|
||||
XPC_CLASS(dictionary)* dict = nil;
|
||||
|
||||
if (!deserializer) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![deserializer readObject: &dict]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![dict isKindOfClass: [XPC_CLASS(dictionary) class]]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
dict.incomingPort = deserializer.remotePort;
|
||||
|
||||
[deserializer release];
|
||||
return [dict autorelease];
|
||||
|
||||
error_out:
|
||||
[dict release];
|
||||
[deserializer release];
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (instancetype)deserializerWithMessage: (dispatch_mach_msg_t)message
|
||||
{
|
||||
return [[[[self class] alloc] initWithMessage: message] autorelease];
|
||||
}
|
||||
|
||||
@end
|
@ -1,10 +0,0 @@
|
||||
#import <xpc/objects/serializer.h>
|
||||
|
||||
XPC_CLASS_SYMBOL_DECL(serializer);
|
||||
|
||||
OS_OBJECT_NONLAZY_CLASS
|
||||
@implementation XPC_CLASS(serializer)
|
||||
|
||||
XPC_CLASS_HEADER(serializer);
|
||||
|
||||
@end
|
70
src/string.m
70
src/string.m
@ -1,6 +1,7 @@
|
||||
#import <xpc/objects/string.h>
|
||||
#import <xpc/util.h>
|
||||
#import <xpc/xpc.h>
|
||||
#import <xpc/serialization.h>
|
||||
|
||||
XPC_CLASS_SYMBOL_DECL(string);
|
||||
|
||||
@ -124,6 +125,75 @@ XPC_CLASS_HEADER(string);
|
||||
|
||||
@end
|
||||
|
||||
@implementation XPC_CLASS(string) (XPCSerialization)
|
||||
|
||||
- (BOOL)serializable
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSUInteger)serializationLength
|
||||
{
|
||||
return xpc_serial_padded_length(sizeof(xpc_serial_type_t)) + xpc_serial_padded_length(sizeof(uint32_t)) + xpc_serial_padded_length(self.byteLength + 1);
|
||||
}
|
||||
|
||||
+ (instancetype)deserialize: (XPC_CLASS(deserializer)*)deserializer
|
||||
{
|
||||
XPC_CLASS(string)* result = nil;
|
||||
xpc_serial_type_t type = XPC_SERIAL_TYPE_INVALID;
|
||||
uint32_t length = 0;
|
||||
const char* string = NULL;
|
||||
|
||||
if (![deserializer readU32: &type]) {
|
||||
goto error_out;
|
||||
}
|
||||
if (type != XPC_SERIAL_TYPE_STRING) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![deserializer readU32: &length]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![deserializer readString: &string]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
// maybe we should check if the string length matches the reported length
|
||||
|
||||
result = [[[self class] alloc] initWithUTF8String: string];
|
||||
|
||||
return result;
|
||||
|
||||
error_out:
|
||||
if (result != nil) {
|
||||
[result release];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)serialize: (XPC_CLASS(serializer)*)serializer
|
||||
{
|
||||
if (![serializer writeU32: XPC_SERIAL_TYPE_STRING]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![serializer writeU32: self.byteLength]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![serializer writeString: self.UTF8String]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
return YES;
|
||||
|
||||
error_out:
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//
|
||||
// C API
|
||||
//
|
||||
|
@ -1,8 +1,10 @@
|
||||
#import <xpc/objects/uint64.h>
|
||||
#import <xpc/util.h>
|
||||
#import <xpc/xpc.h>
|
||||
#import <xpc/serialization.h>
|
||||
|
||||
XPC_WRAPPER_CLASS_IMPL(uint64, uint64_t, "%llu");
|
||||
XPC_WRAPPER_CLASS_SERIAL_IMPL(uint64, uint64_t, UINT64, U64, uint64_t);
|
||||
|
||||
//
|
||||
// C API
|
||||
|
166
src/util.m
166
src/util.m
@ -1,5 +1,13 @@
|
||||
#import <xpc/util.h>
|
||||
#import <objc/runtime.h>
|
||||
#import <xpc/serialization.h>
|
||||
#include <sys/reason.h>
|
||||
#include <sys/syslog.h>
|
||||
#import <xpc/objects/connection.h>
|
||||
|
||||
#ifndef XPC_LOG_TO_STDOUT_TOO
|
||||
#define XPC_LOG_TO_STDOUT_TOO 0
|
||||
#endif
|
||||
|
||||
XPC_CLASS(object)* xpc_retain_for_collection(XPC_CLASS(object)* object) {
|
||||
// connections aren't retained by collections
|
||||
@ -60,10 +68,164 @@ size_t xpc_raw_data_hash(const void* data, size_t data_length) {
|
||||
return result;
|
||||
};
|
||||
|
||||
bool xpc_mach_port_is_dead(mach_port_t port) {
|
||||
mach_port_urefs_t refs = 0;
|
||||
if (MACH_PORT_VALID(port)) {
|
||||
mach_port_get_refs(mach_task_self(), port, MACH_PORT_RIGHT_DEAD_NAME, &refs);
|
||||
}
|
||||
return refs > 0;
|
||||
};
|
||||
|
||||
kern_return_t xpc_mach_port_retain_right(mach_port_name_t port, mach_port_right_t right) {
|
||||
return mach_port_mod_refs(mach_task_self(), port, right, 1);
|
||||
if (!MACH_PORT_VALID(port)) {
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
kern_return_t status = mach_port_mod_refs(mach_task_self(), port, right, 1);
|
||||
if (status == KERN_INVALID_RIGHT) {
|
||||
status = mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_DEAD_NAME, 1);
|
||||
}
|
||||
return status;
|
||||
};
|
||||
|
||||
kern_return_t xpc_mach_port_release_right(mach_port_name_t port, mach_port_right_t right) {
|
||||
return mach_port_mod_refs(mach_task_self(), port, right, -1);
|
||||
if (!MACH_PORT_VALID(port)) {
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
kern_return_t status = mach_port_mod_refs(mach_task_self(), port, right, -1);
|
||||
if (status == KERN_INVALID_RIGHT) {
|
||||
status = mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_DEAD_NAME, -1);
|
||||
}
|
||||
return status;
|
||||
};
|
||||
|
||||
kern_return_t xpc_mach_port_retain_send(mach_port_t port) {
|
||||
return xpc_mach_port_retain_right(port, MACH_PORT_RIGHT_SEND);
|
||||
};
|
||||
|
||||
kern_return_t xpc_mach_port_release_send(mach_port_t port) {
|
||||
return xpc_mach_port_release_right(port, MACH_PORT_RIGHT_SEND);
|
||||
};
|
||||
|
||||
kern_return_t xpc_mach_port_make_send(mach_port_t port) {
|
||||
if (!MACH_PORT_VALID(port)) {
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
return mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);
|
||||
};
|
||||
|
||||
kern_return_t xpc_mach_port_retain_send_once(mach_port_t port) {
|
||||
return xpc_mach_port_retain_right(port, MACH_PORT_RIGHT_SEND_ONCE);
|
||||
};
|
||||
|
||||
kern_return_t xpc_mach_port_release_send_once(mach_port_t port) {
|
||||
return xpc_mach_port_release_right(port, MACH_PORT_RIGHT_SEND_ONCE);
|
||||
};
|
||||
|
||||
kern_return_t xpc_mach_port_retain_send_any(mach_port_t port) {
|
||||
kern_return_t status1 = xpc_mach_port_retain_send(port);
|
||||
kern_return_t status2 = xpc_mach_port_retain_send_once(port);
|
||||
// if either one of them succeeds, consider it a success
|
||||
return (status1 == KERN_SUCCESS || status2 == KERN_SUCCESS) ? KERN_SUCCESS : status1;
|
||||
};
|
||||
|
||||
kern_return_t xpc_mach_port_release_send_any(mach_port_t port) {
|
||||
kern_return_t status1 = xpc_mach_port_release_send(port);
|
||||
kern_return_t status2 = xpc_mach_port_release_send_once(port);
|
||||
// if either one of them succeeds, consider it a success
|
||||
return (status1 == KERN_SUCCESS || status2 == KERN_SUCCESS) ? KERN_SUCCESS : status1;
|
||||
};
|
||||
|
||||
kern_return_t xpc_mach_port_release_receive(mach_port_t port) {
|
||||
return xpc_mach_port_release_right(port, MACH_PORT_RIGHT_RECEIVE);
|
||||
};
|
||||
|
||||
mach_port_right_t xpc_mach_msg_type_name_to_port_right(mach_msg_type_name_t type_name) {
|
||||
switch (type_name) {
|
||||
case MACH_MSG_TYPE_MOVE_RECEIVE:
|
||||
return MACH_PORT_RIGHT_RECEIVE;
|
||||
case MACH_MSG_TYPE_MOVE_SEND:
|
||||
case MACH_MSG_TYPE_COPY_SEND:
|
||||
case MACH_MSG_TYPE_MAKE_SEND:
|
||||
return MACH_PORT_RIGHT_SEND;
|
||||
case MACH_MSG_TYPE_MOVE_SEND_ONCE:
|
||||
case MACH_MSG_TYPE_MAKE_SEND_ONCE:
|
||||
return MACH_PORT_RIGHT_SEND_ONCE;
|
||||
}
|
||||
return (mach_port_right_t)-1;
|
||||
};
|
||||
|
||||
mach_port_t xpc_mach_port_create_receive(void) {
|
||||
mach_port_t port = MACH_PORT_NULL;
|
||||
if (mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port) != KERN_SUCCESS) {
|
||||
return MACH_PORT_NULL;
|
||||
}
|
||||
return port;
|
||||
};
|
||||
|
||||
mach_port_t xpc_mach_port_create_send_receive(void) {
|
||||
mach_port_t port = xpc_mach_port_create_receive();
|
||||
if (!MACH_PORT_VALID(port)) {
|
||||
return MACH_PORT_NULL;
|
||||
}
|
||||
if (xpc_mach_port_make_send(port) != KERN_SUCCESS) {
|
||||
xpc_mach_port_release_receive(port);
|
||||
return MACH_PORT_NULL;
|
||||
}
|
||||
return port;
|
||||
};
|
||||
|
||||
bool xpc_mach_port_is_send_once(mach_port_t port) {
|
||||
mach_port_urefs_t refs = 0;
|
||||
if (MACH_PORT_VALID(port)) {
|
||||
mach_port_get_refs(mach_task_self(), port, MACH_PORT_RIGHT_SEND_ONCE, &refs);
|
||||
}
|
||||
return refs > 0;
|
||||
};
|
||||
|
||||
XPC_NORETURN
|
||||
void _xpc_abortv(const char* function, const char* file, size_t line, const char* reason_format, va_list args) {
|
||||
char* message = NULL;
|
||||
char* reason = NULL;
|
||||
if (reason_format == NULL) {
|
||||
reason = strdup("aborting for unknown reason");
|
||||
} else {
|
||||
vasprintf(&reason, reason_format, args);
|
||||
}
|
||||
asprintf(&message, "libxpc: %s:%zu: %s: %s", file, line, function, reason);
|
||||
free(reason);
|
||||
abort_with_reason(OS_REASON_LIBXPC, 0, message, 0);
|
||||
free(message); // unreachable
|
||||
};
|
||||
|
||||
XPC_NORETURN
|
||||
void _xpc_abort(const char* function, const char* file, size_t line, const char* reason_format, ...) {
|
||||
va_list args;
|
||||
va_start(args, reason_format);
|
||||
_xpc_abortv(function, file, line, reason_format, args);
|
||||
va_end(args); // unreachable
|
||||
};
|
||||
|
||||
XPC_NORETURN
|
||||
void _xpc_assertion_failed(const char* function, const char* file, size_t line, const char* expression) {
|
||||
_xpc_abort(function, file, line, "assertion failed: %s", expression);
|
||||
};
|
||||
|
||||
void _xpc_logv(const char* function, const char* file, size_t line, xpc_log_priority_t priority, const char* format, va_list args) {
|
||||
char* message = NULL;
|
||||
char* reason = NULL;
|
||||
vasprintf(&reason, format, args);
|
||||
#if XPC_LOG_TO_STDOUT_TOO
|
||||
// also log to stdout
|
||||
printf("libxpc: %s:%zu: %s: %s\n", file, line, function, reason);
|
||||
fflush(stdout);
|
||||
#endif
|
||||
syslog(priority, "libxpc: %s:%zu: %s: %s", file, line, function, reason);
|
||||
free(reason);
|
||||
};
|
||||
|
||||
void _xpc_log(const char* function, const char* file, size_t line, xpc_log_priority_t priority, const char* format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
_xpc_logv(function, file, line, priority, format, args);
|
||||
va_end(args);
|
||||
};
|
||||
|
61
src/uuid.m
61
src/uuid.m
@ -1,6 +1,7 @@
|
||||
#import <xpc/objects/uuid.h>
|
||||
#import <xpc/util.h>
|
||||
#import <xpc/xpc.h>
|
||||
#import <xpc/serialization.h>
|
||||
|
||||
#define UUID_STRING_LENGTH 36
|
||||
|
||||
@ -43,6 +44,66 @@ XPC_CLASS_HEADER(uuid);
|
||||
|
||||
@end
|
||||
|
||||
@implementation XPC_CLASS(uuid) (XPCSerialization)
|
||||
|
||||
- (BOOL)serializable
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSUInteger)serializationLength
|
||||
{
|
||||
return xpc_serial_padded_length(sizeof(xpc_serial_type_t)) + xpc_serial_padded_length(sizeof(uuid_t));
|
||||
}
|
||||
|
||||
+ (instancetype)deserialize: (XPC_CLASS(deserializer)*)deserializer
|
||||
{
|
||||
XPC_CLASS(uuid)* result = nil;
|
||||
xpc_serial_type_t type = XPC_SERIAL_TYPE_INVALID;
|
||||
const uint8_t* bytes = NULL;
|
||||
|
||||
if (![deserializer readU32: &type]) {
|
||||
goto error_out;
|
||||
}
|
||||
if (type != XPC_SERIAL_TYPE_UUID) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![deserializer consume: sizeof(uuid_t) region: (const void**)&bytes]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
result = [[[self class] alloc] initWithBytes: bytes];
|
||||
|
||||
return result;
|
||||
|
||||
error_out:
|
||||
if (result != nil) {
|
||||
[result release];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)serialize: (XPC_CLASS(serializer)*)serializer
|
||||
{
|
||||
XPC_THIS_DECL(uuid);
|
||||
|
||||
if (![serializer writeU32: XPC_SERIAL_TYPE_UUID]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
if (![serializer write: this->value length: sizeof(this->value)]) {
|
||||
goto error_out;
|
||||
}
|
||||
|
||||
return YES;
|
||||
|
||||
error_out:
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//
|
||||
// C API
|
||||
//
|
||||
|
@ -1,3 +1,5 @@
|
||||
add_subdirectory(launchd-service)
|
||||
|
||||
set(TEST_SOURCES
|
||||
array.m
|
||||
base.m
|
||||
|
27
test/launchd-service/CMakeLists.txt
Normal file
27
test/launchd-service/CMakeLists.txt
Normal file
@ -0,0 +1,27 @@
|
||||
add_darling_executable(libxpc_test_server server.c)
|
||||
add_darling_executable(libxpc_test_client client.c)
|
||||
|
||||
target_link_libraries(libxpc_test_server
|
||||
xpc_static
|
||||
objc
|
||||
)
|
||||
|
||||
target_link_libraries(libxpc_test_client
|
||||
xpc_static
|
||||
objc
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
libxpc_test_server
|
||||
libxpc_test_client
|
||||
DESTINATION
|
||||
libexec/darling/usr/libexec
|
||||
)
|
||||
|
||||
install(
|
||||
FILES
|
||||
org.darlinghq.libxpc.test-service.plist
|
||||
DESTINATION
|
||||
libexec/darling/System/Library/LaunchDaemons
|
||||
)
|
337
test/launchd-service/client.c
Normal file
337
test/launchd-service/client.c
Normal file
@ -0,0 +1,337 @@
|
||||
#include <xpc/xpc.h>
|
||||
#include <xpc/connection.h>
|
||||
#include <xpc/endpoint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "service.h"
|
||||
|
||||
#define SECONDS_TO_WAIT 1
|
||||
|
||||
#define client_log(format, ...) printf("client connection: " format "\n", ## __VA_ARGS__)
|
||||
#define client_error(format, ...) fprintf(stderr, "client connection: " format "\n", ## __VA_ARGS__)
|
||||
#define reply_log(format, ...) printf("reply handler: " format "\n", ## __VA_ARGS__)
|
||||
#define reply_error(format, ...) fprintf(stderr, "reply handler: " format "\n", ## __VA_ARGS__)
|
||||
#define anon_client_log(format, ...) printf("anonymous client handler: " format "\n", ## __VA_ARGS__)
|
||||
#define anon_client_error(format, ...) fprintf(stderr, "anonymous client handler: " format "\n", ## __VA_ARGS__)
|
||||
|
||||
#define server_peer_log(format, ...) printf("anonymous server peer connection: " format "\n", ## __VA_ARGS__)
|
||||
#define server_peer_error(format, ...) fprintf(stderr, "anonymous server peer connection: " format "\n", ## __VA_ARGS__)
|
||||
#define server_log(format, ...) printf("anonymous server connection: " format "\n", ## __VA_ARGS__)
|
||||
#define server_error(format, ...) fprintf(stderr, "anonymous server connection: " format "\n", ## __VA_ARGS__)
|
||||
|
||||
#include "server_common.h"
|
||||
|
||||
dispatch_semaphore_t waiter;
|
||||
|
||||
static void reply_handler(bool anonymous, test_service_message_type_t outgoing_message_type, xpc_object_t object) {
|
||||
xpc_type_t obj_type = xpc_get_type(object);
|
||||
if (obj_type == (xpc_type_t)XPC_TYPE_DICTIONARY) {
|
||||
test_service_message_type_t incoming_message_type = xpc_dictionary_get_uint64(object, MESSAGE_TYPE_KEY);
|
||||
|
||||
switch (outgoing_message_type) {
|
||||
case test_service_message_type_hello: {
|
||||
if (incoming_message_type != test_service_message_type_hello) {
|
||||
reply_error("server replied to hello message with invalid message with type: %llu", incoming_message_type);
|
||||
return;
|
||||
}
|
||||
|
||||
reply_log("server replied to hello message with: %s", xpc_dictionary_get_string(object, HELLO_KEY));
|
||||
} break;
|
||||
|
||||
case test_service_message_type_echo: {
|
||||
if (incoming_message_type != test_service_message_type_echo) {
|
||||
reply_error("server replied to echo message with invalid message with type: %llu", incoming_message_type);
|
||||
return;
|
||||
}
|
||||
|
||||
char* desc = xpc_copy_description(xpc_dictionary_get_value(object, ECHO_KEY));
|
||||
reply_log("server replied to echo message with: %s", desc);
|
||||
if (desc) {
|
||||
free(desc);
|
||||
}
|
||||
} break;
|
||||
|
||||
case test_service_message_type_meet_a_new_friend: {
|
||||
xpc_connection_t new_conn = NULL;
|
||||
xpc_object_t message = NULL;
|
||||
|
||||
if (incoming_message_type != test_service_message_type_meet_a_new_friend) {
|
||||
reply_error("server replied to meet-a-new-friend message with invalid message with type: %llu", incoming_message_type);
|
||||
dispatch_semaphore_signal(waiter);
|
||||
return;
|
||||
}
|
||||
|
||||
new_conn = xpc_dictionary_create_connection(object, MEET_A_NEW_FRIEND_KEY);
|
||||
if (!new_conn) {
|
||||
reply_log("server didn't have a friend for us to meet :(");
|
||||
dispatch_semaphore_signal(waiter);
|
||||
return;
|
||||
}
|
||||
|
||||
xpc_connection_set_event_handler(new_conn, ^(xpc_object_t peer_object) {
|
||||
xpc_type_t obj_type = xpc_get_type(object);
|
||||
if (obj_type == (xpc_type_t)XPC_TYPE_DICTIONARY) {
|
||||
test_service_message_type_t server_message_type = xpc_dictionary_get_uint64(object, MESSAGE_TYPE_KEY);
|
||||
} else if (obj_type == (xpc_type_t)XPC_TYPE_ERROR) {
|
||||
if (object == XPC_ERROR_CONNECTION_INVALID) {
|
||||
anon_client_log("connection got cancelled\n");
|
||||
} else if (object == XPC_ERROR_CONNECTION_INTERRUPTED) {
|
||||
anon_client_log("connection got interrupted\n");
|
||||
} else {
|
||||
anon_client_error("received unexpected error: %s\n", xpc_copy_description(object));
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
anon_client_error("received non-dictionary, non-error object in event handler: %s\n", xpc_copy_description(object));
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
xpc_connection_resume(new_conn);
|
||||
|
||||
message = xpc_dictionary_create(NULL, NULL, 0);
|
||||
xpc_dictionary_set_uint64(message, MESSAGE_TYPE_KEY, test_service_message_type_hello);
|
||||
xpc_dictionary_set_string(message, HELLO_KEY, "Hello from an anonymous client!");
|
||||
xpc_connection_send_message_with_reply(new_conn, message, NULL, ^(xpc_object_t reply) {
|
||||
reply_handler(true, test_service_message_type_hello, reply);
|
||||
dispatch_semaphore_signal(waiter);
|
||||
});
|
||||
} break;
|
||||
|
||||
default: {
|
||||
reply_error("user requested unknown message type: %llu", outgoing_message_type);
|
||||
} break;
|
||||
}
|
||||
} else if (obj_type == (xpc_type_t)XPC_TYPE_ERROR) {
|
||||
if (object == XPC_ERROR_CONNECTION_INVALID) {
|
||||
reply_log("connection got cancelled\n");
|
||||
} else if (object == XPC_ERROR_CONNECTION_INTERRUPTED) {
|
||||
reply_log("connection got interrupted\n");
|
||||
} else {
|
||||
reply_error("received unexpected error: %s\n", xpc_copy_description(object));
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
reply_error("received non-dictionary, non-error object in reply handler: %s\n", xpc_copy_description(object));
|
||||
abort();
|
||||
}
|
||||
};
|
||||
|
||||
static void handle_server_peer_message(xpc_object_t message) {
|
||||
test_service_message_type_t message_type = xpc_dictionary_get_uint64(message, MESSAGE_TYPE_KEY);
|
||||
|
||||
switch (message_type) {
|
||||
case test_service_message_type_poke: {
|
||||
server_peer_log("received poke from client");
|
||||
} break;
|
||||
|
||||
case test_service_message_type_hello: {
|
||||
xpc_object_t reply = NULL;
|
||||
server_peer_log("received hello message from client: %s", xpc_dictionary_get_string(message, HELLO_KEY));
|
||||
reply = xpc_dictionary_create_reply(message);
|
||||
xpc_dictionary_set_uint64(reply, MESSAGE_TYPE_KEY, test_service_message_type_hello);
|
||||
xpc_dictionary_set_string(reply, HELLO_KEY, "Hello from the anonymous server (as a reply)!");
|
||||
xpc_connection_send_message(xpc_dictionary_get_remote_connection(message), reply);
|
||||
} break;
|
||||
|
||||
case test_service_message_type_echo: {
|
||||
xpc_object_t reply = NULL;
|
||||
xpc_object_t echo_item = xpc_dictionary_get_value(message, ECHO_KEY);
|
||||
char* desc = xpc_copy_description(echo_item);
|
||||
server_peer_log("received echo request from client for dictionary item: %s", desc);
|
||||
if (desc) {
|
||||
free(desc);
|
||||
}
|
||||
reply = xpc_dictionary_create_reply(message);
|
||||
xpc_dictionary_set_uint64(reply, MESSAGE_TYPE_KEY, test_service_message_type_echo);
|
||||
xpc_dictionary_set_value(reply, ECHO_KEY, echo_item ? echo_item : xpc_null_create());
|
||||
xpc_connection_send_message(xpc_dictionary_get_remote_connection(message), reply);
|
||||
} break;
|
||||
|
||||
case test_service_message_type_friendship_invitation: {
|
||||
server_peer_error("received friendship invitation from client, but this is invalid");
|
||||
} break;
|
||||
|
||||
case test_service_message_type_meet_a_new_friend: {
|
||||
server_peer_error("client wants to meet a new friend, but this is invalid");
|
||||
} break;
|
||||
|
||||
default: {
|
||||
server_peer_error("received unknown message type: %llu", message_type);
|
||||
} break;
|
||||
}
|
||||
};
|
||||
|
||||
static void connection_died(xpc_connection_t connection) {};
|
||||
|
||||
static xpc_endpoint_t setup_anonymous_server(void) {
|
||||
xpc_connection_t anon_server = xpc_connection_create(NULL, NULL);
|
||||
|
||||
xpc_connection_set_event_handler(anon_server, ^(xpc_object_t object) {
|
||||
xpc_type_t obj_type = xpc_get_type(object);
|
||||
if (obj_type == (xpc_type_t)XPC_TYPE_CONNECTION) {
|
||||
handle_new_connection(object);
|
||||
} else if (obj_type == (xpc_type_t)XPC_TYPE_ERROR) {
|
||||
handle_server_error(object);
|
||||
} else {
|
||||
server_error("received non-connection, non-error object in event handler: %s\n", xpc_copy_description(object));
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
xpc_connection_resume(anon_server);
|
||||
|
||||
return xpc_endpoint_create(anon_server);
|
||||
};
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
test_service_message_type_t message_type = test_service_message_type_poke;
|
||||
xpc_connection_t client = xpc_connection_create(TEST_SERVICE_NAME, NULL);
|
||||
xpc_object_t message = NULL;
|
||||
bool synchronous_reply = false;
|
||||
void (^send_with_reply)(void) = NULL;
|
||||
waiter = dispatch_semaphore_create(0);
|
||||
|
||||
if (argc > 1) {
|
||||
char first = argv[1][0];
|
||||
if (first == 'p' || first == 'P') {
|
||||
message_type = test_service_message_type_poke;
|
||||
} else if (first == 'h' || first == 'H') {
|
||||
message_type = test_service_message_type_hello;
|
||||
} else if (first == 'e' || first == 'E') {
|
||||
message_type = test_service_message_type_echo;
|
||||
} else if (first == 'f' || first == 'F') {
|
||||
message_type = test_service_message_type_friendship_invitation;
|
||||
} else if (first == 'm' || first == 'M') {
|
||||
message_type = test_service_message_type_meet_a_new_friend;
|
||||
} else {
|
||||
client_error("unparsable argument: %s", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (argc > 2) {
|
||||
synchronous_reply = true;
|
||||
}
|
||||
|
||||
xpc_connection_set_event_handler(client, ^(xpc_object_t object) {
|
||||
xpc_type_t obj_type = xpc_get_type(object);
|
||||
if (obj_type == (xpc_type_t)XPC_TYPE_DICTIONARY) {
|
||||
test_service_message_type_t server_message_type = xpc_dictionary_get_uint64(object, MESSAGE_TYPE_KEY);
|
||||
switch (server_message_type) {
|
||||
case test_service_message_type_poke: {
|
||||
client_log("received poke from server");
|
||||
} break;
|
||||
|
||||
case test_service_message_type_hello: {
|
||||
xpc_object_t reply = NULL;
|
||||
client_log("received hello message from server: %s", xpc_dictionary_get_string(object, HELLO_KEY));
|
||||
reply = xpc_dictionary_create_reply(object);
|
||||
xpc_dictionary_set_uint64(reply, MESSAGE_TYPE_KEY, test_service_message_type_hello);
|
||||
xpc_dictionary_set_string(reply, HELLO_KEY, "Hello from the client (as a reply)!");
|
||||
xpc_connection_send_message(xpc_dictionary_get_remote_connection(object), reply);
|
||||
} break;
|
||||
|
||||
case test_service_message_type_echo: {
|
||||
xpc_object_t reply = NULL;
|
||||
xpc_object_t echo_item = xpc_dictionary_get_value(object, ECHO_KEY);
|
||||
char* desc = xpc_copy_description(echo_item);
|
||||
client_log("received echo request from server for dictionary item: %s", desc);
|
||||
if (desc) {
|
||||
free(desc);
|
||||
}
|
||||
reply = xpc_dictionary_create_reply(object);
|
||||
xpc_dictionary_set_uint64(reply, MESSAGE_TYPE_KEY, test_service_message_type_echo);
|
||||
xpc_dictionary_set_value(reply, ECHO_KEY, echo_item ? echo_item : xpc_null_create());
|
||||
xpc_connection_send_message(xpc_dictionary_get_remote_connection(object), reply);
|
||||
} break;
|
||||
|
||||
case test_service_message_type_friendship_invitation: {
|
||||
client_error("received friendship invitation from server, but this is invalid");
|
||||
} break;
|
||||
|
||||
case test_service_message_type_meet_a_new_friend: {
|
||||
client_error("server wants to meet a new friend, but this is invalid");
|
||||
} break;
|
||||
|
||||
default: {
|
||||
client_error("server sent message with unknown type: %llu", server_message_type);
|
||||
} break;
|
||||
}
|
||||
} else if (obj_type == (xpc_type_t)XPC_TYPE_ERROR) {
|
||||
if (object == XPC_ERROR_CONNECTION_INVALID) {
|
||||
client_log("connection got cancelled\n");
|
||||
} else if (object == XPC_ERROR_CONNECTION_INTERRUPTED) {
|
||||
client_log("connection got interrupted\n");
|
||||
} else {
|
||||
client_error("received unexpected error: %s\n", xpc_copy_description(object));
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
client_error("received non-dictionary, non-error object in event handler: %s\n", xpc_copy_description(object));
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
xpc_connection_resume(client);
|
||||
|
||||
message = xpc_dictionary_create(NULL, NULL, 0);
|
||||
|
||||
xpc_dictionary_set_uint64(message, MESSAGE_TYPE_KEY, message_type);
|
||||
|
||||
send_with_reply = ^{
|
||||
if (synchronous_reply) {
|
||||
reply_handler(false, message_type, xpc_connection_send_message_with_reply_sync(client, message));
|
||||
dispatch_semaphore_signal(waiter);
|
||||
} else {
|
||||
xpc_connection_send_message_with_reply(client, message, NULL, ^(xpc_object_t object) {
|
||||
reply_handler(false, message_type, object);
|
||||
dispatch_semaphore_signal(waiter);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
switch (message_type) {
|
||||
case test_service_message_type_poke: {
|
||||
xpc_connection_send_message(client, message);
|
||||
|
||||
client_log("poke message submitted, now we'll wait for a bit for the system to send it");
|
||||
|
||||
// we gotta wait a little to ensure the connection is established and the message is sent
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, SECONDS_TO_WAIT * NSEC_PER_SEC), dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
|
||||
dispatch_semaphore_signal(waiter);
|
||||
});
|
||||
} break;
|
||||
|
||||
case test_service_message_type_hello: {
|
||||
xpc_dictionary_set_string(message, HELLO_KEY, "Hello from the client!");
|
||||
send_with_reply();
|
||||
} break;
|
||||
|
||||
case test_service_message_type_echo: {
|
||||
xpc_dictionary_set_string(message, ECHO_KEY, "ECHO... Echo... echo.. *whipsers* echo...");
|
||||
send_with_reply();
|
||||
} break;
|
||||
|
||||
case test_service_message_type_friendship_invitation: {
|
||||
xpc_endpoint_t endpoint = setup_anonymous_server();
|
||||
xpc_dictionary_set_value(message, FRIENDSHIP_INVITATION_KEY, endpoint);
|
||||
xpc_connection_send_message(client, message);
|
||||
dispatch_main(); // stay alive forever because we're now a server
|
||||
} break;
|
||||
|
||||
case test_service_message_type_meet_a_new_friend: {
|
||||
send_with_reply();
|
||||
dispatch_semaphore_wait(waiter, DISPATCH_TIME_FOREVER); // wait again because we need to wait for the anonymous server to respond
|
||||
} break;
|
||||
|
||||
default: {
|
||||
client_error("user requested unknown message type: %llu", message_type);
|
||||
exit(1);
|
||||
} break;
|
||||
}
|
||||
|
||||
dispatch_semaphore_wait(waiter, DISPATCH_TIME_FOREVER);
|
||||
|
||||
return 0;
|
||||
};
|
18
test/launchd-service/org.darlinghq.libxpc.test-service.plist
Normal file
18
test/launchd-service/org.darlinghq.libxpc.test-service.plist
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>org.darlinghq.libxpc.test-service</string>
|
||||
<key>Program</key>
|
||||
<string>/usr/libexec/libxpc_test_server</string>
|
||||
<key>MachServices</key>
|
||||
<dict>
|
||||
<key>org.darlinghq.libxpc.test-service</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<!-- redirect output to a logfile because launchd's stdout output is messed up (incorrect line breaks) -->
|
||||
<key>StandardOutPath</key>
|
||||
<string>/var/log/libxpc_test_service.log</string>
|
||||
</dict>
|
||||
</plist>
|
148
test/launchd-service/server.c
Normal file
148
test/launchd-service/server.c
Normal file
@ -0,0 +1,148 @@
|
||||
#include <xpc/xpc.h>
|
||||
#include <xpc/connection.h>
|
||||
#include <stdio.h>
|
||||
#include <pthread/pthread.h>
|
||||
|
||||
#include <xpc/generic_array.h>
|
||||
|
||||
#include "service.h"
|
||||
#include "../test-util.h"
|
||||
|
||||
#define server_peer_log(format, ...) printf("server peer connection: " format "\n", ## __VA_ARGS__)
|
||||
#define server_peer_error(format, ...) fprintf(stderr, "server peer connection: " format "\n", ## __VA_ARGS__)
|
||||
#define server_log(format, ...) printf("server connection: " format "\n", ## __VA_ARGS__)
|
||||
#define server_error(format, ...) fprintf(stderr, "server connection: " format "\n", ## __VA_ARGS__)
|
||||
|
||||
#include "server_common.h"
|
||||
|
||||
XPC_GENARR_DECL(endpoint, xpc_endpoint_t, static);
|
||||
XPC_GENARR_STRUCT(endpoint, xpc_endpoint_t);
|
||||
XPC_GENARR_DEF(endpoint, xpc_endpoint_t, static);
|
||||
|
||||
XPC_GENARR_DECL(connection, xpc_connection_t, static);
|
||||
XPC_GENARR_STRUCT(connection, xpc_connection_t);
|
||||
XPC_GENARR_DEF(connection, xpc_connection_t, static);
|
||||
XPC_GENARR_SEARCH_DECL(connection, xpc_connection_t, static);
|
||||
XPC_GENARR_SEARCH_DEF(connection, xpc_connection_t, static);
|
||||
|
||||
static void endpoint_dtor(xpc_endpoint_t* endpoint) {
|
||||
xpc_release(*endpoint);
|
||||
};
|
||||
|
||||
static pthread_rwlock_t endpoints_lock = PTHREAD_RWLOCK_INITIALIZER;
|
||||
static xpc_genarr_connection_t endpoint_connections = XPC_GENARR_INITIALIZER(true, NULL);
|
||||
static xpc_genarr_endpoint_t endpoints = XPC_GENARR_INITIALIZER(true, endpoint_dtor);
|
||||
|
||||
static void associate_endpoint(xpc_connection_t connection, xpc_endpoint_t endpoint) {
|
||||
size_t existing_index = 0;
|
||||
xpc_retain(endpoint);
|
||||
pthread_rwlock_wrlock(&endpoints_lock);
|
||||
existing_index = xpc_genarr_connection_find(&endpoint_connections, &connection);
|
||||
if (existing_index == SIZE_MAX) {
|
||||
xpc_genarr_connection_append(&endpoint_connections, &connection);
|
||||
xpc_genarr_endpoint_append(&endpoints, &endpoint);
|
||||
} else {
|
||||
xpc_genarr_endpoint_set(&endpoints, existing_index, &endpoint);
|
||||
}
|
||||
pthread_rwlock_unlock(&endpoints_lock);
|
||||
};
|
||||
|
||||
static void connection_died(xpc_connection_t connection) {
|
||||
size_t existing_index = 0;
|
||||
pthread_rwlock_wrlock(&endpoints_lock);
|
||||
existing_index = xpc_genarr_connection_find(&endpoint_connections, &connection);
|
||||
if (existing_index != SIZE_MAX) {
|
||||
xpc_genarr_connection_remove(&endpoint_connections, existing_index);
|
||||
xpc_genarr_endpoint_remove(&endpoints, existing_index);
|
||||
}
|
||||
pthread_rwlock_unlock(&endpoints_lock);
|
||||
};
|
||||
|
||||
static void handle_server_peer_message(xpc_object_t message) {
|
||||
test_service_message_type_t message_type = xpc_dictionary_get_uint64(message, MESSAGE_TYPE_KEY);
|
||||
|
||||
switch (message_type) {
|
||||
case test_service_message_type_poke: {
|
||||
server_peer_log("received poke from client");
|
||||
} break;
|
||||
|
||||
case test_service_message_type_hello: {
|
||||
xpc_object_t reply = NULL;
|
||||
server_peer_log("received hello message from client: %s", xpc_dictionary_get_string(message, HELLO_KEY));
|
||||
reply = xpc_dictionary_create_reply(message);
|
||||
xpc_dictionary_set_uint64(reply, MESSAGE_TYPE_KEY, test_service_message_type_hello);
|
||||
xpc_dictionary_set_string(reply, HELLO_KEY, "Hello from the server (as a reply)!");
|
||||
xpc_connection_send_message(xpc_dictionary_get_remote_connection(message), reply);
|
||||
} break;
|
||||
|
||||
case test_service_message_type_echo: {
|
||||
xpc_object_t reply = NULL;
|
||||
xpc_object_t echo_item = xpc_dictionary_get_value(message, ECHO_KEY);
|
||||
char* desc = xpc_copy_description(echo_item);
|
||||
server_peer_log("received echo request from client for dictionary item: %s", desc);
|
||||
if (desc) {
|
||||
free(desc);
|
||||
}
|
||||
reply = xpc_dictionary_create_reply(message);
|
||||
xpc_dictionary_set_uint64(reply, MESSAGE_TYPE_KEY, test_service_message_type_echo);
|
||||
xpc_dictionary_set_value(reply, ECHO_KEY, echo_item ? echo_item : xpc_null_create());
|
||||
xpc_connection_send_message(xpc_dictionary_get_remote_connection(message), reply);
|
||||
} break;
|
||||
|
||||
case test_service_message_type_friendship_invitation: {
|
||||
xpc_endpoint_t endpoint = xpc_dictionary_get_value(message, FRIENDSHIP_INVITATION_KEY);
|
||||
server_peer_log("received friendship invitation from client");
|
||||
if (!endpoint || xpc_get_type(endpoint) != (xpc_type_t)XPC_TYPE_ENDPOINT) {
|
||||
server_peer_error("friendship invitation was not an endpoint!");
|
||||
return;
|
||||
}
|
||||
associate_endpoint(xpc_dictionary_get_connection(message), endpoint);
|
||||
} break;
|
||||
|
||||
case test_service_message_type_meet_a_new_friend: {
|
||||
xpc_object_t reply = NULL;
|
||||
size_t index = 0;
|
||||
size_t count = 0;
|
||||
server_peer_log("client wants to meet a new friend");
|
||||
reply = xpc_dictionary_create_reply(message);
|
||||
xpc_dictionary_set_uint64(reply, MESSAGE_TYPE_KEY, test_service_message_type_meet_a_new_friend);
|
||||
pthread_rwlock_rdlock(&endpoints_lock);
|
||||
count = xpc_genarr_endpoint_length(&endpoints);
|
||||
if (count == 0) {
|
||||
xpc_dictionary_set_value(reply, MEET_A_NEW_FRIEND_KEY, xpc_null_create());
|
||||
} else {
|
||||
xpc_endpoint_t endpoint = NULL;
|
||||
index = rand_index(count);
|
||||
xpc_genarr_endpoint_get(&endpoints, index, &endpoint);
|
||||
xpc_dictionary_set_value(reply, MEET_A_NEW_FRIEND_KEY, endpoint ? endpoint : xpc_null_create());
|
||||
}
|
||||
pthread_rwlock_unlock(&endpoints_lock);
|
||||
xpc_connection_send_message(xpc_dictionary_get_remote_connection(message), reply);
|
||||
} break;
|
||||
|
||||
default: {
|
||||
server_peer_error("received unknown message type: %llu", message_type);
|
||||
} break;
|
||||
}
|
||||
};
|
||||
|
||||
int main(int arc, char** argv) {
|
||||
xpc_connection_t server = xpc_connection_create_mach_service(TEST_SERVICE_NAME, NULL, XPC_CONNECTION_MACH_SERVICE_LISTENER);
|
||||
|
||||
xpc_connection_set_event_handler(server, ^(xpc_object_t object) {
|
||||
xpc_type_t obj_type = xpc_get_type(object);
|
||||
if (obj_type == (xpc_type_t)XPC_TYPE_CONNECTION) {
|
||||
handle_new_connection(object);
|
||||
} else if (obj_type == (xpc_type_t)XPC_TYPE_ERROR) {
|
||||
handle_server_error(object);
|
||||
} else {
|
||||
server_error("received non-connection, non-error object in event handler: %s\n", xpc_copy_description(object));
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
xpc_connection_resume(server);
|
||||
|
||||
dispatch_main();
|
||||
return 0;
|
||||
};
|
62
test/launchd-service/server_common.h
Normal file
62
test/launchd-service/server_common.h
Normal file
@ -0,0 +1,62 @@
|
||||
#ifndef _XPC_TEST_LAUNCHD_SERVICE_SERVER_COMMON_H_
|
||||
#define _XPC_TEST_LAUNCHD_SERVICE_SERVER_COMMON_H_
|
||||
|
||||
#include <xpc/xpc.h>
|
||||
#include <xpc/connection.h>
|
||||
|
||||
#ifndef server_peer_log
|
||||
#define server_peer_log(...)
|
||||
#endif
|
||||
#ifndef server_peer_error
|
||||
#define server_peer_error(...)
|
||||
#endif
|
||||
|
||||
#ifndef server_log
|
||||
#define server_log(...)
|
||||
#endif
|
||||
#ifndef server_error
|
||||
#define server_error(...)
|
||||
#endif
|
||||
|
||||
static void handle_server_peer_message(xpc_object_t message);
|
||||
static void connection_died(xpc_connection_t connection);
|
||||
|
||||
static void handle_server_peer_error(xpc_connection_t connection, xpc_object_t error) {
|
||||
if (error == XPC_ERROR_CONNECTION_INVALID) {
|
||||
server_peer_log("client died (or the parent server did)\n");
|
||||
connection_died(connection);
|
||||
} else if (error == XPC_ERROR_TERMINATION_IMMINENT) {
|
||||
server_peer_log("someone wants to kill us\n");
|
||||
} else {
|
||||
server_peer_error("received unexpected error: %s", xpc_copy_description(error));
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
static void handle_new_connection(xpc_connection_t connection) {
|
||||
xpc_connection_set_event_handler(connection, ^(xpc_object_t object) {
|
||||
xpc_type_t obj_type = xpc_get_type(object);
|
||||
if (obj_type == (xpc_type_t)XPC_TYPE_DICTIONARY) {
|
||||
handle_server_peer_message(object);
|
||||
} else if (obj_type == (xpc_type_t)XPC_TYPE_ERROR) {
|
||||
handle_server_peer_error(connection, object);
|
||||
} else {
|
||||
server_peer_error("received non-connection, non-error object in event handler: %s\n", xpc_copy_description(object));
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
xpc_connection_resume(connection);
|
||||
};
|
||||
|
||||
static void handle_server_error(xpc_object_t error) {
|
||||
if (error == XPC_ERROR_CONNECTION_INVALID) {
|
||||
server_log("server died (or got cancelled)\n");
|
||||
} else if (error == XPC_ERROR_TERMINATION_IMMINENT) {
|
||||
server_log("someone wants to kill us\n");
|
||||
} else {
|
||||
server_error("received unexpected error: %s\n", xpc_copy_description(error));
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // _XPC_TEST_LAUNCHD_SERVICE_SERVER_COMMON_H_
|
40
test/launchd-service/service.h
Normal file
40
test/launchd-service/service.h
Normal file
@ -0,0 +1,40 @@
|
||||
#ifndef _XPC_TEST_LAUNCHD_SERVICE_SERVICE_H_
|
||||
#define _XPC_TEST_LAUNCHD_SERVICE_SERVICE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define TEST_SERVICE_NAME "org.darlinghq.libxpc.test-service"
|
||||
|
||||
enum test_service_message_type {
|
||||
// an invalid message type
|
||||
test_service_message_type_invalid,
|
||||
|
||||
// simple one-way message from either the client or the server with no content (besides the message type) and no expectation of a reply
|
||||
test_service_message_type_poke,
|
||||
|
||||
// say hello to server and have it say hello back
|
||||
test_service_message_type_hello,
|
||||
|
||||
// have the server echo anything you say
|
||||
test_service_message_type_echo,
|
||||
|
||||
// tell the server you'd like to meet some friends
|
||||
test_service_message_type_friendship_invitation,
|
||||
|
||||
// have the server introduce you to a client that's looking for friends
|
||||
test_service_message_type_meet_a_new_friend,
|
||||
};
|
||||
|
||||
typedef uint64_t test_service_message_type_t;
|
||||
|
||||
#define MESSAGE_TYPE_KEY "message-type"
|
||||
|
||||
#define HELLO_KEY "hello"
|
||||
|
||||
#define ECHO_KEY "echo"
|
||||
|
||||
#define FRIENDSHIP_INVITATION_KEY "friendship-invitation"
|
||||
|
||||
#define MEET_A_NEW_FRIEND_KEY "meet-a-new-friend"
|
||||
|
||||
#endif // _XPC_TEST_LAUNCHD_SERVICE_SERVICE_H_
|
@ -7,6 +7,6 @@
|
||||
|
||||
#include <ctest.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int main(int argc, const char** argv) {
|
||||
return ctest_main(argc, argv);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user