blob: fd4f9ad57d7f28d692d4b44ce92923fac597b189 [file] [log] [blame]
package xpc
/*
#include "xpc_wrapper_darwin.h"
#include "sys/utsname.h"
*/
import "C"
import (
"errors"
"fmt"
"log"
r "reflect"
"unsafe"
)
type XPC struct {
conn C.xpc_connection_t
}
func (x *XPC) Send(msg interface{}, verbose bool) {
// verbose == true converts the type from bool to C._Bool
C.XpcSendMessage(x.conn, goToXpc(msg), true, verbose == true)
}
//
// 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 {
return d[k].(Dict)
}
func (d Dict) MustGetArray(k string) Array {
return d[k].(Array)
}
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) []byte {
u := d[k].(UUID)
return u[:]
}
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 []byte
func MakeUUID(s string) UUID {
var sl []byte
fmt.Sscanf(s, "%32x", &sl)
var uuid [16]byte
copy(uuid[:], sl)
return UUID(uuid[:])
}
func (uuid UUID) String() string {
return fmt.Sprintf("%x", []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{})
)
type XpcEventHandler interface {
HandleXpcEvent(event Dict, err error)
}
func XpcConnect(service string, eh XpcEventHandler) XPC {
cservice := C.CString(service)
defer C.free(unsafe.Pointer(cservice))
return XPC{conn: C.XpcConnect(cservice, unsafe.Pointer(&eh))}
}
//export handleXpcEvent
func handleXpcEvent(event C.xpc_object_t, p unsafe.Pointer) {
//log.Printf("handleXpcEvent %#v %#v\n", event, p)
t := C.xpc_get_type(event)
eh := *((*XpcEventHandler)(p))
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 unsafe.Pointer, i C.int, v C.xpc_object_t) {
a := *(*Array)(u)
a[i] = xpcToGo(v)
}
//export dictSet
func dictSet(u unsafe.Pointer, k *C.char, v C.xpc_object_t) {
d := *(*Dict)(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)))
C.XpcArrayApply(unsafe.Pointer(&a), 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)
C.XpcDictApply(unsafe.Pointer(&d), 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
}