fermentord/vendor/github.com/raff/goble/xpc/xpc.go
Søren Rasmussen 07a23c1845
Some checks reported errors
continuous-integration/drone/push Build encountered an error
Upgrade to go 1.20 and add vendor catalog
2023-04-22 10:37:23 +02:00

388 lines
8.2 KiB
Go

package xpc
/*
#include "xpc_wrapper.h"
*/
import "C"
import (
"errors"
"fmt"
"log"
r "reflect"
"strings"
"unsafe"
)
type XPC struct {
conn C.xpc_connection_t
}
func (x *XPC) Send(msg interface{}, verbose bool) {
C.XpcSendMessage(x.conn, goToXpc(msg), C.bool(true), C.bool(verbose))
}
//
// minimal XPC support required for BLE
//
// a dictionary of things
type Dict map[string]interface{}
func (d Dict) Contains(k string) bool {
_, ok := d[k]
return ok
}
func (d Dict) MustGetDict(k string) Dict {
if v, ok := d[k]; ok {
return v.(Dict)
}
return nil
}
func (d Dict) MustGetArray(k string) Array {
if v, ok := d[k]; ok {
return v.(Array)
}
return nil
}
func (d Dict) MustGetBytes(k string) []byte {
return d[k].([]byte)
}
func (d Dict) MustGetHexBytes(k string) string {
return fmt.Sprintf("%x", d[k].([]byte))
}
func (d Dict) MustGetInt(k string) int {
return int(d[k].(int64))
}
func (d Dict) MustGetUUID(k string) UUID {
return d[k].(UUID)
}
func (d Dict) GetString(k, defv string) string {
if v := d[k]; v != nil {
//log.Printf("GetString %s %#v\n", k, v)
return v.(string)
} else {
//log.Printf("GetString %s default %#v\n", k, defv)
return defv
}
}
func (d Dict) GetBytes(k string, defv []byte) []byte {
if v := d[k]; v != nil {
//log.Printf("GetBytes %s %#v\n", k, v)
return v.([]byte)
} else {
//log.Printf("GetBytes %s default %#v\n", k, defv)
return defv
}
}
func (d Dict) GetInt(k string, defv int) int {
if v := d[k]; v != nil {
//log.Printf("GetString %s %#v\n", k, v)
return int(v.(int64))
} else {
//log.Printf("GetString %s default %#v\n", k, defv)
return defv
}
}
func (d Dict) GetUUID(k string) UUID {
return GetUUID(d[k])
}
// an Array of things
type Array []interface{}
func (a Array) GetUUID(k int) UUID {
return GetUUID(a[k])
}
// a UUID
type UUID [16]byte
func MakeUUID(s string) UUID {
var sl []byte
s = strings.Replace(s, "-", "", -1)
fmt.Sscanf(s, "%32x", &sl)
var uuid [16]byte
copy(uuid[:], sl)
return UUID(uuid)
}
func MustUUID(s string) UUID {
var sl []byte
s = strings.Replace(s, "-", "", -1)
if len(s) != 32 {
log.Fatal("invalid UUID")
}
if n, err := fmt.Sscanf(s, "%32x", &sl); err != nil || n != 1 {
log.Fatal("invalid UUID ", s, " len ", n, " error ", err)
}
var uuid [16]byte
copy(uuid[:], sl)
return UUID(uuid)
}
func (uuid UUID) String() string {
return fmt.Sprintf("%x", [16]byte(uuid))
}
func GetUUID(v interface{}) UUID {
if v == nil {
return UUID{}
}
if uuid, ok := v.(UUID); ok {
return uuid
}
if bytes, ok := v.([]byte); ok {
uuid := UUID{}
for i, b := range bytes {
uuid[i] = b
}
return uuid
}
if bytes, ok := v.([]uint8); ok {
uuid := UUID{}
for i, b := range bytes {
uuid[i] = b
}
return uuid
}
log.Fatalf("invalid type for UUID: %#v", v)
return UUID{}
}
var (
CONNECTION_INVALID = errors.New("connection invalid")
CONNECTION_INTERRUPTED = errors.New("connection interrupted")
CONNECTION_TERMINATED = errors.New("connection terminated")
TYPE_OF_UUID = r.TypeOf(UUID{})
TYPE_OF_BYTES = r.TypeOf([]byte{})
handlers = map[uintptr]XpcEventHandler{}
)
type XpcEventHandler interface {
HandleXpcEvent(event Dict, err error)
}
func XpcConnect(service string, eh XpcEventHandler) XPC {
// func XpcConnect(service string, eh XpcEventHandler) C.xpc_connection_t {
ctx := uintptr(unsafe.Pointer(&eh))
handlers[ctx] = eh
cservice := C.CString(service)
defer C.free(unsafe.Pointer(cservice))
// return C.XpcConnect(cservice, C.uintptr_t(ctx))
return XPC{conn: C.XpcConnect(cservice, C.uintptr_t(ctx))}
}
//export handleXpcEvent
func handleXpcEvent(event C.xpc_object_t, p C.ulong) {
//log.Printf("handleXpcEvent %#v %#v\n", event, p)
t := C.xpc_get_type(event)
eh := handlers[uintptr(p)]
if eh == nil {
//log.Println("no handler for", p)
return
}
if t == C.TYPE_ERROR {
if event == C.ERROR_CONNECTION_INVALID {
// The client process on the other end of the connection has either
// crashed or cancelled the connection. After receiving this error,
// the connection is in an invalid state, and you do not need to
// call xpc_connection_cancel(). Just tear down any associated state
// here.
//log.Println("connection invalid")
eh.HandleXpcEvent(nil, CONNECTION_INVALID)
} else if event == C.ERROR_CONNECTION_INTERRUPTED {
//log.Println("connection interrupted")
eh.HandleXpcEvent(nil, CONNECTION_INTERRUPTED)
} else if event == C.ERROR_CONNECTION_TERMINATED {
// Handle per-connection termination cleanup.
//log.Println("connection terminated")
eh.HandleXpcEvent(nil, CONNECTION_TERMINATED)
} else {
//log.Println("got some error", event)
eh.HandleXpcEvent(nil, fmt.Errorf("%v", event))
}
} else {
eh.HandleXpcEvent(xpcToGo(event).(Dict), nil)
}
}
// goToXpc converts a go object to an xpc object
func goToXpc(o interface{}) C.xpc_object_t {
return valueToXpc(r.ValueOf(o))
}
// valueToXpc converts a go Value to an xpc object
//
// note that not all the types are supported, but only the subset required for Blued
func valueToXpc(val r.Value) C.xpc_object_t {
if !val.IsValid() {
return nil
}
var xv C.xpc_object_t
switch val.Kind() {
case r.Int, r.Int8, r.Int16, r.Int32, r.Int64:
xv = C.xpc_int64_create(C.int64_t(val.Int()))
case r.Uint, r.Uint8, r.Uint16, r.Uint32:
xv = C.xpc_int64_create(C.int64_t(val.Uint()))
case r.String:
xv = C.xpc_string_create(C.CString(val.String()))
case r.Map:
xv = C.xpc_dictionary_create(nil, nil, 0)
for _, k := range val.MapKeys() {
v := valueToXpc(val.MapIndex(k))
C.xpc_dictionary_set_value(xv, C.CString(k.String()), v)
if v != nil {
C.xpc_release(v)
}
}
case r.Array, r.Slice:
if val.Type() == TYPE_OF_UUID {
// Array of bytes
var uuid [16]byte
r.Copy(r.ValueOf(uuid[:]), val)
xv = C.xpc_uuid_create(C.ptr_to_uuid(unsafe.Pointer(&uuid[0])))
} else if val.Type() == TYPE_OF_BYTES {
// slice of bytes
xv = C.xpc_data_create(unsafe.Pointer(val.Pointer()), C.size_t(val.Len()))
} else {
xv = C.xpc_array_create(nil, 0)
l := val.Len()
for i := 0; i < l; i++ {
v := valueToXpc(val.Index(i))
C.xpc_array_append_value(xv, v)
if v != nil {
C.xpc_release(v)
}
}
}
case r.Interface, r.Ptr:
xv = valueToXpc(val.Elem())
default:
log.Fatalf("unsupported %#v", val.String())
}
return xv
}
//export arraySet
func arraySet(u C.uintptr_t, i C.int, v C.xpc_object_t) {
a := *(*Array)(unsafe.Pointer(uintptr(u)))
a[i] = xpcToGo(v)
}
//export dictSet
func dictSet(u C.uintptr_t, k *C.char, v C.xpc_object_t) {
d := *(*Dict)(unsafe.Pointer(uintptr(u)))
d[C.GoString(k)] = xpcToGo(v)
}
// xpcToGo converts an xpc object to a go object
//
// note that not all the types are supported, but only the subset required for Blued
func xpcToGo(v C.xpc_object_t) interface{} {
t := C.xpc_get_type(v)
switch t {
case C.TYPE_ARRAY:
a := make(Array, C.int(C.xpc_array_get_count(v)))
p := uintptr(unsafe.Pointer(&a))
C.XpcArrayApply(C.uintptr_t(p), v)
return a
case C.TYPE_DATA:
return C.GoBytes(C.xpc_data_get_bytes_ptr(v), C.int(C.xpc_data_get_length(v)))
case C.TYPE_DICT:
d := make(Dict)
p := uintptr(unsafe.Pointer(&d))
C.XpcDictApply(C.uintptr_t(p), v)
return d
case C.TYPE_INT64:
return int64(C.xpc_int64_get_value(v))
case C.TYPE_STRING:
return C.GoString(C.xpc_string_get_string_ptr(v))
case C.TYPE_UUID:
a := [16]byte{}
C.XpcUUIDGetBytes(unsafe.Pointer(&a), v)
return UUID(a)
default:
log.Fatalf("unexpected type %#v, value %#v", t, v)
}
return nil
}
// xpc_release is needed by tests, since they can't use CGO
func xpc_release(xv C.xpc_object_t) {
C.xpc_release(xv)
}
// this is used to check the OS version
type Utsname struct {
Sysname string
Nodename string
Release string
Version string
Machine string
}
func Uname(utsname *Utsname) error {
var cstruct C.struct_utsname
if err := C.uname(&cstruct); err != 0 {
return errors.New("utsname error")
}
// XXX: this may crash if any value is exactly 256 characters (no 0 terminator)
utsname.Sysname = C.GoString(&cstruct.sysname[0])
utsname.Nodename = C.GoString(&cstruct.nodename[0])
utsname.Release = C.GoString(&cstruct.release[0])
utsname.Version = C.GoString(&cstruct.version[0])
utsname.Machine = C.GoString(&cstruct.machine[0])
return nil
}