JNI: Add jni code to support the BLE protocol.
Right now this is a close copy of the Bluetooth jni code. In the future
it might be better to share most of the code, but for a first cut I'm
going to just make a copy.
Change-Id: I68d30164eeb0b85372a9b998837a80008bffb3e9
diff --git a/impl/google/rpc/jni.go b/impl/google/rpc/jni.go
index a27f405..5719220 100644
--- a/impl/google/rpc/jni.go
+++ b/impl/google/rpc/jni.go
@@ -14,9 +14,11 @@
"v.io/v23/options"
"v.io/v23/rpc"
"v.io/v23/vdl"
- "v.io/v23/vom"
-
"v.io/v23/verror"
+ "v.io/v23/vom"
+ "v.io/x/lib/vlog"
+
+ jble "v.io/x/jni/impl/google/rpc/protocols/ble"
jbt "v.io/x/jni/impl/google/rpc/protocols/bt"
jutil "v.io/x/jni/util"
jcontext "v.io/x/jni/v23/context"
@@ -92,6 +94,11 @@
if err := jbt.Init(env); err != nil {
return err
}
+ if err := jble.Init(env); err != nil {
+ // The BLE protocol isn't always compiled in, so don't fail if it
+ // isn't available.
+ vlog.Infof("Unable to initialize BLE protocol: %v", err)
+ }
// Cache global references to all Java classes used by the package. This is
// necessary because JNI gets access to the class loader only in the system
// thread, so we aren't able to invoke FindClass in other threads.
diff --git a/impl/google/rpc/protocols/ble/ble.go b/impl/google/rpc/protocols/ble/ble.go
new file mode 100644
index 0000000..76e2ead
--- /dev/null
+++ b/impl/google/rpc/protocols/ble/ble.go
@@ -0,0 +1,18 @@
+// Copyright 2016 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 java
+
+package ble
+
+import (
+ jutil "v.io/x/jni/util"
+)
+
+// Init initializes the JNI code with the given Java environment. This method
+// must be called from the main Java thread.
+// TODO(mattr): Why must this be called from the main thread?
+func Init(env jutil.Env) error {
+ return nil
+}
diff --git a/impl/google/rpc/protocols/ble/ble_android.go b/impl/google/rpc/protocols/ble/ble_android.go
new file mode 100644
index 0000000..c76126d
--- /dev/null
+++ b/impl/google/rpc/protocols/ble/ble_android.go
@@ -0,0 +1,197 @@
+// Copyright 2016 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 android
+
+package ble
+
+import (
+ "net"
+ "runtime"
+ "time"
+
+ "v.io/v23/context"
+ "v.io/v23/flow"
+ "v.io/x/ref/runtime/protocols/lib/framer"
+
+ jutil "v.io/x/jni/util"
+ jcontext "v.io/x/jni/v23/context"
+)
+
+var (
+ contextSign = jutil.ClassSign("io.v.v23.context.VContext")
+ streamSign = jutil.ClassSign("io.v.android.impl.google.rpc.protocols.ble.BLE$Stream")
+ listenerSign = jutil.ClassSign("io.v.android.impl.google.rpc.protocols.ble.BLE$Listener")
+
+ // Global reference for io.v.impl.google.rpc.protocols.ble.BLE class.
+ jBleClass jutil.Class
+)
+
+const ble = "ble"
+
+// Init initializes the JNI code with the given Java environment. This method
+// must be called from the main Java thread.
+func Init(env jutil.Env) error {
+ // Cache global references to all Java classes used by the package. This is
+ // necessary because JNI gets access to the class loader only in the system
+ // thread, so we aren't able to invoke FindClass in other threads.
+ var err error
+ jBleClass, err = jutil.JFindClass(env, "io/v/android/impl/google/rpc/protocols/ble/BLE")
+ if err != nil {
+ return err
+ }
+ flow.RegisterProtocol(ble, bleProtocol{})
+ return nil
+}
+
+// TODO(mattr): We should unify this struct with btProtocol by introducing Protocol java interfaces and allowing
+// you to initialize one with Init and a class name. I'll keep this separate until I get ble working.
+type bleProtocol struct{}
+
+func (bleProtocol) Dial(ctx *context.T, protocol, address string, timeout time.Duration) (flow.Conn, error) {
+ env, freeFunc := jutil.GetEnv()
+ jContext, err := jcontext.JavaContext(env, ctx, nil)
+ if err != nil {
+ freeFunc()
+ return nil, err
+ }
+ // This method will invoke the freeFunc().
+ jStream, err := jutil.CallStaticCallbackMethod(env, freeFunc, jBleClass, "dial", []jutil.Sign{contextSign, jutil.StringSign, jutil.DurationSign}, jContext, address, timeout)
+ if err != nil {
+ return nil, err
+ }
+ env, freeFunc = jutil.GetEnv()
+ defer freeFunc()
+ return newConnection(env, jStream), nil
+}
+
+func (bleProtocol) Resolve(ctx *context.T, protocol, address string) (string, []string, error) {
+ return protocol, []string{address}, nil
+}
+
+func (bleProtocol) Listen(ctx *context.T, protocol, address string) (flow.Listener, error) {
+ env, freeFunc := jutil.GetEnv()
+ defer freeFunc()
+ jContext, err := jcontext.JavaContext(env, ctx, nil)
+ if err != nil {
+ return nil, err
+ }
+ jListener, err := jutil.CallStaticObjectMethod(env, jBleClass, "listen", []jutil.Sign{contextSign, jutil.StringSign}, listenerSign, jContext, address)
+ if err != nil {
+ return nil, err
+ }
+ return newListener(env, jListener), nil
+}
+
+func newListener(env jutil.Env, jListener jutil.Object) flow.Listener {
+ // Reference Java Listener; it will be de-referenced when the Go Listener
+ // created below is garbage-collected (through the finalizer callback we
+ // setup just below).
+ jListener = jutil.NewGlobalRef(env, jListener)
+ l := &bleListener{jListener}
+ runtime.SetFinalizer(l, func(l *bleListener) {
+ env, freeFunc := jutil.GetEnv()
+ defer freeFunc()
+ jutil.DeleteGlobalRef(env, l.jListener)
+ })
+ return l
+}
+
+type bleListener struct {
+ jListener jutil.Object
+}
+
+func (l *bleListener) Accept(ctx *context.T) (flow.Conn, error) {
+ env, freeFunc := jutil.GetEnv()
+ // This method will invoke the freeFunc().
+ jStream, err := jutil.CallCallbackMethod(env, freeFunc, l.jListener, "accept", nil)
+ if err != nil {
+ return nil, err
+ }
+ env, freeFunc = jutil.GetEnv()
+ defer freeFunc()
+ return newConnection(env, jStream), nil
+}
+
+func (l *bleListener) Addr() net.Addr {
+ env, freeFunc := jutil.GetEnv()
+ defer freeFunc()
+ addr, err := jutil.CallStringMethod(env, l.jListener, "address", nil)
+ if err != nil {
+ return bleAddr("")
+ }
+ return bleAddr(addr)
+}
+
+func (l *bleListener) Close() error {
+ env, freeFunc := jutil.GetEnv()
+ defer freeFunc()
+ return jutil.CallVoidMethod(env, l.jListener, "close", nil)
+}
+
+// newConnection creates a new Go connection. The passed-in Java Stream object
+// is assumed to hold a global reference.
+func newConnection(env jutil.Env, jStream jutil.Object) flow.Conn {
+ c := &bleReadWriteCloser{jStream}
+ runtime.SetFinalizer(c, func(c *bleReadWriteCloser) {
+ env, freeFunc := jutil.GetEnv()
+ defer freeFunc()
+ jutil.DeleteGlobalRef(env, c.jStream)
+ })
+ addrStr, _ := jutil.CallStringMethod(env, jStream, "localAddress", nil)
+ localAddr := bleAddr(addrStr)
+ return bleConn{framer.New(c), localAddr}
+}
+
+type bleConn struct {
+ flow.MsgReadWriteCloser
+ localAddr net.Addr
+}
+
+func (c bleConn) LocalAddr() net.Addr {
+ return c.localAddr
+}
+
+type bleReadWriteCloser struct {
+ jStream jutil.Object
+}
+
+func (c *bleReadWriteCloser) Read(b []byte) (n int, err error) {
+ env, freeFunc := jutil.GetEnv()
+ // This method will invoke the freeFunc().
+ jResult, err := jutil.CallCallbackMethod(env, freeFunc, c.jStream, "read", []jutil.Sign{jutil.IntSign}, len(b))
+ if err != nil {
+ return 0, err
+ }
+ env, freeFunc = jutil.GetEnv()
+ defer freeFunc()
+ defer jutil.DeleteGlobalRef(env, jResult)
+ data := jutil.GoByteArray(env, jResult)
+ return copy(b, data), nil
+}
+
+func (c *bleReadWriteCloser) Write(b []byte) (n int, err error) {
+ env, freeFunc := jutil.GetEnv()
+ // This method will invoke the freeFunc().
+ if _, err := jutil.CallCallbackMethod(env, freeFunc, c.jStream, "write", []jutil.Sign{jutil.ByteArraySign}, b); err != nil {
+ return 0, err
+ }
+ return len(b), nil
+}
+
+func (c *bleReadWriteCloser) Close() error {
+ env, freeFunc := jutil.GetEnv()
+ defer freeFunc()
+ return jutil.CallVoidMethod(env, c.jStream, "close", nil)
+}
+
+type bleAddr string
+
+func (bleAddr) Network() string {
+ return ble
+}
+
+func (a bleAddr) String() string {
+ return string(a)
+}