swift: Discovery API for VanadiumCore

Wraps the high-level scan/advertise discovery API using exported CGO
functions.  Uses JSON to marshal the advertising structs back and
forth since we don't yet have VOM.

Also fixes logging for VanadiumCore.

TODO: Support attachments.
Change-Id: I52766dddb7e4e490118db11e7e109b8073e82f9e
diff --git a/swift.go b/swift.go
index 2407db7..a6f1a06 100644
--- a/swift.go
+++ b/swift.go
@@ -49,7 +49,6 @@
 		sutil.ThrowSwiftError(nil, err, unsafe.Pointer(errOut))
 		return
 	}
-
 	vlog.Log.Configure(vlog.OverridePriorConfiguration(true), dir, toStderr, level, vmodule)
 }
 
diff --git a/types.h b/types.h
index 622697d..e1b556e 100644
--- a/types.h
+++ b/types.h
@@ -19,6 +19,17 @@
 typedef _GoHandle GoContextHandle;
 typedef _GoHandle GoCancelableContextHandle;
 typedef _GoHandle GoClientCallHandle;
+typedef _GoHandle GoDiscoveryHandle;
+
+typedef struct {
+    _GoUint64 length;
+    char *data;
+} SwiftCString;
+
+typedef struct {
+    _GoUint64 length;
+    SwiftCString *data;
+} SwiftCStringArray;
 
 // Express byte arrays with lengths
 typedef struct {
@@ -44,7 +55,12 @@
 // Asynchronous callback function pointers, which use their own handle (AsyncCallbackIdentifier) to overcome
 // Swift limitations on context-free closures (function pointers)
 typedef int AsyncCallbackIdentifier;
-typedef void (*SwiftAsyncSuccessCallback)(AsyncCallbackIdentifier);
-typedef void (*SwiftAsyncSuccessHandleCallback)(AsyncCallbackIdentifier, _GoHandle);
-typedef void (*SwiftAsyncSuccessByteArrayArrayCallback)(AsyncCallbackIdentifier, SwiftByteArrayArray);
-typedef void (*SwiftAsyncFailureCallback)(AsyncCallbackIdentifier, SwiftVError);
+typedef void (*SwiftAsyncJsonCallback)(AsyncCallbackIdentifier asyncId, SwiftByteArray json);
+typedef void (*SwiftAsyncSuccessCallback)(AsyncCallbackIdentifier asyncId);
+typedef void (*SwiftAsyncSuccessHandleCallback)(AsyncCallbackIdentifier asyncId, _GoHandle handle);
+typedef void (*SwiftAsyncSuccessByteArrayArrayCallback)(AsyncCallbackIdentifier asyncId, SwiftByteArrayArray byteArrayArray);
+typedef void (*SwiftAsyncFailureCallback)(AsyncCallbackIdentifier asyncId, SwiftVError err);
+
+// Other callbacks
+typedef void (*SwiftAsyncVoidCallback)();
+
diff --git a/util/type.go b/util/type.go
index 63457c4..712ec35 100644
--- a/util/type.go
+++ b/util/type.go
@@ -16,6 +16,7 @@
 )
 
 /*
+#include <string.h> // memcpy
 #include <stdlib.h>
 #import "../types.h"
 */
@@ -61,6 +62,17 @@
 	return time.Duration(int64(d * 1e9))
 }
 
+func GoString(swiftStringPtr unsafe.Pointer, freeWhenDone bool) string {
+	str := *(*C.SwiftCString)(swiftStringPtr)
+	if freeWhenDone && str.data != nil {
+		defer C.free(unsafe.Pointer(str.data))
+	}
+	if str.data == nil || str.length == 0 {
+		return ""
+	}
+	return C.GoStringN(str.data, C.int(str.length))
+}
+
 //export swift_io_v_swift_impl_util_type_nativeBase64UrlDecode
 func swift_io_v_swift_impl_util_type_nativeBase64UrlDecode(base64UrlEncoded *C.char) C.SwiftByteArray {
 	// Decode the base64 url encoded string to bytes in a way that prevents extra copies along the CGO boundary.
diff --git a/v23/discovery/swift.go b/v23/discovery/swift.go
new file mode 100644
index 0000000..0a6f65a
--- /dev/null
+++ b/v23/discovery/swift.go
@@ -0,0 +1,106 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin
+
+package discovery
+
+import (
+	"encoding/json"
+	"unsafe"
+
+	"v.io/v23"
+	"v.io/v23/discovery"
+	"v.io/v23/security"
+	sutil "v.io/x/swift/util"
+	scontext "v.io/x/swift/v23/context"
+)
+
+/*
+#include <stdlib.h>
+#import "../../types.h"
+
+static void CallAdvertisingCallback(SwiftAsyncSuccessCallback callback, AsyncCallbackIdentifier asyncId) {
+  callback(asyncId);
+}
+static void CallScanCallback(SwiftAsyncJsonCallback callback, AsyncCallbackIdentifier asyncId, SwiftByteArray data) {
+  callback(asyncId, data);
+}
+*/
+import "C"
+
+//export swift_io_v_v23_discovery_new
+func swift_io_v_v23_discovery_new(ctxHandle C.GoContextHandle, errOut *C.SwiftVError) C.GoDiscoveryHandle {
+	ctx := scontext.GoContext(uint64(ctxHandle))
+	d, err := v23.NewDiscovery(ctx)
+	if err != nil {
+		sutil.ThrowSwiftError(ctx, err, unsafe.Pointer(errOut))
+		return C.GoDiscoveryHandle(0)
+	}
+	return C.GoDiscoveryHandle(sutil.GoNewRef(&d))
+}
+
+//export swift_io_v_v23_discovery_finalize
+func swift_io_v_v23_discovery_finalize(discoveryHandle C.GoDiscoveryHandle) {
+	sutil.GoUnref(uint64(discoveryHandle))
+}
+
+// Exports the discovery advertise API to CGO using JSON to marshal ads
+//export swift_io_v_v23_discovery_advertise
+func swift_io_v_v23_discovery_advertise(ctxHandle C.GoContextHandle, discoveryHandle C.GoDiscoveryHandle, adJson C.SwiftByteArray, visibilityArray C.SwiftCStringArray, asyncId C.AsyncCallbackIdentifier, doneCallback C.SwiftAsyncSuccessCallback, errOut *C.SwiftVError) bool {
+	ctx := scontext.GoContext(uint64(ctxHandle))
+	d := GoDiscoveryT(uint64(discoveryHandle))
+	ad := discovery.Advertisement{}
+	if err := json.Unmarshal(sutil.GoBytesNoCopy(unsafe.Pointer(&adJson)), &ad); err != nil {
+		sutil.ThrowSwiftError(ctx, err, unsafe.Pointer(errOut))
+		return false
+	}
+	var visibility []security.BlessingPattern
+	for _, v := range visibilityArray.toStrings() {
+		visibility = append(visibility, security.BlessingPattern(v))
+	}
+	doneChan, err := d.Advertise(ctx, &ad, visibility)
+	if err != nil {
+		sutil.ThrowSwiftError(ctx, err, unsafe.Pointer(errOut))
+		return false
+	}
+	go func() {
+		<-doneChan
+		C.CallAdvertisingCallback(doneCallback, asyncId)
+	}()
+	return true
+}
+
+// Exports the discovery scan API to CGO using JSON to marshal ads
+//export swift_io_v_v23_discovery_scan
+func swift_io_v_v23_discovery_scan(ctxHandle C.GoContextHandle, discoveryHandle C.GoDiscoveryHandle, query C.SwiftCString, asyncId C.AsyncCallbackIdentifier, callbackBlock C.SwiftAsyncJsonCallback, errOut *C.SwiftVError) bool {
+	ctx := scontext.GoContext(uint64(ctxHandle))
+	d := GoDiscoveryT(uint64(discoveryHandle))
+	goQuery := sutil.GoString(unsafe.Pointer(&query), false)
+	ch, err := d.Scan(ctx, goQuery)
+	if err != nil {
+		sutil.ThrowSwiftError(ctx, err, unsafe.Pointer(errOut))
+		return false
+	}
+	go func() {
+		for update := range ch {
+			data := struct {
+				IsLost        bool
+				Ad            discovery.Advertisement
+			}{
+				IsLost:        update.IsLost(),
+				Ad:            update.Advertisement(),
+			}
+			b, err := json.Marshal(data)
+			if err != nil {
+				ctx.Fatal("Unable to JSON serialize discovery update: ", err)
+			}
+			ba := swiftBytesCopy(b)
+			C.CallScanCallback(callbackBlock, asyncId, ba)
+			C.free(ba.data)
+		}
+		C.CallScanCallback(callbackBlock, asyncId, emptySwiftByteArray())
+	}()
+	return true
+}
diff --git a/v23/discovery/util.go b/v23/discovery/util.go
new file mode 100644
index 0000000..ce7470e
--- /dev/null
+++ b/v23/discovery/util.go
@@ -0,0 +1,57 @@
+package discovery
+
+import (
+	"fmt"
+	"unsafe"
+
+	"v.io/v23/discovery"
+	"v.io/x/swift/util"
+)
+
+/*
+#include <string.h> // memcpy
+#include <stdlib.h>
+#import "../../types.h"
+*/
+import "C"
+
+// Converts a native pointer from Swift to a Go Discovery.T. Panics if it can't.
+func GoDiscoveryT(discoveryHandle uint64) discovery.T {
+	valptr := util.GoGetRef(discoveryHandle)
+	if d, ok := valptr.(*discovery.T); ok {
+		return *d
+	} else {
+		panic(fmt.Sprintf("Couldn't get discovery.T from handle with id %d", discoveryHandle))
+	}
+}
+
+func swiftBytesCopy(data []byte) C.SwiftByteArray {
+	var a C.SwiftByteArray
+	a.length = C._GoUint64(len(data))
+	a.data = C.malloc(C.size_t(len(data)))
+	if a.data == nil {
+		panic(fmt.Errorf("Unable to allocate %d bytes", a.length))
+	}
+	C.memmove(a.data, unsafe.Pointer(&data[0]), C.size_t(len(data)))
+	return a
+}
+
+func emptySwiftByteArray() C.SwiftByteArray {
+	var empty C.SwiftByteArray
+	empty.length = 0
+	empty.data = nil
+	return empty
+}
+
+func (s *C.SwiftCString) toString() string {
+	return C.GoStringN(s.data, C.int(s.length))
+}
+
+func (a *C.SwiftCStringArray) toStrings() []string {
+	var cstrs = (*[1 << 30]C.SwiftCString)(unsafe.Pointer(a.data))[:int(a.length):int(a.length)]
+	var ret []string
+	for _, cstr := range cstrs {
+		ret = append(ret, cstr.toString())
+	}
+	return ret
+}
\ No newline at end of file
diff --git a/v23/swift.go b/v23/swift.go
index 201603f..60cbc76 100644
--- a/v23/swift.go
+++ b/v23/swift.go
@@ -8,6 +8,7 @@
 
 import (
 	_ "v.io/x/swift/v23/context"
+	_ "v.io/x/swift/v23/discovery"
 	_ "v.io/x/swift/v23/security"
 )