diff --git a/internal/cgohandler/cgohandler.go b/internal/cgohandler/cgohandler.go new file mode 100644 index 00000000..495cd79c --- /dev/null +++ b/internal/cgohandler/cgohandler.go @@ -0,0 +1,40 @@ +package cgohandler + +import ( + "runtime" + "runtime/cgo" +) + +// Handler holds a cgo.Handle for an Object. +// It provides methods to hold and release the handle. +// handle will released when Handler is cleaned up. +type Handler struct { + handle cgo.Handle +} + +// releaseOnCleanup registers a cleanup function to delete the cgo.Handle when cleaned up. +func (h *Handler) releaseOnCleanup() { + runtime.AddCleanup(h, func(h cgo.Handle) { + h.Delete() + }, h.handle) +} + +// New creates a new [Handler] and holds the given value. +func New(v any) (*Handler, uintptr) { + if v == nil { + return nil, 0 + } + h := &Handler{cgo.NewHandle(v)} + h.releaseOnCleanup() + return h, uintptr(h.handle) +} + +// Unwrap unwraps the cgo.Handle from the given uintptr and returns the associated value. +// It does NOT delete the handle; it expects the handle to be managed by [Handler] or caller. +func Unwrap[T any](handle uintptr) T { + if handle == 0 { + var zero T + return zero + } + return cgo.Handle(handle).Value().(T) +} diff --git a/internal/objc/objc.go b/internal/objc/objc.go index cb232eaf..12fcc48a 100644 --- a/internal/objc/objc.go +++ b/internal/objc/objc.go @@ -119,6 +119,9 @@ func Retain(o NSObject) { // Ptr returns unsafe.Pointer of the NSObject func Ptr(o NSObject) unsafe.Pointer { + if o == nil { + return nil + } return o.ptr() } diff --git a/xpc/array.go b/xpc/array.go new file mode 100644 index 00000000..aa741cc9 --- /dev/null +++ b/xpc/array.go @@ -0,0 +1,267 @@ +package xpc + +/* +#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc +# include "xpc_darwin.h" +*/ +import "C" +import ( + "iter" + "runtime/cgo" + "time" + "unsafe" + + "github.com/Code-Hex/vz/v3/internal/cgohandler" + "github.com/Code-Hex/vz/v3/internal/objc" +) + +// Array represents an XPC array([XPC_TYPE_ARRAY]) object. [TypeArray] +// +// [XPC_TYPE_ARRAY]: https://developer.apple.com/documentation/xpc/xpc_type_array-c.macro?language=objc +type Array struct { + *xpcObject +} + +var _ Object = &Array{} + +// MARK: - Constructor + +// NewArray creates a new [Array] from the given [Object]s. +// - https://developer.apple.com/documentation/xpc/xpc_array_create(_:_:)?language=objc +func NewArray(objects ...Object) *Array { + n := len(objects) + if n == 0 { + return ReleaseOnCleanup(&Array{newXpcObject(C.xpcArrayCreate(nil, 0))}) + } + cObjects := make([]unsafe.Pointer, n) + for i, obj := range objects { + cObjects[i] = objc.Ptr(obj) + } + return ReleaseOnCleanup(&Array{newXpcObject(C.xpcArrayCreate( + (*unsafe.Pointer)(unsafe.Pointer(&cObjects[0])), + C.size_t(len(cObjects)), + ))}) +} + +// MARK: - Value Accessors + +// GetValue retrieves the [Object] at the given index in the [Array]. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_value(_:_:)?language=objc +func (a *Array) GetValue(index int) Object { + ptr := C.xpcArrayGetValue(objc.Ptr(a), C.size_t(index)) + return NewObject(ptr) +} + +// SetValue sets the [Object] at the given index in the [Array]. +// The value can not be nil. +// - https://developer.apple.com/documentation/xpc/xpc_array_set_value(_:_:_:)?language=objc +func (a *Array) SetValue(index int, value Object) { + C.xpcArraySetValue(objc.Ptr(a), C.size_t(index), objc.Ptr(value)) +} + +// AppendValue appends the given [Object] to the end of the [Array]. +// - https://developer.apple.com/documentation/xpc/xpc_array_append_value(_:_:)?language=objc +func (a *Array) AppendValue(value Object) { + C.xpcArrayAppendValue(objc.Ptr(a), objc.Ptr(value)) +} + +// MARK: - Iteration + +// Count returns the number of elements in the [Array]. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_count(_:)?language=objc +func (a *Array) Count() int { + return int(C.xpcArrayGetCount(objc.Ptr(a))) +} + +// ArrayApplier is a function type for applying to each element in the Array. +type ArrayApplier func(uint64, Object) bool + +// callArrayApplier is called from C to apply a function to each element in the Array. +// +//export callArrayApplier +func callArrayApplier(cgoApplier uintptr, index C.size_t, cgoValue uintptr) C.bool { + applier := cgohandler.Unwrap[ArrayApplier](cgoApplier) + value := unwrapObject[Object](cgoValue) + result := applier(uint64(index), value) + return C.bool(result) +} + +// All iterates over all elements in the [Array]. +// - https://developer.apple.com/documentation/xpc/xpc_array_apply(_:_:)?language=objc +func (a *Array) All() iter.Seq2[uint64, Object] { + return func(yieald func(uint64, Object) bool) { + cgoApplier := cgo.NewHandle(ArrayApplier(yieald)) + defer cgoApplier.Delete() + _ = C.xpcArrayApply( + objc.Ptr(a), + C.uintptr_t(cgoApplier), + ) + } +} + +// Values iterates over all values in the [Array] using [Array.All]. +func (a *Array) Values() iter.Seq[Object] { + return func(yieald func(Object) bool) { + for _, value := range a.All() { + if !yieald(value) { + return + } + } + } +} + +// MARK: - Typed Getters + +// DupFd retrieves duplicated file descriptors from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_dup_fd(_:_:)?language=objc +func (a *Array) DupFd(index int) uintptr { + return uintptr(C.xpcArrayDupFd(objc.Ptr(a), C.size_t(index))) +} + +// GetArray retrieves an [Array] value from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_array(_:_:)?language=objc +func (a *Array) GetArray(index int) *Array { + ptr := C.xpcArrayGetArray(objc.Ptr(a), C.size_t(index)) + return &Array{newXpcObject(ptr)} +} + +// GetBool retrieves a boolean value from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_bool(_:_:)?language=objc +func (a *Array) GetBool(index int) bool { + return bool(C.xpcArrayGetBool(objc.Ptr(a), C.size_t(index))) +} + +// GetData retrieves a byte slice from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_data(_:_:_:)?language=objc +func (a *Array) GetData(index int) []byte { + var length C.size_t + dataPtr := C.xpcArrayGetData(objc.Ptr(a), C.size_t(index), &length) + if dataPtr == nil || length == 0 { + return nil + } + return C.GoBytes(unsafe.Pointer(dataPtr), C.int(length)) +} + +// GetDate retrieves a date value from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_date(_:_:)?language=objc +func (a *Array) GetDate(index int) time.Time { + unixNano := C.xpcArrayGetDate(objc.Ptr(a), C.size_t(index)) + return time.Unix(0, int64(unixNano)) +} + +// GetDictionary retrieves a [Dictionary] value from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_dictionary(_:_:)?language=objc +func (a *Array) GetDictionary(index int) *Dictionary { + ptr := C.xpcArrayGetDictionary(objc.Ptr(a), C.size_t(index)) + return &Dictionary{newXpcObject(ptr)} +} + +// GetDouble retrieves a double value from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_double(_:_:)?language=objc +func (a *Array) GetDouble(index int) float64 { + return float64(C.xpcArrayGetDouble(objc.Ptr(a), C.size_t(index))) +} + +// GetInt64 retrieves an int64 value from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_int64(_:_:)?language=objc +func (a *Array) GetInt64(index int) int64 { + return int64(C.xpcArrayGetInt64(objc.Ptr(a), C.size_t(index))) +} + +// GetString retrieves a string value from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_string(_:_:)?language=objc +func (a *Array) GetString(index int) string { + cstr := C.xpcArrayGetString(objc.Ptr(a), C.size_t(index)) + return C.GoString(cstr) +} + +// GetUInt64 retrieves a uint64 value from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_uint64(_:_:)?language=objc +func (a *Array) GetUInt64(index int) uint64 { + return uint64(C.xpcArrayGetUInt64(objc.Ptr(a), C.size_t(index))) +} + +// GetUUID retrieves a UUID value from the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_get_uuid(_:_:)?language=objc +func (a *Array) GetUUID(index int) [16]byte { + var uuid [16]byte + ptr := C.xpcArrayGetUUID(objc.Ptr(a), C.size_t(index)) + if ptr == nil { + return uuid + } + copy(uuid[:], unsafe.Slice((*byte)(ptr), 16)) + return uuid +} + +// MARK: - Typed Setters + +// SetBool sets a boolean value in the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_set_bool(_:_:_:)?language=objc +func (a *Array) SetBool(index int, value bool) { + cvalue := C.bool(value) + C.xpcArraySetBool(objc.Ptr(a), C.size_t(index), cvalue) +} + +// SetData sets a byte slice in the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_set_data(_:_:_:_:)?language=objc +func (a *Array) SetData(index int, value []byte) { + var ptr unsafe.Pointer + var length C.size_t + if len(value) > 0 { + ptr = unsafe.Pointer(&value[0]) + length = C.size_t(len(value)) + } + C.xpcArraySetData(objc.Ptr(a), C.size_t(index), ptr, length) +} + +// SetDate sets a date value in the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_set_date(_:_:_:)?language=objc +func (a *Array) SetDate(index int, value time.Time) { + unixNano := C.int64_t(value.UnixNano()) + C.xpcArraySetDate(objc.Ptr(a), C.size_t(index), unixNano) +} + +// SetDouble sets a double value in the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_set_double(_:_:_:)?language=objc +func (a *Array) SetDouble(index int, value float64) { + cvalue := C.double(value) + C.xpcArraySetDouble(objc.Ptr(a), C.size_t(index), cvalue) +} + +// SetFd sets a file descriptor in the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_set_fd(_:_:_:)?language=objc +func (a *Array) SetFd(index int, fd uintptr) { + cfd := C.int(fd) + C.xpcArraySetFd(objc.Ptr(a), C.size_t(index), cfd) +} + +// SetInt64 sets an int64 value in the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_set_int64(_:_:_:)?language=objc +func (a *Array) SetInt64(index int, value int64) { + cvalue := C.int64_t(value) + C.xpcArraySetInt64(objc.Ptr(a), C.size_t(index), cvalue) +} + +// SetString sets a string value in the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_set_string(_:_:_:)?language=objc +func (a *Array) SetString(index int, value string) { + cstr := C.CString(value) + defer C.free(unsafe.Pointer(cstr)) + C.xpcArraySetString(objc.Ptr(a), C.size_t(index), cstr) +} + +// SetUInt64 sets a uint64 value in the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_set_uint64(_:_:_:)?language=objc +func (a *Array) SetUInt64(index int, value uint64) { + cvalue := C.uint64_t(value) + C.xpcArraySetUInt64(objc.Ptr(a), C.size_t(index), cvalue) +} + +// SetUUID sets a UUID value in the [Array] at the given index. +// - https://developer.apple.com/documentation/xpc/xpc_array_set_uuid(_:_:_:)?language=objc +func (a *Array) SetUUID(index int, value [16]byte) { + C.xpcArraySetUUID(objc.Ptr(a), C.size_t(index), (*C.uint8_t)(unsafe.Pointer(&value[0]))) +} + +// ArrayApppendIndex is a constant to append an element to the end of the [Array]. +const ArrayApppendIndex = C.XPC_ARRAY_APPEND diff --git a/xpc/dictionary.go b/xpc/dictionary.go new file mode 100644 index 00000000..d06d02be --- /dev/null +++ b/xpc/dictionary.go @@ -0,0 +1,370 @@ +package xpc + +/* +#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc +# include "xpc_darwin.h" +*/ +import "C" +import ( + "fmt" + "iter" + "runtime/cgo" + "time" + "unsafe" + + "github.com/Code-Hex/vz/v3/internal/cgohandler" + "github.com/Code-Hex/vz/v3/internal/objc" +) + +// Dictionary represents an XPC dictionary ([XPC_TYPE_DICTIONARY]) object. [TypeDictionary] +// +// [XPC_TYPE_DICTIONARY]: https://developer.apple.com/documentation/xpc/xpc_type_dictionary-c.macro?language=objc +type Dictionary struct { + *xpcObject +} + +var _ Object = &Dictionary{} + +// MARK: - Constructor + +// NewDictionary creates a new empty [Dictionary] object and applies the given entries. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_create_empty()?language=objc +// +// The entries can be created using [DictionaryEntry] functions such as [KeyValue]. +func NewDictionary(entries ...DictionaryEntry) *Dictionary { + d := ReleaseOnCleanup(&Dictionary{newXpcObject(C.xpcDictionaryCreateEmpty())}) + for _, e := range entries { + e(d) + } + return d +} + +// DictionaryEntry defines a function type for customizing [NewDictionary] or [Dictionary.CreateReply]. +type DictionaryEntry func(*Dictionary) + +// KeyValue sets a [Object] value for the given key in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_value(_:_:_:)?language=objc +func KeyValue(key string, value Object) DictionaryEntry { + return func(o *Dictionary) { + o.SetValue(key, value) + } +} + +// MARK: - CreateReply + +// DictionaryCreateReply creates a new reply [Dictionary] based on the current [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_create_reply(_:)?language=objc +// +// The entries can be created using [DictionaryEntry] functions such as [KeyValue]. +func (o *Dictionary) CreateReply(entries ...DictionaryEntry) *Dictionary { + // Do not use ReleaseOnCleanup here because the reply dictionary will be released in C after sending. + d := &Dictionary{newXpcObject(C.xpcDictionaryCreateReply(objc.Ptr(o)))} + for _, entry := range entries { + entry(d) + } + return d +} + +// MARK: - Value Accessors + +// SetValue sets an [Object] value for the given key in the [Dictionary]. +// The value may be nil, which removes the key from the dictionary. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_value(_:_:_:)?language=objc +func (o *Dictionary) SetValue(key string, value Object) { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + C.xpcDictionarySetValue(objc.Ptr(o), cKey, objc.Ptr(value)) +} + +// Count returns the number of key-value pairs in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_count(_:)?language=objc +func (o *Dictionary) Count() int { + return int(C.xpcDictionaryGetCount(objc.Ptr(o))) +} + +// GetValue retrieves an [Object] value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_value(_:_:)?language=objc +// +// Returns nil if the key does not exist. +func (o *Dictionary) GetValue(key string) Object { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + value := C.xpcDictionaryGetValue(objc.Ptr(o), cKey) + if value == nil { + return nil + } + return NewObject(value) +} + +// MARK: - Iteration + +// DictionaryApplier is a function type for applying to each key-value pair in the XPC dictionary. +type DictionaryApplier func(string, Object) bool + +// callDictionaryApplier is called from C to apply a function to each key-value pair in the XPC dictionary object. +// +//export callDictionaryApplier +func callDictionaryApplier(cgoApplier uintptr, cKey *C.char, cgoValue uintptr) C.bool { + applier := cgohandler.Unwrap[DictionaryApplier](cgoApplier) + return C.bool(applier(C.GoString(cKey), unwrapObject[Object](cgoValue))) +} + +// All iterates over all key-value pairs in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_apply(_:_:)?language=objc +func (o *Dictionary) All() iter.Seq2[string, Object] { + return func(yieald func(string, Object) bool) { + cgoApplier := cgo.NewHandle(DictionaryApplier(yieald)) + defer cgoApplier.Delete() + C.xpcDictionaryApply(objc.Ptr(o), C.uintptr_t(cgoApplier)) + } +} + +// Keys iterates over all keys in the [Dictionary] using [Dictionary.All]. +func (o *Dictionary) Keys() iter.Seq[string] { + return func(yieald func(string) bool) { + for key := range o.All() { + if !yieald(key) { + return + } + } + } +} + +// Values iterates over all [Object] values in the [Dictionary] using [Dictionary.All]. +func (o *Dictionary) Values() iter.Seq[Object] { + return func(yieald func(Object) bool) { + for _, value := range o.All() { + if !yieald(value) { + return + } + } + } +} + +// Entries iterates over all [DictionaryEntry] entries in the [Dictionary] using [Dictionary.All]. +func (o *Dictionary) Entries() iter.Seq[DictionaryEntry] { + return func(yieald func(DictionaryEntry) bool) { + for key, value := range o.All() { + if !yieald(KeyValue(key, value)) { + return + } + } + } +} + +// MARK: - Typed Getters + +// DupFd retrieves a duplicated file descriptor from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_dup_fd(_:_:)?language=objc +func (o *Dictionary) DupFd(key string) uintptr { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + return uintptr(C.xpcDictionaryDupFd(objc.Ptr(o), cKey)) +} + +// GetArray retrieves an [Array] value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_array(_:_:)?language=objc +// +// Returns nil if the key does not exist. +func (o *Dictionary) GetArray(key string) *Array { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + p := C.xpcDictionaryGetArray(objc.Ptr(o), cKey) + if p == nil { + return nil + } + return &Array{newXpcObject(p)} +} + +// GetBool retrieves a boolean value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_bool(_:_:)?language=objc +func (o *Dictionary) GetBool(key string) bool { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + value := C.xpcDictionaryGetBool(objc.Ptr(o), cKey) + return bool(value) +} + +// GetData retrieves a byte slice value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_data(_:_:_:)?language=objc +// +// Returns nil if the key does not exist. +func (o *Dictionary) GetData(key string) []byte { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + var n C.size_t + p := C.xpcDictionaryGetData(objc.Ptr(o), cKey, &n) + if p == nil || n == 0 { + return nil + } + return C.GoBytes(p, C.int(n)) +} + +// GetDate retrieves a date value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_date(_:_:)?language=objc +func (o *Dictionary) GetDate(key string) time.Time { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + unixNano := C.xpcDictionaryGetDate(objc.Ptr(o), cKey) + return time.Unix(0, int64(unixNano)) +} + +// GetDictionary retrieves a [Dictionary] value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_dictionary(_:_:)?language=objc +func (o *Dictionary) GetDictionary(key string) *Dictionary { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + p := C.xpcDictionaryGetDictionary(objc.Ptr(o), cKey) + if p == nil { + return nil + } + return &Dictionary{newXpcObject(p)} +} + +// GetDouble retrieves a double value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_double(_:_:)?language=objc +func (o *Dictionary) GetDouble(key string) float64 { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + value := C.xpcDictionaryGetDouble(objc.Ptr(o), cKey) + return float64(value) +} + +// GetInt64 retrieves an int64 value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_int64(_:_:)?language=objc +func (o *Dictionary) GetInt64(key string) int64 { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + value := C.xpcDictionaryGetInt64(objc.Ptr(o), cKey) + return int64(value) +} + +// GetString retrieves a string value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_string(_:_:)?language=objc +// +// Returns an empty string if the key does not exist. +func (o *Dictionary) GetString(key string) string { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + value := C.xpcDictionaryGetString(objc.Ptr(o), cKey) + return C.GoString(value) +} + +// GetUInt64 retrieves a uint64 value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_uint64(_:_:)?language=objc +func (o *Dictionary) GetUInt64(key string) uint64 { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + value := C.xpcDictionaryGetUInt64(objc.Ptr(o), cKey) + return uint64(value) +} + +// GetUUID retrieves a UUID value from the [Dictionary] by key. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_get_uuid(_:_:)?language=objc +func (o *Dictionary) GetUUID(key string) [16]byte { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + var uuid [16]byte + ptr := C.xpcDictionaryGetUUID(objc.Ptr(o), cKey) + if ptr == nil { + return uuid + } + copy(uuid[:], unsafe.Slice((*byte)(ptr), 16)) + return uuid +} + +// MARK: - Typed Setters + +// SetBool sets a boolean value for the given key in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_bool(_:_:_:)?language=objc +func (o *Dictionary) SetBool(key string, value bool) { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + C.xpcDictionarySetBool(objc.Ptr(o), cKey, C.bool(value)) +} + +// SetData sets a byte slice value for the given key in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_data(_:_:_:_:)?language=objc +func (o *Dictionary) SetData(key string, data []byte) { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + var ptr unsafe.Pointer + var length C.size_t + if len(data) > 0 { + ptr = unsafe.Pointer(&data[0]) + length = C.size_t(len(data)) + } + C.xpcDictionarySetData(objc.Ptr(o), cKey, ptr, length) +} + +// SetDate sets a date value for the given key in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_date(_:_:_:)?language=objc +func (o *Dictionary) SetDate(key string, t time.Time) { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + timestamp := C.int64_t(t.UnixNano()) + C.xpcDictionarySetDate(objc.Ptr(o), cKey, timestamp) +} + +// SetDouble sets a double value for the given key in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_double(_:_:_:)?language=objc +func (o *Dictionary) SetDouble(key string, value float64) { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + C.xpcDictionarySetDouble(objc.Ptr(o), cKey, C.double(value)) +} + +// SetFd sets a file descriptor value for the given key in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_fd(_:_:_:)?language=objc +func (o *Dictionary) SetFd(key string, fd uintptr) { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + C.xpcDictionarySetFd(objc.Ptr(o), cKey, C.int(fd)) +} + +// SetInt64 sets an int64 value for the given key in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_int64(_:_:_:)?language=objc +func (o *Dictionary) SetInt64(key string, value int64) { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + C.xpcDictionarySetInt64(objc.Ptr(o), cKey, C.int64_t(value)) +} + +// SetString sets a string value for the given key in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_string(_:_:_:)?language=objc +func (o *Dictionary) SetString(key string, value string) { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + cValue := C.CString(value) + defer C.free(unsafe.Pointer(cValue)) + C.xpcDictionarySetString(objc.Ptr(o), cKey, cValue) +} + +// SetUInt64 sets a uint64 value for the given key in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_uint64(_:_:_:)?language=objc +func (o *Dictionary) SetUInt64(key string, value uint64) { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + C.xpcDictionarySetUInt64(objc.Ptr(o), cKey, C.uint64_t(value)) +} + +// SetUUID sets a UUID value for the given key in the [Dictionary]. +// - https://developer.apple.com/documentation/xpc/xpc_dictionary_set_uuid(_:_:_:)?language=objc +func (o *Dictionary) SetUUID(key string, uuid [16]byte) { + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + C.xpcDictionarySetUUID(objc.Ptr(o), cKey, (*C.uint8_t)(unsafe.Pointer(&uuid[0]))) +} + +// MARK: - Peer Requirement + +// SenderSatisfies checks if the sender of the message [Dictionary] satisfies the given [PeerRequirement]. +// - https://developer.apple.com/documentation/xpc/xpc_peer_requirement_match_received_message?language=objc +func (d *Dictionary) SenderSatisfies(requirement *PeerRequirement) (bool, error) { + var err_out unsafe.Pointer + res := C.xpcPeerRequirementMatchReceivedMessage(objc.Ptr(requirement), objc.Ptr(d), &err_out) + if err_out != nil { + return false, fmt.Errorf("error matching peer requirement: %w", newRichError(err_out)) + } + return bool(res), nil +} diff --git a/xpc/error.go b/xpc/error.go new file mode 100644 index 00000000..e38d8ec1 --- /dev/null +++ b/xpc/error.go @@ -0,0 +1,48 @@ +package xpc + +/* +#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc +# include "xpc_darwin.h" +*/ +import "C" +import ( + "unsafe" + + "github.com/Code-Hex/vz/v3/internal/objc" +) + +// RichError represents an XPC rich error. ([XPC_TYPE_RICH_ERROR]) [TypeRichError] +// +// [XPC_TYPE_RICH_ERROR]: https://developer.apple.com/documentation/xpc/xpc_rich_error_t?language=objc +type RichError struct { + *xpcObject +} + +var _ Object = &RichError{} + +var _ error = RichError{} + +// newRichError creates a new RichError from an existing xpc_rich_error_t. +// internal use only. +func newRichError(richErr unsafe.Pointer) *RichError { + if richErr == nil { + return nil + } + return &RichError{newXpcObject(richErr)} +} + +// CanRetry indicates whether the operation that caused the [RichError] can be retried. +// +// - https://developer.apple.com/documentation/xpc/xpc_rich_error_can_retry(_:)?language=objc +func (e RichError) CanRetry() bool { + return bool(C.xpcRichErrorCanRetry(objc.Ptr(e))) +} + +// Error implements the [error] interface. +// +// - https://developer.apple.com/documentation/xpc/xpc_rich_error_copy_description(_:)?language=objc +func (e RichError) Error() string { + desc := C.xpcRichErrorCopyDescription(objc.Ptr(e)) + defer C.free(unsafe.Pointer(desc)) + return C.GoString(desc) +} diff --git a/xpc/listener.go b/xpc/listener.go new file mode 100644 index 00000000..e186c16d --- /dev/null +++ b/xpc/listener.go @@ -0,0 +1,100 @@ +package xpc + +/* +#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc +#cgo darwin LDFLAGS: -lobjc -framework Foundation +# include "xpc_darwin.h" +*/ +import "C" +import ( + "unsafe" + + "github.com/Code-Hex/vz/v3/internal/cgohandler" + "github.com/Code-Hex/vz/v3/internal/objc" +) + +// Listener represents an XPC listener. (macOS 14.0+) +// - https://developer.apple.com/documentation/xpc/xpc_listener_t?language=objc +type Listener struct { + *xpcObject + sessionHandler *cgohandler.Handler +} + +// SessionHandler is a function that handles incoming sessions. +type SessionHandler func(session *Session) + +// Option represents an option for creating a [Listener]. +type ListenerOption interface { + inactiveListenerSet(*Listener) +} + +var ( + _ ListenerOption = (*PeerRequirement)(nil) +) + +// NewListener creates a new [Listener] for the given service name. (macOS 14.0+) +// - https://developer.apple.com/documentation/xpc/xpc_listener_create +// +// You need to call [Listener.Activate] to start accepting incoming connections. +func NewListener(service string, handler SessionHandler, options ...ListenerOption) (*Listener, error) { + if err := macOSAvailable(14); err != nil { + return nil, err + } + + cname := C.CString(service) + defer C.free(unsafe.Pointer(cname)) + // Use a serial dispatch queue for the listener, + // because the vmnet framework API does not seem to work well with concurrent queues. + // For example, vmnet_network_create fails when using a concurrent queue. + q := C.dispatchQueueCreateSerial(cname) + defer C.dispatchRelease(q) + cgoHandler, p := cgohandler.New(handler) + var err_out unsafe.Pointer + ptr := C.xpcListenerCreate(cname, q, C.XPC_LISTENER_CREATE_INACTIVE, C.uintptr_t(p), &err_out) + if err_out != nil { + return nil, newRichError(err_out) + } + listener := ReleaseOnCleanup(&Listener{ + xpcObject: newXpcObject(ptr), + sessionHandler: cgoHandler, + }) + for _, opt := range options { + opt.inactiveListenerSet(listener) + } + return listener, nil +} + +// callSessionHandler is called from C to handle incoming sessions. +// +//export callSessionHandler +func callSessionHandler(cgoSessionHandler, cgoSession uintptr) { + handler := cgohandler.Unwrap[SessionHandler](cgoSessionHandler) + session := unwrapObject[*Session](cgoSession) + handler(session) +} + +// String returns a description of the [Listener]. (macOS 14.0+) +// - https://developer.apple.com/documentation/xpc/xpc_listener_copy_description +func (l *Listener) String() string { + desc := C.xpcListenerCopyDescription(objc.Ptr(l)) + defer C.free(unsafe.Pointer(desc)) + return C.GoString(desc) +} + +// Activate starts the [Listener] to accept incoming connections. (macOS 14.0+) +// - https://developer.apple.com/documentation/xpc/xpc_listener_activate +func (l *Listener) Activate() error { + var err_out unsafe.Pointer + C.xpcListenerActivate(objc.Ptr(l), &err_out) + if err_out != nil { + return newRichError(err_out) + } + return nil +} + +// Close stops the [Listener] from accepting incoming connections. (macOS 14.0+) +// - https://developer.apple.com/documentation/xpc/xpc_listener_cancel +func (l *Listener) Close() error { + C.xpcListenerCancel(objc.Ptr(l)) + return nil +} diff --git a/xpc/object.go b/xpc/object.go new file mode 100644 index 00000000..59355cdf --- /dev/null +++ b/xpc/object.go @@ -0,0 +1,350 @@ +package xpc + +/* +#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc +# include "xpc_darwin.h" +*/ +import "C" +import ( + "runtime/cgo" + "time" + "unsafe" + + "github.com/Code-Hex/vz/v3/internal/objc" +) + +// MARK: - Object: Untyped XPC Object + +// Object represents an untyped XPC object ([xpc_object_t]). +// +// [xpc_object_t]: https://developer.apple.com/documentation/xpc/xpc_object_t?language=objc +type Object interface { + objc.NSObject + String() string // String returns the description of the XPC object. + releaseOnCleanup() // releaseOnCleanup releases the XPC object on cleanup. +} + +// GetType returns the [Type] of the given [Object]. +// - https://developer.apple.com/documentation/xpc/xpc_get_type(_:)?language=objc +func GetType(o Object) Type { + return Type{C.xpcGetType(objc.Ptr(o))} +} + +// NewObject creates a new [Object] from an existing [xpc_object_t]. +// The XPC APIs should be wrapped in C to use void* instead of [xpc_object_t]. +// This function accepts an [unsafe.Pointer] that represents void* in C. +// It determines the specific type and returns the appropriate wrapper. +// +// [xpc_object_t]: https://developer.apple.com/documentation/xpc/xpc_object_t?language=objc +func NewObject(o unsafe.Pointer) Object { + if o == nil { + return nil + } + xpcObject := newXpcObject(o) + // Determine the specific type and return the appropriate wrapper. + // It allows users to use type assertions to access type-specific methods. + switch GetType(xpcObject) { + case TypeArray: + return &Array{xpcObject} + case TypeBool: + return &Bool{xpcObject} + case TypeData: + return &Data{xpcObject} + case TypeDate: + return &Date{xpcObject} + case TypeDictionary: + return &Dictionary{xpcObject} + case TypeDouble: + return &Double{xpcObject} + case TypeFD: + return &Fd{xpcObject} + case TypeInt64: + return &Int64{xpcObject} + case TypeNull: + return &Null{xpcObject} + case TypeRichError: + return &RichError{xpcObject} + case TypeSession: + return &Session{xpcObject: xpcObject} + case TypeString: + return &String{xpcObject} + case TypeUInt64: + return &UInt64{xpcObject} + case TypeUUID: + return &UUID{xpcObject} + default: + return xpcObject + } +} + +// wrapRawObject wraps an existing xpc_object_t into an Object and returns a handle. +// intended to be called from C. +// +//export wrapRawObject +func wrapRawObject(ptr unsafe.Pointer) uintptr { + o := NewObject(ptr) + if o == nil { + return 0 + } + return uintptr(cgo.NewHandle(o)) +} + +// unwrapObject unwraps the [cgo.Handle] from the given uintptr and returns the associated Object. +// It also deletes the handle to avoid memory leaks. +func unwrapObject[T any](handle uintptr) T { + if handle == 0 { + var zero T + return zero + } + defer cgo.Handle(handle).Delete() + return cgo.Handle(handle).Value().(T) +} + +// MARK: - Bool: XPC_TYPE_BOOL represents an XPC boolean object. + +// Bool represents an XPC boolean([XPC_TYPE_BOOL]) object. [TypeBool] +// +// [XPC_TYPE_BOOL]: https://developer.apple.com/documentation/xpc/xpc_type_bool-c.macro?language=objc +type Bool struct{ *xpcObject } + +var _ Object = &Bool{} + +// NewBool returns a new [Bool] object from the given Go bool. +// - https://developer.apple.com/documentation/xpc/xpc_bool_create(_:)?language=objc +func NewBool(b bool) *Bool { + cbool := C.bool(b) + return ReleaseOnCleanup(&Bool{newXpcObject(C.xpcBoolCreate(cbool))}) +} + +// Value returns the boolean value of the [Bool] object. +// - https://developer.apple.com/documentation/xpc/xpc_bool_get_value(_:)?language=objc +func (b *Bool) Bool() bool { + return bool(C.xpcBoolGetValue(objc.Ptr(b))) +} + +var ( + BoolTrue = &Bool{newXpcObject(C.xpcBoolTrue())} + BoolFalse = &Bool{newXpcObject(C.xpcBoolFalse())} +) + +// MARK: - Data: XPC_TYPE_DATA represents an XPC data object. + +// Data represents an XPC data([XPC_TYPE_DATA]) object. [TypeData] +// +// [XPC_TYPE_DATA]: https://developer.apple.com/documentation/xpc/xpc_type_data-c.macro?language=objc +type Data struct{ *xpcObject } + +var _ Object = &Data{} + +// NewData returns a new [Data] object from the given byte slice. +// - https://developer.apple.com/documentation/xpc/xpc_data_create(_:_:)?language=objc +func NewData(b []byte) *Data { + if len(b) == 0 { + return ReleaseOnCleanup(&Data{newXpcObject(C.xpcDataCreate(nil, 0))}) + } + return ReleaseOnCleanup(&Data{newXpcObject(C.xpcDataCreate( + unsafe.Pointer(&b[0]), + C.size_t(len(b)), + ))}) +} + +// Bytes returns the byte slice of the [Data] object. +// - https://developer.apple.com/documentation/xpc/xpc_data_get_bytes_ptr(_:)?language=objc +// - https://developer.apple.com/documentation/xpc/xpc_data_get_length(_:)?language=objc +func (d *Data) Bytes() []byte { + size := C.xpcDataGetLength(objc.Ptr(d)) + ptr := C.xpcDataGetBytesPtr(objc.Ptr(d)) + if ptr == nil || size == 0 { + return nil + } + return C.GoBytes(ptr, C.int(size)) +} + +// MARK: - Double: XPC_TYPE_DOUBLE represents an XPC double object. + +// Double represents an XPC double([XPC_TYPE_DOUBLE]) object. [TypeDouble] +// +// [XPC_TYPE_DOUBLE]: https://developer.apple.com/documentation/xpc/xpc_type_double-c.macro?language=objc +type Double struct{ *xpcObject } + +var _ Object = &Double{} + +// NewDouble returns a new [Double] object from the given Go float64. +// - https://developer.apple.com/documentation/xpc/xpc_double_create(_:)?language=objc +func NewDouble(f float64) *Double { + cdouble := C.double(f) + return ReleaseOnCleanup(&Double{newXpcObject(C.xpcDoubleCreate(cdouble))}) +} + +// Value returns the float64 value of the [Double] object. +// - https://developer.apple.com/documentation/xpc/xpc_double_get_value(_:)?language=objc +func (d *Double) Float64() float64 { + return float64(C.xpcDoubleGetValue(objc.Ptr(d))) +} + +// MARK: - Int64: XPC_TYPE_INT64 represents an XPC int64 object. + +// Int64 represents an XPC int64([XPC_TYPE_INT64]) object. [TypeInt64] +// +// [XPC_TYPE_INT64]: https://developer.apple.com/documentation/xpc/xpc_type_int64-c.macro?language=objc +type Int64 struct{ *xpcObject } + +var _ Object = &Int64{} + +// NewInt64 returns a new [Int64] object from the given Go int64. +// - https://developer.apple.com/documentation/xpc/xpc_int64_create(_:)?language=objc +func NewInt64(i int64) *Int64 { + cint64 := C.int64_t(i) + return ReleaseOnCleanup(&Int64{newXpcObject(C.xpcInt64Create(cint64))}) +} + +// Value returns the int64 value of the [Int64] object. +// - https://developer.apple.com/documentation/xpc/xpc_int64_get_value(_:)?language=objc +func (i *Int64) Int64() int64 { + return int64(C.xpcInt64GetValue(objc.Ptr(i))) +} + +// MARK: - UInt64: XPC_TYPE_UINT64 represents an XPC uint64 object. + +// UInt64 represents an XPC uint64([XPC_TYPE_UINT64]) object. [TypeUInt64] +// +// [XPC_TYPE_UINT64]: https://developer.apple.com/documentation/xpc/xpc_type_uint64-c.macro?language=objc +type UInt64 struct{ *xpcObject } + +var _ Object = &UInt64{} + +// NewUInt64 returns a new [UInt64] object from the given Go uint64. +// - https://developer.apple.com/documentation/xpc/xpc_uint64_create(_:)?language=objc +func NewUInt64(u uint64) *UInt64 { + cuint64 := C.uint64_t(u) + return ReleaseOnCleanup(&UInt64{newXpcObject(C.xpcUInt64Create(cuint64))}) +} + +// Value returns the uint64 value of the [UInt64] object. +// - https://developer.apple.com/documentation/xpc/xpc_uint64_get_value(_:)?language=objc +func (u *UInt64) UInt64() uint64 { + return uint64(C.xpcUInt64GetValue(objc.Ptr(u))) +} + +// MARK: - String: XPC_TYPE_STRING represents an XPC string object. + +// String represents an XPC string([XPC_TYPE_STRING]) object. [TypeString] +// +// [XPC_TYPE_STRING]: https://developer.apple.com/documentation/xpc/xpc_type_string-c.macro?language=objc +type String struct{ *xpcObject } + +var _ Object = &String{} + +// NewString returns a new [String] object from the given Go string. +// - https://developer.apple.com/documentation/xpc/xpc_string_create(_:)?language=objc +func NewString(s string) *String { + cstr := C.CString(s) + defer C.free(unsafe.Pointer(cstr)) + return ReleaseOnCleanup(&String{newXpcObject(C.xpcStringCreate(cstr))}) +} + +// String returns the Go string value of the [String] object. +// - https://developer.apple.com/documentation/xpc/xpc_string_get_string_ptr(_:)?language=objc +func (s *String) String() string { + cstr := C.xpcStringGetStringPtr(objc.Ptr(s)) + len := C.xpcStringGetLength(objc.Ptr(s)) + if cstr == nil || len == 0 { + return "" + } + return C.GoStringN(cstr, C.int(len)) +} + +// MARK: - Fd: XPC_TYPE_FD represents an XPC file descriptor object. + +// Fd represents an XPC file descriptor([XPC_TYPE_FD]) object. [TypeFd] +// +// [XPC_TYPE_FD]: https://developer.apple.com/documentation/xpc/xpc_type_fd-c.macro?language=objc +type Fd struct{ *xpcObject } + +var _ Object = &Fd{} + +// NewFd returns a new [Fd] object from the given file descriptor. +// - https://developer.apple.com/documentation/xpc/xpc_fd_create(_:)?language=objc +func NewFd(fd uintptr) *Fd { + cfd := C.int(fd) + return ReleaseOnCleanup(&Fd{newXpcObject(C.xpcFdCreate(cfd))}) +} + +// Dup returns a duplicated file descriptor from the [Fd] object. +// - https://developer.apple.com/documentation/xpc/xpc_fd_dup(_:)?language=objc +func (f *Fd) Dup() uintptr { + return uintptr(C.xpcFdDup(objc.Ptr(f))) +} + +// MARK: - Date: XPC_TYPE_DATE represents an XPC date object. + +// Date represents an XPC date([XPC_TYPE_DATE]) object. [TypeDate] +// +// [XPC_TYPE_DATE]: https://developer.apple.com/documentation/xpc/xpc_type_date-c.macro?language=objc +type Date struct{ *xpcObject } + +var _ Object = &Date{} + +// NewDate returns a new [Date] object from the given Go int64 representing nanoseconds since epoch. +// - https://developer.apple.com/documentation/xpc/xpc_date_create(_:)?language=objc +func NewDate(nanoseconds int64) *Date { + cinterval := C.int64_t(nanoseconds) + return ReleaseOnCleanup(&Date{newXpcObject(C.xpcDateCreate(cinterval))}) +} + +// NewDateFromCurrent returns a new [Date] object representing the current date and time. +// - https://developer.apple.com/documentation/xpc/xpc_date_from_current()?language=objc +func NewDateFromCurrent() *Date { + return ReleaseOnCleanup(&Date{newXpcObject(C.xpcDateCreateFromCurrent())}) +} + +// Value returns the [time.Time] value of the [Date] object. +// - https://developer.apple.com/documentation/xpc/xpc_date_get_value(_:)?language=objc +func (d *Date) Time() time.Time { + unixNano := int64(C.xpcDateGetValue(objc.Ptr(d))) + return time.Unix(0, unixNano) +} + +// MARK: - UUID: XPC_TYPE_UUID represents an XPC UUID object. + +// UUID represents an XPC UUID([XPC_TYPE_UUID]) object. [TypeUUID] +// +// [XPC_TYPE_UUID]: https://developer.apple.com/documentation/xpc/xpc_type_uuid-c.macro?language=objc +type UUID struct{ *xpcObject } + +var _ Object = &UUID{} + +// NewUUID returns a new [UUID] object from the given UUID byte array. +// - https://developer.apple.com/documentation/xpc/xpc_uuid_create(_:)?language=objc +func NewUUID(uuid [16]byte) *UUID { + cuuid := (*C.uint8_t)(unsafe.Pointer(&uuid[0])) + return ReleaseOnCleanup(&UUID{newXpcObject(C.xpcUUIDCreate(cuuid))}) +} + +// Bytes returns the UUID byte array of the [UUID] object. +// - https://developer.apple.com/documentation/xpc/xpc_uuid_get_bytes(_:)?language=objc +func (u *UUID) Bytes() [16]byte { + var uuid [16]byte + ptr := C.xpcUUIDGetBytes(objc.Ptr(u)) + if ptr == nil { + return uuid + } + copy(uuid[:], unsafe.Slice((*byte)(ptr), 16)) + return uuid +} + +// MARK: - Shared Memory: XPC_TYPE_SHMEM represents an XPC shared memory object. +// MARK: - Null: XPC_TYPE_NULL represents an XPC null object. + +// Null represents an XPC null([XPC_TYPE_NULL]) object. [TypeNull] +// +// [XPC_TYPE_NULL]: https://developer.apple.com/documentation/xpc/xpc_type_null-c.macro?language=objc +type Null struct{ *xpcObject } + +var _ Object = &Null{} + +// NewNull returns a new [Null] object. +// - https://developer.apple.com/documentation/xpc/xpc_null_create()?language=objc +func NewNull() *Null { + return ReleaseOnCleanup(&Null{newXpcObject(C.xpcNullCreate())}) +} diff --git a/xpc/osversion_alias.go b/xpc/osversion_alias.go new file mode 100644 index 00000000..cb747790 --- /dev/null +++ b/xpc/osversion_alias.go @@ -0,0 +1,7 @@ +package xpc + +import ( + "github.com/Code-Hex/vz/v3/internal/osversion" +) + +var macOSAvailable = osversion.MacOSAvailable diff --git a/xpc/peer_requirement.go b/xpc/peer_requirement.go new file mode 100644 index 00000000..d3ea8c20 --- /dev/null +++ b/xpc/peer_requirement.go @@ -0,0 +1,62 @@ +package xpc + +/* +#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc +#cgo darwin LDFLAGS: -lobjc -framework Foundation +# include "xpc_darwin.h" +*/ +import "C" +import ( + "unsafe" + + "github.com/Code-Hex/vz/v3/internal/objc" +) + +// PeerRequirement represents an [xpc_peer_requirement_t]. (macOS 26.0+) +// +// [xpc_peer_requirement_t]: https://developer.apple.com/documentation/xpc/xpc_peer_requirement_t?language=objc +type PeerRequirement struct { + *xpcObject +} + +var _ Object = &PeerRequirement{} + +// NewPeerRequirementLwcr creates a [PeerRequirement] from a LWCR object *[Dictionary]. (macOS 26.0+) +// - https://developer.apple.com/documentation/xpc/xpc_peer_requirement_create_lwcr +// - https://developer.apple.com/documentation/security/defining-launch-environment-and-library-constraints?language=objc +func NewPeerRequirementLwcr(lwcr *Dictionary) (*PeerRequirement, error) { + if err := macOSAvailable(26); err != nil { + return nil, err + } + + var err_out unsafe.Pointer + ptr := C.xpcPeerRequirementCreateLwcr(objc.Ptr(lwcr), &err_out) + if err_out != nil { + return nil, newRichError(err_out) + } + return ReleaseOnCleanup(&PeerRequirement{newXpcObject(ptr)}), nil +} + +// NewPeerRequirementLwcrWithEntries creates a [PeerRequirement] from a LWCR object *[Dictionary] constructed +// with the given [DictionaryEntry]s. (macOS 26.0+) +// - https://developer.apple.com/documentation/xpc/xpc_peer_requirement_create_lwcr +// - https://developer.apple.com/documentation/security/defining-launch-environment-and-library-constraints?language=objc +func NewPeerRequirementLwcrWithEntries(entries ...DictionaryEntry) (*PeerRequirement, error) { + return NewPeerRequirementLwcr(NewDictionary(entries...)) +} + +// inactiveListenerSet configures the given [Listener] with the [PeerRequirement]. (macOS 26.0+) +// - https://developer.apple.com/documentation/xpc/xpc_listener_set_peer_requirement +// +// This method implements the [ListenerOption] interface. +func (pr *PeerRequirement) inactiveListenerSet(listener *Listener) { + C.xpcListenerSetPeerRequirement(objc.Ptr(listener), objc.Ptr(pr)) +} + +// inactiveSessionSet configures the given [Session] with the [PeerRequirement]. (macOS 26.0+) +// - https://developer.apple.com/documentation/xpc/xpc_session_set_peer_requirement +// +// This method implements the [SessionOption] interface. +func (pr *PeerRequirement) inactiveSessionSet(session *Session) { + C.xpcSessionSetPeerRequirement(objc.Ptr(session), objc.Ptr(pr)) +} diff --git a/xpc/session.go b/xpc/session.go new file mode 100644 index 00000000..0985e9f1 --- /dev/null +++ b/xpc/session.go @@ -0,0 +1,228 @@ +package xpc + +/* +#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc +#cgo darwin LDFLAGS: -lobjc -framework Foundation +# include "xpc_darwin.h" +*/ +import "C" +import ( + "context" + "runtime/cgo" + "unsafe" + + "github.com/Code-Hex/vz/v3/internal/cgohandler" + "github.com/Code-Hex/vz/v3/internal/objc" +) + +// Session represents an [xpc_session_t]. (macOS 13.0+) +// +// [xpc_session_t]: https://developer.apple.com/documentation/xpc/xpc_session_t?language=objc +type Session struct { + // Exported for use in other packages since unimplemented XPC API may require direct access to xpc_session_t. + *xpcObject + cancellationHandler *cgohandler.Handler + incomingMessageHandler *cgohandler.Handler +} + +var _ Object = &Session{} + +// SessionOption represents an option for configuring a inactive [Session]. +type SessionOption interface { + inactiveSessionSet(*Session) +} + +var ( + _ SessionOption = (MessageHandler)(nil) + _ SessionOption = (CancellationHandler)(nil) + _ SessionOption = (*PeerRequirement)(nil) +) + +// NewSession creates a new [Session] for the given Mach service name. (macOS 13.0+) +// +// [SessionOption](s) can be provided to configure the inactive session before activation. +// Available options include [MessageHandler], [CancellationHandler], and [PeerRequirement]. +// - https://developer.apple.com/documentation/xpc/xpc_session_create_mach_service +func NewSession(macServiceName string, sessionOpts ...SessionOption) (*Session, error) { + if err := macOSAvailable(13); err != nil { + return nil, err + } + + cServiceName := C.CString(macServiceName) + defer C.free(unsafe.Pointer(cServiceName)) + + var err_out unsafe.Pointer + ptr := C.xpcSessionCreateMachService(cServiceName, nil, C.XPC_SESSION_CREATE_INACTIVE, &err_out) + if err_out != nil { + return nil, newRichError(err_out) + } + session := ReleaseOnCleanup(&Session{xpcObject: newXpcObject(ptr)}) + for _, o := range sessionOpts { + o.inactiveSessionSet(session) + } + err := session.activate() + if err != nil { + session.Cancel() + return nil, err + } + return session, nil +} + +// MessageHandler is a function [SessionOption] that handles incoming messages in a [Session]. +// It receives the incoming message *[Dictionary] and returns a reply message *[Dictionary]. +type MessageHandler func(msg *Dictionary) (reply *Dictionary) + +// inactiveSessionSet configures the given [Session] with the [MessageHandler]. (macOS 13.0+) +// - https://developer.apple.com/documentation/xpc/xpc_session_set_incoming_message_handler +func (mh MessageHandler) inactiveSessionSet(s *Session) { + s.setIncomingMessageHandler(mh) +} + +// CancellationHandler is a function [SessionOption] that handles session cancellation in a [Session] +// It receives the [RichError] that caused the cancellation. +type CancellationHandler func(err *RichError) + +// inactiveSessionSet configures the given [Session] with the [CancellationHandler]. (macOS 13.0+) +// - https://developer.apple.com/documentation/xpc/xpc_session_set_cancel_handler +func (ch CancellationHandler) inactiveSessionSet(s *Session) { + s.setCancellationHandler(ch) +} + +// Reject rejects the incoming [Session] with the given reason. (macOS 14.0+) +// - https://developer.apple.com/documentation/xpc/xpc_listener_reject_peer +func (s *Session) Reject(reason string) { + cReason := C.CString(reason) + defer C.free(unsafe.Pointer(cReason)) + C.xpcListenerRejectPeer(objc.Ptr(s), cReason) +} + +// Accept creates a [SessionHandler] that accepts incoming sessions with the given [SessionOption]s. +func Accept(sessionOptions ...SessionOption) SessionHandler { + return func(session *Session) { + for _, opt := range sessionOptions { + opt.inactiveSessionSet(session) + } + } +} + +// String returns a description of the [Session]. (macOS 13.0+) +// - https://developer.apple.com/documentation/xpc/xpc_session_copy_description +func (s *Session) String() string { + desc := C.xpcSessionCopyDescription(objc.Ptr(s)) + defer C.free(unsafe.Pointer(desc)) + return C.GoString(desc) +} + +// activate activates the [Session]. (macOS 13.0+) +// It is called internally after applying all [SessionOption]s in [NewSession]. +// - https://developer.apple.com/documentation/xpc/xpc_session_activate +func (s *Session) activate() error { + var err_out unsafe.Pointer + C.xpcSessionActivate(objc.Ptr(s), &err_out) + if err_out != nil { + return newRichError(err_out) + } + return nil +} + +// callMessageHandler is called from C to handle incoming messages. +// +//export callMessageHandler +func callMessageHandler(cgoMessageHandler, cgoMessage uintptr) (reply unsafe.Pointer) { + handler := cgohandler.Unwrap[MessageHandler](cgoMessageHandler) + message := unwrapObject[*Dictionary](cgoMessage) + return objc.Ptr(handler(message)) +} + +// setIncomingMessageHandler sets the [MessageHandler] for the inactive [Session]. (macOS 13.0+) +// - https://developer.apple.com/documentation/xpc/xpc_session_set_incoming_message_handler +func (s *Session) setIncomingMessageHandler(handler MessageHandler) { + cgoHandler, p := cgohandler.New(handler) + C.xpcSessionSetIncomingMessageHandler(objc.Ptr(s), C.uintptr_t(p)) + // Store the handler after setting it to avoid premature garbage collection of the previous handler. + s.incomingMessageHandler = cgoHandler +} + +// Cancel cancels the [Session]. (macOS 13.0+) +// - https://developer.apple.com/documentation/xpc/xpc_session_cancel +func (s *Session) Cancel() { + C.xpcSessionCancel(objc.Ptr(s)) +} + +// callCancelHandler is called from C to handle session cancellation. +// +//export callCancelHandler +func callCancelHandler(cgoCancelHandler, cgoErr uintptr) { + handler := cgohandler.Unwrap[CancellationHandler](cgoCancelHandler) + err := unwrapObject[*RichError](cgoErr) + handler(err) +} + +// setCancellationHandler sets the [CancellationHandler] for the inactive [Session]. (macOS 13.0+) +// The handler will call [Session.handleCancellation] after executing the provided handler. +// - https://developer.apple.com/documentation/xpc/xpc_session_set_cancel_handler +func (s *Session) setCancellationHandler(handler CancellationHandler) { + cgoHandler, p := cgohandler.New((CancellationHandler)(func(err *RichError) { + if handler != nil { + handler(err) + } + s.handleCancellation(err) + })) + C.xpcSessionSetCancelHandler(objc.Ptr(s), C.uintptr_t(p)) + // Store the handler after setting it to avoid premature garbage collection of the previous handler. + s.cancellationHandler = cgoHandler +} + +// handleCancellation handles [Session] cancellation by deleting the associated handles. +func (s *Session) handleCancellation(_ *RichError) { +} + +type ReplyHandler func(*Dictionary, *RichError) + +// callReplyHandler is called from C to handle reply messages. +// +//export callReplyHandler +func callReplyHandler(cgoReplyHandler uintptr, cgoReply, cgoError uintptr) { + handler := cgohandler.Unwrap[ReplyHandler](cgoReplyHandler) + reply := unwrapObject[*Dictionary](uintptr(cgoReply)) + err := unwrapObject[*RichError](cgoError) + handler(reply, err) + cgo.Handle(cgoReplyHandler).Delete() +} + +// SendMessageWithReply sends a message *[Dictionary] to the [Session] and waits for a reply *[Dictionary]. (macOS 13.0+) +// +// Use [context.Context] to control cancellation and timeouts. +// - https://developer.apple.com/documentation/xpc/xpc_session_send_message_with_reply_async +func (s *Session) SendMessageWithReply(ctx context.Context, message *Dictionary) (*Dictionary, error) { + replyCh := make(chan *Dictionary, 1) + errCh := make(chan *RichError, 1) + replyHandler := (ReplyHandler)(func(reply *Dictionary, err *RichError) { + defer close(replyCh) + defer close(errCh) + if err != nil { + errCh <- ReleaseOnCleanup(Retain(err)) + } else { + replyCh <- ReleaseOnCleanup(Retain(reply)) + } + }) + // handle will be deleted in callReplyHandler + cgoReplyHandler := cgo.NewHandle(replyHandler) + C.xpcSessionSendMessageWithReplyAsync(objc.Ptr(s), objc.Ptr(message), C.uintptr_t(cgoReplyHandler)) + select { + case reply := <-replyCh: + return reply, nil + case err := <-errCh: + return nil, err + case <-ctx.Done(): + return nil, ctx.Err() + } +} + +// SendDictionaryWithReply creates a message *[Dictionary] and calls [Session.SendMessageWithReply] with it. A`(macOS 13.0+) +// +// Use [context.Context] to control cancellation and timeouts. +// The message *[Dictionary] can be customized using [DictionaryEntry]. +func (s *Session) SendDictionaryWithReply(ctx context.Context, entries ...DictionaryEntry) (*Dictionary, error) { + return s.SendMessageWithReply(ctx, NewDictionary(entries...)) +} diff --git a/xpc/type.go b/xpc/type.go new file mode 100644 index 00000000..5a80d67d --- /dev/null +++ b/xpc/type.go @@ -0,0 +1,46 @@ +package xpc + +/* +#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc +# include "xpc_darwin.h" +*/ +import "C" + +// Type represents an XPC type (xpc_type_t). +// - https://developer.apple.com/documentation/xpc/xpc_type_t?language=objc +type Type struct { + xpcType C.xpc_type_t +} + +var ( + TypeActivity = Type{C.XPC_TYPE_ACTIVITY} // https://developer.apple.com/documentation/xpc/xpc_type_activity-c.macro?language=objc + TypeArray = Type{C.XPC_TYPE_ARRAY} // https://developer.apple.com/documentation/xpc/xpc_type_array-c.macro?language=objc + TypeBool = Type{C.XPC_TYPE_BOOL} // https://developer.apple.com/documentation/xpc/xpc_type_bool-c.macro?language=objc + TypeConnection = Type{C.XPC_TYPE_CONNECTION} // https://developer.apple.com/documentation/xpc/xpc_type_connection-c.macro?language=objc + TypeData = Type{C.XPC_TYPE_DATA} // https://developer.apple.com/documentation/xpc/xpc_type_data-c.macro?language=objc + TypeDate = Type{C.XPC_TYPE_DATE} // https://developer.apple.com/documentation/xpc/xpc_type_date-c.macro?language=objc + TypeDictionary = Type{C.XPC_TYPE_DICTIONARY} // https://developer.apple.com/documentation/xpc/xpc_type_dictionary-c.macro?language=objc + TypeDouble = Type{C.XPC_TYPE_DOUBLE} // https://developer.apple.com/documentation/xpc/xpc_type_double-c.macro?language=objc + TypeEndpoint = Type{C.XPC_TYPE_ENDPOINT} // https://developer.apple.com/documentation/xpc/xpc_type_endpoint-c.macro?language=objc + TypeError = Type{C.XPC_TYPE_ERROR} // https://developer.apple.com/documentation/xpc/xpc_type_error-c.macro?language=objc + TypeFD = Type{C.XPC_TYPE_FD} // https://developer.apple.com/documentation/xpc/xpc_type_fd-c.macro?language=objc + TypeInt64 = Type{C.XPC_TYPE_INT64} // https://developer.apple.com/documentation/xpc/xpc_type_int64-c.macro?language=objc + TypeNull = Type{C.XPC_TYPE_NULL} // https://developer.apple.com/documentation/xpc/xpc_type_null-c.macro?language=objc + TypeRichError = Type{C.XPC_TYPE_RICH_ERROR} // does not have official documentation, but defined in + TypeSession = Type{C.XPC_TYPE_SESSION} // does not have official documentation, but defined in + TypeShmem = Type{C.XPC_TYPE_SHMEM} // https://developer.apple.com/documentation/xpc/xpc_type_shmem-c.macro?language=objc + TypeString = Type{C.XPC_TYPE_STRING} // https://developer.apple.com/documentation/xpc/xpc_type_string-c.macro?language=objc + TypeUInt64 = Type{C.XPC_TYPE_UINT64} // https://developer.apple.com/documentation/xpc/xpc_type_uint64-c.macro?language=objc + TypeUUID = Type{C.XPC_TYPE_UUID} // https://developer.apple.com/documentation/xpc/xpc_type_uuid-c.macro?language=objc +) + +// String returns the name of the [XpcType]. +// see: https://developer.apple.com/documentation/xpc/xpc_type_get_name(_:)?language=objc +func (t Type) String() string { + cs := C.xpc_type_get_name(t.xpcType) + if cs == nil { + return "" + } + // do not free cs since it is managed by XPC runtime. + return C.GoString(cs) +} diff --git a/xpc/xpc_darwin.h b/xpc/xpc_darwin.h new file mode 100644 index 00000000..d0682cdf --- /dev/null +++ b/xpc/xpc_darwin.h @@ -0,0 +1,212 @@ +#pragma once + +#import "../internal/osversion/virtualization_helper.h" +#import + +// MARK: - dispatch_queue_t + +void *dispatchQueueCreateSerial(const char *label); +void dispatchRelease(void *queue); + +// MARK: - xpc.h types +// +// The following types are listed in the same order as the XPC documentation index page. +// https://developer.apple.com/documentation/xpc?language=objc + +// MARK: - xpc_listener_t (macOS 14+) + +void *xpcListenerCreate(const char *service_name, void *queue, uint64_t flags, uintptr_t cgo_session_handler, void **error_out); +const char *xpcListenerCopyDescription(void *listener); +bool xpcListenerActivate(void *listener, void **error_out); +void xpcListenerCancel(void *listener); +void xpcListenerRejectPeer(void *session, const char *reason); +// int xpcListenerSetPeerCodeSigningRequirement(void *listener, const char *requirement); + +// MARK: - xpc_session_t (XPC_TYPE_SESSION) (macOS 13+) + +// void *xpcSessionCreateXpcService(const char *service_name, void *queue, uint64_t flags, void **error_out); +void *xpcSessionCreateMachService(const char *service_name, void *queue, uint64_t flags, void **error_out); +// void xpcSessionSetTargetQueue(void *session, void *queue); +const char *xpcSessionCopyDescription(void *session); +bool xpcSessionActivate(void *session, void **error_out); +void xpcSessionSetIncomingMessageHandler(void *session, uintptr_t cgo_message_handler); +void xpcSessionCancel(void *session); +void xpcSessionSetCancelHandler(void *session, uintptr_t cgo_cancel_handler); +// void *xpcSessionSendMessage(void *session, void *message); +void xpcSessionSendMessageWithReplyAsync(void *session, void *message, uintptr_t cgo_reply_handler); +// void *xpcSessionSendMessageWithReplySync(void *session, void *message, void **error_out); + +// MARK: - xpc_rich_error_t (XPC_TYPE_RICH_ERROR) +bool xpcRichErrorCanRetry(void *err); +const char *xpcRichErrorCopyDescription(void *err); + +// MARK: - Identity + +// # xpc_type_t +xpc_type_t xpcGetType(void *object); +const char *xpcTypeGetName(xpc_type_t type); +// # xpc_object_t +// size_t xpxHash(void *object); + +// MARK: - Comparison + +// # xpc_object_t +// bool xpcEqual(void *object1, void *object2); + +// MARK: - Copying + +// # xpc_object_t +// void *xpcCopy(void *object); +const char *xpcCopyDescription(void *object); + +// MARK: - Boolean objects + +// # xpc_object_t (XPC_TYPE_BOOL) +void *xpcBoolCreate(bool value); +bool xpcBoolGetValue(void *object); +void *xpcBoolTrue(); // XPC_BOOL_TRUE +void *xpcBoolFalse(); // XPC_BOOL_FALSE + +// MARK: - Data objects + +// # xpc_object_t (XPC_TYPE_DATA) +void *xpcDataCreate(const void *bytes, size_t length); +// size_t xpcDataGetBytes(void *object, void *buffer, size_t offset, size_t length); +const void *xpcDataGetBytesPtr(void *object); +size_t xpcDataGetLength(void *object); + +// MARK: - Number objects + +// # xpc_object_t (XPC_TYPE_DOUBLE) +void *xpcDoubleCreate(double value); +double xpcDoubleGetValue(void *object); + +// # xpc_object_t (XPC_TYPE_INT64) +void *xpcInt64Create(int64_t value); +int64_t xpcInt64GetValue(void *object); + +// # xpc_object_t (XPC_TYPE_UINT64) +void *xpcUInt64Create(uint64_t value); +uint64_t xpcUInt64GetValue(void *object); + +// MARK: - Array objects + +// # xpc_object_t (XPC_TYPE_ARRAY) +void *xpcArrayCreate(void *const *object, size_t count); +// void *xpcArrayCreateEmpty(); +// void *xpcArrayCreateConnection +void *xpcArrayGetValue(void *object, size_t index); +void xpcArraySetValue(void *object, size_t index, void *value); +void xpcArrayAppendValue(void *object, void *value); +size_t xpcArrayGetCount(void *object); +bool xpcArrayApply(void *object, uintptr_t cgo_applier); +int xpcArrayDupFd(void *object, size_t index); +void *xpcArrayGetArray(void *object, size_t index); +bool xpcArrayGetBool(void *object, size_t index); +const void *xpcArrayGetData(void *object, size_t index, size_t *length); +int64_t xpcArrayGetDate(void *object, size_t index); +void *xpcArrayGetDictionary(void *object, size_t index); +double xpcArrayGetDouble(void *object, size_t index); +int64_t xpcArrayGetInt64(void *object, size_t index); +const char *xpcArrayGetString(void *object, size_t index); +uint64_t xpcArrayGetUInt64(void *object, size_t index); +const uint8_t *xpcArrayGetUUID(void *object, size_t index); +void xpcArraySetBool(void *object, size_t index, bool value); +// void xpcArraySetConnection +void xpcArraySetData(void *object, size_t index, const void *bytes, size_t length); +void xpcArraySetDate(void *object, size_t index, int64_t value); +void xpcArraySetDouble(void *object, size_t index, double value); +void xpcArraySetFd(void *object, size_t index, int fd); +void xpcArraySetInt64(void *object, size_t index, int64_t value); +void xpcArraySetString(void *object, size_t index, const char *string); +void xpcArraySetUInt64(void *object, size_t index, uint64_t value); +void xpcArraySetUUID(void *object, size_t index, const uuid_t uuid); +// XPC_ARRAY_APPEND + +// MARK: - Dictionary objects + +// # xpc_object_t (XPC_TYPE_DICTIONARY) +// void *xpcDictionaryCreate(const char *const *keys, void *const *values, size_t count); +void *xpcDictionaryCreateEmpty(void); +// void *xpcDictionaryCreateConnection +void *xpcDictionaryCreateReply(void *object); +void xpcDictionarySetValue(void *object, const char *key, void *value); +size_t xpcDictionaryGetCount(void *object); +void *xpcDictionaryGetValue(void *object, const char *key); +bool xpcDictionaryApply(void *object, uintptr_t cgo_applier); +int xpcDictionaryDupFd(void *object, const char *key); +void *xpcDictionaryGetArray(void *object, const char *key); +bool xpcDictionaryGetBool(void *object, const char *key); +const void *xpcDictionaryGetData(void *object, const char *key, size_t *length); +int64_t xpcDictionaryGetDate(void *object, const char *key); +void *xpcDictionaryGetDictionary(void *object, const char *key); +double xpcDictionaryGetDouble(void *object, const char *key); +int64_t xpcDictionaryGetInt64(void *object, const char *key); +// void *xpcDictionaryGetRemoteConnection +const char *xpcDictionaryGetString(void *object, const char *key); +uint64_t xpcDictionaryGetUInt64(void *object, const char *key); +const uint8_t *xpcDictionaryGetUUID(void *object, const char *key); +void xpcDictionarySetBool(void *object, const char *key, bool value); +// void xpcDictionarySetConnection +void xpcDictionarySetData(void *object, const char *key, const void *bytes, size_t length); +void xpcDictionarySetDate(void *object, const char *key, int64_t value); +void xpcDictionarySetDouble(void *object, const char *key, double value); +void xpcDictionarySetFd(void *object, const char *key, int fd); +void xpcDictionarySetInt64(void *object, const char *key, int64_t value); +void xpcDictionarySetString(void *object, const char *key, const char *value); +void xpcDictionarySetUInt64(void *object, const char *key, uint64_t value); +void xpcDictionarySetUUID(void *object, const char *key, const uint8_t *uuid); +// void *xpcDictionaryCopyMachSend +// void xpcDictionarySetMachSend + +// MARK: - String objects + +// # xpc_object_t (XPC_TYPE_STRING) +void *xpcStringCreate(const char *string); +// void *xpcStringCreateWithFormat(const char *format, ...); +// void *xpcStringCreateWithFormatAndArguments(const char *format, va_list args); +size_t xpcStringGetLength(void *object); +const char *xpcStringGetStringPtr(void *object); + +// MARK: - File Descriptor objects + +// # xpc_object_t (XPC_TYPE_FD) +void *xpcFdCreate(int fd); +int xpcFdDup(void *object); + +// MARK: - Date objects + +// # xpc_object_t (XPC_TYPE_DATE) +void *xpcDateCreate(int64_t interval); +void *xpcDateCreateFromCurrent(); +int64_t xpcDateGetValue(void *object); + +// MARK: - UUID objects + +// # xpc_object_t (XPC_TYPE_UUID) +void *xpcUUIDCreate(const uuid_t uuid); +const uint8_t *xpcUUIDGetBytes(void *object); + +// MARK: - Shared Memory objects + +// # xpc_object_t (XPC_TYPE_SHMEM) +// void *xpcShmemCreate(void *region, size_t length); +// size_t xpcShmemMap(void *object, void **region); + +// MARK: - Null objects +// # xpc_object_t (XPC_TYPE_NULL) +void *xpcNullCreate(); + +// MARK: - Object life cycle +void *xpcRetain(void *object); +void xpcRelease(void *object); + +// MARK: - xpc_peer_requirement_t (macOS 26+) +void xpcListenerSetPeerRequirement(void *listener, void *peer_requirement); +// void *xpcPeerRequirementCreateEntitlementExists(const char *entitlement, void **error_out); +// void *xpcPeerRequirementCreateEntitlementMatchesValue(const char *entitlement, void *value, void **error_out); +void *xpcPeerRequirementCreateLwcr(void *lwcr, void **error_out); +// void *xpcPeerRequirementCreatePlatformIdentity(const char * signing_identifier, void **error_out); +// void *xpcPeerRequirementCreateTeamIdentity(const char * team_identifier, void **error_out); +bool xpcPeerRequirementMatchReceivedMessage(void *peer_requirement, void *message, void **error_out); +void xpcSessionSetPeerRequirement(void *session, void *peer_requirement); diff --git a/xpc/xpc_darwin.m b/xpc/xpc_darwin.m new file mode 100644 index 00000000..9a3ea3a7 --- /dev/null +++ b/xpc/xpc_darwin.m @@ -0,0 +1,713 @@ +#include "xpc_darwin.h" + +// MARK: - Helper functions defined in Go + +// # xpc_object_t +extern uintptr_t wrapRawObject(void *obj); +// # xpc_listener_t +extern void callSessionHandler(uintptr_t cgoSessionHandler, uintptr_t cgoSession); + +// # xpc_session_t +extern void callReplyHandler(uintptr_t cgoReplyHandler, uintptr_t cgoReply, uintptr_t cgoError); +extern void callCancelHandler(uintptr_t cgoCancelHandler, uintptr_t cgoError); +extern void *callMessageHandler(uintptr_t cgoMessageHandler, uintptr_t cgoMessage); + +// # xpc_object_t (XPC_TYPE_ARRAY) +extern bool callArrayApplier(uintptr_t cgoApplier, size_t index, uintptr_t cgoValue); +// # xpc_object_t (XPC_TYPE_DICTIONARY) +extern bool callDictionaryApplier(uintptr_t cgoApplier, const char *_Nonnull key, uintptr_t cgoValue); + +// MARK: -dispatch_queue_t + +void *dispatchQueueCreateSerial(const char *label) +{ + return dispatch_queue_create(label, DISPATCH_QUEUE_SERIAL); +} + +void dispatchRelease(void *queue) +{ + dispatch_release((dispatch_queue_t)queue); +} + +// MARK: - xpc.h types +// +// The following types are listed in the same order as the XPC documentation index page. +// https://developer.apple.com/documentation/xpc?language=objc + +// MARK: - xpc_listener_t (macOS 14+) + +void *xpcListenerCreate(const char *service_name, void *queue, uint64_t flags, uintptr_t cgo_session_handler, void **error_out) +{ +#ifdef INCLUDE_TARGET_OSX_14 + if (@available(macOS 14, *)) { + return xpc_listener_create( + service_name, + queue, + flags, + ^(xpc_session_t _Nonnull session) { + callSessionHandler(cgo_session_handler, wrapRawObject(session)); + }, + (xpc_rich_error_t *)error_out); + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +const char *xpcListenerCopyDescription(void *listener) +{ +#ifdef INCLUDE_TARGET_OSX_14 + if (@available(macOS 14, *)) { + return xpc_listener_copy_description((xpc_listener_t)listener); + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +bool xpcListenerActivate(void *listener, void **error_out) +{ +#ifdef INCLUDE_TARGET_OSX_14 + if (@available(macOS 14, *)) { + return xpc_listener_activate((xpc_listener_t)listener, (xpc_rich_error_t *)error_out); + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +void xpcListenerCancel(void *listener) +{ +#ifdef INCLUDE_TARGET_OSX_14 + if (@available(macOS 14, *)) { + xpc_listener_cancel((xpc_listener_t)listener); + return; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +void xpcListenerRejectPeer(void *session, const char *reason) +{ +#ifdef INCLUDE_TARGET_OSX_14 + if (@available(macOS 14, *)) { + xpc_listener_reject_peer((xpc_session_t)session, reason); + return; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +// MARK: - xpc_session_t (XPC_TYPE_SESSION) (macOS 13+) + +void *xpcSessionCreateMachService(const char *service_name, void *queue, uint64_t flags, void **error_out) +{ +#ifdef INCLUDE_TARGET_OSX_13 + if (@available(macOS 13, *)) { + return xpc_session_create_mach_service( + service_name, + (dispatch_queue_t)queue, + flags, + (xpc_rich_error_t *)error_out); + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +const char *xpcSessionCopyDescription(void *session) +{ +#ifdef INCLUDE_TARGET_OSX_13 + if (@available(macOS 13, *)) { + return xpc_session_copy_description((xpc_session_t)session); + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +bool xpcSessionActivate(void *session, void **error_out) +{ +#ifdef INCLUDE_TARGET_OSX_13 + if (@available(macOS 13, *)) { + return xpc_session_activate((xpc_session_t)session, (xpc_rich_error_t *)error_out); + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +void xpcSessionSetIncomingMessageHandler(void *session, uintptr_t cgo_message_handler) +{ +#ifdef INCLUDE_TARGET_OSX_13 + if (@available(macOS 13, *)) { + xpc_session_set_incoming_message_handler( + (xpc_session_t)session, + ^(xpc_object_t _Nonnull message) { + // Ensure the message is a dictionary. + if (xpc_get_type(message) != XPC_TYPE_DICTIONARY) { + xpc_session_cancel((xpc_session_t)session); + return; + } + xpc_object_t reply = (xpc_object_t)callMessageHandler(cgo_message_handler, wrapRawObject(message)); + xpc_rich_error_t err; + do { + err = xpc_session_send_message(session, reply); + } while (err != nil && xpc_rich_error_can_retry(err)); + xpc_release(reply); + }); + return; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +void xpcSessionCancel(void *session) +{ +#ifdef INCLUDE_TARGET_OSX_13 + if (@available(macOS 13, *)) { + xpc_session_cancel((xpc_session_t)session); + return; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +void xpcSessionSetCancelHandler(void *session, uintptr_t cgo_cancel_handler) +{ +#ifdef INCLUDE_TARGET_OSX_13 + if (@available(macOS 13, *)) { + xpc_session_set_cancel_handler( + (xpc_session_t)session, + ^(xpc_rich_error_t _Nonnull err) { + callCancelHandler(cgo_cancel_handler, wrapRawObject(err)); + }); + return; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +void xpcSessionSendMessageWithReplyAsync(void *session, void *message, uintptr_t cgo_reply_handler) +{ +#ifdef INCLUDE_TARGET_OSX_13 + if (@available(macOS 13, *)) { + xpc_session_send_message_with_reply_async( + (xpc_session_t)session, + (xpc_object_t)message, + ^(xpc_object_t _Nonnull reply, xpc_rich_error_t _Nullable error) { + callReplyHandler(cgo_reply_handler, wrapRawObject(reply), wrapRawObject(error)); + }); + return; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +// MARK: - xpc_rich_error_t (XPC_TYPE_RICH_ERROR) + +bool xpcRichErrorCanRetry(void *err) +{ + return xpc_rich_error_can_retry((xpc_rich_error_t)err); +} + +const char *xpcRichErrorCopyDescription(void *err) +{ + return xpc_rich_error_copy_description((xpc_rich_error_t)err); +} + +// MARK: - Identity + +// # xpc_type_t + +xpc_type_t xpcGetType(void *object) +{ + return xpc_get_type((xpc_object_t)object); +} + +const char *xpcTypeGetName(xpc_type_t type) +{ + return xpc_type_get_name(type); +} + +// MARK: - Comparison +// MARK: - Copying + +// # xpc_object_t +const char *xpcCopyDescription(void *object) +{ + return xpc_copy_description((xpc_object_t)object); +} + +// MARK: - Boolean objects + +// # xpc_object_t (XPC_TYPE_BOOL) + +void *xpcBoolCreate(bool value) +{ + return xpc_bool_create(value); +} + +bool xpcBoolGetValue(void *object) +{ + return xpc_bool_get_value((xpc_object_t)object); +} + +void *xpcBoolTrue() +{ + return XPC_BOOL_TRUE; +} + +void *xpcBoolFalse() +{ + return XPC_BOOL_FALSE; +} + +// MARK: - Data objects + +// # xpc_object_t (XPC_TYPE_DATA) + +void *xpcDataCreate(const void *bytes, size_t length) +{ + return xpc_data_create(bytes, length); +} + +const void *xpcDataGetBytesPtr(void *object) +{ + return xpc_data_get_bytes_ptr((xpc_object_t)object); +} + +size_t xpcDataGetLength(void *object) +{ + return xpc_data_get_length((xpc_object_t)object); +} + +// MARK: - Number objects + +// # xpc_object_t (XPC_TYPE_DOUBLE) +void *xpcDoubleCreate(double value) +{ + return xpc_double_create(value); +} + +double xpcDoubleGetValue(void *object) +{ + return xpc_double_get_value((xpc_object_t)object); +} + +// MARK: - Int64 objects +// # xpc_object_t (XPC_TYPE_INT64) +void *xpcInt64Create(int64_t value) +{ + return xpc_int64_create(value); +} + +int64_t xpcInt64GetValue(void *object) +{ + return xpc_int64_get_value((xpc_object_t)object); +} + +// MARK: - UInt64 objects +// # xpc_object_t (XPC_TYPE_UINT64) +void *xpcUInt64Create(uint64_t value) +{ + return xpc_uint64_create(value); +} + +uint64_t xpcUInt64GetValue(void *object) +{ + return xpc_uint64_get_value((xpc_object_t)object); +} + +// MARK: - Array objects + +// # xpc_object_t (XPC_TYPE_ARRAY) + +void *xpcArrayCreate(void *const *object, size_t count) +{ + return xpc_array_create((xpc_object_t const *)object, count); +} + +void *xpcArrayGetValue(void *object, size_t index) +{ + return xpc_array_get_value((xpc_object_t)object, index); +} + +void xpcArraySetValue(void *object, size_t index, void *value) +{ + xpc_array_set_value((xpc_object_t)object, index, (xpc_object_t)value); +} + +void xpcArrayAppendValue(void *object, void *value) +{ + xpc_array_append_value((xpc_object_t)object, (xpc_object_t)value); +} + +size_t xpcArrayGetCount(void *object) +{ + return xpc_array_get_count((xpc_object_t)object); +} + +bool xpcArrayApply(void *object, uintptr_t cgo_applier) +{ + return xpc_array_apply((xpc_object_t)object, ^bool(size_t index, xpc_object_t _Nonnull value) { + return callArrayApplier(cgo_applier, index, wrapRawObject(value)); + }); +} + +int xpcArrayDupFd(void *object, size_t index) +{ + return xpc_array_dup_fd((xpc_object_t)object, index); +} + +void *xpcArrayGetArray(void *object, size_t index) +{ + return xpc_array_get_array((xpc_object_t)object, index); +} + +bool xpcArrayGetBool(void *object, size_t index) +{ + return xpc_array_get_bool((xpc_object_t)object, index); +} + +const void *xpcArrayGetData(void *object, size_t index, size_t *length) +{ + return xpc_array_get_data((xpc_object_t)object, index, length); +} + +int64_t xpcArrayGetDate(void *object, size_t index) +{ + return xpc_array_get_date((xpc_object_t)object, index); +} + +void *xpcArrayGetDictionary(void *object, size_t index) +{ + return xpc_array_get_dictionary((xpc_object_t)object, index); +} + +double xpcArrayGetDouble(void *object, size_t index) +{ + return xpc_array_get_double((xpc_object_t)object, index); +} + +int64_t xpcArrayGetInt64(void *object, size_t index) +{ + return xpc_array_get_int64((xpc_object_t)object, index); +} + +const char *xpcArrayGetString(void *object, size_t index) +{ + return xpc_array_get_string((xpc_object_t)object, index); +} + +uint64_t xpcArrayGetUInt64(void *object, size_t index) +{ + return xpc_array_get_uint64((xpc_object_t)object, index); +} + +const uint8_t *xpcArrayGetUUID(void *object, size_t index) +{ + return xpc_array_get_uuid((xpc_object_t)object, index); +} + +void xpcArraySetBool(void *object, size_t index, bool value) +{ + xpc_array_set_bool((xpc_object_t)object, index, value); +} + +void xpcArraySetData(void *object, size_t index, const void *bytes, size_t length) +{ + xpc_array_set_data((xpc_object_t)object, index, bytes, length); +} + +void xpcArraySetDate(void *object, size_t index, int64_t value) +{ + xpc_array_set_date((xpc_object_t)object, index, value); +} + +void xpcArraySetDouble(void *object, size_t index, double value) +{ + xpc_array_set_double((xpc_object_t)object, index, value); +} + +void xpcArraySetFd(void *object, size_t index, int fd) +{ + xpc_array_set_fd((xpc_object_t)object, index, fd); +} + +void xpcArraySetInt64(void *object, size_t index, int64_t value) +{ + xpc_array_set_int64((xpc_object_t)object, index, value); +} + +void xpcArraySetString(void *object, size_t index, const char *value) +{ + xpc_array_set_string((xpc_object_t)object, index, value); +} + +void xpcArraySetUInt64(void *object, size_t index, uint64_t value) +{ + xpc_array_set_uint64((xpc_object_t)object, index, value); +} + +void xpcArraySetUUID(void *object, size_t index, const uint8_t *uuid) +{ + xpc_array_set_uuid((xpc_object_t)object, index, uuid); +} + +// MARK: - Dictionary objects + +// xpc_object_t (XPC_TYPE_DICTIONARY) + +void *xpcDictionaryCreateEmpty(void) +{ + return xpc_dictionary_create_empty(); +} + +void *xpcDictionaryCreateReply(void *object) +{ + return xpc_dictionary_create_reply((xpc_object_t)object); +} + +void xpcDictionarySetValue(void *object, const char *key, void *value) +{ + xpc_dictionary_set_value((xpc_object_t)object, key, (xpc_object_t)value); +} + +size_t xpcDictionaryGetCount(void *object) +{ + return xpc_dictionary_get_count((xpc_object_t)object); +} + +void *xpcDictionaryGetValue(void *object, const char *key) +{ + return xpc_dictionary_get_value((xpc_object_t)object, key); +} + +bool xpcDictionaryApply(void *object, uintptr_t cgo_applier) +{ + return xpc_dictionary_apply((xpc_object_t)object, ^bool(const char *_Nonnull key, xpc_object_t _Nonnull value) { + return callDictionaryApplier(cgo_applier, key, wrapRawObject(value)); + }); +} + +int xpcDictionaryDupFd(void *object, const char *key) +{ + return xpc_dictionary_dup_fd((xpc_object_t)object, key); +} + +void *xpcDictionaryGetArray(void *object, const char *key) +{ + return xpc_dictionary_get_array((xpc_object_t)object, key); +} + +bool xpcDictionaryGetBool(void *object, const char *key) +{ + return xpc_dictionary_get_bool((xpc_object_t)object, key); +} + +const void *xpcDictionaryGetData(void *object, const char *key, size_t *length) +{ + return xpc_dictionary_get_data((xpc_object_t)object, key, length); +} + +int64_t xpcDictionaryGetDate(void *object, const char *key) +{ + return xpc_dictionary_get_date((xpc_object_t)object, key); +} + +void *xpcDictionaryGetDictionary(void *object, const char *key) +{ + return xpc_dictionary_get_dictionary((xpc_object_t)object, key); +} + +double xpcDictionaryGetDouble(void *object, const char *key) +{ + return xpc_dictionary_get_double((xpc_object_t)object, key); +} + +int64_t xpcDictionaryGetInt64(void *object, const char *key) +{ + return xpc_dictionary_get_int64((xpc_object_t)object, key); +} + +const char *xpcDictionaryGetString(void *object, const char *key) +{ + return xpc_dictionary_get_string((xpc_object_t)object, key); +} + +uint64_t xpcDictionaryGetUInt64(void *object, const char *key) +{ + return xpc_dictionary_get_uint64((xpc_object_t)object, key); +} + +const uint8_t *xpcDictionaryGetUUID(void *object, const char *key) +{ + return xpc_dictionary_get_uuid((xpc_object_t)object, key); +} + +void xpcDictionarySetBool(void *object, const char *key, bool value) +{ + xpc_dictionary_set_bool((xpc_object_t)object, key, value); +} + +void xpcDictionarySetData(void *object, const char *key, const void *bytes, size_t length) +{ + xpc_dictionary_set_data((xpc_object_t)object, key, bytes, length); +} + +void xpcDictionarySetDate(void *object, const char *key, int64_t value) +{ + xpc_dictionary_set_date((xpc_object_t)object, key, value); +} + +void xpcDictionarySetDouble(void *object, const char *key, double value) +{ + xpc_dictionary_set_double((xpc_object_t)object, key, value); +} + +void xpcDictionarySetFd(void *object, const char *key, int fd) +{ + xpc_dictionary_set_fd((xpc_object_t)object, key, fd); +} + +void xpcDictionarySetInt64(void *object, const char *key, int64_t value) +{ + xpc_dictionary_set_int64((xpc_object_t)object, key, value); +} + +void xpcDictionarySetString(void *object, const char *key, const char *value) +{ + xpc_dictionary_set_string((xpc_object_t)object, key, value); +} + +void xpcDictionarySetUInt64(void *object, const char *key, uint64_t value) +{ + xpc_dictionary_set_uint64((xpc_object_t)object, key, value); +} + +void xpcDictionarySetUUID(void *object, const char *key, const uint8_t *uuid) +{ + xpc_dictionary_set_uuid((xpc_object_t)object, key, uuid); +} + +// MARK: - String objects + +void *xpcStringCreate(const char *string) +{ + return xpc_string_create(string); +} + +size_t xpcStringGetLength(void *object) +{ + return xpc_string_get_length((xpc_object_t)object); +} + +const char *xpcStringGetStringPtr(void *object) +{ + return xpc_string_get_string_ptr((xpc_object_t)object); +} + +// MARK: - File descriptor objects + +void *xpcFdCreate(int fd) +{ + return xpc_fd_create(fd); +} + +int xpcFdDup(void *object) +{ + return xpc_fd_dup((xpc_object_t)object); +} + +// MARK: - Date objects + +void *xpcDateCreate(int64_t interval) +{ + return xpc_date_create(interval); +} + +void *xpcDateCreateFromCurrent() +{ + return xpc_date_create_from_current(); +} + +int64_t xpcDateGetValue(void *object) +{ + return xpc_date_get_value((xpc_object_t)object); +} + +// MARK: - UUID objects + +void *xpcUUIDCreate(const uuid_t uuid) +{ + return xpc_uuid_create(uuid); +} + +const uint8_t *xpcUUIDGetBytes(void *object) +{ + return xpc_uuid_get_bytes((xpc_object_t)object); +} + +// MARK: - Shared memory objects +// MARK: - Null objects + +void *xpcNullCreate() +{ + return xpc_null_create(); +} + +// MARK: - Object life cycle + +// xpc_object_t + +void *xpcRetain(void *object) +{ + return xpc_retain((xpc_object_t)object); +} + +void xpcRelease(void *object) +{ + xpc_release((xpc_object_t)object); +} + +// MARK: - xpc_peer_requirement_t (macOS 26+) + +void xpcListenerSetPeerRequirement(void *listener, void *peer_requirement) +{ +#ifdef INCLUDE_TARGET_OSX_26 + if (@available(macOS 26, *)) { + xpc_listener_set_peer_requirement((xpc_listener_t)listener, (xpc_peer_requirement_t)peer_requirement); + return; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +void *xpcPeerRequirementCreateLwcr(void *lwcr, void **error_out) +{ +#ifdef INCLUDE_TARGET_OSX_26 + if (@available(macOS 26, *)) { + return xpc_peer_requirement_create_lwcr((xpc_object_t)lwcr, (xpc_rich_error_t *)error_out); + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +bool xpcPeerRequirementMatchReceivedMessage(void *peer_requirement, void *message, void **error_out) +{ +#ifdef INCLUDE_TARGET_OSX_26 + if (@available(macOS 26, *)) { + return xpc_peer_requirement_match_received_message( + (xpc_peer_requirement_t)peer_requirement, + (xpc_object_t)message, + (xpc_rich_error_t *)error_out); + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +void xpcSessionSetPeerRequirement(void *session, void *peer_requirement) +{ +#ifdef INCLUDE_TARGET_OSX_26 + if (@available(macOS 26, *)) { + xpc_session_set_peer_requirement((xpc_session_t)session, (xpc_peer_requirement_t)peer_requirement); + return; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} \ No newline at end of file diff --git a/xpc/xpc_object.go b/xpc/xpc_object.go new file mode 100644 index 00000000..8dde1070 --- /dev/null +++ b/xpc/xpc_object.go @@ -0,0 +1,64 @@ +package xpc + +/* +#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc +# include "xpc_darwin.h" +*/ +import "C" +import ( + "runtime" + "unsafe" + + "github.com/Code-Hex/vz/v3/internal/objc" +) + +type pointer = objc.Pointer + +// xpcObject wraps an XPC object ([xpc_object_t]). +// It is expected to be embedded in other structs as pointer to provide common functionality. +// +// [xpc_object_t]: https://developer.apple.com/documentation/xpc/xpc_object_t?language=objc +type xpcObject struct { + *pointer +} + +var _ Object = (*xpcObject)(nil) + +// newXpcObject creates a new [xpcObject] from an existing xpc_object_t. +func newXpcObject(ptr unsafe.Pointer) *xpcObject { + return &xpcObject{objc.NewPointer(ptr)} +} + +// String returns the description of the [xpcObject]. +// - https://developer.apple.com/documentation/xpc/xpc_copy_description(_:)?language=objc +func (x *xpcObject) String() string { + cs := C.xpcCopyDescription(objc.Ptr(x)) + defer C.free(unsafe.Pointer(cs)) + return C.GoString(cs) +} + +// retain retains the [xpcObject]. +// - https://developer.apple.com/documentation/xpc/xpc_retain?language=objc +func (x *xpcObject) retain() { + C.xpcRetain(objc.Ptr(x)) +} + +// releaseOnCleanup registers a cleanup function to release the [xpcObject] when cleaned up. +// - https://developer.apple.com/documentation/xpc/xpc_release?language=objc +func (x *xpcObject) releaseOnCleanup() { + runtime.AddCleanup(x, func(p unsafe.Pointer) { + C.xpcRelease(p) + }, objc.Ptr(x)) +} + +// Retain calls retain method on the given object and returns it. +func Retain[T interface{ retain() }](o T) T { + o.retain() + return o +} + +// ReleaseOnCleanup calls releaseOnCleanup method on the given object and returns it. +func ReleaseOnCleanup[T interface{ releaseOnCleanup() }](o T) T { + o.releaseOnCleanup() + return o +}