x/ref: jni cgo bridge for Invite Scan and Neighborhood Ad/Scan

Implements the jni layer on top of cgo.
We expose hooks to Invite Scan and Neighborhood Ad/Scan as well
as the corresponding Stop functions.

This wasn't perfectly tested, but I know that
- It compiles.
- TODOs app compiles with these changes and will at least get to the
  main screen. It will crash on watch and skips the login attempt.

MultiPart: 1/2
Change-Id: I6b32d0006bc6f26f0b3848c9497fc6385c26c95d
diff --git a/services/syncbase/bridge/cgo/impl.go b/services/syncbase/bridge/cgo/impl.go
index 25cceef..a0788e8 100644
--- a/services/syncbase/bridge/cgo/impl.go
+++ b/services/syncbase/bridge/cgo/impl.go
@@ -72,16 +72,10 @@
 static void CallDbSyncgroupInvitesOnInvite(v23_syncbase_DbSyncgroupInvitesCallbacks cbs, v23_syncbase_Invite i) {
   cbs.onInvite(cbs.handle, i);
 }
-static void CallDbSyncgroupInvitesOnDone(v23_syncbase_DbSyncgroupInvitesCallbacks cbs) {
-  cbs.onDone(cbs.handle);
-}
 
 static void CallNeighborhoodScanCallbacksOnPeer(v23_syncbase_NeighborhoodScanCallbacks cbs, v23_syncbase_AppPeer p) {
   cbs.onPeer(cbs.handle, p);
 }
-static void CallNeighborhoodScanCallbacksOnDone(v23_syncbase_NeighborhoodScanCallbacks cbs) {
-  cbs.onDone(cbs.handle);
-}
 */
 import "C"
 
@@ -98,7 +92,6 @@
 	// testLogin indicates if the test login mode is used. This is triggered
 	// by using an empty identity provider.
 	testLogin bool
-
 	// neighborhoodAdStatus tracks the status of the neighborhood advertisement.
 	neighborhoodAdStatus *adStatus
 )
@@ -123,6 +116,7 @@
 	if isLoggedIn() {
 		v23_syncbase_Serve(&cErr)
 	}
+	neighborhoodAdStatus = newAdStatus()
 }
 
 type adStatus struct {
@@ -362,7 +356,6 @@
 		for {
 			peer, ok := <-scanChan
 			if !ok {
-				C.CallNeighborhoodScanCallbacksOnDone(cbs)
 				break
 			}
 			cPeer := C.v23_syncbase_AppPeer{}
@@ -771,7 +764,6 @@
 		for {
 			invite, ok := <-scanChan
 			if !ok {
-				C.CallDbSyncgroupInvitesOnDone(cbs)
 				break
 			}
 			cInvite := C.v23_syncbase_Invite{}
diff --git a/services/syncbase/bridge/cgo/jni.go b/services/syncbase/bridge/cgo/jni.go
index 2493d23..9cc9098 100644
--- a/services/syncbase/bridge/cgo/jni.go
+++ b/services/syncbase/bridge/cgo/jni.go
@@ -43,6 +43,22 @@
   	0, v23_syncbase_internal_onKeyValue, v23_syncbase_internal_onDone};
   return cbs;
 }
+
+void v23_syncbase_internal_onInvite(v23_syncbase_Handle handle, v23_syncbase_Invite);
+
+static v23_syncbase_DbSyncgroupInvitesCallbacks newVSyncgroupInvitesCallbacks() {
+  v23_syncbase_DbSyncgroupInvitesCallbacks cbs = {
+  	0, v23_syncbase_internal_onInvite};
+  return cbs;
+}
+
+void v23_syncbase_internal_onPeer(v23_syncbase_Handle handle, v23_syncbase_AppPeer);
+
+static v23_syncbase_NeighborhoodScanCallbacks newVNeighborhoodScanCallbacks() {
+  v23_syncbase_NeighborhoodScanCallbacks cbs = {
+  	0, v23_syncbase_internal_onPeer};
+  return cbs;
+}
 */
 import "C"
 
@@ -55,7 +71,9 @@
 	hashMapClass                jHashMap
 	idClass                     jIdClass
 	keyValueClass               jKeyValue
+	neighborhoodPeerClass       jNeighborhoodPeer
 	permissionsClass            jPermissions
+	syncgroupInviteClass        jSyncgroupInvite
 	syncgroupMemberInfoClass    jSyncgroupMemberInfo
 	syncgroupSpecClass          jSyncgroupSpec
 	verrorClass                 jVErrorClass
@@ -85,7 +103,9 @@
 	hashMapClass = newJHashMap(env)
 	idClass = newJIdClass(env)
 	keyValueClass = newJKeyValue(env)
+	neighborhoodPeerClass = newJNeighborhoodPeer(env)
 	permissionsClass = newJPermissions(env)
+	syncgroupInviteClass = newJSyncgroupInvite(env)
 	syncgroupMemberInfoClass = newJSyncgroupMemberInfo(env)
 	syncgroupSpecClass = newJSyncgroupSpec(env)
 	verrorClass = newJVErrorClass(env)
@@ -433,6 +453,46 @@
 	maybeThrowException(env, &cErr)
 }
 
+//export v23_syncbase_internal_onInvite
+func v23_syncbase_internal_onInvite(handle C.v23_syncbase_Handle, invite C.v23_syncbase_Invite) {
+	id := uint64(uintptr(handle))
+	h := globalRefMap.Get(id).(*inviteCallbacksHandle)
+	env, free := getEnv()
+	obj := invite.extractToJava(env)
+	arg := *(*C.jvalue)(unsafe.Pointer(&obj))
+	C.CallVoidMethodA(env, C.jobject(unsafe.Pointer(h.obj)), h.callbacks.onInvite, &arg)
+	if C.ExceptionOccurred(env) != nil {
+		C.ExceptionDescribe(env)
+		panic("java exception")
+	}
+	free()
+}
+
+type inviteCallbacksHandle struct {
+	obj       uintptr
+	callbacks jSyncgroupInvitesCallbacks
+}
+
+//export Java_io_v_syncbase_internal_Database_SyncgroupInvitesNewScan
+func Java_io_v_syncbase_internal_Database_SyncgroupInvitesNewScan(env *C.JNIEnv, cls C.jclass, name C.jstring, callbacks C.jobject) C.jlong {
+	cName := newVStringFromJava(env, name)
+	cbs := C.newVSyncgroupInvitesCallbacks()
+	cbs.handle = C.v23_syncbase_Handle(uintptr(globalRefMap.Add(&inviteCallbacksHandle{
+		obj:       uintptr(unsafe.Pointer(C.NewGlobalRef(env, callbacks))),
+		callbacks: newJSyncgroupInvitesCallbacks(env, callbacks),
+	})))
+	var cErr C.v23_syncbase_VError
+	var scanId C.uint64_t
+	v23_syncbase_DbSyncgroupInvitesNewScan(cName, cbs, &scanId, &cErr)
+	maybeThrowException(env, &cErr)
+	return C.jlong(scanId)
+}
+
+//export Java_io_v_syncbase_internal_Database_SyncgroupInvitesStopScan
+func Java_io_v_syncbase_internal_Database_SyncgroupInvitesStopScan(env *C.JNIEnv, cls C.jclass, scanId C.jlong) {
+	v23_syncbase_DbSyncgroupInvitesStopScan(C.uint64_t(scanId))
+}
+
 //export Java_io_v_syncbase_internal_Collection_GetPermissions
 func Java_io_v_syncbase_internal_Collection_GetPermissions(env *C.JNIEnv, cls C.jclass, name C.jstring, handle C.jstring) C.jobject {
 	cName := newVStringFromJava(env, name)
@@ -550,6 +610,65 @@
 	maybeThrowException(env, &cErr)
 }
 
+//export Java_io_v_syncbase_internal_Neighborhood_StartAdvertising
+func Java_io_v_syncbase_internal_Neighborhood_StartAdvertising(env *C.JNIEnv, cls C.jclass, visibility C.jobject) {
+	cVisibility := newVStringsFromJava(env, visibility)
+	var cErr C.v23_syncbase_VError
+	v23_syncbase_NeighborhoodStartAdvertising(cVisibility, &cErr)
+	maybeThrowException(env, &cErr)
+}
+
+//export Java_io_v_syncbase_internal_Neighborhood_StopAdvertising
+func Java_io_v_syncbase_internal_Neighborhood_StopAdvertising(env *C.JNIEnv, cls C.jclass) {
+	v23_syncbase_NeighborhoodStopAdvertising()
+}
+
+//export Java_io_v_syncbase_internal_Neighborhood_IsAdvertising
+func Java_io_v_syncbase_internal_Neighborhood_IsAdvertising(env *C.JNIEnv, cls C.jclass) C.jboolean {
+	var x C.v23_syncbase_Bool
+	v23_syncbase_NeighborhoodIsAdvertising(&x)
+	return C.jboolean(x)
+}
+
+//export v23_syncbase_internal_onPeer
+func v23_syncbase_internal_onPeer(handle C.v23_syncbase_Handle, peer C.v23_syncbase_AppPeer) {
+	id := uint64(uintptr(handle))
+	h := globalRefMap.Get(id).(*peerCallbacksHandle)
+	env, free := getEnv()
+	obj := peer.extractToJava(env)
+	arg := *(*C.jvalue)(unsafe.Pointer(&obj))
+	C.CallVoidMethodA(env, C.jobject(unsafe.Pointer(h.obj)), h.callbacks.onPeer, &arg)
+	if C.ExceptionOccurred(env) != nil {
+		C.ExceptionDescribe(env)
+		panic("java exception")
+	}
+	free()
+}
+
+type peerCallbacksHandle struct {
+	obj       uintptr
+	callbacks jNeighborhoodScanCallbacks
+}
+
+//export Java_io_v_syncbase_internal_Neighborhood_NewScan
+func Java_io_v_syncbase_internal_Neighborhood_NewScan(env *C.JNIEnv, cls C.jclass, callbacks C.jobject) C.jlong {
+	cbs := C.newVNeighborhoodScanCallbacks()
+	cbs.handle = C.v23_syncbase_Handle(uintptr(globalRefMap.Add(&peerCallbacksHandle{
+		obj:       uintptr(unsafe.Pointer(C.NewGlobalRef(env, callbacks))),
+		callbacks: newJNeigbhorhoodScanCallbacks(env, callbacks),
+	})))
+	var cErr C.v23_syncbase_VError
+	var scanId C.uint64_t
+	v23_syncbase_NeighborhoodNewScan(cbs, &scanId, &cErr)
+	maybeThrowException(env, &cErr)
+	return C.jlong(scanId)
+}
+
+//export Java_io_v_syncbase_internal_Neighborhood_StopScan
+func Java_io_v_syncbase_internal_Neighborhood_StopScan(env *C.JNIEnv, cls C.jclass, scanId C.jlong) {
+	v23_syncbase_NeighborhoodStopScan(C.uint64_t(scanId))
+}
+
 //export Java_io_v_syncbase_internal_Row_Exists
 func Java_io_v_syncbase_internal_Row_Exists(env *C.JNIEnv, cls C.jclass, name C.jstring, handle C.jstring) C.jboolean {
 	cName := newVStringFromJava(env, name)
@@ -647,6 +766,14 @@
 // All the extractToJava methods return Java types and deallocate all the
 // pointers inside v23_syncbase_* variable.
 
+func (x *C.v23_syncbase_AppPeer) extractToJava(env *C.JNIEnv) C.jobject {
+	obj := C.NewObjectA(env, neighborhoodPeerClass.class, neighborhoodPeerClass.init, nil)
+	C.SetObjectField(env, obj, neighborhoodPeerClass.appName, x.appName.extractToJava(env))
+	C.SetObjectField(env, obj, neighborhoodPeerClass.blessings, x.blessings.extractToJava(env))
+	C.SetBooleanField(env, obj, neighborhoodPeerClass.isLost, x.isLost.extractToJava())
+	return obj
+}
+
 func (x *C.v23_syncbase_ChangeType) extractToJava(env *C.JNIEnv) C.jobject {
 	var obj C.jobject
 	switch *x {
@@ -722,6 +849,14 @@
 	return obj
 }
 
+func (x *C.v23_syncbase_Invite) extractToJava(env *C.JNIEnv) C.jobject {
+	obj := C.NewObjectA(env, syncgroupInviteClass.class, syncgroupInviteClass.init, nil)
+	C.SetObjectField(env, obj, syncgroupInviteClass.syncgroup, x.syncgroup.extractToJava(env))
+	C.SetObjectField(env, obj, syncgroupInviteClass.addresses, x.addresses.extractToJava(env))
+	C.SetObjectField(env, obj, syncgroupInviteClass.blessingNames, x.blessingNames.extractToJava(env))
+	return obj
+}
+
 func (x *C.v23_syncbase_KeyValue) extractToJava(env *C.JNIEnv) C.jobject {
 	obj := C.NewObjectA(env, keyValueClass.class, keyValueClass.init, nil)
 	C.SetObjectField(env, obj, keyValueClass.key, x.key.extractToJava(env))
diff --git a/services/syncbase/bridge/cgo/jni_lib.go b/services/syncbase/bridge/cgo/jni_lib.go
index 45aa6b3..9287212 100644
--- a/services/syncbase/bridge/cgo/jni_lib.go
+++ b/services/syncbase/bridge/cgo/jni_lib.go
@@ -321,6 +321,66 @@
 	}
 }
 
+type jSyncgroupInvite struct {
+	class         C.jclass
+	init          C.jmethodID
+	syncgroup     C.jfieldID
+	addresses     C.jfieldID
+	blessingNames C.jfieldID
+}
+
+func newJSyncgroupInvite(env *C.JNIEnv) jSyncgroupInvite {
+	cls, init := initClass(env, "io/v/syncbase/core/SyncgroupInvite")
+	return jSyncgroupInvite{
+		class:         cls,
+		init:          init,
+		syncgroup:     jGetFieldID(env, cls, "syncgroup", "Lio/v/syncbase/core/Id;"),
+		addresses:     jGetFieldID(env, cls, "addresses", "Ljava/util/List;"),
+		blessingNames: jGetFieldID(env, cls, "blessingNames", "Ljava/util/List;"),
+	}
+}
+
+type jSyncgroupInvitesCallbacks struct {
+	onInvite C.jmethodID
+}
+
+func newJSyncgroupInvitesCallbacks(env *C.JNIEnv, obj C.jobject) jSyncgroupInvitesCallbacks {
+	cls := C.GetObjectClass(env, obj)
+	return jSyncgroupInvitesCallbacks{
+		onInvite: jGetMethodID(env, cls, "onInvite", "(Lio/v/syncbase/core/SyncgroupInvite;)V"),
+	}
+}
+
+type jNeighborhoodPeer struct {
+	class     C.jclass
+	init      C.jmethodID
+	appName   C.jfieldID
+	blessings C.jfieldID
+	isLost    C.jfieldID
+}
+
+func newJNeighborhoodPeer(env *C.JNIEnv) jNeighborhoodPeer {
+	cls, init := initClass(env, "io/v/syncbase/core/NeighborhoodPeer")
+	return jNeighborhoodPeer{
+		class:     cls,
+		init:      init,
+		appName:   jGetFieldID(env, cls, "appName", "Ljava/lang/String;"),
+		blessings: jGetFieldID(env, cls, "blessings", "Ljava/lang/String;"),
+		isLost:    jGetFieldID(env, cls, "isLost", "Z"),
+	}
+}
+
+type jNeighborhoodScanCallbacks struct {
+	onPeer C.jmethodID
+}
+
+func newJNeigbhorhoodScanCallbacks(env *C.JNIEnv, obj C.jobject) jNeighborhoodScanCallbacks {
+	cls := C.GetObjectClass(env, obj)
+	return jNeighborhoodScanCallbacks{
+		onPeer: jGetMethodID(env, cls, "onPeer", "(Lio/v/syncbase/core/NeighborhoodPeer;)V"),
+	}
+}
+
 // initClass returns the jclass and the jmethodID of the default constructor for
 // a class.
 func initClass(env *C.JNIEnv, name string) (C.jclass, C.jmethodID) {
diff --git a/services/syncbase/bridge/cgo/lib.h b/services/syncbase/bridge/cgo/lib.h
index 553eadc..c129a0b 100644
--- a/services/syncbase/bridge/cgo/lib.h
+++ b/services/syncbase/bridge/cgo/lib.h
@@ -140,6 +140,7 @@
 typedef struct {
   v23_syncbase_Id syncgroup;
   v23_syncbase_Strings addresses;
+  v23_syncbase_Strings blessingNames;
 } v23_syncbase_Invite;
 
 // syncbase.discovery.AppPeer
@@ -169,13 +170,11 @@
 typedef struct {
   v23_syncbase_Handle handle;
   void (*onInvite)(v23_syncbase_Handle handle, v23_syncbase_Invite);
-  void (*onDone)(v23_syncbase_Handle handle);
 } v23_syncbase_DbSyncgroupInvitesCallbacks;
 
 typedef struct {
   v23_syncbase_Handle handle;
   void (*onPeer)(v23_syncbase_Handle handle, v23_syncbase_AppPeer);
-  void (*onDone)(v23_syncbase_Handle handle);
 } v23_syncbase_NeighborhoodScanCallbacks;
 
 #endif  // V23_SYNCBASE_LIB_H_
diff --git a/services/syncbase/bridge/cgo/types.go b/services/syncbase/bridge/cgo/types.go
index 680011d..6492064 100644
--- a/services/syncbase/bridge/cgo/types.go
+++ b/services/syncbase/bridge/cgo/types.go
@@ -452,6 +452,7 @@
 func (x *C.v23_syncbase_Invite) init(invite discovery.Invite) {
 	x.syncgroup.init(invite.Syncgroup)
 	x.addresses.init(invite.Addresses)
+	x.blessingNames.init(invite.BlessingNames)
 }
 
 ////////////////////////////////////////////////////////////
diff --git a/services/syncbase/discovery/discovery.go b/services/syncbase/discovery/discovery.go
index 3273aad..9dea36a 100644
--- a/services/syncbase/discovery/discovery.go
+++ b/services/syncbase/discovery/discovery.go
@@ -15,6 +15,7 @@
 	"v.io/v23/context"
 	"v.io/v23/conventions"
 	"v.io/v23/discovery"
+	"v.io/v23/naming"
 	"v.io/v23/security"
 	wire "v.io/v23/services/syncbase"
 	"v.io/v23/syncbase/util"
@@ -920,10 +921,11 @@
 
 // Invite represents an invitation to join a syncgroup as found via Discovery.
 type Invite struct {
-	Syncgroup wire.Id  // Syncgroup is the Id of the syncgroup you've been invited to.
-	Addresses []string // Addresses are the list of addresses of the inviting server.
-	Lost      bool     // If this invite is a lost invite or not.
-	key       string   // Unexported. The implementation uses this key to de-dupe ads.
+	Syncgroup     wire.Id  // Syncgroup is the Id of the syncgroup you've been invited to.
+	Addresses     []string // Addresses are the list of addresses of the inviting server.
+	BlessingNames []string // BlessingNames are the list of blessings of the inviting server.
+	Lost          bool     // If this invite is a lost invite or not.
+	key           string   // Unexported. The implementation uses this key to de-dupe ads.
 }
 
 func makeInvite(ad discovery.Advertisement) (Invite, wire.Id, bool) {
@@ -942,17 +944,38 @@
 		return Invite{}, dbId, false
 	}
 	i := Invite{
-		Addresses: append([]string{}, ad.Addresses...),
-		Syncgroup: sgId,
+		Addresses:     append([]string{}, ad.Addresses...),
+		BlessingNames: computeBlessingNamesFromEndpoints(ad.Addresses),
+		Syncgroup:     sgId,
 	}
 	sort.Strings(i.Addresses)
 	i.key = fmt.Sprintf("%v", i)
 	return i, dbId, true
 }
 
+// computeBlessingNamesFromEndpoints parses endpoints and concatenates found blessings together.
+func computeBlessingNamesFromEndpoints(addresses []string) []string {
+	blessingsMap := make(map[string]struct{})
+
+	for _, address := range addresses {
+		if e, err := naming.ParseEndpoint(address); err != nil {
+			for _, name := range e.BlessingNames() {
+				blessingsMap[name] = struct{}{}
+			}
+		}
+	}
+
+	blessingNames := make([]string, len(blessingsMap))
+	for blessings, _ := range blessingsMap {
+		blessingNames = append(blessingNames, blessings)
+	}
+	return blessingNames
+}
+
 func (i Invite) copy() copyable {
 	cp := i
 	cp.Addresses = append([]string(nil), i.Addresses...)
+	cp.BlessingNames = append([]string(nil), i.BlessingNames...)
 	return cp
 }
 
@@ -967,6 +990,7 @@
 func (i Invite) copyAsLost() copyable {
 	cp := i
 	cp.Addresses = append([]string(nil), i.Addresses...)
+	cp.BlessingNames = append([]string(nil), i.BlessingNames...)
 	cp.Lost = true
 	return cp
 }