389 lines
8.2 KiB
Go
389 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
|
||
|
}
|