Browse Source

Merge branch 'android-jni-dev' into edge

Grant Limberg 9 years ago
parent
commit
7756b5bf1c

BIN
artwork/ZeroTierIcon512x512.png


+ 1 - 0
java/CMakeLists.txt

@@ -59,6 +59,7 @@ set(src_files
 
 
 set(include_dirs
 set(include_dirs
     ${CMAKE_CURRENT_SOURCE_DIR}/../include/
     ${CMAKE_CURRENT_SOURCE_DIR}/../include/
+    ${CMAKE_CURRENT_SOURCE_DIR}/../node/
     ${Java_INCLUDE_DIRS})
     ${Java_INCLUDE_DIRS})
 
 
 if(WIN32)
 if(WIN32)

+ 24 - 29
java/build.xml

@@ -1,4 +1,4 @@
-<project default="build" name="ZeroTierOneSDK" basedir=".">
+<project default="build_jar" name="ZeroTierOneSDK" basedir=".">
     <property environment="env"/>
     <property environment="env"/>
 
 
     <condition property="isWindows">
     <condition property="isWindows">
@@ -9,7 +9,7 @@
       <os family="mac"/>
       <os family="mac"/>
     </condition>
     </condition>
 
 
-    <target name="clean">
+    <target name="clean_ant">
         <delete dir="bin" failonerror="false"/>
         <delete dir="bin" failonerror="false"/>
         <delete dir="classes" failonerror="false"/>
         <delete dir="classes" failonerror="false"/>
         <delete dir="build_win32" failonerror="false"/>
         <delete dir="build_win32" failonerror="false"/>
@@ -24,6 +24,10 @@
         <echo message="os.arch          = ${os.arch}"/>
         <echo message="os.arch          = ${os.arch}"/>
         <echo message="ant.java.version = ${ant.java.version}"/>
         <echo message="ant.java.version = ${ant.java.version}"/>
         <echo message="java.version     = ${java.version}"/>
         <echo message="java.version     = ${java.version}"/>
+        <echo message="ndk.loc          = ${env.NDK_BUILD_LOC}"/>
+        <echo message="sdk.loc          = ${env.ANDROID_PLATFORM}"/>
+        <echo message="user.dir         = ${user.dir}"/>
+        <echo message="zt1.dir          = ${env.ZT}"/>
         <mkdir dir="bin"/>
         <mkdir dir="bin"/>
         <mkdir dir="classes"/>
         <mkdir dir="classes"/>
         <javac srcdir="src"
         <javac srcdir="src"
@@ -36,20 +40,30 @@
 
 
     <target name="build_android">
     <target name="build_android">
         <exec dir="jni" executable="${env.NDK_BUILD_LOC}" failonerror="true">
         <exec dir="jni" executable="${env.NDK_BUILD_LOC}" failonerror="true">
-            <arg value="ZT1=${user.dir}/../"/>
+            <arg value="ZT1=${env.ZT}"/>
             <arg value="V=1"/>
             <arg value="V=1"/>
+            <!-- <arg value="NDK_DEBUG=1"/> -->
         </exec>
         </exec>
-        <copy file="libs/armeabi/libZeroTierOneJNI.so" 
-              tofile="${user.dir}/classes/lib/armeabi/libZeroTierOneJNI.so" 
-              overwrite="true"/>
         <copy file="libs/arm64-v8a/libZeroTierOneJNI.so"
         <copy file="libs/arm64-v8a/libZeroTierOneJNI.so"
-              tofile="${user.dir}/classes/lib/arm64-v8a/libZeroTierOneJNI.so"
+              tofile="classes/lib/arm64-v8a/libZeroTierOneJNI.so"
+              overwrite="true"/>
+        <copy file="libs/armeabi/libZeroTierOneJNI.so" 
+              tofile="classes/lib/armeabi/libZeroTierOneJNI.so"
               overwrite="true"/>
               overwrite="true"/>
         <copy file="libs/armeabi-v7a/libZeroTierOneJNI.so"
         <copy file="libs/armeabi-v7a/libZeroTierOneJNI.so"
-              tofile="${user.dir}/classes/lib/armeabi-v7a/libZeroTierOneJNI.so"
+              tofile="classes/lib/armeabi-v7a/libZeroTierOneJNI.so"
+              overwrite="true"/>
+        <copy file="libs/mips/libZeroTierOneJNI.so"
+              tofile="classes/lib/mips/libZeroTierOneJNI.so"
+              overwrite="true"/>
+        <copy file="libs/mips64/libZeroTierOneJNI.so"
+              tofile="classes/lib/mips64/libZeroTierOne.so"
               overwrite="true"/>
               overwrite="true"/>
         <copy file="libs/x86/libZeroTierOneJNI.so"
         <copy file="libs/x86/libZeroTierOneJNI.so"
-              tofile="${user.dir}/classes/lib/x86/libZeroTierOneJNI.so"
+              tofile="classes/lib/x86/libZeroTierOneJNI.so"
+              overwrite="true"/>
+        <copy file="libs/x86_64/libZeroTierOneJNI.so"
+              tofile="classes/lib/x86_64/libZeroTierOneJNI.so"
               overwrite="true"/>
               overwrite="true"/>
     </target>
     </target>
 
 
@@ -91,7 +105,7 @@
             overwrite="true"/>
             overwrite="true"/>
     </target>
     </target>
 
 
-    <target name="build" depends="build_java,build_android,windows,mac">    
+    <target name="build_jar" depends="build_java,build_android,windows,mac">
         <jar destfile="bin/ZeroTierOneSDK.jar" basedir="classes"/>
         <jar destfile="bin/ZeroTierOneSDK.jar" basedir="classes"/>
     </target>
     </target>
 
 
@@ -101,23 +115,4 @@
       <javadoc sourcepath="src/"
       <javadoc sourcepath="src/"
                destdir="doc/"/>
                destdir="doc/"/>
     </target>
     </target>
-
-<!--    <target name="android" depends="build">
-        <echo message="OS is Android, installing..."/>
-        <copy file="libs/armeabi/libZeroTierOneJNI.so" 
-              tofile="${aproj_loc}/libs/armeabi/libZeroTierOneJNI.so" 
-              overwrite="true"/>
-        <copy file="libs/arm64-v8a/libZeroTierOneJNI.so"
-              tofile="${aproj_loc}/libs/arm64-v8a/libZeroTierOneJNI.so"
-              overwrite="true"/>
-        <copy file="libs/armeabi-v7a/libZeroTierOneJNI.so"
-              tofile="${aproj_loc}/libs/armeabi-v7a/libZeroTierOneJNI.so"
-              overwrite="true"/>
-        <copy file="libs/x86/libZeroTierOneJNI.so"
-              tofile="${aproj_loc}/libs/x86/libZeroTierOneJNI.so"
-              overwrite="true"/>
-        <copy   file="bin/ZeroTierOneSDK.jar" 
-                tofile="${aproj_loc}/libs/ZeroTierOneSDK.jar" 
-                overwrite="true"/>
-    </target> -->
 </project>
 </project>

+ 4 - 2
java/jni/Android.mk

@@ -4,7 +4,9 @@ include $(CLEAR_VARS)
 
 
 LOCAL_MODULE := ZeroTierOneJNI
 LOCAL_MODULE := ZeroTierOneJNI
 LOCAL_C_INCLUDES := $(ZT1)/include
 LOCAL_C_INCLUDES := $(ZT1)/include
+LOCAL_C_INCLUDES += $(ZT1)/node
 LOCAL_LDLIBS := -llog
 LOCAL_LDLIBS := -llog
+# LOCAL_CFLAGS := -g
 
 
 # ZeroTierOne SDK source files
 # ZeroTierOne SDK source files
 LOCAL_SRC_FILES := \
 LOCAL_SRC_FILES := \
@@ -13,7 +15,6 @@ LOCAL_SRC_FILES := \
 	$(ZT1)/ext/http-parser/http_parser.c \
 	$(ZT1)/ext/http-parser/http_parser.c \
 	$(ZT1)/node/C25519.cpp \
 	$(ZT1)/node/C25519.cpp \
 	$(ZT1)/node/CertificateOfMembership.cpp \
 	$(ZT1)/node/CertificateOfMembership.cpp \
-	$(ZT1)/node/Defaults.cpp \
 	$(ZT1)/node/Dictionary.cpp \
 	$(ZT1)/node/Dictionary.cpp \
 	$(ZT1)/node/Identity.cpp \
 	$(ZT1)/node/Identity.cpp \
 	$(ZT1)/node/IncomingPacket.cpp \
 	$(ZT1)/node/IncomingPacket.cpp \
@@ -24,6 +25,7 @@ LOCAL_SRC_FILES := \
 	$(ZT1)/node/Node.cpp \
 	$(ZT1)/node/Node.cpp \
 	$(ZT1)/node/OutboundMulticast.cpp \
 	$(ZT1)/node/OutboundMulticast.cpp \
 	$(ZT1)/node/Packet.cpp \
 	$(ZT1)/node/Packet.cpp \
+	$(ZT1)/node/Path.cpp \
 	$(ZT1)/node/Peer.cpp \
 	$(ZT1)/node/Peer.cpp \
 	$(ZT1)/node/Poly1305.cpp \
 	$(ZT1)/node/Poly1305.cpp \
 	$(ZT1)/node/Salsa20.cpp \
 	$(ZT1)/node/Salsa20.cpp \
@@ -39,6 +41,6 @@ LOCAL_SRC_FILES := \
 LOCAL_SRC_FILES += \
 LOCAL_SRC_FILES += \
 	com_zerotierone_sdk_Node.cpp \
 	com_zerotierone_sdk_Node.cpp \
 	ZT_jniutils.cpp \
 	ZT_jniutils.cpp \
-	ZT_jnicache.cpp
+	ZT_jnilookup.cpp
 
 
 include $(BUILD_SHARED_LIBRARY)
 include $(BUILD_SHARED_LIBRARY)

+ 5 - 4
java/jni/Application.mk

@@ -1,4 +1,5 @@
-APP_ABI := armeabi armeabi-v7a arm64-v8a x86
-APP_STL := gnustl_static
-APP_CPPFLAGS += -Wall -fPIE -fstack-protector -fexceptions -DZT_TRACE
-
+NDK_TOOLCHAIN_VERSION := clang
+APP_STL := c++_static
+APP_CPPFLAGS := -O3 -fPIC -fPIE -fvectorize -Wall -fstack-protector -fexceptions -fno-strict-aliasing -Wno-deprecated-register -DZT_NO_TYPE_PUNNING=1
+APP_PLATFORM := android-14
+APP_ABI := all

+ 0 - 242
java/jni/ZT1_jnicache.cpp

@@ -1,242 +0,0 @@
-/*
- * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2015  ZeroTier, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * --
- *
- * ZeroTier may be used and distributed under the terms of the GPLv3, which
- * are available at: http://www.gnu.org/licenses/gpl-3.0.html
- *
- * If you would like to embed ZeroTier into a commercial application or
- * redistribute it in a modified binary form, please contact ZeroTier Networks
- * LLC. Start here: http://www.zerotier.com/
- */
-
-#include "ZT_jnicache.h"
-#include "ZT_jniutils.h"
-
-JniCache::JniCache()
-    : m_jvm(NULL)
-    , m_classes()
-    , m_fields()
-    , m_staticFields()
-    , m_methods()
-    , m_staticMethods()
-{
-    LOGV("JNI Cache Created");
-}
-
-JniCache::JniCache(JavaVM *jvm)
-    : m_jvm(jvm)
-    , m_classes()
-    , m_fields()
-    , m_staticFields()
-    , m_methods()
-    , m_staticMethods()
-{
-    LOGV("JNI Cache Created");
-}
-
-JniCache::~JniCache()
-{
-    LOGV("JNI Cache Destroyed");
-    clearCache();
-}
-
-void JniCache::clearCache()
-{
-    if(m_jvm)
-    {
-        JNIEnv *env = NULL;
-        if(m_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK)
-            return;
-
-        for(ClassMap::iterator iter = m_classes.begin(), end = m_classes.end();
-            iter != end; ++iter)
-        {
-            env->DeleteGlobalRef(iter->second);
-        }
-    }
-
-    m_classes.clear();
-    m_fields.clear();
-    m_staticFields.clear();
-    m_methods.clear();
-    m_staticMethods.clear();
-}
-
-void JniCache::setJavaVM(JavaVM *jvm)
-{ 
-    LOGV("Assigned JVM to object");
-    m_jvm = jvm; 
-}
-
-
-jclass JniCache::findClass(const std::string &name)
-{
-    if(!m_jvm)
-        return NULL;
-
-    ClassMap::iterator found = m_classes.find(name);
-
-    if(found == m_classes.end())
-    {
-        // get the class from the JVM
-        JNIEnv *env = NULL;
-        if(m_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK)
-        {
-            LOGE("Error retreiving JNI Environment");
-            return NULL;
-        }
-
-        jclass localCls = env->FindClass(name.c_str());
-        if(env->ExceptionCheck())
-        {
-            LOGE("Error finding class: %s", name.c_str());
-            return NULL;
-        }
-
-        jclass cls = (jclass)env->NewGlobalRef(localCls);
-
-        m_classes.insert(std::make_pair(name, cls));
-
-        return cls;
-    }
-
-    LOGV("Returning cached %s", name.c_str());
-    return found->second;
-}
-
-
-jmethodID JniCache::findMethod(jclass cls, const std::string &methodName, const std::string &methodSig)
-{
-    if(!m_jvm)
-        return NULL;
-
-    std::string id = methodName + methodSig;
-
-    MethodMap::iterator found = m_methods.find(id);
-    if(found == m_methods.end())
-    {
-        JNIEnv *env = NULL;
-        if(m_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK)
-        {
-            return NULL;
-        }
-
-        jmethodID mid = env->GetMethodID(cls, methodName.c_str(), methodSig.c_str());
-        if(env->ExceptionCheck())
-        {
-            return NULL;
-        }
-
-        m_methods.insert(std::make_pair(id, mid));
-
-        return mid;
-    }
-
-    return found->second;
-}
-
-jmethodID JniCache::findStaticMethod(jclass cls, const std::string &methodName, const std::string &methodSig)
-{
-    if(!m_jvm)
-        return NULL;
-
-    std::string id = methodName + methodSig;
-
-    MethodMap::iterator found = m_staticMethods.find(id);
-    if(found == m_staticMethods.end())
-    {
-        JNIEnv *env = NULL;
-        if(m_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK)
-        {
-            return NULL;
-        }
-
-        jmethodID mid = env->GetStaticMethodID(cls, methodName.c_str(), methodSig.c_str());
-        if(env->ExceptionCheck())
-        {
-            return NULL;
-        }
-
-        m_staticMethods.insert(std::make_pair(id, mid));
-
-        return mid;
-    }
-
-    return found->second;
-}
-
-jfieldID JniCache::findField(jclass cls, const std::string &fieldName, const std::string &typeStr)
-{
-    if(!m_jvm)
-        return NULL;
-
-    std::string id = fieldName + typeStr;
-
-    FieldMap::iterator found = m_fields.find(id);
-    if(found == m_fields.end())
-    {
-        JNIEnv *env = NULL;
-        if(m_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK)
-        {
-            return NULL;
-        }
-
-        jfieldID fid = env->GetFieldID(cls, fieldName.c_str(), typeStr.c_str());
-        if(env->ExceptionCheck())
-        {
-            return NULL;
-        }
-
-        m_fields.insert(std::make_pair(id, fid));
-
-        return fid;
-    }
-
-    return found->second;
-}
-
-jfieldID JniCache::findStaticField(jclass cls, const std::string &fieldName, const std::string &typeStr)
-{
-    if(!m_jvm)
-        return NULL;
-
-    std::string id = fieldName + typeStr;
-
-    FieldMap::iterator found = m_staticFields.find(id);
-    if(found == m_staticFields.end())
-    {
-        JNIEnv *env = NULL;
-        if(m_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK)
-        {
-            return NULL;
-        }
-
-        jfieldID fid = env->GetStaticFieldID(cls, fieldName.c_str(), typeStr.c_str());
-        if(env->ExceptionCheck())
-        {
-            return NULL;
-        }
-
-        m_staticFields.insert(std::make_pair(id, fid));
-
-        return fid;
-    }
-
-    return found->second;
-}

+ 158 - 0
java/jni/ZT_jnilookup.cpp

@@ -0,0 +1,158 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2015  ZeroTier, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * --
+ *
+ * ZeroTier may be used and distributed under the terms of the GPLv3, which
+ * are available at: http://www.gnu.org/licenses/gpl-3.0.html
+ *
+ * If you would like to embed ZeroTier into a commercial application or
+ * redistribute it in a modified binary form, please contact ZeroTier Networks
+ * LLC. Start here: http://www.zerotier.com/
+ */
+
+#include "ZT_jnilookup.h"
+#include "ZT_jniutils.h"
+
+JniLookup::JniLookup()
+    : m_jvm(NULL)
+{
+    LOGV("JNI Cache Created");
+}
+
+JniLookup::JniLookup(JavaVM *jvm)
+    : m_jvm(jvm)
+{
+    LOGV("JNI Cache Created");
+}
+
+JniLookup::~JniLookup()
+{
+    LOGV("JNI Cache Destroyed");
+}
+
+
+void JniLookup::setJavaVM(JavaVM *jvm)
+{ 
+    LOGV("Assigned JVM to object");
+    m_jvm = jvm; 
+}
+
+
+jclass JniLookup::findClass(const std::string &name)
+{
+    if(!m_jvm)
+        return NULL;
+
+    // get the class from the JVM
+    JNIEnv *env = NULL;
+    if(m_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK)
+    {
+        LOGE("Error retreiving JNI Environment");
+        return NULL;
+    }
+
+    jclass cls = env->FindClass(name.c_str());
+    if(env->ExceptionCheck())
+    {
+        LOGE("Error finding class: %s", name.c_str());
+        return NULL;
+    }
+
+    return cls;
+}
+
+
+jmethodID JniLookup::findMethod(jclass cls, const std::string &methodName, const std::string &methodSig)
+{
+    if(!m_jvm)
+        return NULL;
+
+    JNIEnv *env = NULL;
+    if(m_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK)
+    {
+        return NULL;
+    }
+
+    jmethodID mid = env->GetMethodID(cls, methodName.c_str(), methodSig.c_str());
+    if(env->ExceptionCheck())
+    {
+        return NULL;
+    }
+
+    return mid;
+}
+
+jmethodID JniLookup::findStaticMethod(jclass cls, const std::string &methodName, const std::string &methodSig)
+{
+    if(!m_jvm)
+        return NULL;
+
+    JNIEnv *env = NULL;
+    if(m_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK)
+    {
+        return NULL;
+    }
+
+    jmethodID mid = env->GetStaticMethodID(cls, methodName.c_str(), methodSig.c_str());
+    if(env->ExceptionCheck())
+    {
+        return NULL;
+    }
+
+    return mid;
+}
+
+jfieldID JniLookup::findField(jclass cls, const std::string &fieldName, const std::string &typeStr)
+{
+    if(!m_jvm)
+        return NULL;
+
+    JNIEnv *env = NULL;
+    if(m_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK)
+    {
+        return NULL;
+    }
+
+    jfieldID fid = env->GetFieldID(cls, fieldName.c_str(), typeStr.c_str());
+    if(env->ExceptionCheck())
+    {
+        return NULL;
+    }
+
+    return fid;
+}
+
+jfieldID JniLookup::findStaticField(jclass cls, const std::string &fieldName, const std::string &typeStr)
+{
+    if(!m_jvm)
+        return NULL;
+
+    JNIEnv *env = NULL;
+    if(m_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK)
+    {
+        return NULL;
+    }
+
+    jfieldID fid = env->GetStaticFieldID(cls, fieldName.c_str(), typeStr.c_str());
+    if(env->ExceptionCheck())
+    {
+        return NULL;
+    }
+
+    return fid;
+}

+ 6 - 17
java/jni/ZT1_jnicache.h → java/jni/ZT_jnilookup.h

@@ -25,8 +25,8 @@
  * LLC. Start here: http://www.zerotier.com/
  * LLC. Start here: http://www.zerotier.com/
  */
  */
 
 
-#ifndef ZT_JNICACHE_H_
-#define ZT_JNICACHE_H_
+#ifndef ZT_JNILOOKUP_H_
+#define ZT_JNILOOKUP_H_
 
 
 #include <jni.h>
 #include <jni.h>
 #include <map>
 #include <map>
@@ -34,14 +34,13 @@
 
 
 
 
 
 
-class JniCache {
+class JniLookup {
 public:
 public:
-    JniCache();
-    JniCache(JavaVM *jvm);
-    ~JniCache();
+    JniLookup();
+    JniLookup(JavaVM *jvm);
+    ~JniLookup();
 
 
     void setJavaVM(JavaVM *jvm);
     void setJavaVM(JavaVM *jvm);
-    void clearCache();
 
 
     jclass findClass(const std::string &name);
     jclass findClass(const std::string &name);
     jmethodID findMethod(jclass cls, const std::string &methodName, const std::string &methodSig);
     jmethodID findMethod(jclass cls, const std::string &methodName, const std::string &methodSig);
@@ -49,17 +48,7 @@ public:
     jfieldID findField(jclass cls, const std::string &fieldName, const std::string &typeStr);
     jfieldID findField(jclass cls, const std::string &fieldName, const std::string &typeStr);
     jfieldID findStaticField(jclass cls, const std::string &fieldName, const std::string &typeStr);
     jfieldID findStaticField(jclass cls, const std::string &fieldName, const std::string &typeStr);
 private:
 private:
-    typedef std::map<std::string, jmethodID> MethodMap;
-    typedef std::map<std::string, jfieldID> FieldMap;
-    typedef std::map<std::string, jclass> ClassMap;
-
     JavaVM *m_jvm;
     JavaVM *m_jvm;
-    ClassMap m_classes;
-    FieldMap m_fields;
-    FieldMap m_staticFields;
-    MethodMap m_methods;
-    MethodMap m_staticMethods;
-
 };
 };
 
 
 #endif
 #endif

+ 75 - 92
java/jni/ZT1_jniutils.cpp → java/jni/ZT_jniutils.cpp

@@ -1,9 +1,9 @@
 #include "ZT_jniutils.h"
 #include "ZT_jniutils.h"
-#include "ZT_jnicache.h"
+#include "ZT_jnilookup.h"
 #include <string>
 #include <string>
 #include <assert.h>
 #include <assert.h>
 
 
-extern JniCache cache;
+extern JniLookup lookup;
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 extern "C" {
 extern "C" {
@@ -15,7 +15,7 @@ jobject createResultObject(JNIEnv *env, ZT_ResultCode code)
     
     
     jobject resultObject = NULL;
     jobject resultObject = NULL;
 
 
-    resultClass = cache.findClass("com/zerotier/sdk/ResultCode");
+    resultClass = lookup.findClass("com/zerotier/sdk/ResultCode");
     if(resultClass == NULL)
     if(resultClass == NULL)
     {
     {
         LOGE("Couldnt find ResultCode class");
         LOGE("Couldnt find ResultCode class");
@@ -48,7 +48,7 @@ jobject createResultObject(JNIEnv *env, ZT_ResultCode code)
         break;
         break;
     }
     }
 
 
-    jfieldID enumField = cache.findStaticField(resultClass, fieldName.c_str(), "Lcom/zerotier/sdk/ResultCode;");
+    jfieldID enumField = lookup.findStaticField(resultClass, fieldName.c_str(), "Lcom/zerotier/sdk/ResultCode;");
     if(env->ExceptionCheck() || enumField == NULL) 
     if(env->ExceptionCheck() || enumField == NULL) 
     {
     {
         LOGE("Error on FindStaticField");
         LOGE("Error on FindStaticField");
@@ -68,7 +68,7 @@ jobject createVirtualNetworkStatus(JNIEnv *env, ZT_VirtualNetworkStatus status)
 {
 {
     jobject statusObject = NULL;
     jobject statusObject = NULL;
 
 
-    jclass statusClass = cache.findClass("com/zerotier/sdk/VirtualNetworkStatus");
+    jclass statusClass = lookup.findClass("com/zerotier/sdk/VirtualNetworkStatus");
     if(statusClass == NULL)
     if(statusClass == NULL)
     {
     {
         return NULL; // exception thrown
         return NULL; // exception thrown
@@ -97,7 +97,7 @@ jobject createVirtualNetworkStatus(JNIEnv *env, ZT_VirtualNetworkStatus status)
         break;
         break;
     }
     }
 
 
-    jfieldID enumField = cache.findStaticField(statusClass, fieldName.c_str(), "Lcom/zerotier/sdk/VirtualNetworkStatus;");
+    jfieldID enumField = lookup.findStaticField(statusClass, fieldName.c_str(), "Lcom/zerotier/sdk/VirtualNetworkStatus;");
 
 
     statusObject = env->GetStaticObjectField(statusClass, enumField);
     statusObject = env->GetStaticObjectField(statusClass, enumField);
 
 
@@ -109,7 +109,7 @@ jobject createEvent(JNIEnv *env, ZT_Event event)
     jclass eventClass = NULL;
     jclass eventClass = NULL;
     jobject eventObject = NULL;
     jobject eventObject = NULL;
 
 
-    eventClass = cache.findClass("com/zerotier/sdk/Event");
+    eventClass = lookup.findClass("com/zerotier/sdk/Event");
     if(eventClass == NULL)
     if(eventClass == NULL)
     {
     {
         return NULL;
         return NULL;
@@ -133,21 +133,12 @@ jobject createEvent(JNIEnv *env, ZT_Event event)
     case ZT_EVENT_FATAL_ERROR_IDENTITY_COLLISION:
     case ZT_EVENT_FATAL_ERROR_IDENTITY_COLLISION:
         fieldName = "EVENT_FATAL_ERROR_IDENTITY_COLLISION";
         fieldName = "EVENT_FATAL_ERROR_IDENTITY_COLLISION";
         break;
         break;
-    case ZT_EVENT_SAW_MORE_RECENT_VERSION:
-        fieldName = "EVENT_SAW_MORE_RECENT_VERSION";
-        break;
-    case ZT_EVENT_AUTHENTICATION_FAILURE:
-        fieldName = "EVENT_AUTHENTICATION_FAILURE";
-        break;
-    case ZT_EVENT_INVALID_PACKET:
-        fieldName = "EVENT_INVALID_PACKET";
-        break;
     case ZT_EVENT_TRACE:
     case ZT_EVENT_TRACE:
         fieldName = "EVENT_TRACE";
         fieldName = "EVENT_TRACE";
         break;
         break;
     }
     }
 
 
-    jfieldID enumField = cache.findStaticField(eventClass, fieldName.c_str(), "Lcom/zerotier/sdk/Event;");
+    jfieldID enumField = lookup.findStaticField(eventClass, fieldName.c_str(), "Lcom/zerotier/sdk/Event;");
 
 
     eventObject = env->GetStaticObjectField(eventClass, enumField);
     eventObject = env->GetStaticObjectField(eventClass, enumField);
 
 
@@ -159,7 +150,7 @@ jobject createPeerRole(JNIEnv *env, ZT_PeerRole role)
     jclass peerRoleClass = NULL;
     jclass peerRoleClass = NULL;
     jobject peerRoleObject = NULL;
     jobject peerRoleObject = NULL;
 
 
-    peerRoleClass = cache.findClass("com/zerotier/sdk/PeerRole");
+    peerRoleClass = lookup.findClass("com/zerotier/sdk/PeerRole");
     if(peerRoleClass == NULL)
     if(peerRoleClass == NULL)
     {
     {
         return NULL;
         return NULL;
@@ -171,15 +162,15 @@ jobject createPeerRole(JNIEnv *env, ZT_PeerRole role)
     case ZT_PEER_ROLE_LEAF:
     case ZT_PEER_ROLE_LEAF:
         fieldName = "PEER_ROLE_LEAF";
         fieldName = "PEER_ROLE_LEAF";
         break;
         break;
-    case ZT_PEER_ROLE_HUB:
-        fieldName = "PEER_ROLE_HUB";
+    case ZT_PEER_ROLE_RELAY:
+        fieldName = "PEER_ROLE_RELAY";
         break;
         break;
-    case ZT_PEER_ROLE_ROOTSERVER:
-        fieldName = "PEER_ROLE_ROOTSERVER";
+    case ZT_PEER_ROLE_ROOT:
+        fieldName = "PEER_ROLE_ROOTS";
         break;
         break;
     }
     }
 
 
-    jfieldID enumField = cache.findStaticField(peerRoleClass, fieldName.c_str(), "Lcom/zerotier/sdk/PeerRole;");
+    jfieldID enumField = lookup.findStaticField(peerRoleClass, fieldName.c_str(), "Lcom/zerotier/sdk/PeerRole;");
 
 
     peerRoleObject = env->GetStaticObjectField(peerRoleClass, enumField);
     peerRoleObject = env->GetStaticObjectField(peerRoleClass, enumField);
 
 
@@ -191,7 +182,7 @@ jobject createVirtualNetworkType(JNIEnv *env, ZT_VirtualNetworkType type)
     jclass vntypeClass = NULL;
     jclass vntypeClass = NULL;
     jobject vntypeObject = NULL;
     jobject vntypeObject = NULL;
 
 
-    vntypeClass = cache.findClass("com/zerotier/sdk/VirtualNetworkType");
+    vntypeClass = lookup.findClass("com/zerotier/sdk/VirtualNetworkType");
     if(env->ExceptionCheck() || vntypeClass == NULL)
     if(env->ExceptionCheck() || vntypeClass == NULL)
     {
     {
         return NULL;
         return NULL;
@@ -208,7 +199,7 @@ jobject createVirtualNetworkType(JNIEnv *env, ZT_VirtualNetworkType type)
         break;
         break;
     }
     }
 
 
-    jfieldID enumField = cache.findStaticField(vntypeClass, fieldName.c_str(), "Lcom/zerotier/sdk/VirtualNetworkType;");
+    jfieldID enumField = lookup.findStaticField(vntypeClass, fieldName.c_str(), "Lcom/zerotier/sdk/VirtualNetworkType;");
     vntypeObject = env->GetStaticObjectField(vntypeClass, enumField);
     vntypeObject = env->GetStaticObjectField(vntypeClass, enumField);
     return vntypeObject;
     return vntypeObject;
 }
 }
@@ -218,7 +209,7 @@ jobject createVirtualNetworkConfigOperation(JNIEnv *env, ZT_VirtualNetworkConfig
     jclass vnetConfigOpClass = NULL;
     jclass vnetConfigOpClass = NULL;
     jobject vnetConfigOpObject = NULL;
     jobject vnetConfigOpObject = NULL;
 
 
-    vnetConfigOpClass = cache.findClass("com/zerotier/sdk/VirtualNetworkConfigOperation");
+    vnetConfigOpClass = lookup.findClass("com/zerotier/sdk/VirtualNetworkConfigOperation");
     if(env->ExceptionCheck() || vnetConfigOpClass == NULL)
     if(env->ExceptionCheck() || vnetConfigOpClass == NULL)
     {
     {
         return NULL;
         return NULL;
@@ -241,7 +232,7 @@ jobject createVirtualNetworkConfigOperation(JNIEnv *env, ZT_VirtualNetworkConfig
         break;
         break;
     }
     }
 
 
-    jfieldID enumField = cache.findStaticField(vnetConfigOpClass, fieldName.c_str(), "Lcom/zerotier/sdk/VirtualNetworkConfigOperation;");
+    jfieldID enumField = lookup.findStaticField(vnetConfigOpClass, fieldName.c_str(), "Lcom/zerotier/sdk/VirtualNetworkConfigOperation;");
     vnetConfigOpObject = env->GetStaticObjectField(vnetConfigOpClass, enumField);
     vnetConfigOpObject = env->GetStaticObjectField(vnetConfigOpClass, enumField);
     return vnetConfigOpObject;
     return vnetConfigOpObject;
 }
 }
@@ -252,14 +243,14 @@ jobject newInetAddress(JNIEnv *env, const sockaddr_storage &addr)
     jclass inetAddressClass = NULL;
     jclass inetAddressClass = NULL;
     jmethodID inetAddress_getByAddress = NULL;
     jmethodID inetAddress_getByAddress = NULL;
 
 
-    inetAddressClass = cache.findClass("java/net/InetAddress");
+    inetAddressClass = lookup.findClass("java/net/InetAddress");
     if(env->ExceptionCheck() || inetAddressClass == NULL)
     if(env->ExceptionCheck() || inetAddressClass == NULL)
     {
     {
         LOGE("Error finding InetAddress class");
         LOGE("Error finding InetAddress class");
         return NULL;
         return NULL;
     }
     }
 
 
-    inetAddress_getByAddress = cache.findStaticMethod(
+    inetAddress_getByAddress = lookup.findStaticMethod(
         inetAddressClass, "getByAddress", "([B)Ljava/net/InetAddress;");
         inetAddressClass, "getByAddress", "([B)Ljava/net/InetAddress;");
     if(env->ExceptionCheck() || inetAddress_getByAddress == NULL)
     if(env->ExceptionCheck() || inetAddress_getByAddress == NULL)
     {
     {
@@ -315,7 +306,7 @@ jobject newInetSocketAddress(JNIEnv *env, const sockaddr_storage &addr)
     jclass inetSocketAddressClass = NULL;
     jclass inetSocketAddressClass = NULL;
     jmethodID inetSocketAddress_constructor = NULL;
     jmethodID inetSocketAddress_constructor = NULL;
 
 
-    inetSocketAddressClass = cache.findClass("java/net/InetSocketAddress");
+    inetSocketAddressClass = lookup.findClass("java/net/InetSocketAddress");
     if(env->ExceptionCheck() || inetSocketAddressClass == NULL)
     if(env->ExceptionCheck() || inetSocketAddressClass == NULL)
     {
     {
         LOGE("Error finding InetSocketAddress Class");
         LOGE("Error finding InetSocketAddress Class");
@@ -330,7 +321,7 @@ jobject newInetSocketAddress(JNIEnv *env, const sockaddr_storage &addr)
         return NULL;
         return NULL;
     }
     }
 
 
-    inetSocketAddress_constructor = cache.findMethod(
+    inetSocketAddress_constructor = lookup.findMethod(
         inetSocketAddressClass, "<init>", "(Ljava/net/InetAddress;I)V");
         inetSocketAddressClass, "<init>", "(Ljava/net/InetAddress;I)V");
     if(env->ExceptionCheck() || inetSocketAddress_constructor == NULL)
     if(env->ExceptionCheck() || inetSocketAddress_constructor == NULL)
     {
     {
@@ -343,18 +334,18 @@ jobject newInetSocketAddress(JNIEnv *env, const sockaddr_storage &addr)
     {
     {
         case AF_INET6:
         case AF_INET6:
         {
         {
-            LOGD("IPV6 Address");
+            LOGV("IPV6 Address");
             sockaddr_in6 *ipv6 = (sockaddr_in6*)&addr;
             sockaddr_in6 *ipv6 = (sockaddr_in6*)&addr;
             port = ntohs(ipv6->sin6_port);
             port = ntohs(ipv6->sin6_port);
-            LOGD("Port %d", port);
+            LOGV("Port %d", port);
         }
         }
         break;
         break;
         case AF_INET:
         case AF_INET:
         {
         {
-            LOGD("IPV4 Address");
+            LOGV("IPV4 Address");
             sockaddr_in *ipv4 = (sockaddr_in*)&addr;
             sockaddr_in *ipv4 = (sockaddr_in*)&addr;
             port = ntohs(ipv4->sin_port);
             port = ntohs(ipv4->sin_port);
-            LOGD("Port: %d", port);
+            LOGV("Port: %d", port);
         }
         }
         break;
         break;
         default:
         default:
@@ -380,13 +371,13 @@ jobject newMulticastGroup(JNIEnv *env, const ZT_MulticastGroup &mc)
     jfieldID macField = NULL;
     jfieldID macField = NULL;
     jfieldID adiField = NULL;
     jfieldID adiField = NULL;
 
 
-    multicastGroupClass = cache.findClass("com/zerotier/sdk/MulticastGroup");
+    multicastGroupClass = lookup.findClass("com/zerotier/sdk/MulticastGroup");
     if(env->ExceptionCheck() || multicastGroupClass == NULL)
     if(env->ExceptionCheck() || multicastGroupClass == NULL)
     {
     {
         return NULL;
         return NULL;
     }
     }
 
 
-    multicastGroup_constructor = cache.findMethod(
+    multicastGroup_constructor = lookup.findMethod(
         multicastGroupClass, "<init>", "()V");
         multicastGroupClass, "<init>", "()V");
     if(env->ExceptionCheck() || multicastGroup_constructor == NULL)
     if(env->ExceptionCheck() || multicastGroup_constructor == NULL)
     {
     {
@@ -399,13 +390,13 @@ jobject newMulticastGroup(JNIEnv *env, const ZT_MulticastGroup &mc)
         return NULL;
         return NULL;
     }
     }
 
 
-    macField = cache.findField(multicastGroupClass, "mac", "J");
+    macField = lookup.findField(multicastGroupClass, "mac", "J");
     if(env->ExceptionCheck() || macField == NULL)
     if(env->ExceptionCheck() || macField == NULL)
     {
     {
         return NULL;
         return NULL;
     }
     }
 
 
-    adiField = cache.findField(multicastGroupClass, "adi", "J");
+    adiField = lookup.findField(multicastGroupClass, "adi", "J");
     if(env->ExceptionCheck() || adiField == NULL)
     if(env->ExceptionCheck() || adiField == NULL)
     {
     {
         return NULL;
         return NULL;
@@ -425,62 +416,54 @@ jobject newPeerPhysicalPath(JNIEnv *env, const ZT_PeerPhysicalPath &ppp)
     jfieldID addressField = NULL;
     jfieldID addressField = NULL;
     jfieldID lastSendField = NULL;
     jfieldID lastSendField = NULL;
     jfieldID lastReceiveField = NULL;
     jfieldID lastReceiveField = NULL;
-    jfieldID fixedField = NULL;
     jfieldID activeField = NULL;
     jfieldID activeField = NULL;
     jfieldID preferredField = NULL;
     jfieldID preferredField = NULL;
 
 
     jmethodID ppp_constructor = NULL;
     jmethodID ppp_constructor = NULL;
 
 
-    pppClass = cache.findClass("com/zerotier/sdk/PeerPhysicalPath");
+    pppClass = lookup.findClass("com/zerotier/sdk/PeerPhysicalPath");
     if(env->ExceptionCheck() || pppClass == NULL)
     if(env->ExceptionCheck() || pppClass == NULL)
     {
     {
         LOGE("Error finding PeerPhysicalPath class");
         LOGE("Error finding PeerPhysicalPath class");
         return NULL;
         return NULL;
     }
     }
 
 
-    addressField = cache.findField(pppClass, "address", "Ljava/net/InetSocketAddress;");
+    addressField = lookup.findField(pppClass, "address", "Ljava/net/InetSocketAddress;");
     if(env->ExceptionCheck() || addressField == NULL)
     if(env->ExceptionCheck() || addressField == NULL)
     {
     {
         LOGE("Error finding address field");
         LOGE("Error finding address field");
         return NULL;
         return NULL;
     }
     }
 
 
-    lastSendField = cache.findField(pppClass, "lastSend", "J");
+    lastSendField = lookup.findField(pppClass, "lastSend", "J");
     if(env->ExceptionCheck() || lastSendField == NULL)
     if(env->ExceptionCheck() || lastSendField == NULL)
     {
     {
         LOGE("Error finding lastSend field");
         LOGE("Error finding lastSend field");
         return NULL;
         return NULL;
     }
     }
 
 
-    lastReceiveField = cache.findField(pppClass, "lastReceive", "J");
+    lastReceiveField = lookup.findField(pppClass, "lastReceive", "J");
     if(env->ExceptionCheck() || lastReceiveField == NULL)
     if(env->ExceptionCheck() || lastReceiveField == NULL)
     {
     {
         LOGE("Error finding lastReceive field");
         LOGE("Error finding lastReceive field");
         return NULL;
         return NULL;
     }
     }
 
 
-    fixedField = cache.findField(pppClass, "fixed", "Z");
-    if(env->ExceptionCheck() || fixedField == NULL)
-    {
-        LOGE("Error finding fixed field");
-        return NULL;
-    }
-
-    activeField = cache.findField(pppClass, "active", "Z");
+    activeField = lookup.findField(pppClass, "active", "Z");
     if(env->ExceptionCheck() || activeField == NULL)
     if(env->ExceptionCheck() || activeField == NULL)
     {
     {
         LOGE("Error finding active field");
         LOGE("Error finding active field");
         return NULL;
         return NULL;
     }
     }
 
 
-    preferredField = cache.findField(pppClass, "preferred", "Z");
+    preferredField = lookup.findField(pppClass, "preferred", "Z");
     if(env->ExceptionCheck() || preferredField == NULL)
     if(env->ExceptionCheck() || preferredField == NULL)
     {
     {
         LOGE("Error finding preferred field");
         LOGE("Error finding preferred field");
         return NULL;
         return NULL;
     }
     }
 
 
-    ppp_constructor = cache.findMethod(pppClass, "<init>", "()V");
+    ppp_constructor = lookup.findMethod(pppClass, "<init>", "()V");
     if(env->ExceptionCheck() || ppp_constructor == NULL)
     if(env->ExceptionCheck() || ppp_constructor == NULL)
     {
     {
         LOGE("Error finding PeerPhysicalPath constructor");
         LOGE("Error finding PeerPhysicalPath constructor");
@@ -503,7 +486,6 @@ jobject newPeerPhysicalPath(JNIEnv *env, const ZT_PeerPhysicalPath &ppp)
     env->SetObjectField(pppObject, addressField, addressObject);
     env->SetObjectField(pppObject, addressField, addressObject);
     env->SetLongField(pppObject, lastSendField, ppp.lastSend);
     env->SetLongField(pppObject, lastSendField, ppp.lastSend);
     env->SetLongField(pppObject, lastReceiveField, ppp.lastReceive);
     env->SetLongField(pppObject, lastReceiveField, ppp.lastReceive);
-    env->SetBooleanField(pppObject, fixedField, ppp.fixed);
     env->SetBooleanField(pppObject, activeField, ppp.active);
     env->SetBooleanField(pppObject, activeField, ppp.active);
     env->SetBooleanField(pppObject, preferredField, ppp.preferred);
     env->SetBooleanField(pppObject, preferredField, ppp.preferred);
 
 
@@ -532,77 +514,77 @@ jobject newPeer(JNIEnv *env, const ZT_Peer &peer)
 
 
     jmethodID peer_constructor = NULL;
     jmethodID peer_constructor = NULL;
 
 
-    peerClass = cache.findClass("com/zerotier/sdk/Peer");
+    peerClass = lookup.findClass("com/zerotier/sdk/Peer");
     if(env->ExceptionCheck() || peerClass == NULL)
     if(env->ExceptionCheck() || peerClass == NULL)
     {
     {
         LOGE("Error finding Peer class");
         LOGE("Error finding Peer class");
         return NULL;
         return NULL;
     }
     }
 
 
-    addressField = cache.findField(peerClass, "address", "J");
+    addressField = lookup.findField(peerClass, "address", "J");
     if(env->ExceptionCheck() || addressField == NULL)
     if(env->ExceptionCheck() || addressField == NULL)
     {
     {
         LOGE("Error finding address field of Peer object");
         LOGE("Error finding address field of Peer object");
         return NULL;
         return NULL;
     }
     }
 
 
-    lastUnicastFrameField = cache.findField(peerClass, "lastUnicastFrame", "J");
+    lastUnicastFrameField = lookup.findField(peerClass, "lastUnicastFrame", "J");
     if(env->ExceptionCheck() || lastUnicastFrameField == NULL)
     if(env->ExceptionCheck() || lastUnicastFrameField == NULL)
     {
     {
         LOGE("Error finding lastUnicastFrame field of Peer object");
         LOGE("Error finding lastUnicastFrame field of Peer object");
         return NULL;
         return NULL;
     }
     }
 
 
-    lastMulticastFrameField = cache.findField(peerClass, "lastMulticastFrame", "J");
+    lastMulticastFrameField = lookup.findField(peerClass, "lastMulticastFrame", "J");
     if(env->ExceptionCheck() || lastMulticastFrameField == NULL)
     if(env->ExceptionCheck() || lastMulticastFrameField == NULL)
     {
     {
         LOGE("Error finding lastMulticastFrame field of Peer object");
         LOGE("Error finding lastMulticastFrame field of Peer object");
         return NULL;
         return NULL;
     }
     }
 
 
-    versionMajorField = cache.findField(peerClass, "versionMajor", "I");
+    versionMajorField = lookup.findField(peerClass, "versionMajor", "I");
     if(env->ExceptionCheck() || versionMajorField == NULL)
     if(env->ExceptionCheck() || versionMajorField == NULL)
     {
     {
         LOGE("Error finding versionMajor field of Peer object");
         LOGE("Error finding versionMajor field of Peer object");
         return NULL;
         return NULL;
     }
     }
 
 
-    versionMinorField = cache.findField(peerClass, "versionMinor", "I");
+    versionMinorField = lookup.findField(peerClass, "versionMinor", "I");
     if(env->ExceptionCheck() || versionMinorField == NULL)
     if(env->ExceptionCheck() || versionMinorField == NULL)
     {
     {
         LOGE("Error finding versionMinor field of Peer object");
         LOGE("Error finding versionMinor field of Peer object");
         return NULL;
         return NULL;
     }
     }
 
 
-    versionRevField = cache.findField(peerClass, "versionRev", "I");
+    versionRevField = lookup.findField(peerClass, "versionRev", "I");
     if(env->ExceptionCheck() || versionRevField == NULL)
     if(env->ExceptionCheck() || versionRevField == NULL)
     {
     {
         LOGE("Error finding versionRev field of Peer object");
         LOGE("Error finding versionRev field of Peer object");
         return NULL;
         return NULL;
     }
     }
 
 
-    latencyField = cache.findField(peerClass, "latency", "I");
+    latencyField = lookup.findField(peerClass, "latency", "I");
     if(env->ExceptionCheck() || latencyField == NULL)
     if(env->ExceptionCheck() || latencyField == NULL)
     {
     {
         LOGE("Error finding latency field of Peer object");
         LOGE("Error finding latency field of Peer object");
         return NULL;
         return NULL;
     }
     }
 
 
-    roleField = cache.findField(peerClass, "role", "Lcom/zerotier/sdk/PeerRole;");
+    roleField = lookup.findField(peerClass, "role", "Lcom/zerotier/sdk/PeerRole;");
     if(env->ExceptionCheck() || roleField == NULL)
     if(env->ExceptionCheck() || roleField == NULL)
     {
     {
         LOGE("Error finding role field of Peer object");
         LOGE("Error finding role field of Peer object");
         return NULL;
         return NULL;
     }
     }
 
 
-    pathsField = cache.findField(peerClass, "paths", "[Lcom/zerotier/sdk/PeerPhysicalPath;");
+    pathsField = lookup.findField(peerClass, "paths", "[Lcom/zerotier/sdk/PeerPhysicalPath;");
     if(env->ExceptionCheck() || pathsField == NULL)
     if(env->ExceptionCheck() || pathsField == NULL)
     {
     {
         LOGE("Error finding paths field of Peer object");
         LOGE("Error finding paths field of Peer object");
         return NULL;
         return NULL;
     }
     }
 
 
-    peer_constructor = cache.findMethod(peerClass, "<init>", "()V");
+    peer_constructor = lookup.findMethod(peerClass, "<init>", "()V");
     if(env->ExceptionCheck() || peer_constructor == NULL)
     if(env->ExceptionCheck() || peer_constructor == NULL)
     {
     {
         LOGE("Error finding Peer constructor");
         LOGE("Error finding Peer constructor");
@@ -625,7 +607,7 @@ jobject newPeer(JNIEnv *env, const ZT_Peer &peer)
     env->SetIntField(peerObject, latencyField, peer.latency);
     env->SetIntField(peerObject, latencyField, peer.latency);
     env->SetObjectField(peerObject, roleField, createPeerRole(env, peer.role));
     env->SetObjectField(peerObject, roleField, createPeerRole(env, peer.role));
 
 
-    jclass peerPhysicalPathClass = cache.findClass("com/zerotier/sdk/PeerPhysicalPath");
+    jclass peerPhysicalPathClass = lookup.findClass("com/zerotier/sdk/PeerPhysicalPath");
     if(env->ExceptionCheck() || peerPhysicalPathClass == NULL)
     if(env->ExceptionCheck() || peerPhysicalPathClass == NULL)
     {
     {
         LOGE("Error finding PeerPhysicalPath class");
         LOGE("Error finding PeerPhysicalPath class");
@@ -675,14 +657,14 @@ jobject newNetworkConfig(JNIEnv *env, const ZT_VirtualNetworkConfig &vnetConfig)
     jfieldID multicastSubscriptionsField = NULL;
     jfieldID multicastSubscriptionsField = NULL;
     jfieldID assignedAddressesField = NULL;
     jfieldID assignedAddressesField = NULL;
 
 
-    vnetConfigClass = cache.findClass("com/zerotier/sdk/VirtualNetworkConfig");
+    vnetConfigClass = lookup.findClass("com/zerotier/sdk/VirtualNetworkConfig");
     if(vnetConfigClass == NULL)
     if(vnetConfigClass == NULL)
     {
     {
         LOGE("Couldn't find com.zerotier.sdk.VirtualNetworkConfig");
         LOGE("Couldn't find com.zerotier.sdk.VirtualNetworkConfig");
         return NULL;
         return NULL;
     }
     }
 
 
-    vnetConfig_constructor = cache.findMethod(
+    vnetConfig_constructor = lookup.findMethod(
         vnetConfigClass, "<init>", "()V");
         vnetConfigClass, "<init>", "()V");
     if(env->ExceptionCheck() || vnetConfig_constructor == NULL)
     if(env->ExceptionCheck() || vnetConfig_constructor == NULL)
     {
     {
@@ -697,98 +679,98 @@ jobject newNetworkConfig(JNIEnv *env, const ZT_VirtualNetworkConfig &vnetConfig)
         return NULL;
         return NULL;
     }
     }
 
 
-    nwidField = cache.findField(vnetConfigClass, "nwid", "J");
+    nwidField = lookup.findField(vnetConfigClass, "nwid", "J");
     if(env->ExceptionCheck() || nwidField == NULL)
     if(env->ExceptionCheck() || nwidField == NULL)
     {
     {
         LOGE("Error getting nwid field");
         LOGE("Error getting nwid field");
         return NULL;
         return NULL;
     }
     }
 
 
-    macField = cache.findField(vnetConfigClass, "mac", "J");
+    macField = lookup.findField(vnetConfigClass, "mac", "J");
     if(env->ExceptionCheck() || macField == NULL)
     if(env->ExceptionCheck() || macField == NULL)
     {
     {
         LOGE("Error getting mac field");
         LOGE("Error getting mac field");
         return NULL;
         return NULL;
     }
     }
 
 
-    nameField = cache.findField(vnetConfigClass, "name", "Ljava/lang/String;");
+    nameField = lookup.findField(vnetConfigClass, "name", "Ljava/lang/String;");
     if(env->ExceptionCheck() || nameField == NULL)
     if(env->ExceptionCheck() || nameField == NULL)
     {
     {
         LOGE("Error getting name field");
         LOGE("Error getting name field");
         return NULL;
         return NULL;
     }
     }
 
 
-    statusField = cache.findField(vnetConfigClass, "status", "Lcom/zerotier/sdk/VirtualNetworkStatus;");
+    statusField = lookup.findField(vnetConfigClass, "status", "Lcom/zerotier/sdk/VirtualNetworkStatus;");
     if(env->ExceptionCheck() || statusField == NULL)
     if(env->ExceptionCheck() || statusField == NULL)
     {
     {
         LOGE("Error getting status field");
         LOGE("Error getting status field");
         return NULL;
         return NULL;
     }
     }
 
 
-    typeField = cache.findField(vnetConfigClass, "type", "Lcom/zerotier/sdk/VirtualNetworkType;");
+    typeField = lookup.findField(vnetConfigClass, "type", "Lcom/zerotier/sdk/VirtualNetworkType;");
     if(env->ExceptionCheck() || typeField == NULL)
     if(env->ExceptionCheck() || typeField == NULL)
     {
     {
         LOGE("Error getting type field");
         LOGE("Error getting type field");
         return NULL;
         return NULL;
     }
     }
 
 
-    mtuField = cache.findField(vnetConfigClass, "mtu", "I");
+    mtuField = lookup.findField(vnetConfigClass, "mtu", "I");
     if(env->ExceptionCheck() || mtuField == NULL)
     if(env->ExceptionCheck() || mtuField == NULL)
     {
     {
         LOGE("Error getting mtu field");
         LOGE("Error getting mtu field");
         return NULL;
         return NULL;
     }
     }
 
 
-    dhcpField = cache.findField(vnetConfigClass, "dhcp", "Z");
+    dhcpField = lookup.findField(vnetConfigClass, "dhcp", "Z");
     if(env->ExceptionCheck() || dhcpField == NULL)
     if(env->ExceptionCheck() || dhcpField == NULL)
     {
     {
         LOGE("Error getting dhcp field");
         LOGE("Error getting dhcp field");
         return NULL;
         return NULL;
     }
     }
 
 
-    bridgeField = cache.findField(vnetConfigClass, "bridge", "Z");
+    bridgeField = lookup.findField(vnetConfigClass, "bridge", "Z");
     if(env->ExceptionCheck() || bridgeField == NULL)
     if(env->ExceptionCheck() || bridgeField == NULL)
     {
     {
         LOGE("Error getting bridge field");
         LOGE("Error getting bridge field");
         return NULL;
         return NULL;
     }
     }
 
 
-    broadcastEnabledField = cache.findField(vnetConfigClass, "broadcastEnabled", "Z");
+    broadcastEnabledField = lookup.findField(vnetConfigClass, "broadcastEnabled", "Z");
     if(env->ExceptionCheck() || broadcastEnabledField == NULL)
     if(env->ExceptionCheck() || broadcastEnabledField == NULL)
     {
     {
         LOGE("Error getting broadcastEnabled field");
         LOGE("Error getting broadcastEnabled field");
         return NULL;
         return NULL;
     }
     }
 
 
-    portErrorField = cache.findField(vnetConfigClass, "portError", "I");
+    portErrorField = lookup.findField(vnetConfigClass, "portError", "I");
     if(env->ExceptionCheck() || portErrorField == NULL)
     if(env->ExceptionCheck() || portErrorField == NULL)
     {
     {
         LOGE("Error getting portError field");
         LOGE("Error getting portError field");
         return NULL;
         return NULL;
     }
     }
 
 
-    enabledField = cache.findField(vnetConfigClass, "enabled", "Z");
+    enabledField = lookup.findField(vnetConfigClass, "enabled", "Z");
     if(env->ExceptionCheck() || enabledField == NULL)
     if(env->ExceptionCheck() || enabledField == NULL)
     {
     {
         LOGE("Error getting enabled field");
         LOGE("Error getting enabled field");
         return NULL;
         return NULL;
     }
     }
 
 
-    netconfRevisionField = cache.findField(vnetConfigClass, "netconfRevision", "J");
+    netconfRevisionField = lookup.findField(vnetConfigClass, "netconfRevision", "J");
     if(env->ExceptionCheck() || netconfRevisionField == NULL)
     if(env->ExceptionCheck() || netconfRevisionField == NULL)
     {
     {
         LOGE("Error getting netconfRevision field");
         LOGE("Error getting netconfRevision field");
         return NULL;
         return NULL;
     }
     }
 
 
-    multicastSubscriptionsField = cache.findField(vnetConfigClass, "multicastSubscriptions", "[Lcom/zerotier/sdk/MulticastGroup;");
+    multicastSubscriptionsField = lookup.findField(vnetConfigClass, "multicastSubscriptions", "[Lcom/zerotier/sdk/MulticastGroup;");
     if(env->ExceptionCheck() || multicastSubscriptionsField == NULL)
     if(env->ExceptionCheck() || multicastSubscriptionsField == NULL)
     {
     {
         LOGE("Error getting multicastSubscriptions field");
         LOGE("Error getting multicastSubscriptions field");
         return NULL;
         return NULL;
     }
     }
 
 
-    assignedAddressesField = cache.findField(vnetConfigClass, "assignedAddresses", "[Ljava/net/InetSocketAddress;");
+    assignedAddressesField = lookup.findField(vnetConfigClass, "assignedAddresses", "[Ljava/net/InetSocketAddress;");
     if(env->ExceptionCheck() || assignedAddressesField == NULL)
     if(env->ExceptionCheck() || assignedAddressesField == NULL)
     {
     {
         LOGE("Error getting assignedAddresses field");
         LOGE("Error getting assignedAddresses field");
@@ -818,13 +800,14 @@ jobject newNetworkConfig(JNIEnv *env, const ZT_VirtualNetworkConfig &vnetConfig)
     }
     }
     env->SetObjectField(vnetConfigObj, typeField, typeObject);
     env->SetObjectField(vnetConfigObj, typeField, typeObject);
 
 
-    env->SetIntField(vnetConfigObj, mtuField, vnetConfig.mtu);
+    env->SetIntField(vnetConfigObj, mtuField, (int)vnetConfig.mtu);
     env->SetBooleanField(vnetConfigObj, dhcpField, vnetConfig.dhcp);
     env->SetBooleanField(vnetConfigObj, dhcpField, vnetConfig.dhcp);
     env->SetBooleanField(vnetConfigObj, bridgeField, vnetConfig.bridge);
     env->SetBooleanField(vnetConfigObj, bridgeField, vnetConfig.bridge);
     env->SetBooleanField(vnetConfigObj, broadcastEnabledField, vnetConfig.broadcastEnabled);
     env->SetBooleanField(vnetConfigObj, broadcastEnabledField, vnetConfig.broadcastEnabled);
+    env->SetBooleanField(vnetConfigObj, enabledField, vnetConfig.enabled);
     env->SetIntField(vnetConfigObj, portErrorField, vnetConfig.portError);
     env->SetIntField(vnetConfigObj, portErrorField, vnetConfig.portError);
 
 
-    jclass multicastGroupClass = cache.findClass("com/zerotier/sdk/MulticastGroup");
+    jclass multicastGroupClass = lookup.findClass("com/zerotier/sdk/MulticastGroup");
     if(env->ExceptionCheck() || multicastGroupClass == NULL) 
     if(env->ExceptionCheck() || multicastGroupClass == NULL) 
     {
     {
         LOGE("Error finding MulticastGroup class");
         LOGE("Error finding MulticastGroup class");
@@ -849,7 +832,7 @@ jobject newNetworkConfig(JNIEnv *env, const ZT_VirtualNetworkConfig &vnetConfig)
     }
     }
     env->SetObjectField(vnetConfigObj, multicastSubscriptionsField, mcastSubsArrayObj);
     env->SetObjectField(vnetConfigObj, multicastSubscriptionsField, mcastSubsArrayObj);
 
 
-    jclass inetSocketAddressClass = cache.findClass("java/net/InetSocketAddress");
+    jclass inetSocketAddressClass = lookup.findClass("java/net/InetSocketAddress");
     if(env->ExceptionCheck() || inetSocketAddressClass == NULL)
     if(env->ExceptionCheck() || inetSocketAddressClass == NULL)
     {
     {
         LOGE("Error finding InetSocketAddress class");
         LOGE("Error finding InetSocketAddress class");
@@ -886,13 +869,13 @@ jobject newVersion(JNIEnv *env, int major, int minor, int rev, long featureFlags
     jclass versionClass = NULL;
     jclass versionClass = NULL;
     jmethodID versionConstructor = NULL;
     jmethodID versionConstructor = NULL;
 
 
-    versionClass = cache.findClass("com/zerotier/sdk/Version");
+    versionClass = lookup.findClass("com/zerotier/sdk/Version");
     if(env->ExceptionCheck() || versionClass == NULL)
     if(env->ExceptionCheck() || versionClass == NULL)
     {
     {
         return NULL;
         return NULL;
     }
     }
 
 
-    versionConstructor = cache.findMethod(
+    versionConstructor = lookup.findMethod(
         versionClass, "<init>", "()V");
         versionClass, "<init>", "()V");
     if(env->ExceptionCheck() || versionConstructor == NULL)
     if(env->ExceptionCheck() || versionConstructor == NULL)
     {
     {
@@ -911,25 +894,25 @@ jobject newVersion(JNIEnv *env, int major, int minor, int rev, long featureFlags
     jfieldID revisionField = NULL;
     jfieldID revisionField = NULL;
     jfieldID featureFlagsField = NULL;
     jfieldID featureFlagsField = NULL;
 
 
-    majorField = cache.findField(versionClass, "major", "I");
+    majorField = lookup.findField(versionClass, "major", "I");
     if(env->ExceptionCheck() || majorField == NULL)
     if(env->ExceptionCheck() || majorField == NULL)
     {
     {
         return NULL;
         return NULL;
     }
     }
 
 
-    minorField = cache.findField(versionClass, "minor", "I");
+    minorField = lookup.findField(versionClass, "minor", "I");
     if(env->ExceptionCheck() || minorField == NULL)
     if(env->ExceptionCheck() || minorField == NULL)
     {
     {
         return NULL;
         return NULL;
     }
     }
 
 
-    revisionField = cache.findField(versionClass, "revision", "I");
+    revisionField = lookup.findField(versionClass, "revision", "I");
     if(env->ExceptionCheck() || revisionField == NULL)
     if(env->ExceptionCheck() || revisionField == NULL)
     {
     {
         return NULL;
         return NULL;
     }
     }
 
 
-    featureFlagsField = cache.findField(versionClass, "featureFlags", "J");
+    featureFlagsField = lookup.findField(versionClass, "featureFlags", "J");
     if(env->ExceptionCheck() || featureFlagsField == NULL)
     if(env->ExceptionCheck() || featureFlagsField == NULL)
     {
     {
         return NULL;
         return NULL;

+ 0 - 0
java/jni/ZT1_jniutils.h → java/jni/ZT_jniutils.h


+ 206 - 127
java/jni/com_zerotierone_sdk_Node.cpp

@@ -27,17 +27,18 @@
 
 
 #include "com_zerotierone_sdk_Node.h"
 #include "com_zerotierone_sdk_Node.h"
 #include "ZT_jniutils.h"
 #include "ZT_jniutils.h"
-#include "ZT_jnicache.h"
+#include "ZT_jnilookup.h"
 
 
 #include <ZeroTierOne.h>
 #include <ZeroTierOne.h>
+#include "Mutex.hpp"
 
 
 #include <map>
 #include <map>
 #include <string>
 #include <string>
 #include <assert.h>
 #include <assert.h>
 #include <string.h>
 #include <string.h>
 
 
-// global static JNI Cache Object
-JniCache cache;
+// global static JNI Lookup Object
+JniLookup lookup;
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 extern "C" {
 extern "C" {
@@ -92,7 +93,7 @@ namespace {
         enum ZT_VirtualNetworkConfigOperation operation,
         enum ZT_VirtualNetworkConfigOperation operation,
         const ZT_VirtualNetworkConfig *config)
         const ZT_VirtualNetworkConfig *config)
     {
     {
-        LOGD("VritualNetworkConfigFunctionCallback");
+        LOGV("VritualNetworkConfigFunctionCallback");
         JniRef *ref = (JniRef*)userData;
         JniRef *ref = (JniRef*)userData;
         JNIEnv *env = NULL;
         JNIEnv *env = NULL;
         ref->jvm->GetEnv((void**)&env, JNI_VERSION_1_6);
         ref->jvm->GetEnv((void**)&env, JNI_VERSION_1_6);
@@ -104,7 +105,7 @@ namespace {
             return -1;
             return -1;
         }
         }
 
 
-        jmethodID configListenerCallbackMethod = cache.findMethod(configListenerClass,
+        jmethodID configListenerCallbackMethod = lookup.findMethod(configListenerClass,
             "onNetworkConfigurationUpdated",
             "onNetworkConfigurationUpdated",
             "(JLcom/zerotier/sdk/VirtualNetworkConfigOperation;Lcom/zerotier/sdk/VirtualNetworkConfig;)I");
             "(JLcom/zerotier/sdk/VirtualNetworkConfigOperation;Lcom/zerotier/sdk/VirtualNetworkConfig;)I");
         if(configListenerCallbackMethod == NULL)
         if(configListenerCallbackMethod == NULL)
@@ -133,7 +134,8 @@ namespace {
             (jlong)nwid, operationObject, networkConfigObject);
             (jlong)nwid, operationObject, networkConfigObject);
     }
     }
 
 
-    void VirtualNetworkFrameFunctionCallback(ZT_Node *node,void *userData,
+    void VirtualNetworkFrameFunctionCallback(ZT_Node *node,
+        void *userData,
         uint64_t nwid,
         uint64_t nwid,
         uint64_t sourceMac,
         uint64_t sourceMac,
         uint64_t destMac,
         uint64_t destMac,
@@ -142,7 +144,9 @@ namespace {
         const void *frameData,
         const void *frameData,
         unsigned int frameLength)
         unsigned int frameLength)
     {
     {
-        LOGD("VirtualNetworkFrameFunctionCallback");
+        LOGV("VirtualNetworkFrameFunctionCallback");
+        unsigned char* local = (unsigned char*)frameData;
+        LOGV("Type Bytes: 0x%02x%02x", local[12], local[13]);
         JniRef *ref = (JniRef*)userData;
         JniRef *ref = (JniRef*)userData;
         assert(ref->node == node);
         assert(ref->node == node);
         JNIEnv *env = NULL;
         JNIEnv *env = NULL;
@@ -156,7 +160,7 @@ namespace {
             return;
             return;
         }
         }
 
 
-        jmethodID frameListenerCallbackMethod = cache.findMethod(
+        jmethodID frameListenerCallbackMethod = lookup.findMethod(
             frameListenerClass,
             frameListenerClass,
             "onVirtualNetworkFrame", "(JJJJJ[B)V");
             "onVirtualNetworkFrame", "(JJJJJ[B)V");
         if(env->ExceptionCheck() || frameListenerCallbackMethod == NULL)
         if(env->ExceptionCheck() || frameListenerCallbackMethod == NULL)
@@ -172,9 +176,9 @@ namespace {
             return;
             return;
         }
         }
 
 
-        jbyte *data = env->GetByteArrayElements(dataArray, NULL);
+        void *data = env->GetPrimitiveArrayCritical(dataArray, NULL);
         memcpy(data, frameData, frameLength);
         memcpy(data, frameData, frameLength);
-        env->ReleaseByteArrayElements(dataArray, data, 0);
+        env->ReleasePrimitiveArrayCritical(dataArray, data, 0);
 
 
         if(env->ExceptionCheck())
         if(env->ExceptionCheck())
         {
         {
@@ -186,11 +190,18 @@ namespace {
     }
     }
 
 
 
 
-    void EventCallback(ZT_Node *node,void *userData,enum ZT_Event event, const void *data)
+    void EventCallback(ZT_Node *node,
+        void *userData,
+        enum ZT_Event event, 
+        const void *data)
     {
     {
-        LOGD("EventCallback");
+        LOGV("EventCallback");
         JniRef *ref = (JniRef*)userData;
         JniRef *ref = (JniRef*)userData;
-        assert(ref->node == node);
+        if(ref->node != node && event != ZT_EVENT_UP)
+        {
+            LOGE("Nodes not equal. ref->node %p, node %p. Event: %d", ref->node, node, event);
+            return;
+        }
         JNIEnv *env = NULL;
         JNIEnv *env = NULL;
         ref->jvm->GetEnv((void**)&env, JNI_VERSION_1_6);
         ref->jvm->GetEnv((void**)&env, JNI_VERSION_1_6);
 
 
@@ -202,7 +213,7 @@ namespace {
             return;
             return;
         }
         }
 
 
-        jmethodID onEventMethod = cache.findMethod(eventListenerClass,
+        jmethodID onEventMethod = lookup.findMethod(eventListenerClass,
             "onEvent", "(Lcom/zerotier/sdk/Event;)V");
             "onEvent", "(Lcom/zerotier/sdk/Event;)V");
         if(onEventMethod == NULL)
         if(onEventMethod == NULL)
         {
         {
@@ -210,26 +221,7 @@ namespace {
             return;
             return;
         }
         }
 
 
-
-        jmethodID onOutOfDateMethod = cache.findMethod(eventListenerClass,
-            "onOutOfDate", "(Lcom/zerotier/sdk/Version;)V");
-        if(onOutOfDateMethod == NULL)
-        {
-            LOGE("Couldn't find onOutOfDate method");
-            return;
-        }
-
-
-        jmethodID onNetworkErrorMethod = cache.findMethod(eventListenerClass,
-            "onNetworkError", "(Lcom/zerotier/sdk/Event;Ljava/net/InetSocketAddress;)V");
-        if(onNetworkErrorMethod == NULL)
-        {
-            LOGE("Couldn't find onNetworkError method");
-            return;
-        }
-
-
-        jmethodID onTraceMethod = cache.findMethod(eventListenerClass,
+        jmethodID onTraceMethod = lookup.findMethod(eventListenerClass,
             "onTrace", "(Ljava/lang/String;)V");
             "onTrace", "(Ljava/lang/String;)V");
         if(onTraceMethod == NULL)
         if(onTraceMethod == NULL)
         {
         {
@@ -246,39 +238,34 @@ namespace {
         switch(event)
         switch(event)
         {
         {
         case ZT_EVENT_UP:
         case ZT_EVENT_UP:
+        {
+            LOGD("Event Up");
+            env->CallVoidMethod(ref->eventListener, onEventMethod, eventObject);
+            break;
+        }
         case ZT_EVENT_OFFLINE:
         case ZT_EVENT_OFFLINE:
+        {
+            LOGD("Event Offline");
+            env->CallVoidMethod(ref->eventListener, onEventMethod, eventObject);
+            break;
+        }
         case ZT_EVENT_ONLINE:
         case ZT_EVENT_ONLINE:
-        case ZT_EVENT_DOWN:
-        case ZT_EVENT_FATAL_ERROR_IDENTITY_COLLISION:
         {
         {
-            LOGV("Regular Event");
-            // call onEvent()
+            LOGD("Event Online");
             env->CallVoidMethod(ref->eventListener, onEventMethod, eventObject);
             env->CallVoidMethod(ref->eventListener, onEventMethod, eventObject);
+            break;
         }
         }
-        break;
-        case ZT_EVENT_SAW_MORE_RECENT_VERSION:
+        case ZT_EVENT_DOWN:
         {
         {
-            LOGV("Version Event");
-            // call onOutOfDate()
-            if(data != NULL)
-            {
-                int *version = (int*)data;
-                jobject verisonObj = newVersion(env, version[0], version[1], version[2], 0);
-                env->CallVoidMethod(ref->eventListener, onOutOfDateMethod, verisonObj);
-            }
+            LOGD("Event Down");
+            env->CallVoidMethod(ref->eventListener, onEventMethod, eventObject);
+            break;
         }
         }
-        break;
-        case ZT_EVENT_AUTHENTICATION_FAILURE:
-        case ZT_EVENT_INVALID_PACKET:
+        case ZT_EVENT_FATAL_ERROR_IDENTITY_COLLISION:
         {
         {
-            LOGV("Network Error Event");
-            // call onNetworkError()
-            if(data != NULL)
-            {
-                sockaddr_storage *addr = (sockaddr_storage*)data;
-                jobject addressObj = newInetSocketAddress(env, *addr);
-                env->CallVoidMethod(ref->eventListener, onNetworkErrorMethod, addressObj);
-            }
+            LOGV("Identity Collision");
+            // call onEvent()
+            env->CallVoidMethod(ref->eventListener, onEventMethod, eventObject);
         }
         }
         break;
         break;
         case ZT_EVENT_TRACE:
         case ZT_EVENT_TRACE:
@@ -296,7 +283,8 @@ namespace {
         }
         }
     }
     }
 
 
-    long DataStoreGetFunction(ZT_Node *node,void *userData,
+    long DataStoreGetFunction(ZT_Node *node,
+        void *userData,
         const char *objectName,
         const char *objectName,
         void *buffer,
         void *buffer,
         unsigned long bufferSize,
         unsigned long bufferSize,
@@ -314,7 +302,7 @@ namespace {
             return -2;
             return -2;
         }
         }
 
 
-        jmethodID dataStoreGetCallbackMethod = cache.findMethod(
+        jmethodID dataStoreGetCallbackMethod = lookup.findMethod(
             dataStoreGetClass,
             dataStoreGetClass,
             "onDataStoreGet",
             "onDataStoreGet",
             "(Ljava/lang/String;[BJ[J)J");
             "(Ljava/lang/String;[BJ[J)J");
@@ -354,21 +342,22 @@ namespace {
 
 
         if(retval > 0)
         if(retval > 0)
         {
         {
-            jbyte *data = env->GetByteArrayElements(bufferObj, NULL);
+            void *data = env->GetPrimitiveArrayCritical(bufferObj, NULL);
             memcpy(buffer, data, retval);
             memcpy(buffer, data, retval);
-            env->ReleaseByteArrayElements(bufferObj, data, JNI_ABORT);
+            env->ReleasePrimitiveArrayCritical(bufferObj, data, 0);
 
 
-            jlong *objSize = env->GetLongArrayElements(objectSizeObj, NULL);
+            jlong *objSize = (jlong*)env->GetPrimitiveArrayCritical(objectSizeObj, NULL);
             *out_objectSize = (unsigned long)objSize[0];
             *out_objectSize = (unsigned long)objSize[0];
-            env->ReleaseLongArrayElements(objectSizeObj, objSize, JNI_ABORT);
+            env->ReleasePrimitiveArrayCritical(objectSizeObj, objSize, 0);
         }
         }
 
 
-        LOGI("Out Object Size: %lu", *out_objectSize);
+        LOGV("Out Object Size: %lu", *out_objectSize);
 
 
         return retval;
         return retval;
     }
     }
 
 
-    int DataStorePutFunction(ZT_Node *node,void *userData,
+    int DataStorePutFunction(ZT_Node *node,
+        void *userData,
         const char *objectName,
         const char *objectName,
         const void *buffer,
         const void *buffer,
         unsigned long bufferSize,
         unsigned long bufferSize,
@@ -386,7 +375,7 @@ namespace {
             return -1;
             return -1;
         }
         }
 
 
-        jmethodID dataStorePutCallbackMethod = cache.findMethod(
+        jmethodID dataStorePutCallbackMethod = lookup.findMethod(
             dataStorePutClass,
             dataStorePutClass,
             "onDataStorePut",
             "onDataStorePut",
             "(Ljava/lang/String;[BZ)I");
             "(Ljava/lang/String;[BZ)I");
@@ -396,7 +385,7 @@ namespace {
             return -2;
             return -2;
         }
         }
 
 
-        jmethodID deleteMethod = cache.findMethod(dataStorePutClass,
+        jmethodID deleteMethod = lookup.findMethod(dataStorePutClass,
             "onDelete", "(Ljava/lang/String;)I");
             "onDelete", "(Ljava/lang/String;)I");
         if(deleteMethod == NULL)
         if(deleteMethod == NULL)
         {
         {
@@ -408,30 +397,39 @@ namespace {
 
 
         if(buffer == NULL)
         if(buffer == NULL)
         {
         {
+            LOGD("JNI: Delete file: %s", objectName);
             // delete operation
             // delete operation
             return env->CallIntMethod(
             return env->CallIntMethod(
                 ref->dataStorePutListener, deleteMethod, nameStr);
                 ref->dataStorePutListener, deleteMethod, nameStr);
         }
         }
         else
         else
         {
         {
+            LOGD("JNI: Write file: %s", objectName);
             // set operation
             // set operation
             jbyteArray bufferObj = env->NewByteArray(bufferSize);
             jbyteArray bufferObj = env->NewByteArray(bufferSize);
+            if(env->ExceptionCheck() || bufferObj == NULL)
+            {
+                LOGE("Error creating byte array buffer!");
+                return -4;
+            }
+
             env->SetByteArrayRegion(bufferObj, 0, bufferSize, (jbyte*)buffer);
             env->SetByteArrayRegion(bufferObj, 0, bufferSize, (jbyte*)buffer);
             bool bsecure = secure != 0;
             bool bsecure = secure != 0;
 
 
-
             return env->CallIntMethod(ref->dataStorePutListener,
             return env->CallIntMethod(ref->dataStorePutListener,
                 dataStorePutCallbackMethod,
                 dataStorePutCallbackMethod,
                 nameStr, bufferObj, bsecure);
                 nameStr, bufferObj, bsecure);
         }
         }
     }
     }
 
 
-    int WirePacketSendFunction(ZT_Node *node,void *userData,\
-        const struct sockaddr_storage *address,
+    int WirePacketSendFunction(ZT_Node *node,
+        void *userData,
+        const struct sockaddr_storage *localAddress,
+        const struct sockaddr_storage *remoteAddress,
         const void *buffer,
         const void *buffer,
         unsigned int bufferSize)
         unsigned int bufferSize)
     {
     {
-        LOGD("WirePacketSendFunction(%p, %p, %d)", address, buffer, bufferSize);
+        LOGV("WirePacketSendFunction(%p, %p, %p, %d)", localAddress, remoteAddress, buffer, bufferSize);
         JniRef *ref = (JniRef*)userData;
         JniRef *ref = (JniRef*)userData;
         assert(ref->node == node);
         assert(ref->node == node);
 
 
@@ -446,28 +444,36 @@ namespace {
             return -1;
             return -1;
         }
         }
 
 
-        jmethodID packetSenderCallbackMethod = cache.findMethod(packetSenderClass,
-            "onSendPacketRequested", "(Ljava/net/InetSocketAddress;[B)I");
+        jmethodID packetSenderCallbackMethod = lookup.findMethod(packetSenderClass,
+            "onSendPacketRequested", "(Ljava/net/InetSocketAddress;Ljava/net/InetSocketAddress;[B)I");
         if(packetSenderCallbackMethod == NULL)
         if(packetSenderCallbackMethod == NULL)
         {
         {
             LOGE("Couldn't find onSendPacketRequested method");
             LOGE("Couldn't find onSendPacketRequested method");
             return -2;
             return -2;
         }
         }
         
         
-        jobject addressObj = newInetSocketAddress(env, *address);
+        jobject localAddressObj = NULL;
+        if(memcmp(localAddress, &ZT_SOCKADDR_NULL, sizeof(sockaddr_storage)) != 0)
+        {
+            localAddressObj = newInetSocketAddress(env, *localAddress);
+        }
+
+        jobject remoteAddressObj = newInetSocketAddress(env, *remoteAddress);
         jbyteArray bufferObj = env->NewByteArray(bufferSize);
         jbyteArray bufferObj = env->NewByteArray(bufferSize);
         env->SetByteArrayRegion(bufferObj, 0, bufferSize, (jbyte*)buffer);
         env->SetByteArrayRegion(bufferObj, 0, bufferSize, (jbyte*)buffer);
-        int retval = env->CallIntMethod(ref->packetSender, packetSenderCallbackMethod, addressObj, bufferObj);
+        int retval = env->CallIntMethod(ref->packetSender, packetSenderCallbackMethod, localAddressObj, remoteAddressObj, bufferObj);
 
 
-        LOGD("JNI Packet Sender returned: %d", retval);
+        LOGV("JNI Packet Sender returned: %d", retval);
         return retval;
         return retval;
     }
     }
 
 
     typedef std::map<uint64_t, JniRef*> NodeMap;
     typedef std::map<uint64_t, JniRef*> NodeMap;
     static NodeMap nodeMap;
     static NodeMap nodeMap;
+    ZeroTier::Mutex nodeMapMutex;
 
 
     ZT_Node* findNode(uint64_t nodeId)
     ZT_Node* findNode(uint64_t nodeId)
     {
     {
+        ZeroTier::Mutex::Lock lock(nodeMapMutex);
         NodeMap::iterator found = nodeMap.find(nodeId);
         NodeMap::iterator found = nodeMap.find(nodeId);
         if(found != nodeMap.end())
         if(found != nodeMap.end())
         {
         {
@@ -480,13 +486,13 @@ namespace {
 
 
 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) 
 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) 
 {
 {
-    cache.setJavaVM(vm);
+    lookup.setJavaVM(vm);
     return JNI_VERSION_1_6;
     return JNI_VERSION_1_6;
 }
 }
 
 
 JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved)
 JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved)
 {
 {
-    cache.clearCache();
+
 }
 }
 
 
 
 
@@ -507,7 +513,7 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_node_1init(
     env->GetJavaVM(&ref->jvm);
     env->GetJavaVM(&ref->jvm);
 
 
     jclass cls = env->GetObjectClass(obj);
     jclass cls = env->GetObjectClass(obj);
-    jfieldID fid = cache.findField(
+    jfieldID fid = lookup.findField(
         cls, "getListener", "Lcom/zerotier/sdk/DataStoreGetListener;");
         cls, "getListener", "Lcom/zerotier/sdk/DataStoreGetListener;");
 
 
     if(fid == NULL)
     if(fid == NULL)
@@ -522,7 +528,7 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_node_1init(
     }
     }
     ref->dataStoreGetListener = env->NewGlobalRef(tmp);
     ref->dataStoreGetListener = env->NewGlobalRef(tmp);
 
 
-    fid = cache.findField(
+    fid = lookup.findField(
         cls, "putListener", "Lcom/zerotier/sdk/DataStorePutListener;");
         cls, "putListener", "Lcom/zerotier/sdk/DataStorePutListener;");
 
 
     if(fid == NULL)
     if(fid == NULL)
@@ -537,7 +543,7 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_node_1init(
     }
     }
     ref->dataStorePutListener = env->NewGlobalRef(tmp);
     ref->dataStorePutListener = env->NewGlobalRef(tmp);
 
 
-    fid = cache.findField(
+    fid = lookup.findField(
         cls, "sender", "Lcom/zerotier/sdk/PacketSender;");
         cls, "sender", "Lcom/zerotier/sdk/PacketSender;");
     if(fid == NULL)
     if(fid == NULL)
     {
     {
@@ -551,7 +557,7 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_node_1init(
     }
     }
     ref->packetSender = env->NewGlobalRef(tmp);
     ref->packetSender = env->NewGlobalRef(tmp);
 
 
-    fid = cache.findField(
+    fid = lookup.findField(
         cls, "frameListener", "Lcom/zerotier/sdk/VirtualNetworkFrameListener;");
         cls, "frameListener", "Lcom/zerotier/sdk/VirtualNetworkFrameListener;");
     if(fid == NULL)
     if(fid == NULL)
     {
     {
@@ -565,7 +571,7 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_node_1init(
     }
     }
     ref->frameListener = env->NewGlobalRef(tmp);
     ref->frameListener = env->NewGlobalRef(tmp);
 
 
-    fid = cache.findField(
+    fid = lookup.findField(
         cls, "configListener", "Lcom/zerotier/sdk/VirtualNetworkConfigListener;");
         cls, "configListener", "Lcom/zerotier/sdk/VirtualNetworkConfigListener;");
     if(fid == NULL)
     if(fid == NULL)
     {
     {
@@ -579,7 +585,7 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_node_1init(
     }
     }
     ref->configListener = env->NewGlobalRef(tmp);
     ref->configListener = env->NewGlobalRef(tmp);
 
 
-    fid = cache.findField(
+    fid = lookup.findField(
         cls, "eventListener", "Lcom/zerotier/sdk/EventListener;");
         cls, "eventListener", "Lcom/zerotier/sdk/EventListener;");
     if(fid == NULL)
     if(fid == NULL)
     {
     {
@@ -618,8 +624,10 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_node_1init(
         return resultObject;
         return resultObject;
     }
     }
 
 
+    ZeroTier::Mutex::Lock lock(nodeMapMutex);
     ref->node = node;
     ref->node = node;
     nodeMap.insert(std::make_pair(ref->id, ref));
     nodeMap.insert(std::make_pair(ref->id, ref));
+    
 
 
     return resultObject;
     return resultObject;
 }
 }
@@ -635,7 +643,12 @@ JNIEXPORT void JNICALL Java_com_zerotier_sdk_Node_node_1delete(
     LOGV("Destroying ZT_Node struct");
     LOGV("Destroying ZT_Node struct");
     uint64_t nodeId = (uint64_t)id;
     uint64_t nodeId = (uint64_t)id;
 
 
-    NodeMap::iterator found = nodeMap.find(nodeId);
+    NodeMap::iterator found;
+    {
+        ZeroTier::Mutex::Lock lock(nodeMapMutex);  
+        found = nodeMap.find(nodeId);
+    }
+
     if(found != nodeMap.end())
     if(found != nodeMap.end())
     {
     {
         JniRef *ref = found->second;
         JniRef *ref = found->second;
@@ -693,7 +706,10 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processVirtualNetworkFrame(
     unsigned int vlanId = (unsigned int)in_vlanId;
     unsigned int vlanId = (unsigned int)in_vlanId;
 
 
     unsigned int frameLength = env->GetArrayLength(in_frameData);
     unsigned int frameLength = env->GetArrayLength(in_frameData);
-    jbyte *frameData =env->GetByteArrayElements(in_frameData, NULL);
+    void *frameData = env->GetPrimitiveArrayCritical(in_frameData, NULL);
+    void *localData = malloc(frameLength);
+    memcpy(localData, frameData, frameLength);
+    env->ReleasePrimitiveArrayCritical(in_frameData, frameData, 0);
 
 
     uint64_t nextBackgroundTaskDeadline = 0;
     uint64_t nextBackgroundTaskDeadline = 0;
 
 
@@ -705,15 +721,13 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processVirtualNetworkFrame(
         destMac,
         destMac,
         etherType,
         etherType,
         vlanId,
         vlanId,
-        (const void*)frameData,
+        (const void*)localData,
         frameLength,
         frameLength,
         &nextBackgroundTaskDeadline);
         &nextBackgroundTaskDeadline);
 
 
-    jlong *outDeadline = env->GetLongArrayElements(out_nextBackgroundTaskDeadline, NULL);
+    jlong *outDeadline = (jlong*)env->GetPrimitiveArrayCritical(out_nextBackgroundTaskDeadline, NULL);
     outDeadline[0] = (jlong)nextBackgroundTaskDeadline;
     outDeadline[0] = (jlong)nextBackgroundTaskDeadline;
-    env->ReleaseLongArrayElements(out_nextBackgroundTaskDeadline, outDeadline, 0);
-
-    env->ReleaseByteArrayElements(in_frameData, frameData, 0);
+    env->ReleasePrimitiveArrayCritical(out_nextBackgroundTaskDeadline, outDeadline, 0);
 
 
     return createResultObject(env, rc);
     return createResultObject(env, rc);
 }
 }
@@ -727,6 +741,7 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processWirePacket(
     JNIEnv *env, jobject obj, 
     JNIEnv *env, jobject obj, 
     jlong id,
     jlong id,
     jlong in_now, 
     jlong in_now, 
+    jobject in_localAddress,
     jobject in_remoteAddress,
     jobject in_remoteAddress,
     jbyteArray in_packetData,
     jbyteArray in_packetData,
     jlongArray out_nextBackgroundTaskDeadline)
     jlongArray out_nextBackgroundTaskDeadline)
@@ -736,26 +751,29 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processWirePacket(
     if(node == NULL)
     if(node == NULL)
     {
     {
         // cannot find valid node.  We should  never get here.
         // cannot find valid node.  We should  never get here.
+        LOGE("Couldn't find a valid node!");
         return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL);
         return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL);
     }
     }
 
 
     unsigned int nbtd_len = env->GetArrayLength(out_nextBackgroundTaskDeadline);
     unsigned int nbtd_len = env->GetArrayLength(out_nextBackgroundTaskDeadline);
     if(nbtd_len < 1)
     if(nbtd_len < 1)
     {
     {
+        LOGE("nbtd_len < 1");
         return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL);
         return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL);
     }
     }
 
 
     uint64_t now = (uint64_t)in_now;
     uint64_t now = (uint64_t)in_now;
 
 
     // get the java.net.InetSocketAddress class and getAddress() method
     // get the java.net.InetSocketAddress class and getAddress() method
-    jclass inetAddressClass = cache.findClass("java/net/InetAddress");
+    jclass inetAddressClass = lookup.findClass("java/net/InetAddress");
     if(inetAddressClass == NULL)
     if(inetAddressClass == NULL)
     {
     {
+        LOGE("Can't find InetAddress class");
         // can't find java.net.InetAddress
         // can't find java.net.InetAddress
         return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL);
         return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL);
     }
     }
 
 
-    jmethodID getAddressMethod = cache.findMethod(
+    jmethodID getAddressMethod = lookup.findMethod(
         inetAddressClass, "getAddress", "()[B");
         inetAddressClass, "getAddress", "()[B");
     if(getAddressMethod == NULL)
     if(getAddressMethod == NULL)
     {
     {
@@ -763,23 +781,29 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processWirePacket(
         return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL);
         return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL);
     }
     }
 
 
-    jclass InetSocketAddressClass = cache.findClass("java/net/InetSocketAddress");
+    jclass InetSocketAddressClass = lookup.findClass("java/net/InetSocketAddress");
     if(InetSocketAddressClass == NULL)
     if(InetSocketAddressClass == NULL)
     {
     {
         return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL);
         return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL);
     }
     }
 
 
-    jmethodID inetSockGetAddressMethod = cache.findMethod(
+    jmethodID inetSockGetAddressMethod = lookup.findMethod(
         InetSocketAddressClass, "getAddress", "()Ljava/net/InetAddress;");
         InetSocketAddressClass, "getAddress", "()Ljava/net/InetAddress;");
 
 
-    jobject addrObject = env->CallObjectMethod(in_remoteAddress, inetSockGetAddressMethod);
+    jobject localAddrObj = NULL;
+    if(in_localAddress != NULL)
+    {
+        localAddrObj = env->CallObjectMethod(in_localAddress, inetSockGetAddressMethod);
+    }
+
+    jobject remoteAddrObject = env->CallObjectMethod(in_remoteAddress, inetSockGetAddressMethod);
 
 
-    if(addrObject == NULL)
+    if(remoteAddrObject == NULL)
     {
     {
         return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL);
         return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL);
     }
     }
 
 
-    jmethodID inetSock_getPort = cache.findMethod(
+    jmethodID inetSock_getPort = lookup.findMethod(
         InetSocketAddressClass, "getPort", "()I");
         InetSocketAddressClass, "getPort", "()I");
 
 
     if(env->ExceptionCheck() || inetSock_getPort == NULL) 
     if(env->ExceptionCheck() || inetSock_getPort == NULL) 
@@ -789,7 +813,7 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processWirePacket(
     }
     }
 
 
     // call InetSocketAddress.getPort()
     // call InetSocketAddress.getPort()
-    int port = env->CallIntMethod(in_remoteAddress, inetSock_getPort);
+    int remotePort = env->CallIntMethod(in_remoteAddress, inetSock_getPort);
     if(env->ExceptionCheck())
     if(env->ExceptionCheck())
     {
     {
         LOGE("Exception calling InetSocketAddress.getPort()");
         LOGE("Exception calling InetSocketAddress.getPort()");
@@ -797,18 +821,60 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processWirePacket(
     }
     }
 
 
     // Call InetAddress.getAddress()
     // Call InetAddress.getAddress()
-    jbyteArray addressArray = (jbyteArray)env->CallObjectMethod(addrObject, getAddressMethod);
-    if(addressArray == NULL)
+    jbyteArray remoteAddressArray = (jbyteArray)env->CallObjectMethod(remoteAddrObject, getAddressMethod);
+    if(remoteAddressArray == NULL)
     {
     {
+        LOGE("Unable to call getAddress()");
         // unable to call getAddress()
         // unable to call getAddress()
         return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL);
         return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL);
     }
     }
 
 
-    unsigned int addrSize = env->GetArrayLength(addressArray);
-    // get the address bytes
-    jbyte *addr = env->GetByteArrayElements(addressArray, NULL);
+    unsigned int addrSize = env->GetArrayLength(remoteAddressArray);
+    
+
+    sockaddr_storage localAddress = {};
+    
+    if(localAddrObj == NULL)
+    {
+        localAddress = ZT_SOCKADDR_NULL;
+    }
+    else
+    {
+        int localPort = env->CallIntMethod(in_localAddress, inetSock_getPort);
+        jbyteArray localAddressArray = (jbyteArray)env->CallObjectMethod(localAddrObj, getAddressMethod);
+        if(localAddressArray != NULL)
+        {
+
+            unsigned int localAddrSize = env->GetArrayLength(localAddressArray);
+            jbyte *addr = (jbyte*)env->GetPrimitiveArrayCritical(localAddressArray, NULL);
 
 
+            if(localAddrSize == 16)
+            {
+                sockaddr_in6 ipv6 = {};
+                ipv6.sin6_family = AF_INET6;
+                ipv6.sin6_port = htons(localPort);
+                memcpy(ipv6.sin6_addr.s6_addr, addr, 16);
+                memcpy(&localAddress, &ipv6, sizeof(sockaddr_in6));
+            }
+            else if(localAddrSize)
+            {
+                // IPV4 address
+                sockaddr_in ipv4 = {};
+                ipv4.sin_family = AF_INET;
+                ipv4.sin_port = htons(localPort);
+                memcpy(&ipv4.sin_addr, addr, 4);
+                memcpy(&localAddress, &ipv4, sizeof(sockaddr_in));
+            }
+            else
+            {
+                localAddress = ZT_SOCKADDR_NULL;
+            }
+            env->ReleasePrimitiveArrayCritical(localAddressArray, addr, 0);
+        }
+    }
 
 
+    // get the address bytes
+    jbyte *addr = (jbyte*)env->GetPrimitiveArrayCritical(remoteAddressArray, NULL);
     sockaddr_storage remoteAddress = {};
     sockaddr_storage remoteAddress = {};
 
 
     if(addrSize == 16)
     if(addrSize == 16)
@@ -816,7 +882,7 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processWirePacket(
         // IPV6 address
         // IPV6 address
         sockaddr_in6 ipv6 = {};
         sockaddr_in6 ipv6 = {};
         ipv6.sin6_family = AF_INET6;
         ipv6.sin6_family = AF_INET6;
-        ipv6.sin6_port = htons(port);
+        ipv6.sin6_port = htons(remotePort);
         memcpy(ipv6.sin6_addr.s6_addr, addr, 16);
         memcpy(ipv6.sin6_addr.s6_addr, addr, 16);
         memcpy(&remoteAddress, &ipv6, sizeof(sockaddr_in6));
         memcpy(&remoteAddress, &ipv6, sizeof(sockaddr_in6));
     }
     }
@@ -825,37 +891,50 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processWirePacket(
         // IPV4 address
         // IPV4 address
         sockaddr_in ipv4 = {};
         sockaddr_in ipv4 = {};
         ipv4.sin_family = AF_INET;
         ipv4.sin_family = AF_INET;
-        ipv4.sin_port = htons(port);
+        ipv4.sin_port = htons(remotePort);
         memcpy(&ipv4.sin_addr, addr, 4);
         memcpy(&ipv4.sin_addr, addr, 4);
         memcpy(&remoteAddress, &ipv4, sizeof(sockaddr_in));
         memcpy(&remoteAddress, &ipv4, sizeof(sockaddr_in));
     }
     }
     else
     else
     {
     {
+        LOGE("Unknown IP version");
         // unknown address type
         // unknown address type
-        env->ReleaseByteArrayElements(addressArray, addr, 0);
+        env->ReleasePrimitiveArrayCritical(remoteAddressArray, addr, 0);
         return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL);
         return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL);
     }
     }
-
+    env->ReleasePrimitiveArrayCritical(remoteAddressArray, addr, 0);
 
 
     unsigned int packetLength = env->GetArrayLength(in_packetData);
     unsigned int packetLength = env->GetArrayLength(in_packetData);
-    jbyte *packetData = env->GetByteArrayElements(in_packetData, NULL);
+    if(packetLength == 0)
+    {
+        LOGE("Empty packet?!?");
+        return createResultObject(env, ZT_RESULT_FATAL_ERROR_INTERNAL);
+    }
+    void *packetData = env->GetPrimitiveArrayCritical(in_packetData, NULL);
+    void *localData = malloc(packetLength);
+    memcpy(localData, packetData, packetLength);
+    env->ReleasePrimitiveArrayCritical(in_packetData, packetData, 0);
 
 
     uint64_t nextBackgroundTaskDeadline = 0;
     uint64_t nextBackgroundTaskDeadline = 0;
 
 
     ZT_ResultCode rc = ZT_Node_processWirePacket(
     ZT_ResultCode rc = ZT_Node_processWirePacket(
         node,
         node,
         now,
         now,
+        &localAddress,
         &remoteAddress,
         &remoteAddress,
-        packetData,
+        localData,
         packetLength,
         packetLength,
         &nextBackgroundTaskDeadline);
         &nextBackgroundTaskDeadline);
+    if(rc != ZT_RESULT_OK) 
+    {
+        LOGE("ZT_Node_processWirePacket returned: %d", rc);
+    }
 
 
-    jlong *outDeadline = env->GetLongArrayElements(out_nextBackgroundTaskDeadline, NULL);
-    outDeadline[0] = (jlong)nextBackgroundTaskDeadline;
-    env->ReleaseLongArrayElements(out_nextBackgroundTaskDeadline, outDeadline, 0);
+    free(localData);
 
 
-    env->ReleaseByteArrayElements(addressArray, addr, 0);
-    env->ReleaseByteArrayElements(in_packetData, packetData, 0);
+    jlong *outDeadline = (jlong*)env->GetPrimitiveArrayCritical(out_nextBackgroundTaskDeadline, NULL);
+    outDeadline[0] = (jlong)nextBackgroundTaskDeadline;
+    env->ReleasePrimitiveArrayCritical(out_nextBackgroundTaskDeadline, outDeadline, 0);
 
 
     return createResultObject(env, rc);
     return createResultObject(env, rc);
 }
 }
@@ -890,9 +969,9 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processBackgroundTasks(
 
 
     ZT_ResultCode rc = ZT_Node_processBackgroundTasks(node, now, &nextBackgroundTaskDeadline);
     ZT_ResultCode rc = ZT_Node_processBackgroundTasks(node, now, &nextBackgroundTaskDeadline);
 
 
-    jlong *outDeadline = env->GetLongArrayElements(out_nextBackgroundTaskDeadline, NULL);
+    jlong *outDeadline = (jlong*)env->GetPrimitiveArrayCritical(out_nextBackgroundTaskDeadline, NULL);
     outDeadline[0] = (jlong)nextBackgroundTaskDeadline;
     outDeadline[0] = (jlong)nextBackgroundTaskDeadline;
-    env->ReleaseLongArrayElements(out_nextBackgroundTaskDeadline, outDeadline, 0);
+    env->ReleasePrimitiveArrayCritical(out_nextBackgroundTaskDeadline, outDeadline, 0);
 
 
     return createResultObject(env, rc);
     return createResultObject(env, rc);
 }
 }
@@ -1043,13 +1122,13 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_status
     jmethodID nodeStatusConstructor = NULL;
     jmethodID nodeStatusConstructor = NULL;
 
 
     // create a com.zerotier.sdk.NodeStatus object
     // create a com.zerotier.sdk.NodeStatus object
-    nodeStatusClass = cache.findClass("com/zerotier/sdk/NodeStatus");
+    nodeStatusClass = lookup.findClass("com/zerotier/sdk/NodeStatus");
     if(nodeStatusClass == NULL)
     if(nodeStatusClass == NULL)
     {
     {
         return NULL;
         return NULL;
     }
     }
     
     
-    nodeStatusConstructor = cache.findMethod(
+    nodeStatusConstructor = lookup.findMethod(
         nodeStatusClass, "<init>", "()V");
         nodeStatusClass, "<init>", "()V");
     if(nodeStatusConstructor == NULL)
     if(nodeStatusConstructor == NULL)
     {
     {
@@ -1070,25 +1149,25 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_status
     jfieldID secretIdentityField = NULL;
     jfieldID secretIdentityField = NULL;
     jfieldID onlineField = NULL;
     jfieldID onlineField = NULL;
 
 
-    addressField = cache.findField(nodeStatusClass, "address", "J");
+    addressField = lookup.findField(nodeStatusClass, "address", "J");
     if(addressField == NULL)
     if(addressField == NULL)
     {
     {
         return NULL;
         return NULL;
     }
     }
 
 
-    publicIdentityField = cache.findField(nodeStatusClass, "publicIdentity", "Ljava/lang/String;");
+    publicIdentityField = lookup.findField(nodeStatusClass, "publicIdentity", "Ljava/lang/String;");
     if(publicIdentityField == NULL)
     if(publicIdentityField == NULL)
     {
     {
         return NULL;
         return NULL;
     }
     }
 
 
-    secretIdentityField = cache.findField(nodeStatusClass, "secretIdentity", "Ljava/lang/String;");
+    secretIdentityField = lookup.findField(nodeStatusClass, "secretIdentity", "Ljava/lang/String;");
     if(secretIdentityField == NULL)
     if(secretIdentityField == NULL)
     {
     {
         return NULL;
         return NULL;
     }
     }
 
 
-    onlineField = cache.findField(nodeStatusClass, "online", "Z");
+    onlineField = lookup.findField(nodeStatusClass, "online", "Z");
     if(onlineField == NULL)
     if(onlineField == NULL)
     {
     {
         return NULL;
         return NULL;
@@ -1191,7 +1270,7 @@ JNIEXPORT jobjectArray JNICALL Java_com_zerotier_sdk_Node_peers(
         return NULL;
         return NULL;
     }
     }
 
 
-    jclass peerClass = cache.findClass("com/zerotier/sdk/Peer");
+    jclass peerClass = lookup.findClass("com/zerotier/sdk/Peer");
     if(env->ExceptionCheck() || peerClass == NULL)
     if(env->ExceptionCheck() || peerClass == NULL)
     {
     {
         LOGE("Error finding Peer class");
         LOGE("Error finding Peer class");
@@ -1249,7 +1328,7 @@ JNIEXPORT jobjectArray JNICALL Java_com_zerotier_sdk_Node_networks(
         return NULL;
         return NULL;
     }
     }
 
 
-    jclass vnetConfigClass = cache.findClass("com/zerotier/sdk/VirtualNetworkConfig");
+    jclass vnetConfigClass = lookup.findClass("com/zerotier/sdk/VirtualNetworkConfig");
     if(env->ExceptionCheck() || vnetConfigClass == NULL)
     if(env->ExceptionCheck() || vnetConfigClass == NULL)
     {
     {
         LOGE("Error finding VirtualNetworkConfig class");
         LOGE("Error finding VirtualNetworkConfig class");

+ 2 - 2
java/jni/com_zerotierone_sdk_Node.h

@@ -34,10 +34,10 @@ JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processVirtualNetworkFrame
 /*
 /*
  * Class:     com_zerotier_sdk_Node
  * Class:     com_zerotier_sdk_Node
  * Method:    processWirePacket
  * Method:    processWirePacket
- * Signature: (JJLjava/net/InetSockAddress;[B[J)Lcom/zerotier/sdk/ResultCode;
+ * Signature: (JJLjava/net/InetSockAddress;Ljava/net/InetSockAddress;[B[J)Lcom/zerotier/sdk/ResultCode;
  */
  */
 JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processWirePacket
 JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processWirePacket
-  (JNIEnv *, jobject, jlong, jlong, jobject, jbyteArray, jlongArray);
+  (JNIEnv *, jobject, jlong, jlong, jobject, jobject, jbyteArray, jlongArray);
 
 
 /*
 /*
  * Class:     com_zerotier_sdk_Node
  * Class:     com_zerotier_sdk_Node

+ 0 - 43
java/src/com/zerotier/one/AndroidFileProvider.java

@@ -1,43 +0,0 @@
-package com.zerotier.one;
-
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-import android.content.Context;
-import android.util.Log;
-
-public class AndroidFileProvider implements DataStoreFileProvider {
-	private static final String TAG = "AndroidFileProvider";
-
-	Context _ctx;
-		
-	public AndroidFileProvider(Context ctx) {
-		this._ctx = ctx;
-	}
-
-	@Override
-	public FileInputStream getInputFileStream(String name)
-			throws FileNotFoundException {
-		Log.d(TAG, "Returning FileInputStream for: " + name);
-		return _ctx.openFileInput(name);
-	}
-
-	@Override
-	public FileOutputStream getOutputFileStream(String name)
-			throws FileNotFoundException {
-		Log.d(TAG, "Returning FileOutputStream for: " + name);
-		return _ctx.openFileOutput(name, Context.MODE_PRIVATE);
-	}
-
-	@Override
-	public void deleteFile(String name) throws IOException {
-		boolean success = _ctx.deleteFile(name);
-		if(!success)
-		{
-			throw new IOException("Unable to delete file.");
-		}
-	}
-
-}

+ 0 - 73
java/src/com/zerotier/one/DataStore.java

@@ -1,73 +0,0 @@
-package com.zerotier.one;
-
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-import com.zerotier.sdk.DataStoreGetListener;
-import com.zerotier.sdk.DataStorePutListener;
-
-public class DataStore implements DataStoreGetListener, DataStorePutListener {
-
-	private DataStoreFileProvider _provider;
-
-	public DataStore(DataStoreFileProvider provider) {
-		this._provider = provider;
-	}
-	
-	@Override
-	public int onDataStorePut(String name, byte[] buffer, boolean secure) {
-		System.out.println("Writing File: " + name);
-		try {
-			FileOutputStream fos = _provider.getOutputFileStream(name);
-            fos.write(buffer);
-            fos.close();
-            return 0;
-		} catch (FileNotFoundException fnf) {
-			fnf.printStackTrace();
-			return -1;
-		} catch (IOException io) {
-			io.printStackTrace();
-			return -2;
-		}
-	}
-
-	@Override
-	public int onDelete(String name) {
-		System.out.println("Deleting File: " + name);
-		try {
-			_provider.deleteFile(name);
-			return 0;
-		} catch (IOException ex) {
-			ex.printStackTrace();
-			return -1;
-		}
-	}
-
-	@Override
-	public long onDataStoreGet(String name, byte[] out_buffer,
-			long bufferIndex, long[] out_objectSize) {
-		System.out.println("Reading File: " + name);
-		try {
-            FileInputStream fin = _provider.getInputFileStream(name);
-			out_objectSize[0] = fin.getChannel().size();
-            if(bufferIndex > 0)
-            {
-                fin.skip(bufferIndex);
-            }
-            int read = fin.read(out_buffer);
-            fin.close();
-            return read;
-		} catch (FileNotFoundException fnf) {
-			// Can't read a file that doesn't exist!
-			out_objectSize[0] = 0;
-			return 0;
-		} catch (IOException io) {
-			io.printStackTrace();
-			return -2;
-		}
-	}
-	
-
-}

+ 0 - 12
java/src/com/zerotier/one/DataStoreFileProvider.java

@@ -1,12 +0,0 @@
-package com.zerotier.one;
-
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-public interface DataStoreFileProvider {
-	FileInputStream getInputFileStream(String name) throws FileNotFoundException;
-	FileOutputStream getOutputFileStream(String name) throws FileNotFoundException;
-	void deleteFile(String name) throws IOException;
-}

+ 0 - 46
java/src/com/zerotier/one/JavaFileProvider.java

@@ -1,46 +0,0 @@
-package com.zerotier.one;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-public class JavaFileProvider implements DataStoreFileProvider {
-	private String _path;
-
-	public JavaFileProvider(String path) {
-		this._path = path;
-	}
-	
-	@Override
-	public FileInputStream getInputFileStream(String name)
-			throws FileNotFoundException {
-		File f = new File(_path + File.separator + name);
-		return new FileInputStream(f);
-	}
-
-	@Override
-	public FileOutputStream getOutputFileStream(String name)
-			throws FileNotFoundException {
-		File f = new File(_path + File.separator + name);
-		if(!f.exists())
-		{
-			try {
-				f.createNewFile();
-			} catch (IOException e) {
-				e.printStackTrace();
-			}
-		}
-		return new FileOutputStream(f);
-	}
-
-	@Override
-	public void deleteFile(String name) throws IOException {
-		File f = new File(_path + File.separator + name);
-		boolean success = f.delete();
-		if(!success) {
-			throw new IOException("Unable to delete file: " + _path + File.pathSeparator + name);
-		}
-	}
-}

+ 0 - 204
java/src/com/zerotier/one/OneService.java

@@ -1,204 +0,0 @@
-/*
- * ZeroTier One - Network Virtualization Everywhere
- * Copyright (C) 2011-2015  ZeroTier, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * --
- *
- * ZeroTier may be used and distributed under the terms of the GPLv3, which
- * are available at: http://www.gnu.org/licenses/gpl-3.0.html
- *
- * If you would like to embed ZeroTier into a commercial application or
- * redistribute it in a modified binary form, please contact ZeroTier Networks
- * LLC. Start here: http://www.zerotier.com/
- */
-
-
-package com.zerotier.one;
-
-import java.io.IOException;
-import java.net.DatagramPacket;
-import java.net.DatagramSocket;
-import java.net.InetSocketAddress;
-import java.net.ServerSocket;
-import java.net.SocketException;
-import java.net.SocketTimeoutException;
-
-import com.zerotier.sdk.Event;
-import com.zerotier.sdk.EventListener;
-import com.zerotier.sdk.Node;
-import com.zerotier.sdk.PacketSender;
-import com.zerotier.sdk.ResultCode;
-import com.zerotier.sdk.Version;
-import com.zerotier.sdk.VirtualNetworkConfig;
-import com.zerotier.sdk.VirtualNetworkConfigListener;
-import com.zerotier.sdk.VirtualNetworkConfigOperation;
-import com.zerotier.sdk.VirtualNetworkFrameListener;
-
-public class OneService extends Thread implements Runnable, PacketSender,
-								   EventListener, VirtualNetworkConfigListener, 
-								   VirtualNetworkFrameListener {
-	private Node _node;
-	private int _port;
-
-	private DatagramSocket _udpSocket;
-	private ServerSocket _tcpSocket;
-	private DataStore _ds;
-	private long _nextBackgroundTaskDeadline = 0;
-	
-	private final Thread _udpReceiveThread = new Thread() {
-		@Override
-		public void run() {
-			try {
-				long[] bgtask = new long[1];
-				byte[] buffer = new byte[16384];
-				while(true) {
-	    			
-	    			bgtask[0] = 0;
-	    			DatagramPacket p = new DatagramPacket(buffer, buffer.length);
-	    			
-	    			try {
-	    				_udpSocket.receive(p);
-	    				if(p.getLength() > 0)
-		    			{
-		    				System.out.println("Got Data From: " + p.getAddress().toString() +":" + p.getPort());
-		    				
-		    				_node.processWirePacket(System.currentTimeMillis(), new InetSocketAddress(p.getAddress(), p.getPort()), p.getData(), bgtask);
-		    				_nextBackgroundTaskDeadline = bgtask[0];
-		    			}
-	    			} catch (SocketTimeoutException e) {}
-				}
-			} catch (Exception e) {
-				e.printStackTrace();
-			}
-		}
-	};
-	
-	
-	public OneService(DataStoreFileProvider prov, int port) {
-		this._port = port;
-		this._ds = new DataStore(prov);
-		
-		try {
-			_udpSocket = new DatagramSocket(_port);
-			_udpSocket.setSoTimeout(100);
-			_tcpSocket = new ServerSocket();
-			_tcpSocket.bind(new InetSocketAddress("127.0.0.1", _port));
-		} catch (SocketException e) {
-			e.printStackTrace();
-			return;
-		} catch (IOException e) {
-			e.printStackTrace();
-			return;
-		}
-		
-		_udpReceiveThread.start();
-		
-		_node = new Node(
-				System.currentTimeMillis(),
-				_ds,
-				_ds,
-				this,
-				this,
-				this,
-				this);
-	}
-
-	@Override
-	public void run() {
-		if(_node == null)
-			return;
-		
-		while(true) {
-		    try {
-		
-		        long dl = _nextBackgroundTaskDeadline;
-		        long now = System.currentTimeMillis();
-		
-		        if (dl <= now) {
-		            long[] returnDeadline = {0};
-		            ResultCode rc = _node.processBackgroundTasks(now, returnDeadline);
-		            _nextBackgroundTaskDeadline = returnDeadline[0];
-		            
-		            if(rc != ResultCode.RESULT_OK) {
-		            	System.out.println(rc.toString());
-		            }
-		        }
-		        
-		        long delay = (dl > now) ? (dl - now) : 100;
-		        Thread.sleep(delay);
-		
-		    } catch (Exception ex) {
-		    	System.out.println("Exception in run loop: " + ex.getMessage());
-		    	ex.printStackTrace();
-		    }
-        }
-	}
-
-	@Override
-	public int onSendPacketRequested(InetSocketAddress addr, byte[] packetData) {
-		System.out.println("onSendPacketRequested to: " + addr.getHostString() +":"+ addr.getPort() + " ");
-
-    	if(_udpSocket == null)
-    		return -1;
-    	try {
-    		DatagramPacket p = new DatagramPacket(packetData, packetData.length, addr);
-    		_udpSocket.send(p);
-    		System.out.println("Sent");
-    	} catch (Exception e) {
-    		System.out.println("Error sending datagram: " + e.getMessage());
-    		return -1;
-    	}
-        return 0;
-	}
-
-	@Override
-	public void onVirtualNetworkFrame(long nwid, long srcMac, long destMac,
-			long etherType, long vlanId, byte[] frameData) {
-		// TODO Auto-generated method stub
-		
-	}
-
-	@Override
-	public int onNetworkConfigurationUpdated(long nwid,
-			VirtualNetworkConfigOperation op, VirtualNetworkConfig config) {
-		// TODO Auto-generated method stub
-		return 0;
-	}
-
-	@Override
-	public void onEvent(Event event) {
-		// TODO Auto-generated method stub
-		
-	}
-
-	@Override
-	public void onNetworkError(Event event, InetSocketAddress source) {
-		// TODO Auto-generated method stub
-		
-	}
-
-	@Override
-	public void onOutOfDate(Version newVersion) {
-		// TODO Auto-generated method stub
-		
-	}
-
-	@Override
-	public void onTrace(String message) {
-		// TODO Auto-generated method stub
-		
-	}
-}

+ 0 - 25
java/src/com/zerotier/sdk/Event.java

@@ -86,31 +86,6 @@ public enum Event {
      * umbrellas prevent rain and smoke detectors prevent fires. They do, right?</p>
      * umbrellas prevent rain and smoke detectors prevent fires. They do, right?</p>
      */
      */
 	EVENT_FATAL_ERROR_IDENTITY_COLLISION,
 	EVENT_FATAL_ERROR_IDENTITY_COLLISION,
-    
-    /**
-     * A more recent version was observed on the network
-     * 
-     * <p>Right now this is only triggered if a hub or rootserver reports a
-     * more recent version, and only once. It can be used to trigger a
-     * software update check.</p>
-     * 
-     * <p>Meta-data: {@link Version}, more recent version number</p>
-     */
-    EVENT_SAW_MORE_RECENT_VERSION,
-
-    /**
-     * A packet failed authentication
-     *
-     * <p>Meta-data: {@link InetSocketAddress} containing origin address of packet</p>
-     */
-	EVENT_AUTHENTICATION_FAILURE,
-
-    /**
-     * A received packet was not valid
-     *
-     * <p>Meta-data: {@link InetSocketAddress} containing origin address of packet</p>
-     */
-	EVENT_INVALID_PACKET,
 
 
     /**
     /**
      * Trace (debugging) message
      * Trace (debugging) message

+ 0 - 15
java/src/com/zerotier/sdk/EventListener.java

@@ -41,21 +41,6 @@ public interface EventListener {
      */
      */
     public void onEvent(Event event);
     public void onEvent(Event event);
     
     
-    /**
-     * Callback for network error events: {@link Event.EVENT_AUTHENTICATION_FAILUER}, {link Event.EVENT_INVALID_PACKET}
-     *
-     * @param event {@link Event} enum
-     * @param source {@link InetSocketAddress} containing the origin address of the packet
-     */
-    public void onNetworkError(Event event, InetSocketAddress source);
-
-    /**
-     * Callback when the node detects that it's out of date.
-     *
-     * @param newVersion {@link Version} object with the latest version of ZeroTier One
-     */
-    public void onOutOfDate(Version newVersion);
-
     /**
     /**
      * Trace messages
      * Trace messages
      * 
      * 

+ 4 - 0
java/src/com/zerotier/sdk/MulticastGroup.java

@@ -33,6 +33,10 @@ public final class MulticastGroup {
     private long mac;
     private long mac;
     private long adi;
     private long adi;
 
 
+    public boolean equals(MulticastGroup other) {
+        return mac == other.mac && adi == other.adi;
+    }
+
     /**
     /**
      * MAC address (least significant 48 bits)
      * MAC address (least significant 48 bits)
      */
      */

+ 3 - 1
java/src/com/zerotier/sdk/Node.java

@@ -169,11 +169,12 @@ public class Node {
      */
      */
     public ResultCode processWirePacket(
     public ResultCode processWirePacket(
         long now,
         long now,
+        InetSocketAddress localAddress,
         InetSocketAddress remoteAddress,
         InetSocketAddress remoteAddress,
         byte[] packetData,
         byte[] packetData,
         long[] nextBackgroundTaskDeadline) {
         long[] nextBackgroundTaskDeadline) {
         return processWirePacket(
         return processWirePacket(
-            nodeId, now, remoteAddress, packetData, 
+            nodeId, now, localAddress, remoteAddress, packetData, 
             nextBackgroundTaskDeadline);
             nextBackgroundTaskDeadline);
     }
     }
 
 
@@ -393,6 +394,7 @@ public class Node {
     private native ResultCode processWirePacket(
     private native ResultCode processWirePacket(
         long nodeId,
         long nodeId,
         long now,
         long now,
+        InetSocketAddress localAddress,
         InetSocketAddress remoteAddress,
         InetSocketAddress remoteAddress,
         byte[] packetData,
         byte[] packetData,
         long[] nextBackgroundTaskDeadline);
         long[] nextBackgroundTaskDeadline);

+ 4 - 2
java/src/com/zerotier/sdk/PacketSender.java

@@ -37,11 +37,13 @@ public interface PacketSender {
      * on failure. Note that success does not (of course) guarantee packet
      * on failure. Note that success does not (of course) guarantee packet
      * delivery. It only means that the packet appears to have been sent.</p>
      * delivery. It only means that the packet appears to have been sent.</p>
      *
      *
-     * @param addr {@link InetSocketAddress} to send to
+     * @param localAddr {@link InetSocketAddress} to send from.  Set to null if not specified.
+     * @param remoteAddr {@link InetSocketAddress} to send to
      * @param packetData data to send
      * @param packetData data to send
      * @return 0 on success, any error code on failure.
      * @return 0 on success, any error code on failure.
      */
      */
     public int onSendPacketRequested(
     public int onSendPacketRequested(
-            InetSocketAddress addr,
+            InetSocketAddress localAddr,
+            InetSocketAddress remoteAddr,
             byte[] packetData);
             byte[] packetData);
 }
 }

+ 4 - 4
java/src/com/zerotier/sdk/PeerRole.java

@@ -34,12 +34,12 @@ public enum PeerRole {
     PEER_ROLE_LEAF,
     PEER_ROLE_LEAF,
 
 
     /**
     /**
-     * Locally federated hub
+     * relay node
      */
      */
-    PEER_ROLE_HUB,
+    PEER_ROLE_RELAY,
 
 
     /**
     /**
-     * planetary rootserver
+     * root server
      */
      */
-    PEER_ROLE_ROOTSERVER
+    PEER_ROLE_ROOT
 }
 }

+ 37 - 1
java/src/com/zerotier/sdk/VirtualNetworkConfig.java

@@ -27,11 +27,13 @@
 
 
 package com.zerotier.sdk;
 package com.zerotier.sdk;
 
 
+import java.lang.Comparable;
+import java.lang.Override;
 import java.lang.String;
 import java.lang.String;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.net.InetSocketAddress;
 import java.net.InetSocketAddress;
 
 
-public final class VirtualNetworkConfig {
+public final class VirtualNetworkConfig implements Comparable<VirtualNetworkConfig> {
     public static final int MAX_MULTICAST_SUBSCRIPTIONS = 4096;
     public static final int MAX_MULTICAST_SUBSCRIPTIONS = 4096;
     public static final int ZT_MAX_ZT_ASSIGNED_ADDRESSES = 16;
     public static final int ZT_MAX_ZT_ASSIGNED_ADDRESSES = 16;
 
 
@@ -54,6 +56,40 @@ public final class VirtualNetworkConfig {
 
 
     }
     }
 
 
+    public boolean equals(VirtualNetworkConfig cfg) {
+        boolean aaEqual = true;
+        if(assignedAddresses.length == cfg.assignedAddresses.length) {
+            for(int i = 0; i < assignedAddresses.length; ++i) {
+                if(!assignedAddresses[i].equals(cfg.assignedAddresses[i])) {
+                    return false;
+                }
+            }
+        } else {
+            aaEqual = false;
+        }
+
+        return nwid == cfg.nwid &&
+               mac == cfg.mac &&
+               name.equals(cfg.name) &&
+               status.equals(cfg.status) &&
+               type.equals(cfg.type) &&
+               mtu == cfg.mtu &&
+               dhcp == cfg.dhcp &&
+               bridge == cfg.bridge &&
+               broadcastEnabled == cfg.broadcastEnabled &&
+               portError == cfg.portError &&
+               enabled == cfg.enabled &&
+               aaEqual;
+    }
+
+    public int compareTo(VirtualNetworkConfig cfg) {
+        if(cfg.nwid == this.nwid) {
+            return 0;
+        } else {
+            return this.nwid > cfg.nwid ? 1 : -1;
+        }
+    }
+
     /**
     /**
      * 64-bit ZeroTier network ID
      * 64-bit ZeroTier network ID
      */
      */