summaryrefslogtreecommitdiff
path: root/java/jni
diff options
context:
space:
mode:
Diffstat (limited to 'java/jni')
-rw-r--r--java/jni/Android.mk44
-rw-r--r--java/jni/Application.mk3
-rw-r--r--java/jni/ZT1_jnicache.cpp242
-rw-r--r--java/jni/ZT1_jnicache.h65
-rw-r--r--java/jni/ZT1_jniutils.cpp875
-rw-r--r--java/jni/ZT1_jniutils.h50
-rw-r--r--java/jni/com_zerotierone_sdk_Node.cpp1201
-rw-r--r--java/jni/com_zerotierone_sdk_Node.h133
8 files changed, 2613 insertions, 0 deletions
diff --git a/java/jni/Android.mk b/java/jni/Android.mk
new file mode 100644
index 00000000..bbf14348
--- /dev/null
+++ b/java/jni/Android.mk
@@ -0,0 +1,44 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := ZeroTierOneJNI
+LOCAL_C_INCLUDES := $(ZT1)/include
+LOCAL_LDLIBS := -llog
+
+# ZeroTierOne SDK source files
+LOCAL_SRC_FILES := \
+ $(ZT1)/ext/lz4/lz4.c \
+ $(ZT1)/ext/json-parser/json.c \
+ $(ZT1)/ext/http-parser/http_parser.c \
+ $(ZT1)/node/C25519.cpp \
+ $(ZT1)/node/CertificateOfMembership.cpp \
+ $(ZT1)/node/Defaults.cpp \
+ $(ZT1)/node/Dictionary.cpp \
+ $(ZT1)/node/Identity.cpp \
+ $(ZT1)/node/IncomingPacket.cpp \
+ $(ZT1)/node/InetAddress.cpp \
+ $(ZT1)/node/Multicaster.cpp \
+ $(ZT1)/node/Network.cpp \
+ $(ZT1)/node/NetworkConfig.cpp \
+ $(ZT1)/node/Node.cpp \
+ $(ZT1)/node/OutboundMulticast.cpp \
+ $(ZT1)/node/Packet.cpp \
+ $(ZT1)/node/Peer.cpp \
+ $(ZT1)/node/Poly1305.cpp \
+ $(ZT1)/node/Salsa20.cpp \
+ $(ZT1)/node/SelfAwareness.cpp \
+ $(ZT1)/node/SHA512.cpp \
+ $(ZT1)/node/Switch.cpp \
+ $(ZT1)/node/Topology.cpp \
+ $(ZT1)/node/Utils.cpp \
+ $(ZT1)/osdep/Http.cpp \
+ $(ZT1)/osdep/OSUtils.cpp
+
+# JNI Files
+LOCAL_SRC_FILES += \
+ com_zerotierone_sdk_Node.cpp \
+ ZT1_jniutils.cpp \
+ ZT1_jnicache.cpp
+
+include $(BUILD_SHARED_LIBRARY) \ No newline at end of file
diff --git a/java/jni/Application.mk b/java/jni/Application.mk
new file mode 100644
index 00000000..225169bc
--- /dev/null
+++ b/java/jni/Application.mk
@@ -0,0 +1,3 @@
+APP_ABI := armeabi armeabi-v7a arm64-v8a x86
+APP_STL := gnustl_static
+APP_CPPFLAGS += -Wall -fPIE -fstack-protector -fexceptions
diff --git a/java/jni/ZT1_jnicache.cpp b/java/jni/ZT1_jnicache.cpp
new file mode 100644
index 00000000..daa1679a
--- /dev/null
+++ b/java/jni/ZT1_jnicache.cpp
@@ -0,0 +1,242 @@
+/*
+ * 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 "ZT1_jnicache.h"
+#include "ZT1_jniutils.h"
+
+JniCache::JniCache()
+ : m_jvm(NULL)
+ , m_classes()
+ , m_fields()
+ , m_staticFields()
+ , m_methods()
+ , m_staticMethods()
+{
+ LOGD("JNI Cache Created");
+}
+
+JniCache::JniCache(JavaVM *jvm)
+ : m_jvm(jvm)
+ , m_classes()
+ , m_fields()
+ , m_staticFields()
+ , m_methods()
+ , m_staticMethods()
+{
+ LOGD("JNI Cache Created");
+}
+
+JniCache::~JniCache()
+{
+ LOGD("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)
+{
+ LOGD("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;
+ }
+
+ LOGD("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;
+} \ No newline at end of file
diff --git a/java/jni/ZT1_jnicache.h b/java/jni/ZT1_jnicache.h
new file mode 100644
index 00000000..43f43a08
--- /dev/null
+++ b/java/jni/ZT1_jnicache.h
@@ -0,0 +1,65 @@
+/*
+ * 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/
+ */
+
+#ifndef ZT1_JNICACHE_H_
+#define ZT1_JNICACHE_H_
+
+#include <jni.h>
+#include <map>
+#include <string>
+
+
+
+class JniCache {
+public:
+ JniCache();
+ JniCache(JavaVM *jvm);
+ ~JniCache();
+
+ void setJavaVM(JavaVM *jvm);
+ void clearCache();
+
+ jclass findClass(const std::string &name);
+ jmethodID findMethod(jclass cls, const std::string &methodName, const std::string &methodSig);
+ jmethodID findStaticMethod(jclass cls, const std::string &methodName, const std::string &methodSig);
+ jfieldID findField(jclass cls, const std::string &fieldName, const std::string &typeStr);
+ jfieldID findStaticField(jclass cls, const std::string &fieldName, const std::string &typeStr);
+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;
+ ClassMap m_classes;
+ FieldMap m_fields;
+ FieldMap m_staticFields;
+ MethodMap m_methods;
+ MethodMap m_staticMethods;
+
+};
+
+#endif \ No newline at end of file
diff --git a/java/jni/ZT1_jniutils.cpp b/java/jni/ZT1_jniutils.cpp
new file mode 100644
index 00000000..5bda6ae9
--- /dev/null
+++ b/java/jni/ZT1_jniutils.cpp
@@ -0,0 +1,875 @@
+#include "ZT1_jniutils.h"
+#include "ZT1_jnicache.h"
+#include <string>
+#include <assert.h>
+
+extern JniCache cache;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+jobject createResultObject(JNIEnv *env, ZT1_ResultCode code)
+{
+ jclass resultClass = NULL;
+
+ jobject resultObject = NULL;
+
+ resultClass = cache.findClass("com/zerotier/sdk/ResultCode");
+ if(resultClass == NULL)
+ {
+ return NULL; // exception thrown
+ }
+
+ std::string fieldName;
+ switch(code)
+ {
+ case ZT1_RESULT_OK:
+ fieldName = "RESULT_OK";
+ break;
+ case ZT1_RESULT_FATAL_ERROR_OUT_OF_MEMORY:
+ fieldName = "RESULT_FATAL_ERROR_OUT_OF_MEMORY";
+ break;
+ case ZT1_RESULT_FATAL_ERROR_DATA_STORE_FAILED:
+ fieldName = "RESULT_FATAL_ERROR_DATA_STORE_FAILED";
+ break;
+ case ZT1_RESULT_ERROR_NETWORK_NOT_FOUND:
+ fieldName = "RESULT_ERROR_NETWORK_NOT_FOUND";
+ break;
+ case ZT1_RESULT_FATAL_ERROR_INTERNAL:
+ default:
+ fieldName = "RESULT_FATAL_ERROR_INTERNAL";
+ break;
+ }
+
+ jfieldID enumField = cache.findStaticField(resultClass, fieldName.c_str(), "Lcom/zerotier/sdk/ResultCode;");
+
+ resultObject = env->GetStaticObjectField(resultClass, enumField);
+
+ return resultObject;
+}
+
+
+jobject createVirtualNetworkStatus(JNIEnv *env, ZT1_VirtualNetworkStatus status)
+{
+ jobject statusObject = NULL;
+
+ jclass statusClass = cache.findClass("com/zerotier/sdk/VirtualNetworkStatus");
+ if(statusClass == NULL)
+ {
+ return NULL; // exception thrown
+ }
+
+ std::string fieldName;
+ switch(status)
+ {
+ case ZT1_NETWORK_STATUS_REQUESTING_CONFIGURATION:
+ fieldName = "NETWORK_STATUS_REQUESTING_CONFIGURATION";
+ break;
+ case ZT1_NETWORK_STATUS_OK:
+ fieldName = "NETWORK_STATUS_OK";
+ break;
+ case ZT1_NETWORK_STATUS_ACCESS_DENIED:
+ fieldName = "NETWORK_STATUS_ACCESS_DENIED";
+ break;
+ case ZT1_NETWORK_STATUS_NOT_FOUND:
+ fieldName = "NETWORK_STATUS_NOT_FOUND";
+ break;
+ case ZT1_NETWORK_STATUS_PORT_ERROR:
+ fieldName = "NETWORK_STATUS_PORT_ERROR";
+ break;
+ case ZT1_NETWORK_STATUS_CLIENT_TOO_OLD:
+ fieldName = "NETWORK_STATUS_CLIENT_TOO_OLD";
+ break;
+ }
+
+ jfieldID enumField = cache.findStaticField(statusClass, fieldName.c_str(), "Lcom/zerotier/sdk/VirtualNetworkStatus;");
+
+ statusObject = env->GetStaticObjectField(statusClass, enumField);
+
+ return statusObject;
+}
+
+jobject createEvent(JNIEnv *env, ZT1_Event event)
+{
+ jclass eventClass = NULL;
+ jobject eventObject = NULL;
+
+ eventClass = cache.findClass("com/zerotier/sdk/Event");
+ if(eventClass == NULL)
+ {
+ return NULL;
+ }
+
+ std::string fieldName;
+ switch(event)
+ {
+ case ZT1_EVENT_UP:
+ fieldName = "EVENT_UP";
+ break;
+ case ZT1_EVENT_OFFLINE:
+ fieldName = "EVENT_OFFLINE";
+ break;
+ case ZT1_EVENT_ONLINE:
+ fieldName = "EVENT_ONLINE";
+ break;
+ case ZT1_EVENT_DOWN:
+ fieldName = "EVENT_DOWN";
+ break;
+ case ZT1_EVENT_FATAL_ERROR_IDENTITY_COLLISION:
+ fieldName = "EVENT_FATAL_ERROR_IDENTITY_COLLISION";
+ break;
+ case ZT1_EVENT_SAW_MORE_RECENT_VERSION:
+ fieldName = "EVENT_SAW_MORE_RECENT_VERSION";
+ break;
+ case ZT1_EVENT_AUTHENTICATION_FAILURE:
+ fieldName = "EVENT_AUTHENTICATION_FAILURE";
+ break;
+ case ZT1_EVENT_INVALID_PACKET:
+ fieldName = "EVENT_INVALID_PACKET";
+ break;
+ case ZT1_EVENT_TRACE:
+ fieldName = "EVENT_TRACE";
+ break;
+ }
+
+ jfieldID enumField = cache.findStaticField(eventClass, fieldName.c_str(), "Lcom/zerotier/sdk/Event;");
+
+ eventObject = env->GetStaticObjectField(eventClass, enumField);
+
+ return eventObject;
+}
+
+jobject createPeerRole(JNIEnv *env, ZT1_PeerRole role)
+{
+ jclass peerRoleClass = NULL;
+ jobject peerRoleObject = NULL;
+
+ peerRoleClass = cache.findClass("com/zerotier/sdk/PeerRole");
+ if(peerRoleClass == NULL)
+ {
+ return NULL;
+ }
+
+ std::string fieldName;
+ switch(role)
+ {
+ case ZT1_PEER_ROLE_LEAF:
+ fieldName = "PEER_ROLE_LEAF";
+ break;
+ case ZT1_PEER_ROLE_HUB:
+ fieldName = "PEER_ROLE_HUB";
+ break;
+ case ZT1_PEER_ROLE_SUPERNODE:
+ fieldName = "PEER_ROLE_SUPERNODE";
+ break;
+ }
+
+ jfieldID enumField = cache.findStaticField(peerRoleClass, fieldName.c_str(), "Lcom/zerotier/sdk/PeerRole;");
+
+ peerRoleObject = env->GetStaticObjectField(peerRoleClass, enumField);
+
+ return peerRoleObject;
+}
+
+jobject createVirtualNetworkType(JNIEnv *env, ZT1_VirtualNetworkType type)
+{
+ jclass vntypeClass = NULL;
+ jobject vntypeObject = NULL;
+
+ vntypeClass = cache.findClass("com/zerotier/sdk/VirtualNetworkType");
+ if(vntypeClass == NULL)
+ {
+ return NULL;
+ }
+
+ std::string fieldName;
+ switch(type)
+ {
+ case ZT1_NETWORK_TYPE_PRIVATE:
+ fieldName = "NETWORK_TYPE_PRIVATE";
+ break;
+ case ZT1_NETWORK_TYPE_PUBLIC:
+ fieldName = "NETWORK_TYPE_PUBLIC";
+ break;
+ }
+
+ jfieldID enumField = cache.findStaticField(vntypeClass, fieldName.c_str(), "Lcom/zerotier/sdk/VirtualNetworkType;");
+ vntypeObject = env->GetStaticObjectField(vntypeClass, enumField);
+ return vntypeObject;
+}
+
+jobject createVirtualNetworkConfigOperation(JNIEnv *env, ZT1_VirtualNetworkConfigOperation op)
+{
+ jclass vnetConfigOpClass = NULL;
+ jobject vnetConfigOpObject = NULL;
+
+ vnetConfigOpClass = cache.findClass("com/zerotier/sdk/VirtualNetworkConfigOperation");
+ if(vnetConfigOpClass == NULL)
+ {
+ return NULL;
+ }
+
+ std::string fieldName;
+ switch(op)
+ {
+ case ZT1_VIRTUAL_NETWORK_CONFIG_OPERATION_UP:
+ fieldName = "VIRTUAL_NETWORK_CONFIG_OPERATION_UP";
+ break;
+ case ZT1_VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE:
+ fieldName = "VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE";
+ break;
+ case ZT1_VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN:
+ fieldName = "VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN";
+ break;
+ case ZT1_VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY:
+ fieldName = "VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY";
+ break;
+ }
+
+ jfieldID enumField = cache.findStaticField(vnetConfigOpClass, fieldName.c_str(), "Lcom/zerotier/sdk/VirtualNetworkConfigOperation;");
+ vnetConfigOpObject = env->GetStaticObjectField(vnetConfigOpClass, enumField);
+ return vnetConfigOpObject;
+}
+
+jobject newArrayList(JNIEnv *env)
+{
+ jclass arrayListClass = NULL;
+ jmethodID arrayList_constructor = NULL;
+
+ arrayListClass = cache.findClass("java/util/ArrayList");
+ if(arrayListClass == NULL)
+ {
+ return NULL;
+ }
+
+ arrayList_constructor = cache.findMethod(
+ arrayListClass, "<init>", "()V");
+ if(arrayList_constructor == NULL)
+ {
+ return NULL;
+ }
+
+ jobject arrayListObj = env->NewObject(arrayListClass, arrayList_constructor);
+
+ return arrayListObj;
+}
+
+bool appendItemToArrayList(JNIEnv *env, jobject array, jobject object)
+{
+ assert(array != NULL);
+ assert(object != NULL);
+
+ jclass arrayListClass = NULL;
+ jmethodID arrayList_add = NULL;
+
+ arrayListClass = cache.findClass("java/util/ArrayList");
+ if(arrayListClass == NULL)
+ {
+ return NULL;
+ }
+
+ arrayList_add = cache.findMethod(arrayListClass, "add", "(Ljava.lang.Object;)Z");
+ if(arrayList_add == NULL)
+ {
+ return false;
+ }
+
+ return env->CallBooleanMethod(array, arrayList_add, object);
+}
+
+jobject newInetAddress(JNIEnv *env, const sockaddr_storage &addr)
+{
+ jclass inetAddressClass = NULL;
+ jmethodID inetAddress_getByAddress = NULL;
+
+ inetAddressClass = cache.findClass("java/net/InetAddress");
+ if(inetAddressClass == NULL)
+ {
+ return NULL;
+ }
+
+ inetAddress_getByAddress = cache.findStaticMethod(
+ inetAddressClass, "getByAddress", "([B)Ljava/net/InetAddress;");
+ if(inetAddress_getByAddress == NULL)
+ {
+ return NULL;
+ }
+
+ jobject inetAddressObj = NULL;
+ switch(addr.ss_family)
+ {
+ case AF_INET6:
+ {
+ sockaddr_in6 *ipv6 = (sockaddr_in6*)&addr;
+ jbyteArray buff = env->NewByteArray(16);
+ if(buff == NULL)
+ {
+ return NULL;
+ }
+
+ env->SetByteArrayRegion(buff, 0, 16, (jbyte*)ipv6->sin6_addr.s6_addr);
+ inetAddressObj = env->CallStaticObjectMethod(
+ inetAddressClass, inetAddress_getByAddress, buff);
+ }
+ break;
+ case AF_INET:
+ {
+ sockaddr_in *ipv4 = (sockaddr_in*)&addr;
+ jbyteArray buff = env->NewByteArray(4);
+ if(buff == NULL)
+ {
+ return NULL;
+ }
+
+ env->SetByteArrayRegion(buff, 0, 4, (jbyte*)&ipv4->sin_addr);
+ inetAddressObj = env->CallStaticObjectMethod(
+ inetAddressClass, inetAddress_getByAddress, buff);
+ }
+ break;
+ }
+
+ return inetAddressObj;
+}
+
+jobject newInetSocketAddress(JNIEnv *env, const sockaddr_storage &addr)
+{
+ jclass inetSocketAddressClass = NULL;
+ jmethodID inetSocketAddress_constructor = NULL;
+
+ inetSocketAddressClass = cache.findClass("java/net/InetSocketAddress");
+ if(inetSocketAddressClass == NULL)
+ {
+ return NULL;
+ }
+
+ jobject inetAddressObject = newInetAddress(env, addr);
+
+ if(inetAddressObject == NULL)
+ {
+ return NULL;
+ }
+
+ inetSocketAddress_constructor = cache.findMethod(
+ inetSocketAddressClass, "<init>", "(Ljava/net/InetAddress;I)V");
+ if(inetSocketAddress_constructor == NULL)
+ {
+ return NULL;
+ }
+
+ int port = 0;
+ switch(addr.ss_family)
+ {
+ case AF_INET6:
+ {
+ sockaddr_in6 *ipv6 = (sockaddr_in6*)&addr;
+ port = ntohs(ipv6->sin6_port);
+ }
+ break;
+ case AF_INET:
+ {
+ sockaddr_in *ipv4 = (sockaddr_in*)&addr;
+ port = ntohs(ipv4->sin_port);
+ }
+ break;
+ };
+
+ jobject inetSocketAddressObject = env->NewObject(inetSocketAddressClass, inetSocketAddress_constructor, inetAddressObject, port);
+ return inetSocketAddressObject;
+}
+
+jobject newMulticastGroup(JNIEnv *env, const ZT1_MulticastGroup &mc)
+{
+ jclass multicastGroupClass = NULL;
+ jmethodID multicastGroup_constructor = NULL;
+
+ jfieldID macField = NULL;
+ jfieldID adiField = NULL;
+
+ multicastGroupClass = cache.findClass("com/zerotier/sdk/MulticastGroup");
+ if(multicastGroupClass == NULL)
+ {
+ return NULL;
+ }
+
+ multicastGroup_constructor = cache.findMethod(
+ multicastGroupClass, "<init>", "()V");
+ if(multicastGroup_constructor == NULL)
+ {
+ return NULL;
+ }
+
+ jobject multicastGroupObj = env->NewObject(multicastGroupClass, multicastGroup_constructor);
+ if(multicastGroupObj == NULL)
+ {
+ return NULL;
+ }
+
+ if(macField == NULL)
+ {
+ macField = cache.findField(multicastGroupClass, "mac", "J");
+ if(macField == NULL)
+ {
+ return NULL;
+ }
+ }
+
+ if(adiField == NULL)
+ {
+ adiField = cache.findField(multicastGroupClass, "adi", "J");
+ if(adiField == NULL)
+ {
+ return NULL;
+ }
+ }
+
+ env->SetLongField(multicastGroupObj, macField, mc.mac);
+ env->SetLongField(multicastGroupObj, adiField, mc.adi);
+
+ return multicastGroupObj;
+}
+
+jobject newPeerPhysicalPath(JNIEnv *env, const ZT1_PeerPhysicalPath &ppp)
+{
+ jclass pppClass = NULL;
+
+ jfieldID addressField = NULL;
+ jfieldID lastSendField = NULL;
+ jfieldID lastReceiveField = NULL;
+ jfieldID fixedField = NULL;
+ jfieldID activeField = NULL;
+ jfieldID preferredField = NULL;
+
+ jmethodID ppp_constructor = NULL;
+
+ pppClass = cache.findClass("com/zerotier/sdk/PeerPhysicalPath");
+ if(pppClass == NULL)
+ {
+ return NULL;
+ }
+
+ addressField = cache.findField(pppClass, "address", "Ljava/net/InetAddress;");
+ if(addressField == NULL)
+ {
+ return NULL;
+ }
+
+ lastSendField = cache.findField(pppClass, "lastSend", "J");
+ if(lastSendField == NULL)
+ {
+ return NULL;
+ }
+
+ lastReceiveField = cache.findField(pppClass, "lastReceive", "J");
+ if(lastReceiveField == NULL)
+ {
+ return NULL;
+ }
+
+ fixedField = cache.findField(pppClass, "fixed", "Z");
+ if(fixedField == NULL)
+ {
+ return NULL;
+ }
+
+ activeField = cache.findField(pppClass, "active", "Z");
+ if(activeField == NULL)
+ {
+ return NULL;
+ }
+
+ preferredField = cache.findField(pppClass, "preferred", "Z");
+ if(preferredField == NULL)
+ {
+ return NULL;
+ }
+
+ ppp_constructor = cache.findMethod(pppClass, "<init>", "()V");
+ if(ppp_constructor == NULL)
+ {
+ return NULL;
+ }
+
+ jobject pppObject = env->NewObject(pppClass, ppp_constructor);
+ if(pppObject == NULL)
+ {
+ return NULL; // out of memory
+ }
+
+ jobject addressObject = newInetAddress(env, ppp.address);
+
+ env->SetObjectField(pppClass, addressField, addressObject);
+ env->SetLongField(pppClass, lastSendField, ppp.lastSend);
+ env->SetLongField(pppClass, lastReceiveField, ppp.lastReceive);
+ env->SetBooleanField(pppClass, fixedField, ppp.fixed);
+ env->SetBooleanField(pppClass, activeField, ppp.active);
+ env->SetBooleanField(pppClass, preferredField, ppp.preferred);
+
+ return pppObject;
+}
+
+jobject newPeer(JNIEnv *env, const ZT1_Peer &peer)
+{
+ jclass peerClass = NULL;
+
+ jfieldID addressField = NULL;
+ jfieldID lastUnicastFrameField = NULL;
+ jfieldID lastMulticastFrameField = NULL;
+ jfieldID versionMajorField = NULL;
+ jfieldID versionMinorField = NULL;
+ jfieldID versionRevField = NULL;
+ jfieldID latencyField = NULL;
+ jfieldID roleField = NULL;
+ jfieldID pathsField = NULL;
+
+ jmethodID peer_constructor = NULL;
+
+ peerClass = cache.findClass("com/zerotier/sdk/Peer");
+ if(peerClass == NULL)
+ {
+ return NULL;
+ }
+
+ addressField = cache.findField(peerClass, "address", "J");
+ if(addressField == NULL)
+ {
+ return NULL;
+ }
+
+ lastUnicastFrameField = cache.findField(peerClass, "lastUnicastFrame", "J");
+ if(lastUnicastFrameField == NULL)
+ {
+ return NULL;
+ }
+
+ lastMulticastFrameField = cache.findField(peerClass, "lastMulticastFrame", "J");
+ if(lastMulticastFrameField == NULL)
+ {
+ return NULL;
+ }
+
+ versionMajorField = cache.findField(peerClass, "versionMajor", "I");
+ if(versionMajorField == NULL)
+ {
+ return NULL;
+ }
+
+ versionMinorField = cache.findField(peerClass, "versionMinor", "I");
+ if(versionMinorField == NULL)
+ {
+ return NULL;
+ }
+
+ versionRevField = cache.findField(peerClass, "versionRev", "I");
+ if(versionRevField == NULL)
+ {
+ return NULL;
+ }
+
+ latencyField = cache.findField(peerClass, "latency", "I");
+ if(latencyField == NULL)
+ {
+ return NULL;
+ }
+
+ roleField = cache.findField(peerClass, "role", "Lcom/zerotier/sdk/PeerRole;");
+ if(roleField == NULL)
+ {
+ return NULL;
+ }
+
+ pathsField = cache.findField(peerClass, "paths", "Ljava.util.ArrayList;");
+ if(pathsField == NULL)
+ {
+ return NULL;
+ }
+
+ peer_constructor = cache.findMethod(peerClass, "<init>", "()V");
+ if(peer_constructor == NULL)
+ {
+ return NULL;
+ }
+
+ jobject peerObject = env->NewObject(peerClass, peer_constructor);
+ if(peerObject == NULL)
+ {
+ return NULL; // out of memory
+ }
+
+ env->SetLongField(peerClass, addressField, (jlong)peer.address);
+ env->SetLongField(peerClass, lastUnicastFrameField, (jlong)peer.lastUnicastFrame);
+ env->SetLongField(peerClass, lastMulticastFrameField, (jlong)peer.lastMulticastFrame);
+ env->SetIntField(peerClass, versionMajorField, peer.versionMajor);
+ env->SetIntField(peerClass, versionMinorField, peer.versionMinor);
+ env->SetIntField(peerClass, versionRevField, peer.versionRev);
+ env->SetIntField(peerClass, latencyField, peer.latency);
+ env->SetObjectField(peerClass, roleField, createPeerRole(env, peer.role));
+
+ jobject arrayObject = newArrayList(env);
+ for(unsigned int i = 0; i < peer.pathCount; ++i)
+ {
+ jobject path = newPeerPhysicalPath(env, peer.paths[i]);
+ appendItemToArrayList(env, arrayObject, path);
+ }
+
+ env->SetObjectField(peerClass, pathsField, arrayObject);
+
+ return peerObject;
+}
+
+jobject newNetworkConfig(JNIEnv *env, const ZT1_VirtualNetworkConfig &vnetConfig)
+{
+ jclass vnetConfigClass = NULL;
+ jmethodID vnetConfig_constructor = NULL;
+ jfieldID nwidField = NULL;
+ jfieldID macField = NULL;
+ jfieldID nameField = NULL;
+ jfieldID statusField = NULL;
+ jfieldID typeField = NULL;
+ jfieldID mtuField = NULL;
+ jfieldID dhcpField = NULL;
+ jfieldID bridgeField = NULL;
+ jfieldID broadcastEnabledField = NULL;
+ jfieldID portErrorField = NULL;
+ jfieldID enabledField = NULL;
+ jfieldID netconfRevisionField = NULL;
+ jfieldID multicastSubscriptionsField = NULL;
+ jfieldID assignedAddressesField = NULL;
+
+ vnetConfigClass = cache.findClass("com/zerotier/sdk/VirtualNetworkConfig");
+ if(vnetConfigClass == NULL)
+ {
+ LOGE("Couldn't find com.zerotier.sdk.VirtualNetworkConfig");
+ return NULL;
+ }
+
+ vnetConfig_constructor = cache.findMethod(
+ vnetConfigClass, "<init>", "()V");
+ if(vnetConfig_constructor == NULL)
+ {
+ LOGE("Couldn't find VirtualNetworkConfig Constructor");
+ return NULL;
+ }
+
+ jobject vnetConfigObj = env->NewObject(vnetConfigClass, vnetConfig_constructor);
+ if(vnetConfigObj == NULL)
+ {
+ LOGE("Error creating new VirtualNetworkConfig object");
+ return NULL;
+ }
+
+ nwidField = cache.findField(vnetConfigClass, "nwid", "J");
+ if(nwidField == NULL)
+ {
+ LOGE("Error getting nwid field");
+ return NULL;
+ }
+
+ macField = cache.findField(vnetConfigClass, "mac", "J");
+ if(macField == NULL)
+ {
+ LOGE("Error getting mac field");
+ return NULL;
+ }
+
+ nameField = cache.findField(vnetConfigClass, "name", "Ljava/lang/String;");
+ if(nameField == NULL)
+ {
+ LOGE("Error getting name field");
+ return NULL;
+ }
+
+ statusField = cache.findField(vnetConfigClass, "status", "Lcom/zerotier/sdk/VirtualNetworkStatus;");
+ if(statusField == NULL)
+ {
+ LOGE("Error getting status field");
+ return NULL;
+ }
+
+ typeField = cache.findField(vnetConfigClass, "type", "Lcom/zerotier/sdk/VirtualNetworkType;");
+ if(typeField == NULL)
+ {
+ LOGE("Error getting type field");
+ return NULL;
+ }
+
+ mtuField = cache.findField(vnetConfigClass, "mtu", "I");
+ if(mtuField == NULL)
+ {
+ LOGE("Error getting mtu field");
+ return NULL;
+ }
+
+ dhcpField = cache.findField(vnetConfigClass, "dhcp", "Z");
+ if(dhcpField == NULL)
+ {
+ LOGE("Error getting dhcp field");
+ return NULL;
+ }
+
+ bridgeField = cache.findField(vnetConfigClass, "bridge", "Z");
+ if(bridgeField == NULL)
+ {
+ LOGE("Error getting bridge field");
+ return NULL;
+ }
+
+ broadcastEnabledField = cache.findField(vnetConfigClass, "broadcastEnabled", "Z");
+ if(broadcastEnabledField == NULL)
+ {
+ LOGE("Error getting broadcastEnabled field");
+ return NULL;
+ }
+
+ portErrorField = cache.findField(vnetConfigClass, "portError", "I");
+ if(portErrorField == NULL)
+ {
+ LOGE("Error getting portError field");
+ return NULL;
+ }
+
+ enabledField = cache.findField(vnetConfigClass, "enabled", "Z");
+ if(enabledField == NULL)
+ {
+ LOGE("Error getting enabled field");
+ return NULL;
+ }
+
+ netconfRevisionField = cache.findField(vnetConfigClass, "netconfRevision", "J");
+ if(netconfRevisionField == NULL)
+ {
+ LOGE("Error getting netconfRevision field");
+ return NULL;
+ }
+
+ multicastSubscriptionsField = cache.findField(vnetConfigClass, "multicastSubscriptions", "Ljava/util/ArrayList;");
+ if(multicastSubscriptionsField == NULL)
+ {
+ LOGE("Error getting multicastSubscriptions field");
+ return NULL;
+ }
+
+ assignedAddressesField = cache.findField(vnetConfigClass, "assignedAddresses", "Ljava/util/ArrayList;");
+ if(assignedAddressesField == NULL)
+ {
+ LOGE("Error getting assignedAddresses field");
+ return NULL;
+ }
+
+ env->SetLongField(vnetConfigObj, nwidField, vnetConfig.nwid);
+ env->SetLongField(vnetConfigObj, macField, vnetConfig.mac);
+ jstring nameStr = env->NewStringUTF(vnetConfig.name);
+ if(nameStr == NULL)
+ {
+ return NULL; // out of memory
+ }
+ env->SetObjectField(vnetConfigObj, nameField, nameStr);
+
+ jobject statusObject = createVirtualNetworkStatus(env, vnetConfig.status);
+ if(statusObject == NULL)
+ {
+ return NULL;
+ }
+ env->SetObjectField(vnetConfigObj, statusField, statusObject);
+
+ jobject typeObject = createVirtualNetworkType(env, vnetConfig.type);
+ if(typeObject == NULL)
+ {
+ return NULL;
+ }
+ env->SetObjectField(vnetConfigObj, typeField, typeObject);
+
+ env->SetIntField(vnetConfigObj, mtuField, vnetConfig.mtu);
+ env->SetBooleanField(vnetConfigObj, dhcpField, vnetConfig.dhcp);
+ env->SetBooleanField(vnetConfigObj, bridgeField, vnetConfig.bridge);
+ env->SetBooleanField(vnetConfigObj, broadcastEnabledField, vnetConfig.broadcastEnabled);
+ env->SetIntField(vnetConfigObj, portErrorField, vnetConfig.portError);
+
+
+ jobject mcastSubsArrayObj = newArrayList(env);
+ for(unsigned int i = 0; i < vnetConfig.multicastSubscriptionCount; ++i)
+ {
+ jobject mcastObj = newMulticastGroup(env, vnetConfig.multicastSubscriptions[i]);
+ appendItemToArrayList(env, mcastSubsArrayObj, mcastObj);
+ }
+ env->SetObjectField(vnetConfigObj, multicastSubscriptionsField, mcastSubsArrayObj);
+
+
+ jobject assignedAddrArrayObj = newArrayList(env);
+ for(unsigned int i = 0; i < vnetConfig.assignedAddressCount; ++i)
+ {
+ jobject inetAddrObj = newInetAddress(env, vnetConfig.assignedAddresses[i]);
+ appendItemToArrayList(env, assignedAddrArrayObj, inetAddrObj);
+ }
+
+ env->SetObjectField(vnetConfigObj, assignedAddressesField, assignedAddrArrayObj);
+
+ return vnetConfigObj;
+}
+
+jobject newVersion(JNIEnv *env, int major, int minor, int rev, long featureFlags)
+{
+ // create a com.zerotier.sdk.Version object
+ jclass versionClass = NULL;
+ jmethodID versionConstructor = NULL;
+
+ versionClass = cache.findClass("com/zerotier/sdk/Version");
+ if(versionClass == NULL)
+ {
+ return NULL;
+ }
+
+ versionConstructor = cache.findMethod(
+ versionClass, "<init>", "()V");
+ if(versionConstructor == NULL)
+ {
+ return NULL;
+ }
+
+ jobject versionObj = env->NewObject(versionClass, versionConstructor);
+ if(versionObj == NULL)
+ {
+ return NULL;
+ }
+
+ // copy data to Version object
+ jfieldID majorField = NULL;
+ jfieldID minorField = NULL;
+ jfieldID revisionField = NULL;
+ jfieldID featureFlagsField = NULL;
+
+ majorField = cache.findField(versionClass, "major", "I");
+ if(majorField == NULL)
+ {
+ return NULL;
+ }
+
+ minorField = cache.findField(versionClass, "minor", "I");
+ if(minorField == NULL)
+ {
+ return NULL;
+ }
+
+ revisionField = cache.findField(versionClass, "revision", "I");
+ if(revisionField == NULL)
+ {
+ return NULL;
+ }
+
+ featureFlagsField = cache.findField(versionClass, "featureFlags", "J");
+ if(featureFlagsField == NULL)
+ {
+ return NULL;
+ }
+
+ env->SetIntField(versionObj, majorField, (jint)major);
+ env->SetIntField(versionObj, minorField, (jint)minor);
+ env->SetIntField(versionObj, revisionField, (jint)rev);
+ env->SetLongField(versionObj, featureFlagsField, (jlong)featureFlags);
+
+ return versionObj;
+}
+
+#ifdef __cplusplus
+}
+#endif \ No newline at end of file
diff --git a/java/jni/ZT1_jniutils.h b/java/jni/ZT1_jniutils.h
new file mode 100644
index 00000000..d8baf85d
--- /dev/null
+++ b/java/jni/ZT1_jniutils.h
@@ -0,0 +1,50 @@
+#ifndef ZT1_jniutils_h_
+#define ZT1_jniutils_h_
+#include <stdio.h>
+#include <jni.h>
+#include <ZeroTierOne.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define LOG_TAG "ZeroTierOneJNI"
+
+#if __ANDROID__
+#include <android/log.h>
+#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
+#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
+#else
+#define LOGI(...) fprintf(stdout, __VA_ARGS__)
+#define LOGD(...) fprintf(stdout, __VA_ARGS__)
+#define LOGE(...) fprintf(stdout, __VA_ARGS__)
+#endif
+
+jobject createResultObject(JNIEnv *env, ZT1_ResultCode code);
+jobject createVirtualNetworkStatus(JNIEnv *env, ZT1_VirtualNetworkStatus status);
+jobject createVirtualNetworkType(JNIEnv *env, ZT1_VirtualNetworkType type);
+jobject createEvent(JNIEnv *env, ZT1_Event event);
+jobject createPeerRole(JNIEnv *env, ZT1_PeerRole role);
+jobject createVirtualNetworkConfigOperation(JNIEnv *env, ZT1_VirtualNetworkConfigOperation op);
+
+jobject newArrayList(JNIEnv *env);
+bool appendItemToArrayList(JNIEnv *env, jobject array, jobject object);
+
+jobject newInetSocketAddress(JNIEnv *env, const sockaddr_storage &addr);
+jobject newInetAddress(JNIEnv *env, const sockaddr_storage &addr);
+
+jobject newMulticastGroup(JNIEnv *env, const ZT1_MulticastGroup &mc);
+
+jobject newPeer(JNIEnv *env, const ZT1_Peer &peer);
+jobject newPeerPhysicalPath(JNIEnv *env, const ZT1_PeerPhysicalPath &ppp);
+
+jobject newNetworkConfig(JNIEnv *env, const ZT1_VirtualNetworkConfig &config);
+
+jobject newVersion(JNIEnv *env, int major, int minor, int rev, long featureFlags);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif \ No newline at end of file
diff --git a/java/jni/com_zerotierone_sdk_Node.cpp b/java/jni/com_zerotierone_sdk_Node.cpp
new file mode 100644
index 00000000..55eec9a6
--- /dev/null
+++ b/java/jni/com_zerotierone_sdk_Node.cpp
@@ -0,0 +1,1201 @@
+/*
+ * 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 "com_zerotierone_sdk_Node.h"
+#include "ZT1_jniutils.h"
+#include "ZT1_jnicache.h"
+
+#include <ZeroTierOne.h>
+
+#include <map>
+#include <string>
+#include <assert.h>
+#include <string.h>
+
+// global static JNI Cache Object
+JniCache cache;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+namespace {
+ struct JniRef
+ {
+ JniRef()
+ : jvm(NULL)
+ , node(NULL)
+ , dataStoreGetListener(NULL)
+ , dataStorePutListener(NULL)
+ , packetSender(NULL)
+ , eventListener(NULL)
+ , frameListener(NULL)
+ , configListener(NULL)
+ {}
+
+ ~JniRef()
+ {
+ JNIEnv *env = NULL;
+ jvm->GetEnv((void**)&env, JNI_VERSION_1_6);
+
+ env->DeleteGlobalRef(dataStoreGetListener);
+ env->DeleteGlobalRef(dataStorePutListener);
+ env->DeleteGlobalRef(packetSender);
+ env->DeleteGlobalRef(eventListener);
+ env->DeleteGlobalRef(frameListener);
+ env->DeleteGlobalRef(configListener);
+ }
+
+ uint64_t id;
+
+ JavaVM *jvm;
+
+ ZT1_Node *node;
+
+ jobject dataStoreGetListener;
+ jobject dataStorePutListener;
+ jobject packetSender;
+ jobject eventListener;
+ jobject frameListener;
+ jobject configListener;
+ };
+
+
+ int VirtualNetworkConfigFunctionCallback(
+ ZT1_Node *node,
+ void *userData,
+ uint64_t nwid,
+ enum ZT1_VirtualNetworkConfigOperation operation,
+ const ZT1_VirtualNetworkConfig *config)
+ {
+ JniRef *ref = (JniRef*)userData;
+ JNIEnv *env = NULL;
+ ref->jvm->GetEnv((void**)&env, JNI_VERSION_1_6);
+
+ jclass configListenerClass = env->GetObjectClass(ref->configListener);
+ if(configListenerClass == NULL)
+ {
+ LOGE("Couldn't find class for VirtualNetworkConfigListener instance");
+ return -1;
+ }
+
+ jmethodID configListenerCallbackMethod = cache.findMethod(configListenerClass,
+ "onNetworkConfigurationUpdated",
+ "(JLcom/zerotier/sdk/VirtualNetworkConfigOperation;Lcom/zerotier/sdk/VirtualNetworkConfig;)I");
+ if(configListenerCallbackMethod == NULL)
+ {
+ LOGE("Couldn't find onVirtualNetworkFrame() method");
+ return -2;
+ }
+
+ jobject operationObject = createVirtualNetworkConfigOperation(env, operation);
+ if(operationObject == NULL)
+ {
+ LOGE("Error creating VirtualNetworkConfigOperation object");
+ return -3;
+ }
+
+ jobject networkConfigObject = newNetworkConfig(env, *config);
+ if(networkConfigObject == NULL)
+ {
+ LOGE("Error creating VirtualNetworkConfig object");
+ return -4;
+ }
+
+ return env->CallIntMethod(
+ ref->configListener,
+ configListenerCallbackMethod,
+ (jlong)nwid, operationObject, networkConfigObject);
+ }
+
+ void VirtualNetworkFrameFunctionCallback(ZT1_Node *node,void *userData,
+ uint64_t nwid,
+ uint64_t sourceMac,
+ uint64_t destMac,
+ unsigned int etherType,
+ unsigned int vlanid,
+ const void *frameData,
+ unsigned int frameLength)
+ {
+ JniRef *ref = (JniRef*)userData;
+ assert(ref->node == node);
+ JNIEnv *env = NULL;
+ ref->jvm->GetEnv((void**)&env, JNI_VERSION_1_6);
+
+
+ jclass frameListenerClass = env->GetObjectClass(ref->frameListener);
+ if(frameListenerClass == NULL)
+ {
+ LOGE("Couldn't find class for VirtualNetworkFrameListener instance");
+ return;
+ }
+
+ jmethodID frameListenerCallbackMethod = cache.findMethod(
+ frameListenerClass,
+ "onVirtualNetworkFrame", "(JJJJJ[B)V");
+ if(frameListenerCallbackMethod == NULL)
+ {
+ LOGE("Couldn't find onVirtualNetworkFrame() method");
+ return;
+ }
+
+ jbyteArray dataArray = env->NewByteArray(frameLength);
+ env->SetByteArrayRegion(dataArray, 0, frameLength, (jbyte*)frameData);
+
+ env->CallVoidMethod(ref->frameListener, frameListenerCallbackMethod, nwid, sourceMac, destMac, etherType, vlanid, dataArray);
+ }
+
+
+ void EventCallback(ZT1_Node *node,void *userData,enum ZT1_Event event, const void *data)
+ {
+ JniRef *ref = (JniRef*)userData;
+ assert(ref->node == node);
+ JNIEnv *env = NULL;
+ ref->jvm->GetEnv((void**)&env, JNI_VERSION_1_6);
+
+
+ jclass eventListenerClass = env->GetObjectClass(ref->eventListener);
+ if(eventListenerClass == NULL)
+ {
+ LOGE("Couldn't class for EventListener instance");
+ return;
+ }
+
+ jmethodID onEventMethod = cache.findMethod(eventListenerClass,
+ "onEvent", "(Lcom/zerotier/sdk/Event;)V");
+ if(onEventMethod == NULL)
+ {
+ LOGE("Couldn't find onEvent method");
+ 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,
+ "onTrace", "(Ljava/lang/String;)V");
+ if(onTraceMethod == NULL)
+ {
+ LOGE("Couldn't find onTrace method");
+ return;
+ }
+
+ jobject eventObject = createEvent(env, event);
+ if(eventObject == NULL)
+ {
+ return;
+ }
+
+ switch(event)
+ {
+ case ZT1_EVENT_UP:
+ case ZT1_EVENT_OFFLINE:
+ case ZT1_EVENT_ONLINE:
+ case ZT1_EVENT_DOWN:
+ case ZT1_EVENT_FATAL_ERROR_IDENTITY_COLLISION:
+ {
+ // call onEvent()
+ env->CallVoidMethod(ref->eventListener, onEventMethod, eventObject);
+ }
+ break;
+ case ZT1_EVENT_SAW_MORE_RECENT_VERSION:
+ {
+ // 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);
+ }
+ }
+ break;
+ case ZT1_EVENT_AUTHENTICATION_FAILURE:
+ case ZT1_EVENT_INVALID_PACKET:
+ {
+ // call onNetworkError()
+ if(data != NULL)
+ {
+ sockaddr_storage *addr = (sockaddr_storage*)data;
+ jobject addressObj = newInetSocketAddress(env, *addr);
+ env->CallVoidMethod(ref->eventListener, onNetworkErrorMethod, addressObj);
+ }
+ }
+ case ZT1_EVENT_TRACE:
+ {
+ // call onTrace()
+ if(data != NULL)
+ {
+ const char* message = (const char*)data;
+ jstring messageStr = env->NewStringUTF(message);
+ env->CallVoidMethod(ref->eventListener, onTraceMethod, messageStr);
+ }
+ }
+ break;
+ }
+ }
+
+ long DataStoreGetFunction(ZT1_Node *node,void *userData,
+ const char *objectName,
+ void *buffer,
+ unsigned long bufferSize,
+ unsigned long bufferIndex,
+ unsigned long *out_objectSize)
+ {
+ JniRef *ref = (JniRef*)userData;
+ JNIEnv *env = NULL;
+ ref->jvm->GetEnv((void**)&env, JNI_VERSION_1_6);
+
+ jclass dataStoreGetClass = env->GetObjectClass(ref->dataStoreGetListener);
+ if(dataStoreGetClass == NULL)
+ {
+ LOGE("Couldn't find class for DataStoreGetListener instance");
+ return -2;
+ }
+
+ jmethodID dataStoreGetCallbackMethod = cache.findMethod(
+ dataStoreGetClass,
+ "onDataStoreGet",
+ "(Ljava/lang/String;[BJ[J)J");
+ if(dataStoreGetCallbackMethod == NULL)
+ {
+ LOGE("Couldn't find onDataStoreGet method");
+ return -2;
+ }
+
+ jstring nameStr = env->NewStringUTF(objectName);
+ if(nameStr == NULL)
+ {
+ LOGE("Error creating name string object");
+ return -2; // out of memory
+ }
+
+ jbyteArray bufferObj = env->NewByteArray(bufferSize);
+ if(bufferObj == NULL)
+ {
+ LOGE("Error creating byte[] buffer of size: %lu", bufferSize);
+ return -2;
+ }
+
+ jlongArray objectSizeObj = env->NewLongArray(1);
+ if(objectSizeObj == NULL)
+ {
+ LOGE("Error creating long[1] array for actual object size");
+ return -2; // couldn't create long[1] array
+ }
+
+ LOGD("Calling onDataStoreGet(%s, %p, %lu, %p)",
+ objectName, buffer, bufferIndex, objectSizeObj);
+
+ long retval = (long)env->CallLongMethod(
+ ref->dataStoreGetListener, dataStoreGetCallbackMethod,
+ nameStr, bufferObj, (jlong)bufferIndex, objectSizeObj);
+
+ if(retval > 0)
+ {
+ env->GetByteArrayRegion(bufferObj, 0, bufferSize, (jbyte*)buffer);
+ env->GetLongArrayRegion(objectSizeObj, 0, 1, (jlong*)out_objectSize);
+ env->ReleaseByteArrayElements(bufferObj, (jbyte*)buffer, 0);
+ env->ReleaseLongArrayElements(objectSizeObj, (jlong*)out_objectSize, 0);
+ }
+
+ LOGI("Out Object Size: %lu", *out_objectSize);
+
+ return retval;
+ }
+
+ int DataStorePutFunction(ZT1_Node *node,void *userData,
+ const char *objectName,
+ const void *buffer,
+ unsigned long bufferSize,
+ int secure)
+ {
+ JniRef *ref = (JniRef*)userData;
+ JNIEnv *env = NULL;
+ ref->jvm->GetEnv((void**)&env, JNI_VERSION_1_6);
+
+
+ jclass dataStorePutClass = env->GetObjectClass(ref->dataStorePutListener);
+ if(dataStorePutClass == NULL)
+ {
+ LOGE("Couldn't find class for DataStorePutListener instance");
+ return -1;
+ }
+
+ jmethodID dataStorePutCallbackMethod = cache.findMethod(
+ dataStorePutClass,
+ "onDataStorePut",
+ "(Ljava/lang/String;[BZ)I");
+ if(dataStorePutCallbackMethod == NULL)
+ {
+ LOGE("Couldn't find onDataStorePut method");
+ return -2;
+ }
+
+ jmethodID deleteMethod = cache.findMethod(dataStorePutClass,
+ "onDelete", "(Ljava/lang/String;)I");
+ if(deleteMethod == NULL)
+ {
+ LOGE("Couldn't find onDelete method");
+ return -3;
+ }
+
+ jstring nameStr = env->NewStringUTF(objectName);
+
+ if(buffer == NULL)
+ {
+ // delete operation
+ return env->CallIntMethod(
+ ref->dataStorePutListener, deleteMethod, nameStr);
+ }
+ else
+ {
+ // set operation
+ jbyteArray bufferObj = env->NewByteArray(bufferSize);
+ env->SetByteArrayRegion(bufferObj, 0, bufferSize, (jbyte*)buffer);
+ bool bsecure = secure != 0;
+
+
+ return env->CallIntMethod(ref->dataStorePutListener,
+ dataStorePutCallbackMethod,
+ nameStr, bufferObj, bsecure);
+ }
+ }
+
+ int WirePacketSendFunction(ZT1_Node *node,void *userData,\
+ const struct sockaddr_storage *address,
+ unsigned int linkDesparation,
+ const void *buffer,
+ unsigned int bufferSize)
+ {
+ JniRef *ref = (JniRef*)userData;
+ assert(ref->node == node);
+
+ JNIEnv *env = NULL;
+ ref->jvm->GetEnv((void**)&env, JNI_VERSION_1_6);
+
+
+ jclass packetSenderClass = env->GetObjectClass(ref->packetSender);
+ if(packetSenderClass == NULL)
+ {
+ LOGE("Couldn't find class for PacketSender instance");
+ return -1;
+ }
+
+ jmethodID packetSenderCallbackMethod = cache.findMethod(packetSenderClass,
+ "onSendPacketRequested", "(Ljava/net/InetSocketAddress;I[B)I");
+ if(packetSenderCallbackMethod == NULL)
+ {
+ LOGE("Couldn't find onSendPacketRequested method");
+ return -2;
+ }
+
+ jobject addressObj = newInetSocketAddress(env, *address);
+ jbyteArray bufferObj = env->NewByteArray(bufferSize);
+ env->SetByteArrayRegion(bufferObj, 0, bufferSize, (jbyte*)buffer);
+ return env->CallIntMethod(ref->packetSender, packetSenderCallbackMethod, addressObj, linkDesparation, bufferObj);
+ }
+
+ typedef std::map<uint64_t, JniRef*> NodeMap;
+ static NodeMap nodeMap;
+
+ ZT1_Node* findNode(uint64_t nodeId)
+ {
+ NodeMap::iterator found = nodeMap.find(nodeId);
+ if(found != nodeMap.end())
+ {
+ JniRef *ref = found->second;
+ return ref->node;
+ }
+ return NULL;
+ }
+}
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
+{
+ cache.setJavaVM(vm);
+ return JNI_VERSION_1_6;
+}
+
+JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved)
+{
+ cache.clearCache();
+}
+
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: node_init
+ * Signature: (J)Lcom/zerotier/sdk/ResultCode;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_node_1init(
+ JNIEnv *env, jobject obj, jlong now)
+{
+ LOGD("Creating ZT1_Node struct");
+ jobject resultObject = createResultObject(env, ZT1_RESULT_OK);
+
+ ZT1_Node *node;
+ JniRef *ref = new JniRef;
+ ref->id = (uint64_t)now;
+ env->GetJavaVM(&ref->jvm);
+
+ jclass cls = env->GetObjectClass(obj);
+ jfieldID fid = cache.findField(
+ cls, "getListener", "Lcom/zerotier/sdk/DataStoreGetListener;");
+
+ if(fid == NULL)
+ {
+ return NULL; // exception already thrown
+ }
+
+ jobject tmp = env->GetObjectField(obj, fid);
+ if(tmp == NULL)
+ {
+ return NULL;
+ }
+ ref->dataStoreGetListener = env->NewGlobalRef(tmp);
+
+ fid = cache.findField(
+ cls, "putListener", "Lcom/zerotier/sdk/DataStorePutListener;");
+
+ if(fid == NULL)
+ {
+ return NULL; // exception already thrown
+ }
+
+ tmp = env->GetObjectField(obj, fid);
+ if(tmp == NULL)
+ {
+ return NULL;
+ }
+ ref->dataStorePutListener = env->NewGlobalRef(tmp);
+
+ fid = cache.findField(
+ cls, "sender", "Lcom/zerotier/sdk/PacketSender;");
+ if(fid == NULL)
+ {
+ return NULL; // exception already thrown
+ }
+
+ tmp = env->GetObjectField(obj, fid);
+ if(tmp == NULL)
+ {
+ return NULL;
+ }
+ ref->packetSender = env->NewGlobalRef(tmp);
+
+ fid = cache.findField(
+ cls, "frameListener", "Lcom/zerotier/sdk/VirtualNetworkFrameListener;");
+ if(fid == NULL)
+ {
+ return NULL; // exception already thrown
+ }
+
+ tmp = env->GetObjectField(obj, fid);
+ if(tmp == NULL)
+ {
+ return NULL;
+ }
+ ref->frameListener = env->NewGlobalRef(tmp);
+
+ fid = cache.findField(
+ cls, "configListener", "Lcom/zerotier/sdk/VirtualNetworkConfigListener;");
+ if(fid == NULL)
+ {
+ return NULL; // exception already thrown
+ }
+
+ tmp = env->GetObjectField(obj, fid);
+ if(tmp == NULL)
+ {
+ return NULL;
+ }
+ ref->configListener = env->NewGlobalRef(tmp);
+
+ fid = cache.findField(
+ cls, "eventListener", "Lcom/zerotier/sdk/EventListener;");
+ if(fid == NULL)
+ {
+ return NULL;
+ }
+
+ tmp = env->GetObjectField(obj, fid);
+ if(tmp == NULL)
+ {
+ return NULL;
+ }
+ ref->eventListener = env->NewGlobalRef(tmp);
+
+ ZT1_ResultCode rc = ZT1_Node_new(
+ &node,
+ ref,
+ (uint64_t)now,
+ &DataStoreGetFunction,
+ &DataStorePutFunction,
+ &WirePacketSendFunction,
+ &VirtualNetworkFrameFunctionCallback,
+ &VirtualNetworkConfigFunctionCallback,
+ &EventCallback);
+
+ if(rc != ZT1_RESULT_OK)
+ {
+ LOGE("Error creating Node: %d", rc);
+ resultObject = createResultObject(env, rc);
+ if(node)
+ {
+ ZT1_Node_delete(node);
+ node = NULL;
+ }
+ delete ref;
+ ref = NULL;
+ return resultObject;
+ }
+
+ ref->node = node;
+ nodeMap.insert(std::make_pair(ref->id, ref));
+
+ return resultObject;
+}
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: node_delete
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_zerotier_sdk_Node_node_1delete(
+ JNIEnv *env, jobject obj, jlong id)
+{
+ LOGD("Destroying ZT1_Node struct");
+ uint64_t nodeId = (uint64_t)id;
+
+ NodeMap::iterator found = nodeMap.find(nodeId);
+ if(found != nodeMap.end())
+ {
+ JniRef *ref = found->second;
+ nodeMap.erase(found);
+
+ ZT1_Node_delete(ref->node);
+
+ delete ref;
+ ref = NULL;
+ }
+ else
+ {
+ LOGE("Attempted to delete a node that doesn't exist!");
+ }
+}
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: processVirtualNetworkFrame
+ * Signature: (JJJJJII[B[J)Lcom/zerotier/sdk/ResultCode;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processVirtualNetworkFrame(
+ JNIEnv *env, jobject obj,
+ jlong id,
+ jlong in_now,
+ jlong in_nwid,
+ jlong in_sourceMac,
+ jlong in_destMac,
+ jint in_etherType,
+ jint in_vlanId,
+ jbyteArray in_frameData,
+ jlongArray out_nextBackgroundTaskDeadline)
+{
+ uint64_t nodeId = (uint64_t) id;
+
+ ZT1_Node *node = findNode(nodeId);
+ if(node == NULL)
+ {
+ // cannot find valid node. We should never get here.
+ return createResultObject(env, ZT1_RESULT_FATAL_ERROR_INTERNAL);
+ }
+
+ unsigned int nbtd_len = env->GetArrayLength(out_nextBackgroundTaskDeadline);
+ if(nbtd_len < 1)
+ {
+ // array for next background task length has 0 elements!
+ return createResultObject(env, ZT1_RESULT_FATAL_ERROR_INTERNAL);
+ }
+
+ uint64_t now = (uint64_t)in_now;
+ uint64_t nwid = (uint64_t)in_nwid;
+ uint64_t sourceMac = (uint64_t)in_sourceMac;
+ uint64_t destMac = (uint64_t)in_destMac;
+ unsigned int etherType = (unsigned int)in_etherType;
+ unsigned int vlanId = (unsigned int)in_vlanId;
+
+ unsigned int frameLength = env->GetArrayLength(in_frameData);
+ jbyte *frameData =env->GetByteArrayElements(in_frameData, NULL);
+
+ uint64_t nextBackgroundTaskDeadline = 0;
+
+ ZT1_ResultCode rc = ZT1_Node_processVirtualNetworkFrame(
+ node,
+ now,
+ nwid,
+ sourceMac,
+ destMac,
+ etherType,
+ vlanId,
+ (const void*)frameData,
+ frameLength,
+ &nextBackgroundTaskDeadline);
+
+ jlong *outDeadline = env->GetLongArrayElements(out_nextBackgroundTaskDeadline, NULL);
+ outDeadline[0] = (jlong)nextBackgroundTaskDeadline;
+ env->ReleaseLongArrayElements(out_nextBackgroundTaskDeadline, outDeadline, 0);
+
+ env->ReleaseByteArrayElements(in_frameData, frameData, 0);
+
+ return createResultObject(env, rc);
+}
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: processWirePacket
+ * Signature: (JJLjava/net/InetSocketAddress;I[B[J)Lcom/zerotier/sdk/ResultCode;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processWirePacket(
+ JNIEnv *env, jobject obj,
+ jlong id,
+ jlong in_now,
+ jobject in_remoteAddress,
+ jint in_linkDesparation,
+ jbyteArray in_packetData,
+ jlongArray out_nextBackgroundTaskDeadline)
+{
+ uint64_t nodeId = (uint64_t) id;
+ ZT1_Node *node = findNode(nodeId);
+ if(node == NULL)
+ {
+ // cannot find valid node. We should never get here.
+ return createResultObject(env, ZT1_RESULT_FATAL_ERROR_INTERNAL);
+ }
+
+ unsigned int nbtd_len = env->GetArrayLength(out_nextBackgroundTaskDeadline);
+ if(nbtd_len < 1)
+ {
+ return createResultObject(env, ZT1_RESULT_FATAL_ERROR_INTERNAL);
+ }
+
+ uint64_t now = (uint64_t)in_now;
+ unsigned int linkDesparation = (unsigned int)in_linkDesparation;
+
+ // get the java.net.InetSocketAddress class and getAddress() method
+ jclass inetAddressClass = cache.findClass("java/net/InetAddress");
+ if(inetAddressClass == NULL)
+ {
+ // can't find java.net.InetAddress
+ return createResultObject(env, ZT1_RESULT_FATAL_ERROR_INTERNAL);
+ }
+
+ jmethodID getAddressMethod = cache.findMethod(
+ inetAddressClass, "getAddress", "()[B");
+ if(getAddressMethod == NULL)
+ {
+ // cant find InetAddress.getAddres()
+ return createResultObject(env, ZT1_RESULT_FATAL_ERROR_INTERNAL);
+ }
+
+ jclass InetSocketAddressClass = cache.findClass("java/net/InetSocketAddress");
+ if(InetSocketAddressClass == NULL)
+ {
+ return createResultObject(env, ZT1_RESULT_FATAL_ERROR_INTERNAL);
+ }
+
+ jmethodID inetSockGetAddressMethod = cache.findMethod(
+ InetSocketAddressClass, "getAddress", "()Ljava/net/InetAddress;");
+
+ jobject addrObject = env->CallObjectMethod(in_remoteAddress, inetSockGetAddressMethod);
+
+ if(addrObject == NULL)
+ {
+ return createResultObject(env, ZT1_RESULT_FATAL_ERROR_INTERNAL);
+ }
+
+ // Call InetAddress.getAddress()
+ jbyteArray addressArray = (jbyteArray)env->CallObjectMethod(addrObject, getAddressMethod);
+ if(addressArray == NULL)
+ {
+ // unable to call getAddress()
+ return createResultObject(env, ZT1_RESULT_FATAL_ERROR_INTERNAL);
+ }
+
+ unsigned int addrSize = env->GetArrayLength(addressArray);
+ // get the address bytes
+ jbyte *addr = env->GetByteArrayElements(addressArray, NULL);
+
+
+ sockaddr_storage remoteAddress = {};
+
+ if(addrSize == 16)
+ {
+ // IPV6 address
+ sockaddr_in6 ipv6 = {};
+ ipv6.sin6_family = AF_INET6;
+ memcpy(ipv6.sin6_addr.s6_addr, addr, 16);
+ memcpy(&remoteAddress, &ipv6, sizeof(sockaddr_in6));
+ }
+ else if(addrSize == 4)
+ {
+ // IPV4 address
+ sockaddr_in ipv4 = {};
+ ipv4.sin_family = AF_INET;
+ memcpy(&ipv4.sin_addr, addr, 4);
+ memcpy(&remoteAddress, &ipv4, sizeof(sockaddr_in));
+ }
+ else
+ {
+ // unknown address type
+ env->ReleaseByteArrayElements(addressArray, addr, 0);
+ return createResultObject(env, ZT1_RESULT_FATAL_ERROR_INTERNAL);
+ }
+
+
+ unsigned int packetLength = env->GetArrayLength(in_packetData);
+ jbyte *packetData = env->GetByteArrayElements(in_packetData, NULL);
+
+ uint64_t nextBackgroundTaskDeadline = 0;
+
+ ZT1_ResultCode rc = ZT1_Node_processWirePacket(
+ node,
+ now,
+ &remoteAddress,
+ linkDesparation,
+ packetData,
+ packetLength,
+ &nextBackgroundTaskDeadline);
+
+ jlong *outDeadline = env->GetLongArrayElements(out_nextBackgroundTaskDeadline, NULL);
+ outDeadline[0] = (jlong)nextBackgroundTaskDeadline;
+ env->ReleaseLongArrayElements(out_nextBackgroundTaskDeadline, outDeadline, 0);
+
+ env->ReleaseByteArrayElements(addressArray, addr, 0);
+ env->ReleaseByteArrayElements(in_packetData, packetData, 0);
+
+ return createResultObject(env, rc);
+}
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: processBackgroundTasks
+ * Signature: (JJ[J)Lcom/zerotier/sdk/ResultCode;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processBackgroundTasks(
+ JNIEnv *env, jobject obj,
+ jlong id,
+ jlong in_now,
+ jlongArray out_nextBackgroundTaskDeadline)
+{
+ uint64_t nodeId = (uint64_t) id;
+ ZT1_Node *node = findNode(nodeId);
+ if(node == NULL)
+ {
+ // cannot find valid node. We should never get here.
+ return createResultObject(env, ZT1_RESULT_FATAL_ERROR_INTERNAL);
+ }
+
+ unsigned int nbtd_len = env->GetArrayLength(out_nextBackgroundTaskDeadline);
+ if(nbtd_len < 1)
+ {
+ return createResultObject(env, ZT1_RESULT_FATAL_ERROR_INTERNAL);
+ }
+
+ uint64_t now = (uint64_t)in_now;
+ uint64_t nextBackgroundTaskDeadline = 0;
+
+ ZT1_ResultCode rc = ZT1_Node_processBackgroundTasks(node, now, &nextBackgroundTaskDeadline);
+
+ jlong *outDeadline = env->GetLongArrayElements(out_nextBackgroundTaskDeadline, NULL);
+ outDeadline[0] = (jlong)nextBackgroundTaskDeadline;
+ env->ReleaseLongArrayElements(out_nextBackgroundTaskDeadline, outDeadline, 0);
+
+ return createResultObject(env, rc);
+}
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: join
+ * Signature: (JJ)Lcom/zerotier/sdk/ResultCode;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_join(
+ JNIEnv *env, jobject obj, jlong id, jlong in_nwid)
+{
+ uint64_t nodeId = (uint64_t) id;
+ ZT1_Node *node = findNode(nodeId);
+ if(node == NULL)
+ {
+ // cannot find valid node. We should never get here.
+ return createResultObject(env, ZT1_RESULT_FATAL_ERROR_INTERNAL);
+ }
+
+ uint64_t nwid = (uint64_t)in_nwid;
+
+ ZT1_ResultCode rc = ZT1_Node_join(node, nwid);
+
+ return createResultObject(env, rc);
+}
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: leave
+ * Signature: (JJ)Lcom/zerotier/sdk/ResultCode;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_leave(
+ JNIEnv *env, jobject obj, jlong id, jlong in_nwid)
+{
+ uint64_t nodeId = (uint64_t) id;
+ ZT1_Node *node = findNode(nodeId);
+ if(node == NULL)
+ {
+ // cannot find valid node. We should never get here.
+ return createResultObject(env, ZT1_RESULT_FATAL_ERROR_INTERNAL);
+ }
+
+ uint64_t nwid = (uint64_t)in_nwid;
+
+ ZT1_ResultCode rc = ZT1_Node_leave(node, nwid);
+
+ return createResultObject(env, rc);
+}
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: multicastSubscribe
+ * Signature: (JJJJ)Lcom/zerotier/sdk/ResultCode;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_multicastSubscribe(
+ JNIEnv *env, jobject obj,
+ jlong id,
+ jlong in_nwid,
+ jlong in_multicastGroup,
+ jlong in_multicastAdi)
+{
+ uint64_t nodeId = (uint64_t) id;
+ ZT1_Node *node = findNode(nodeId);
+ if(node == NULL)
+ {
+ // cannot find valid node. We should never get here.
+ return createResultObject(env, ZT1_RESULT_FATAL_ERROR_INTERNAL);
+ }
+
+ uint64_t nwid = (uint64_t)in_nwid;
+ uint64_t multicastGroup = (uint64_t)in_multicastGroup;
+ unsigned long multicastAdi = (unsigned long)in_multicastAdi;
+
+ ZT1_ResultCode rc = ZT1_Node_multicastSubscribe(
+ node, nwid, multicastGroup, multicastAdi);
+
+ return createResultObject(env, rc);
+}
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: multicastUnsubscribe
+ * Signature: (JJJJ)Lcom/zerotier/sdk/ResultCode;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_multicastUnsubscribe(
+ JNIEnv *env, jobject obj,
+ jlong id,
+ jlong in_nwid,
+ jlong in_multicastGroup,
+ jlong in_multicastAdi)
+{
+ uint64_t nodeId = (uint64_t) id;
+ ZT1_Node *node = findNode(nodeId);
+ if(node == NULL)
+ {
+ // cannot find valid node. We should never get here.
+ return createResultObject(env, ZT1_RESULT_FATAL_ERROR_INTERNAL);
+ }
+
+ uint64_t nwid = (uint64_t)in_nwid;
+ uint64_t multicastGroup = (uint64_t)in_multicastGroup;
+ unsigned long multicastAdi = (unsigned long)in_multicastAdi;
+
+ ZT1_ResultCode rc = ZT1_Node_multicastUnsubscribe(
+ node, nwid, multicastGroup, multicastAdi);
+
+ return createResultObject(env, rc);
+}
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: address
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_com_zerotier_sdk_Node_address(
+ JNIEnv *env , jobject obj, jlong id)
+{
+ uint64_t nodeId = (uint64_t) id;
+ ZT1_Node *node = findNode(nodeId);
+ if(node == NULL)
+ {
+ // cannot find valid node. We should never get here.
+ return 0;
+ }
+
+ uint64_t address = ZT1_Node_address(node);
+ return (jlong)address;
+}
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: status
+ * Signature: (J)Lcom/zerotier/sdk/NodeStatus;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_status
+ (JNIEnv *env, jobject obj, jlong id)
+{
+ uint64_t nodeId = (uint64_t) id;
+ ZT1_Node *node = findNode(nodeId);
+ if(node == NULL)
+ {
+ // cannot find valid node. We should never get here.
+ return 0;
+ }
+
+ jclass nodeStatusClass = NULL;
+ jmethodID nodeStatusConstructor = NULL;
+
+ // create a com.zerotier.sdk.NodeStatus object
+ nodeStatusClass = cache.findClass("com/zerotier/sdk/NodeStatus");
+ if(nodeStatusClass == NULL)
+ {
+ return NULL;
+ }
+
+ nodeStatusConstructor = cache.findMethod(
+ nodeStatusClass, "<init>", "()V");
+ if(nodeStatusConstructor == NULL)
+ {
+ return NULL;
+ }
+
+ jobject nodeStatusObj = env->NewObject(nodeStatusClass, nodeStatusConstructor);
+ if(nodeStatusObj == NULL)
+ {
+ return NULL;
+ }
+
+ ZT1_NodeStatus nodeStatus;
+ ZT1_Node_status(node, &nodeStatus);
+
+ jfieldID addressField = NULL;
+ jfieldID publicIdentityField = NULL;
+ jfieldID secretIdentityField = NULL;
+ jfieldID onlineField = NULL;
+
+ addressField = cache.findField(nodeStatusClass, "address", "J");
+ if(addressField == NULL)
+ {
+ return NULL;
+ }
+
+ publicIdentityField = cache.findField(nodeStatusClass, "publicIdentity", "Ljava/lang/String;");
+ if(publicIdentityField == NULL)
+ {
+ return NULL;
+ }
+
+ secretIdentityField = cache.findField(nodeStatusClass, "secretIdentity", "Ljava/lang/String;");
+ if(secretIdentityField == NULL)
+ {
+ return NULL;
+ }
+
+ onlineField = cache.findField(nodeStatusClass, "online", "Z");
+ if(onlineField == NULL)
+ {
+ return NULL;
+ }
+
+ env->SetLongField(nodeStatusObj, addressField, nodeStatus.address);
+
+ jstring pubIdentStr = env->NewStringUTF(nodeStatus.publicIdentity);
+ if(pubIdentStr == NULL)
+ {
+ return NULL; // out of memory
+ }
+ env->SetObjectField(nodeStatusObj, publicIdentityField, pubIdentStr);
+
+ jstring secIdentStr = env->NewStringUTF(nodeStatus.secretIdentity);
+ if(secIdentStr == NULL)
+ {
+ return NULL; // out of memory
+ }
+ env->SetObjectField(nodeStatusObj, secretIdentityField, secIdentStr);
+
+ env->SetBooleanField(nodeStatusObj, onlineField, nodeStatus.online);
+
+ return nodeStatusObj;
+}
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: networkConfig
+ * Signature: (J)Lcom/zerotier/sdk/VirtualNetworkConfig;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_networkConfig(
+ JNIEnv *env, jobject obj, jlong id, jlong nwid)
+{
+ uint64_t nodeId = (uint64_t) id;
+ ZT1_Node *node = findNode(nodeId);
+ if(node == NULL)
+ {
+ // cannot find valid node. We should never get here.
+ return 0;
+ }
+
+ ZT1_VirtualNetworkConfig *vnetConfig = ZT1_Node_networkConfig(node, nwid);
+
+ jobject vnetConfigObject = newNetworkConfig(env, *vnetConfig);
+
+ ZT1_Node_freeQueryResult(node, vnetConfig);
+
+ return vnetConfigObject;
+}
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: version
+ * Signature: (J)Lcom/zerotier/sdk/Version;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_version(
+ JNIEnv *env, jobject obj)
+{
+ int major = 0;
+ int minor = 0;
+ int revision = 0;
+ unsigned long featureFlags = 0;
+
+ ZT1_version(&major, &minor, &revision, &featureFlags);
+
+ return newVersion(env, major, minor, revision, featureFlags);
+}
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: peers
+ * Signature: (J)Ljava/util/ArrayList;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_peers(
+ JNIEnv *env, jobject obj, jlong id)
+{
+ uint64_t nodeId = (uint64_t) id;
+ ZT1_Node *node = findNode(nodeId);
+ if(node == NULL)
+ {
+ // cannot find valid node. We should never get here.
+ return 0;
+ }
+
+ ZT1_PeerList *peerList = ZT1_Node_peers(node);
+
+ if(peerList == NULL)
+ {
+ return NULL;
+ }
+
+ jobject peerListObject = newArrayList(env);
+ if(peerListObject == NULL)
+ {
+ ZT1_Node_freeQueryResult(node, peerList);
+ return NULL;
+ }
+
+ for(unsigned int i = 0; i < peerList->peerCount; ++i)
+ {
+ jobject peerObj = newPeer(env, peerList->peers[i]);
+ appendItemToArrayList(env, peerListObject, peerObj);
+ }
+
+ ZT1_Node_freeQueryResult(node, peerList);
+ peerList = NULL;
+
+ return peerListObject;
+}
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: networks
+ * Signature: (J)Ljava/util/ArrayList;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_networks(
+ JNIEnv *env, jobject obj, jlong id)
+{
+ uint64_t nodeId = (uint64_t) id;
+ ZT1_Node *node = findNode(nodeId);
+ if(node == NULL)
+ {
+ // cannot find valid node. We should never get here.
+ return 0;
+ }
+
+ ZT1_VirtualNetworkList *networkList = ZT1_Node_networks(node);
+ if(networkList == NULL)
+ {
+ return NULL;
+ }
+
+ jobject networkListObject = newArrayList(env);
+ if(networkListObject == NULL)
+ {
+ ZT1_Node_freeQueryResult(node, networkList);
+ return NULL;
+ }
+
+ for(unsigned int i = 0; i < networkList->networkCount; ++i)
+ {
+ jobject networkObject = newNetworkConfig(env, networkList->networks[i]);
+ appendItemToArrayList(env, networkListObject, networkObject);
+ }
+
+ ZT1_Node_freeQueryResult(node, networkList);
+
+ return networkListObject;
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif \ No newline at end of file
diff --git a/java/jni/com_zerotierone_sdk_Node.h b/java/jni/com_zerotierone_sdk_Node.h
new file mode 100644
index 00000000..a55c6702
--- /dev/null
+++ b/java/jni/com_zerotierone_sdk_Node.h
@@ -0,0 +1,133 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_zerotier_sdk_Node */
+
+#ifndef _Included_com_zerotierone_sdk_Node
+#define _Included_com_zerotierone_sdk_Node
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: node_init
+ * Signature: (J)Lcom/zerotier/sdk/ResultCode;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_node_1init
+ (JNIEnv *, jobject, jlong);
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: node_delete
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_zerotier_sdk_Node_node_1delete
+ (JNIEnv *, jobject, jlong);
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: processVirtualNetworkFrame
+ * Signature: (JJJJJII[B[J)Lcom/zerotier/sdk/ResultCode;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processVirtualNetworkFrame
+ (JNIEnv *, jobject, jlong, jlong, jlong, jlong, jlong, jint, jint, jbyteArray, jlongArray);
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: processWirePacket
+ * Signature: (JJLjava/net/InetSockAddress;I[B[J)Lcom/zerotier/sdk/ResultCode;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processWirePacket
+ (JNIEnv *, jobject, jlong, jlong, jobject, jint, jbyteArray, jlongArray);
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: processBackgroundTasks
+ * Signature: (JJ[J)Lcom/zerotier/sdk/ResultCode;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_processBackgroundTasks
+ (JNIEnv *, jobject, jlong, jlong, jlongArray);
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: join
+ * Signature: (JJ)Lcom/zerotier/sdk/ResultCode;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_join
+ (JNIEnv *, jobject, jlong, jlong);
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: leave
+ * Signature: (JJ)Lcom/zerotier/sdk/ResultCode;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_leave
+ (JNIEnv *, jobject, jlong, jlong);
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: multicastSubscribe
+ * Signature: (JJJJ)Lcom/zerotier/sdk/ResultCode;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_multicastSubscribe
+ (JNIEnv *, jobject, jlong, jlong, jlong, jlong);
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: multicastUnsubscribe
+ * Signature: (JJJJ)Lcom/zerotier/sdk/ResultCode;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_multicastUnsubscribe
+ (JNIEnv *, jobject, jlong, jlong, jlong, jlong);
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: address
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_com_zerotier_sdk_Node_address
+ (JNIEnv *, jobject, jlong);
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: status
+ * Signature: (J)Lcom/zerotier/sdk/NodeStatus;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_status
+ (JNIEnv *, jobject, jlong);
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: networkConfig
+ * Signature: (JJ)Lcom/zerotier/sdk/VirtualNetworkConfig;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_networkConfig
+ (JNIEnv *, jobject, jlong, jlong);
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: version
+ * Signature: ()Lcom/zerotier/sdk/Version;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_version
+ (JNIEnv *, jobject);
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: peers
+ * Signature: (J)Ljava/util/ArrayList;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_peers
+ (JNIEnv *, jobject, jlong);
+
+/*
+ * Class: com_zerotier_sdk_Node
+ * Method: networks
+ * Signature: (J)Ljava/util/ArrayList;
+ */
+JNIEXPORT jobject JNICALL Java_com_zerotier_sdk_Node_networks
+ (JNIEnv *, jobject, jlong);
+
+#ifdef __cplusplus
+}
+#endif
+#endif