diff --git a/ports/android/.gitignore b/ports/android/.gitignore new file mode 100644 index 0000000..5edb4ee --- /dev/null +++ b/ports/android/.gitignore @@ -0,0 +1,10 @@ +*.iml +.gradle +/local.properties +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/ports/android/.project b/ports/android/.project new file mode 100644 index 0000000..3964dd3 --- /dev/null +++ b/ports/android/.project @@ -0,0 +1,17 @@ + + + android + Project android created by Buildship. + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/ports/android/.settings/org.eclipse.buildship.core.prefs b/ports/android/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000..e889521 --- /dev/null +++ b/ports/android/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,2 @@ +connection.project.dir= +eclipse.preferences.version=1 diff --git a/ports/android/app/.gitignore b/ports/android/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/ports/android/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/ports/android/app/build.gradle b/ports/android/app/build.gradle new file mode 100644 index 0000000..d168a4b --- /dev/null +++ b/ports/android/app/build.gradle @@ -0,0 +1,42 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 28 + defaultConfig { + minSdkVersion 14 + targetSdkVersion 23 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + externalNativeBuild { + cmake { + cppFlags "" + } + } + ndk { + // Tells Gradle to build outputs for the following ABIs and package + // them into your APK. + abiFilters 'armeabi-v7a' + } + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + externalNativeBuild { + cmake { + path "../../../CMakeLists.txt" + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:28.0.0-alpha3' + implementation 'com.android.support.constraint:constraint-layout:1.1.2' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' +} \ No newline at end of file diff --git a/ports/android/app/proguard-rules.pro b/ports/android/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/ports/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/ports/android/app/src/androidTest/java/com/example/zerotier/ExampleInstrumentedTest.java b/ports/android/app/src/androidTest/java/com/example/zerotier/ExampleInstrumentedTest.java new file mode 100644 index 0000000..fe8e36c --- /dev/null +++ b/ports/android/app/src/androidTest/java/com/example/zerotier/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.example.zerotier; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.example.zerotier", appContext.getPackageName()); + } +} diff --git a/ports/android/app/src/main/AndroidManifest.xml b/ports/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..42365c2 --- /dev/null +++ b/ports/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + diff --git a/ports/android/app/src/main/java/com/example/zerotier/MainActivity.java b/ports/android/app/src/main/java/com/example/zerotier/MainActivity.java new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/ports/android/app/src/main/java/com/example/zerotier/MainActivity.java @@ -0,0 +1 @@ + diff --git a/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTier.java b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTier.java new file mode 100644 index 0000000..d1b87fb --- /dev/null +++ b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTier.java @@ -0,0 +1,194 @@ +/* + * ZeroTier SDK - Network Virtualization Everywhere + * Copyright (C) 2011-2019 ZeroTier, Inc. https://www.zerotier.com/ + * + * 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 . + * + * -- + * + * You can be released from the requirements of the license by purchasing + * a commercial license. Buying such a license is mandatory as soon as you + * develop commercial closed-source software that incorporates or links + * directly against ZeroTier software without disclosing the source code + * of your own application. + */ + +package com.zerotier.libzt; + +import java.net.*; + +public class ZeroTier +{ + static + { + // loads libzt.so or libzt.dylib + System.loadLibrary("zt"); + init(); + } + + public static int ZTS_ERR_OK = 0; // Everything is ok + public static int ZTS_ERR_INVALID_ARG = -1; // A parameter provided by the user application is invalid (e.g. our of range, NULL, etc) + public static int ZTS_ERR_SERVICE = -2; // The service isn't initialized or is for some other reason currently unavailable + public static int ZTS_ERR_INVALID_OP = -3; // For some reason this API operation is not permitted (perhaps the service is still starting?) + + // Events generated by ZeroTier service or by libzt + // See ext/ZeroTierOne/include/ZeroTierOne.h + public static int EVENT_NONE = -1; + // Node-specific events + public static int EVENT_NODE_UP = 0; + public static int EVENT_NODE_OFFLINE = 1; + public static int EVENT_NODE_ONLINE = 2; + public static int EVENT_NODE_DOWN = 3; + public static int EVENT_NODE_IDENTITY_COLLISION = 4; + // libzt node events + public static int EVENT_NODE_UNRECOVERABLE_ERROR = 16; + public static int EVENT_NODE_NORMAL_TERMINATION = 17; + // Network-specific events + public static int EVENT_NETWORK_NOT_FOUND = 32; + public static int EVENT_NETWORK_CLIENT_TOO_OLD = 33; + public static int EVENT_NETWORK_REQUESTING_CONFIG = 34; + public static int EVENT_NETWORK_OK = 35; + public static int EVENT_NETWORK_ACCESS_DENIED = 36; + public static int EVENT_NETWORK_READY_IP4 = 37; + public static int EVENT_NETWORK_READY_IP6 = 38; + public static int EVENT_NETWORK_DOWN = 39; + // lwIP netif events + public static int EVENT_NETIF_UP_IP4 = 64; + public static int EVENT_NETIF_UP_IP6 = 65; + public static int EVENT_NETIF_DOWN_IP4 = 66; + public static int EVENT_NETIF_DOWN_IP6 = 67; + public static int EVENT_NETIF_REMOVED = 68; + public static int EVENT_NETIF_LINK_UP = 69; + public static int EVENT_NETIF_LINK_DOWN = 70; + public static int EVENT_NETIF_NEW_ADDRESS = 71; + // Peer events + public static int EVENT_PEER_P2P = 96; + public static int EVENT_PEER_RELAY = 97; + public static int EVENT_PEER_UNREACHABLE = 98; + + // Socket protocol types + public static int SOCK_STREAM = 0x00000001; + public static int SOCK_DGRAM = 0x00000002; + public static int SOCK_RAW = 0x00000003; + // Socket family types + public static int AF_INET = 0x00000002; + public static int AF_INET6 = 0x0000000a; + public static int PF_INET = AF_INET; + public static int PF_INET6 = AF_INET6; + // Used as level numbers for setsockopt() and getsockopt() + public static int IPPROTO_IP = 0x00000000; + public static int IPPROTO_ICMP = 0x00000001; + public static int IPPROTO_TCP = 0x00000006; + public static int IPPROTO_UDP = 0x00000011; + public static int IPPROTO_IPV6 = 0x00000029; + public static int IPPROTO_ICMPV6 = 0x0000003a; + public static int IPPROTO_UDPLITE = 0x00000088; + public static int IPPROTO_RAW = 0x000000ff; + // send() and recv() flags + public static int MSG_PEEK = 0x00000001; + public static int MSG_WAITALL = 0x00000002; + public static int MSG_OOB = 0x00000004; + public static int MSG_DONTWAIT = 0x00000008; + public static int MSG_MORE = 0x00000010; + // fnctl() commands + public static int F_GETFL = 0x00000003; + public static int F_SETFL = 0x00000004; + // fnctl() flags + public static int O_NONBLOCK = 0x00000001; + public static int O_NDELAY = 0x00000001; + // Shutdown commands + public static int SHUT_RD = 0x00000000; + public static int SHUT_WR = 0x00000001; + public static int SHUT_RDWR = 0x00000002; + // ioctl() commands + public static int FIONREAD = 0x4008667F; + public static int FIONBIO = 0x8008667E; + // Socket level option number + public static int SOL_SOCKET = 0x00000FFF; + // Socket options + public static int SO_REUSEADDR = 0x00000004; + public static int SO_KEEPALIVE = 0x00000008; + public static int SO_BROADCAST = 0x00000020; + // Socket options + public static int SO_DEBUG = 0x00000001; // NOT YET SUPPORTED + public static int SO_ACCEPTCONN = 0x00000002; + public static int SO_DONTROUTE = 0x00000010; // NOT YET SUPPORTED + public static int SO_USELOOPBACK = 0x00000040; // NOT YET SUPPORTED + public static int SO_LINGER = 0x00000080; + public static int SO_DONTLINGER = ((int)(~SO_LINGER)); + public static int SO_OOBINLINE = 0x00000100; // NOT YET SUPPORTED + public static int SO_REUSEPORT = 0x00000200; // NOT YET SUPPORTED + public static int SO_SNDBUF = 0x00001001; // NOT YET SUPPORTED + public static int SO_RCVBUF = 0x00001002; + public static int SO_SNDLOWAT = 0x00001003; // NOT YET SUPPORTED + public static int SO_RCVLOWAT = 0x00001004; // NOT YET SUPPORTED + public static int SO_SNDTIMEO = 0x00001005; + public static int SO_RCVTIMEO = 0x00001006; + public static int SO_ERROR = 0x00001007; + public static int SO_TYPE = 0x00001008; + public static int SO_CONTIMEO = 0x00001009; // NOT YET SUPPORTED + public static int SO_NO_CHECK = 0x0000100a; + // IPPROTO_IP options + public static int IP_TOS = 0x00000001; + public static int IP_TTL = 0x00000002; + // IPPROTO_TCP options + public static int TCP_NODELAY = 0x00000001; + public static int TCP_KEEPALIVE = 0x00000002; + public static int TCP_KEEPIDLE = 0x00000003; + public static int TCP_KEEPINTVL = 0x00000004; + public static int TCP_KEEPCNT = 0x00000005; + + public static native void start(String path, ZeroTierEventListener callbackClass, int port); + public static native void stop(); + public static native int join(long nwid); + public static native int leave(long nwid); + public static native long get_node_id(); + public static native int get_num_assigned_addresses(long nwid); + public static native void get_6plane_addr(long nwid, long nodeId, ZeroTierSocketAddress addr); + public static native void get_rfc4193_addr(long nwid, long nodeId, ZeroTierSocketAddress addr); + public static native int get_peer_status(long nodeId); + + public static native int socket(int family, int type, int protocol); + public static native int connect(int fd, ZeroTierSocketAddress addr); + public static native int bind(int fd, ZeroTierSocketAddress addr); + public static native int listen(int fd, int backlog); + public static native int accept(int fd, ZeroTierSocketAddress addr); + public static native int accept4(int fd, String addr, int port); + + public static native int setsockopt(int fd, int level, int optname, ZeroTierSocketOptionValue optval); + public static native int getsockopt(int fd, int level, int optname, ZeroTierSocketOptionValue optval); + + public static native int read(int fd, byte[] buf); + public static native int read_offset(int fd, byte[] buf, int offset, int len); + public static native int read_length(int fd, byte[] buf, int len); + public static native int recv(int fd, byte[] buf, int flags); + public static native int recvfrom(int fd, byte[] buf, int flags, ZeroTierSocketAddress addr); + + public static native int write(int fd, byte[] buf); + public static native int write_byte(int fd, byte b); + public static native int write_offset(int fd, byte[] buf, int offset, int len); + public static native int sendto(int fd, byte[] buf, int flags, ZeroTierSocketAddress addr); + public static native int send(int fd, byte[] buf, int flags); + + public static native int shutdown(int fd, int how); + public static native int close(int fd); + + public static native boolean getsockname(int fd, ZeroTierSocketAddress addr); + public static native int getpeername(int fd, ZeroTierSocketAddress addr); + public static native int fcntl(int sock, int cmd, int flag); + public static native int ioctl(int fd, long request, ZeroTierIoctlArg arg); + public static native int select(int nfds, ZeroTierFileDescriptorSet readfds, ZeroTierFileDescriptorSet writefds, ZeroTierFileDescriptorSet exceptfds, int timeout_sec, int timeout_usec); + + public static native void init(); // Only to be called by static initializer of this class +} diff --git a/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierEventListener.java b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierEventListener.java new file mode 100644 index 0000000..fd5e54e --- /dev/null +++ b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierEventListener.java @@ -0,0 +1,9 @@ +package com.zerotier.libzt; + +public interface ZeroTierEventListener { + + /* + * Called when an even occurs in the native section of the ZeroTier library service + */ + public void onZeroTierEvent(long nwid, int eventCode); +} \ No newline at end of file diff --git a/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierFileDescriptorSet.java b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierFileDescriptorSet.java new file mode 100644 index 0000000..000902b --- /dev/null +++ b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierFileDescriptorSet.java @@ -0,0 +1,28 @@ +package com.zerotier.libzt; + +public class ZeroTierFileDescriptorSet +{ + byte[] fds_bits = new byte[1024]; + + public void CLR(int fd) + { + fds_bits[fd] = 0x00; + } + + public boolean ISSET(int fd) + { + return fds_bits[fd] == 0x01; + } + + public void SET(int fd) + { + fds_bits[fd] = 0x01; + } + + public void ZERO() + { + for (int i=0; i<1024; i++) { + fds_bits[i] = 0x00; + } + } +} \ No newline at end of file diff --git a/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierInputStream.java b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierInputStream.java new file mode 100644 index 0000000..7a02d3a --- /dev/null +++ b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierInputStream.java @@ -0,0 +1,209 @@ +package com.zerotier.libzt; + +import com.zerotier.libzt.ZeroTier; + +import java.io.*; +import java.util.Objects; + +public class ZeroTierInputStream extends InputStream +{ + private static final int MAX_SKIP_BUFFER_SIZE = 2048; + private static final int DEFAULT_BUFFER_SIZE = 8192; + + /* + * File descriptor used by lower native layer + */ + int zfd; + + /* + * + */ + public int available() + throws IOException + { + return 0; // NOT YET SUPPORTED + } + + /* + * + */ + public void close​() + throws IOException + { + /* Note: this operation currently only stops RX on a socket that is shared + between both I/OStreams. This means that closing this stream will only shutdown + that aspect of the socket but not actually close it and free resources. Resources + will be properly freed when the socket implementation's close() is called or if + both I/OStreams are closed separately */ + ZeroTier.shutdown(zfd, ZeroTier.SHUT_RD); + zfd = -1; + } + + /* + * + */ + public void mark​(int readlimit) + { + System.err.println("mark​: ZeroTierInputStream does not currently support this feature"); + } + + /* + * + */ + public void reset​() + throws IOException + { + System.err.println("reset​: ZeroTierInputStream does not currently support this feature"); + } + + /* + * + */ + public boolean markSupported​() + { + return false; // mark() is not supported + } + + /* + * + */ + public long transferTo​(OutputStream out) + throws IOException + { + Objects.requireNonNull(out, "out must not be null"); + int bytesTransferred = 0, bytesRead; + byte[] buf = new byte[DEFAULT_BUFFER_SIZE]; + while (((bytesRead = ZeroTier.read(zfd, buf)) >= 0)) { + out.write(buf, 0, bytesRead); + bytesTransferred += bytesRead; + } + return bytesTransferred; + } + + /* + * + */ + public int read​() + throws IOException + { + byte[] buf = new byte[1]; + // Unlike a native read(), if nothing is read we should return -1 + int retval = ZeroTier.read(zfd, buf); + if ((retval == 0) | (retval == -104) /* EINTR, from SO_RCVTIMEO */) { + return -1; + } + if (retval < 0) { + throw new IOException("read​(), errno="+retval); + } + return buf[0]; + } + + /* + * + */ + public int read​(byte[] b) + throws IOException + { + Objects.requireNonNull(b, "input byte array must not be null"); + // Unlike a native read(), if nothing is read we should return -1 + int retval = ZeroTier.read(zfd, b); + if ((retval == 0) | (retval == -104) /* EINTR, from SO_RCVTIMEO */) { + return -1; + } + if (retval < 0) { + throw new IOException("read​(b), errno="+retval); + } + return retval; + } + + /* + * + */ + public int read​(byte[] b, int off, int len) + throws IOException + { + Objects.requireNonNull(b, "input byte array must not be null"); + if ((off < 0) | (len < 0) | (len > b.length - off)) { + throw new IndexOutOfBoundsException("invalid argument"); + } + if (len == 0) { + return 0; + } + // Unlike a native read(), if nothing is read we should return -1 + int retval = ZeroTier.read_offset(zfd, b, off, len); + if ((retval == 0) | (retval == -104) /* EINTR, from SO_RCVTIMEO */) { + return -1; + } + if (retval < 0) { + throw new IOException("read​(b,off,len), errno="+retval); + } + //System.out.println("readNBytes​(byte[] b, int off="+off+", int len="+len+")="+retval); + return retval; + } + + /* + * + */ + public byte[] readAllBytes​() + throws IOException + { + //System.out.println("readAllBytes​()"); + ZeroTierIoctlArg ztarg = new ZeroTierIoctlArg(); + int err = ZeroTier.ioctl(zfd, ZeroTier.FIONREAD, ztarg); + byte[] buf = new byte[ztarg.integer]; + int retval = ZeroTier.read(zfd, buf); + if ((retval == 0) | (retval == -104) /* EINTR, from SO_RCVTIMEO */) { + // No action needed + } + if (retval < 0) { + throw new IOException("readAllBytes​(b,off,len), errno="+retval); + } + return buf; + } + + /* + * + */ + public int readNBytes​(byte[] b, int off, int len) + throws IOException + { + Objects.requireNonNull(b, "input byte array must not be null"); + if ((off < 0) | (len < 0) | (len > b.length - off)) { + throw new IndexOutOfBoundsException("invalid argument"); + } + if (len == 0) { + return 0; + } + int retval = ZeroTier.read_offset(zfd, b, off, len); + if ((retval == 0) | (retval == -104) /* EINTR, from SO_RCVTIMEO */) { + // No action needed + } + if (retval < 0) { + throw new IOException("readNBytes​(b,off,len), errno="+retval); + } + //System.out.println("readNBytes​(byte[] b, int off="+off+", int len="+len+")="+retval); + return retval; + } + + /* + * + */ + public long skip​(long n) + throws IOException + { + //System.out.println("skip()"); + if (n <= 0) { + return 0; + } + long bytesRemaining = n, bytesRead; + int bufSize = (int)Math.min(MAX_SKIP_BUFFER_SIZE, bytesRemaining); + byte[] buf = new byte[bufSize]; + while (bytesRemaining > 0) { + if ((bytesRead = ZeroTier.read_length(zfd, buf, (int)Math.min(bufSize, bytesRemaining))) < 0) { + break; + } + bytesRemaining -= bytesRead; + } + return n - bytesRemaining; // skipped + } +} \ No newline at end of file diff --git a/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierIoctlArg.java b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierIoctlArg.java new file mode 100644 index 0000000..54c6131 --- /dev/null +++ b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierIoctlArg.java @@ -0,0 +1,6 @@ +package com.zerotier.libzt; + +public class ZeroTierIoctlArg +{ + int integer; // General integer to be used or updated by the zts_ioctl() call +} \ No newline at end of file diff --git a/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierOutputStream.java b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierOutputStream.java new file mode 100644 index 0000000..dd39a8a --- /dev/null +++ b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierOutputStream.java @@ -0,0 +1,81 @@ +package com.zerotier.libzt; + +import com.zerotier.libzt.ZeroTier; + +import java.io.*; +import java.util.Arrays; +import java.util.Objects; + +class ZeroTierOutputStream extends OutputStream +{ + /* + * File descriptor used by lower native layer + */ + int zfd; + + /* + * + */ + public void flush() + throws IOException + { + // System.err.println("flush: ZeroTierOutputStream does not currently support this feature"); + // Not fully supported since we don't maintain a buffer + } + + /* + * + */ + public void close() + throws IOException + { + /* Note: this operation currently only stops RX on a socket that is shared + between both I/OStreams. This means that closing this stream will only shutdown + that aspect of the socket but not actually close it and free resources. Resources + will be properly freed when the socket implementation's close() is called or if + both I/OStreams are closed separately */ + ZeroTier.shutdown(zfd, ZeroTier.SHUT_WR); + zfd = -1; + } + + /* + * + */ + public void write(byte[] b) + throws IOException + { + int err = ZeroTier.write(zfd, b); + if (err < 0) { + throw new IOException("write(b[]), errno="+err); + } + } + + /* + * + */ + public void write(byte[] b, int off, int len) + throws IOException + { + Objects.requireNonNull(b, "input byte array must not be null"); + if ((off < 0) | (len < 0) | (off+len > b.length)) { + throw new IndexOutOfBoundsException("write(b,off,len)"); + } + int err = ZeroTier.write_offset(zfd, b, off, len); + if (err < 0) { + throw new IOException("write(b[],off,len), errno="+err); + } + } + + /* + * + */ + public void write(int b) + throws IOException + { + byte lowByte = (byte)(b & 0xFF); + int err = ZeroTier.write_byte(zfd, lowByte); + if (err < 0) { + throw new IOException("write(b), errno="+err); + } + } +} \ No newline at end of file diff --git a/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSSLSocketFactory.java b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSSLSocketFactory.java new file mode 100644 index 0000000..2930f7a --- /dev/null +++ b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSSLSocketFactory.java @@ -0,0 +1,101 @@ +package com.zerotier.libzt; + +import com.zerotier.libzt.ZeroTierSocket; + +import java.net.*; +import javax.net.SocketFactory; +import java.io.IOException; +import java.io.InputStream; +import java.security.*; +import java.util.Locale; + +import javax.net.ssl.SSLSocketFactory; + +public class ZeroTierSSLSocketFactory extends SSLSocketFactory +{ + private final SSLSocketFactory delegate; + + /* + * + */ + public ZeroTierSSLSocketFactory(SSLSocketFactory delegate) + { + this.delegate = delegate; + } + + /* + * + */ + public Socket createSocket(Socket s, String host, int port, boolean autoClose) + throws IOException + { + ZeroTierSocket zs = new ZeroTierSocket(); + zs.connect((SocketAddress)new InetSocketAddress(host, port), 10); + return delegate.createSocket(zs, host, port, autoClose); + } + + /* + * + */ + public Socket createSocket(Socket s, InputStream consumed, boolean autoClose) + throws IOException + { + throw new UnsupportedOperationException(); + } + + /* + * + */ + public Socket createSocket(InetAddress a,int b,InetAddress c,int d) + throws IOException + { + ZeroTierSocket s = new ZeroTierSocket(); + return delegate.createSocket(a, b, c, d); + } + + /* + * + */ + public Socket createSocket(InetAddress a,int b) + throws IOException + { + ZeroTierSocket s = new ZeroTierSocket(); + return delegate.createSocket(a, b); + } + + /* + * + */ + public Socket createSocket(String a,int b,InetAddress c,int d) + throws IOException + { + ZeroTierSocket s = new ZeroTierSocket(); + return delegate.createSocket(a, b, c, d); + } + + /* + * + */ + public Socket createSocket(String a,int b) + throws IOException + { + ZeroTierSocket s = new ZeroTierSocket(); + return delegate.createSocket(a, b); + } + + /* + * + */ + public String [] getSupportedCipherSuites() + { + return new String[0]; + } + + /* + * + */ + public String [] getDefaultCipherSuites() + { + return new String[0]; + } +} diff --git a/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSocket.java b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSocket.java new file mode 100644 index 0000000..d89e324 --- /dev/null +++ b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSocket.java @@ -0,0 +1,746 @@ +package com.zerotier.libzt; + +import com.zerotier.libzt.ZeroTier; +import com.zerotier.libzt.ZeroTierSocketAddress; +import com.zerotier.libzt.ZeroTierSocketImpl; +import com.zerotier.libzt.ZeroTierSocketImplFactory; +import com.zerotier.libzt.ZeroTierInputStream; +import com.zerotier.libzt.ZeroTierOutputStream; + +import java.io.*; +import java.net.*; +import java.util.Objects; +import java.nio.channels.SocketChannel; +import java.net.InetAddress; + +public class ZeroTierSocket extends Socket +{ + /* + * Factory designated to create the underlying ZeroTierSocket implementation + */ + static ZeroTierSocketImplFactory factory = new ZeroTierSocketImplFactory(); + + /* + * Underlying implementation of this ZeroTierSocket + */ + ZeroTierSocketImpl impl; + + /* + * Misc. state flags + */ + private boolean created = false; + private boolean closed = false; + private boolean connected = false; + private boolean bound = false; + private boolean inputShutdown = false; + private boolean outputShutdown = false; + + /* + * Creates and sets the implementation + */ + void setImpl() + { + if (factory != null) { + impl = factory.createSocketImpl(); + } + if (impl != null) { + impl.setSocket(this); + } + } + + /* + * Returns the underlying socket implementation + */ + private ZeroTierSocketImpl getImpl() + throws SocketException + { + if (!created) { + try { + impl.create(true); + } + catch (IOException ex) { + throw (SocketException) new SocketException().initCause(ex); + } + created = true; + } + return impl; + } + + /* + * Create the underlying socket implementation + */ + void createImpl(boolean stream) + throws SocketException + { + if (impl == null) { + setImpl(); + } + try { + impl.create(stream); + created = true; + } + catch (IOException ex) { + throw new SocketException(e.getMessage()); + } + } + + /* + * Constructor for ZeroTierSocket + */ + public ZeroTierSocket() + throws IOException + { + this((InetAddress)null, 0, null, 0); + } + + /* + * Creates an unconnected socket + */ + protected ZeroTierSocket(ZeroTierSocketImpl impl) + throws SocketException + { + this.impl = impl; + if (impl != null) { + this.impl.setSocket(this); + } + } + + /* + * Constructor for ZeroTierSocket + */ + public ZeroTierSocket(InetAddress raddr, int rport, InetAddress laddr, int lport) + throws IOException + { + setImpl(); + + try { + if (laddr != null) { + bind(new InetSocketAddress(laddr, lport)); + } + if (raddr != null) { + connect(new InetSocketAddress(raddr, rport)); + } + } + catch (Exception ex) + { + try { + close(); + } + catch (IOException _ex) { + ex.addSuppressed(_ex); + } + throw ex; + } + } + + /* + * Constructor for ZeroTierSocket + */ + public ZeroTierSocket(InetAddress address, int port) + throws IOException + { + this(address, port, null, 0); + } + + /* + * Constructor for ZeroTierSocket + */ + public ZeroTierSocket(String address, int port) + throws IOException + { + this(InetAddress.getByName(address), port, null, 0); + } + + /* + * Constructor for ZeroTierSocket + */ + public ZeroTierSocket(String address, int port, InetAddress localHost, int localPort) + throws IOException + { + this(InetAddress.getByName(address), port, localHost, localPort); + } + + /* + * Binds the socket to a local address + */ + public void bind(SocketAddress localAddr) + throws IOException + { + if (isSocketBound()) { + throw new SocketException("bind: ZeroTierSocket is already bound"); + } + if (isSocketClosed()) { + throw new SocketException("bind: ZeroTierSocket is closed"); + } + if (localAddr != null && (!(localAddr instanceof InetSocketAddress))) { + throw new IllegalArgumentException("bind: Unsupported address type"); + } + InetSocketAddress addr = (InetSocketAddress)localAddr; + if (addr != null && addr.isUnresolved()) { + throw new SocketException("bind: Unresolved address"); + } + if (addr == null) { + addr = new InetSocketAddress(0); + } + getImpl().bind(addr.getAddress(), addr.getPort()); + bound = true; + } + + /* + * Closes the socket + */ + public synchronized void close() + throws IOException + { + if (isSocketClosed()) { + return; + } + getOutputStream().flush(); + impl.close(); + closed = true; + } + + /* + * Connects the socket to a remote address + */ + public void connect(SocketAddress remoteAddr) + throws IOException + { + connect(remoteAddr, 0); + } + + /* + * Connects the socket to a remote address + */ + public void connect(SocketAddress remoteAddr, int timeout) + throws IOException + { + if (isSocketClosed()) { + throw new SocketException("connect: ZeroTierSocket is closed"); + } + if (isSocketConnected()) { + throw new SocketException("connect: already connected"); + } + if (remoteAddr == null) { + throw new IllegalArgumentException("connect: The address can't be null"); + } + if (!(remoteAddr instanceof InetSocketAddress)) { + throw new IllegalArgumentException("connect: Unsupported address type"); + } + if (timeout < 0) { + throw new IllegalArgumentException("connect: timeout cannot be negative"); + } + if (!created) { + createImpl(true); + } + getImpl().connect(remoteAddr, 0); + bound = true; + connected = true; + } + + /* + * Returns the associated Channel + */ + public SocketChannel getChannel​() + { + System.err.println("getChannel​: ZeroTierSocket does not currently support this feature"); + return null; + } + + /* + * Returns the address to which the socket is connected + */ + public InetAddress getInetAddress​() + { + if (!isSocketConnected()) { + return null; + } + try { + return getImpl().getInetAddress(); + } + catch (SocketException ex) { + // Not Reachable + } + return null; + } + + /* + * Returns the input stream + */ + public ZeroTierInputStream getInputStream() + throws IOException + { + if (!isSocketConnected()) { + throw new SocketException("ZeroTierSocket is not connected"); + } + if (isSocketClosed()) { + throw new SocketException("ZeroTierSocket is closed"); + } + if (isInputStreamShutdown()) { + throw new SocketException("ZeroTierSocket input is shutdown"); + } + return getImpl().getInputStream(); + } + + /* + * Returns whether SO_KEEPALIVE is enabled + */ + public boolean getKeepAlive() + throws SocketException + { + if (isSocketClosed()) { + throw new SocketException("ZeroTierSocket is closed"); + } + return ((Boolean) getImpl().getOption(ZeroTier.SO_KEEPALIVE)).booleanValue(); + } + + /* + * Returns the local address to which the socket is bound + */ + public InetAddress getLocalAddress() + { + System.err.println("getLocalAddress: ZeroTierSocket does not currently support this feature"); + /* + // This is for backward compatibility + if (!isSocketBound()) { + return InetAddress.anyLocalAddress(); + } + InetAddress inAddr = null; + try { + inAddr = (InetAddress) getImpl().getOption(ZeroTier.SO_BINDADDR); + if (inAddr.isAnyLocalAddress()) { + inAddr = InetAddress.anyLocalAddress(); + } + } + catch (Exception ex) { + // "0.0.0.0" + inAddr = InetAddress.anyLocalAddress(); + } + return inAddr; + */ + return null; + } + + /* + * Return the local port to which the socket is bound + */ + public int getLocalPort() + { + if (!isSocketBound()) { + return -1; + } + try { + return getImpl().getLocalPort(); + } + catch(SocketException ex) { + // Unreachable + } + return -1; + } + + /* + * Returns the address of the endpoint that the socket is bound to. + */ + public SocketAddress getLocalSocketAddress() + { + if (!isSocketBound()) { + return null; + } + return new InetSocketAddress(getLocalAddress(), getLocalPort()); + } + + /* + * Returns whether SO_OOBINLINE is enabled. + */ + public boolean getOOBInline​() + throws SocketException + { + System.err.println("getOOBInline​: ZeroTierSocket does not currently support this feature"); + return false; + } + + /* + * Returns the output stream. + */ + public ZeroTierOutputStream getOutputStream() + throws IOException + { + if (!isSocketConnected()) { + throw new SocketException("ZeroTierSocket is not connected"); + } + if (isSocketClosed()) { + throw new SocketException("ZeroTierSocket is closed"); + } + if (isOutputStreamShutdown()) { + throw new SocketException("ZeroTierSocket output is shutdown"); + } + return getImpl().getOutputStream(); + } + + /* + * Return the remote port to which the socket is connected + */ + public int getPort() + { + if (!isSocketConnected()) { + return 0; + } + try { + return getImpl().getPort(); + } + catch (SocketException ex) { + // Not reachable + } + return -1; + } + + /* + * Returns SO_RCVBUF + */ + public synchronized int getReceiveBufferSize() + throws SocketException + { + if (isSocketClosed()) { + throw new SocketException("ZeroTierSocket is closed"); + } + Object opt = getImpl().getOption(ZeroTier.SO_RCVBUF); + int sz = 0; + if (opt instanceof Integer) { + sz = ((Integer)opt).intValue(); + } + return sz; + } + + /* + * Returns the remote address to which this socket is connected + */ + public SocketAddress getRemoteSocketAddress() + { + if (!isSocketConnected()) { + return null; + } + return new InetSocketAddress(getInetAddress(), getPort()); + } + + /* + * Checks whether SO_REUSEADDR is enabled. + */ + public boolean getReuseAddress() + throws SocketException + { + if (isSocketClosed()) { + throw new SocketException("ZeroTierSocket is closed"); + } + return ((Boolean)(getImpl().getOption(ZeroTier.SO_REUSEADDR))).booleanValue(); + } + + /* + * Returns SO_SNDBUF. + */ + public synchronized int getSendBufferSize() + throws SocketException + { + if (isSocketClosed()) { + throw new SocketException("ZeroTierSocket is closed"); + } + int sz = 0; + Object opt = getImpl().getOption(ZeroTier.SO_SNDBUF); + if (opt instanceof Integer) { + sz = ((Integer)opt).intValue(); + } + return sz; + } + + /* + * Returns SO_LINGER. + */ + public int getSoLinger() + throws SocketException + { + if (isSocketClosed()) { + throw new SocketException("ZeroTierSocket is closed"); + } + Object opt = getImpl().getOption(ZeroTier.SO_LINGER); + if (opt instanceof Integer) { + return ((Integer)opt).intValue(); + } + return -1; + } + + /* + * Returns SO_TIMEOUT. + */ + public synchronized int getSoTimeout() + throws SocketException + { + if (isSocketClosed()) { + throw new SocketException("ZeroTierSocket is closed"); + } + Object opt = getImpl().getOption(ZeroTier.SO_RCVTIMEO); + if (opt instanceof Integer) { + return ((Integer)opt).intValue(); + } + else { + return 0; + } + } + + /* + * Checks whether TCP_NODELAY is enabled. + */ + public boolean getTcpNoDelay() + throws SocketException + { + if (isSocketClosed()) { + throw new SocketException("ZeroTierSocket is closed"); + } + return ((Boolean)getImpl().getOption(ZeroTier.TCP_NODELAY)).booleanValue(); + } + + /* + * Gets traffic class or type-of-service in the IP header for packets sent from this Socket + */ + public int getTrafficClass​() + throws SocketException + { + System.err.println("getTrafficClass​: ZeroTierSocket does not currently support this feature"); + return 0; + } + + /* + * Returns whether or not the socket is bound to a local interface. + */ + public boolean isSocketBound​() + { + return bound; + } + + /* + * Returns whether or not the socket has been closed. + */ + public boolean isSocketClosed​()() + { + return closed; + } + + /* + * Returns whether or not the socket is connected to a remote host. + */ + public boolean isSocketConnected​() + { + return connected; + } + + /* + * Returns whether the input aspect of the socket has been disabled. + */ + public boolean isInputStreamShutdown​() + { + return inputShutdown; + } + + /* + * Returns whether the output aspect of the socket has been disabled. + */ + public boolean isOutputStreamShutdown​() + { + return outputShutdown; + } + + /* + * Send a byte of urgent data on the socket. + */ + public void sendUrgentData​(int data) + throws IOException + { + System.err.println("sendUrgentData​: ZeroTierSocket does not currently support this feature"); + } + + /* + * Enable or disable SO_KEEPALIVE. + */ + public void setKeepAlive(boolean on) + throws SocketException + { + if (isSocketClosed()) { + throw new SocketException("ZeroTierSocket is closed"); + } + getImpl().setOption(ZeroTier.SO_KEEPALIVE, Boolean.valueOf(on)); + } + + /* + * Enable or disable SO_OOBINLINE. + */ + public void setOOBInline​(boolean on) + throws SocketException + { + System.err.println("setOOBInline​: ZeroTierSocket does not currently support this feature"); + } + + /* + * Set performance preferences. + */ + public void setPerformancePreferences​(int connectionTime, int latency, int bandwidth) + { + System.err.println("setPerformancePreferences​: ZeroTierSocket does not currently support this feature"); + } + + /* + * Set SO_RCVBUF. + */ + public synchronized void setReceiveBufferSize(int size) + throws SocketException + { + if (isSocketClosed()) { + throw new SocketException("ZeroTierSocket is closed"); + } + if (size <= 0) { + throw new IllegalArgumentException("invalid receive buffer size argument"); + } + getImpl().setOption(ZeroTier.SO_RCVBUF, new Integer(size)); + } + + /* + * Enable or disable SO_REUSEADDR. + */ + public void setReuseAddress(boolean on) + throws SocketException + { + if (isSocketClosed()) { + throw new SocketException("ZeroTierSocket is closed"); + } + getImpl().setOption(ZeroTier.SO_REUSEADDR, Boolean.valueOf(on)); + } + + /* + * Set SO_SNDBUF. + */ + public synchronized void setSendBufferSize(int size) + throws SocketException + { + if (isSocketClosed()) { + throw new SocketException("ZeroTierSocket is closed"); + } + if (size < 0) { + throw new IllegalArgumentException("size argument cannot be negative"); + } + getImpl().setOption(ZeroTier.SO_SNDBUF, new Integer(size)); + } + + /* + * Set Socket implementation factory for all clients. + */ + public static void setSocketImplFactory​(ZeroTierSocketImplFactory fact) + throws IOException + { + if (factory != null) { + throw new SocketException("ZeroTierSocket factory is already defined"); + } + factory = fact; + } + + /* + * Enable or disable SO_LINGER time (seconds). + */ + public void setSoLinger(boolean on, int linger) + throws SocketException + { + if (isSocketClosed()) { + throw new SocketException("ZeroTierSocket is closed"); + } + if (!on) { + getImpl().setOption(ZeroTier.SO_LINGER, new Boolean(on)); + } + else { + if (linger < 0) { + throw new IllegalArgumentException("linger argument is invalid"); + } + if (linger > 0xFFFF) { + linger = 0xFFFF; + } + getImpl().setOption(ZeroTier.SO_LINGER, new Integer(linger)); + } + } + + /* + * Enable or disable SO_TIMEOUT with the specified timeout, in milliseconds. + */ + public void setSoTimeout​(int timeout) + throws SocketException + { + if (timeout < 0) { + throw new IllegalArgumentException("timeout argument cannot be negative"); + } + if (isSocketClosed()) { + throw new SocketException("ZeroTierSocket is closed"); + } + getImpl().setOption(ZeroTier.SO_RCVTIMEO, new Integer(timeout)); + } + + /* + * Enable or disable TCP_NODELAY (Nagle's algorithm). + */ + public void setTcpNoDelay(boolean on) + throws SocketException + { + if (isSocketClosed()) { + throw new SocketException("ZeroTierSocket is closed"); + } + getImpl().setOption(ZeroTier.TCP_NODELAY, Boolean.valueOf(on)); + } + + /* + * Sets traffic class or ToS. + */ + public void setTrafficClass​(int tc) + throws SocketException + { + System.err.println("setTrafficClass​: ZeroTierSocket does not currently support this feature"); + } + + /* + * Disable the input stream for this socket. + */ + public void shutdownInput() + throws IOException + { + if (isSocketClosed()) { + throw new SocketException("ZeroTierSocket is closed"); + } + if (isInputStreamShutdown()) { + throw new SocketException("ZeroTierSocket input is already shutdown"); + } + if (!isSocketConnected()) { + throw new SocketException("ZeroTierSocket is not connected"); + } + getImpl().shutdownInput(); + inputShutdown = true; + } + + /* + * Disable the output stream for this socket. + */ + public void shutdownOutput() + throws IOException + { + if (isSocketClosed()) { + throw new SocketException("ZeroTierSocket is closed"); + } + if (isOutputStreamShutdown()) { + throw new SocketException("ZeroTierSocket output is already shutdown"); + } + if (!isSocketConnected()) { + throw new SocketException("ZeroTierSocket is not connected"); + } + getImpl().shutdownOutput(); + outputShutdown = true; + } + + /* + * Gets the underlying implementation's file descriptor. + */ + public FileDescriptor getFileDescriptor() + { + return impl.getFileDescriptor(); + } +} \ No newline at end of file diff --git a/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSocketAddress.java b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSocketAddress.java new file mode 100644 index 0000000..e773c09 --- /dev/null +++ b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSocketAddress.java @@ -0,0 +1,66 @@ +package com.zerotier.libzt; + +import com.zerotier.libzt.ZeroTier; + +import java.net.InetAddress; + +// Designed to transport address information across the JNI boundary +public class ZeroTierSocketAddress +{ + public byte[] _ip6 = new byte[16]; + public byte[] _ip4 = new byte[4]; + + public int _family; + public int _port; // Also reused for netmask or prefix + + public ZeroTierSocketAddress() {} + + public ZeroTierSocketAddress(String ipStr, int port) + { + if(ipStr.contains(":")) { + _family = ZeroTier.AF_INET6; + try { + InetAddress ip = InetAddress.getByName(ipStr); + _ip6 = ip.getAddress(); + } + catch (Exception e) { } + } + else if(ipStr.contains(".")) { + _family = ZeroTier.AF_INET; + try { + InetAddress ip = InetAddress.getByName(ipStr); + _ip4 = ip.getAddress(); + } + catch (Exception e) { } + } + _port = port; + } + + public int getPort() { return _port; } + public int getNetmask() { return _port; } + public int getPrefix() { return _port; } + + public String ipString() + { + if (_family == ZeroTier.AF_INET) { + try { + InetAddress inet = InetAddress.getByAddress(_ip4); + return "" + inet.getHostAddress(); + } catch (Exception e) { + System.out.println(e); + } + } + if (_family == ZeroTier.AF_INET6) { + try { + InetAddress inet = InetAddress.getByAddress(_ip6); + return "" + inet.getHostAddress(); + } catch (Exception e) { + System.out.println(e); + } + } + return ""; + } + + public String toString() { return ipString() + ":" + _port; } + public String toCIDR() { return ipString() + "/" + _port; } +} diff --git a/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSocketFactory.java b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSocketFactory.java new file mode 100644 index 0000000..0e79a65 --- /dev/null +++ b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSocketFactory.java @@ -0,0 +1,51 @@ +package com.zerotier.libzt; + +import com.zerotier.libzt.ZeroTier; +import com.zerotier.libzt.ZeroTierSocket; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import javax.net.SocketFactory; +import java.util.Objects; + +public class ZeroTierSocketFactory extends SocketFactory +{ + public ZeroTierSocketFactory() { } + + public static SocketFactory getDefault() + { + return null; + } + + public Socket createSocket() + throws IOException, UnknownHostException + { + return new ZeroTierSocket(); + } + + public Socket createSocket(String host, int port) + throws IOException, UnknownHostException + { + return new ZeroTierSocket(host, port); + } + + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) + throws IOException, UnknownHostException + { + return new ZeroTierSocket(host, port, localHost, localPort); + } + + public Socket createSocket(InetAddress host, int port) + throws IOException + { + return new ZeroTierSocket(host, port); + } + + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) + throws IOException + { + return new ZeroTierSocket(address, port, localAddress, localPort); + } +} diff --git a/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSocketImpl.java b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSocketImpl.java new file mode 100644 index 0000000..cfbd72b --- /dev/null +++ b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSocketImpl.java @@ -0,0 +1,772 @@ +package com.zerotier.libzt; + +import com.zerotier.libzt.ZeroTier; +import com.zerotier.libzt.ZeroTierSocketAddress; +import com.zerotier.libzt.ZeroTierInputStream; +import com.zerotier.libzt.ZeroTierOutputStream; +import com.zerotier.libzt.ZeroTierSocketOptionValue; + +import java.io.*; +import java.net.*; +import java.util.Objects; +import java.lang.Object; +import java.net.SocketImpl; +import java.net.InetSocketAddress; +import java.util.Set; +import java.lang.Boolean; + +import com.zerotier.libzt.ZeroTier; + +import android.os.ParcelFileDescriptor; + +public class ZeroTierSocketImpl extends SocketImpl +{ + private int defaultProtocol = 0; + + /* + * File descriptor from lower native layer + */ + private int zfd = -1; + private int zfd4 = -1; + private int zfd6 = -1; + + /* + * Input and Output streams + */ + private ZeroTierInputStream in = new ZeroTierInputStream(); + private ZeroTierOutputStream out = new ZeroTierOutputStream(); + + Socket socket = null; + ServerSocket serverSocket = null; + + /* + * The remote address the socket is connected to + */ + protected InetAddress address; + + /* + * Sets the underlying file descriptor valud for the SocketImpl as well as the Input/OutputStream + */ + private void setNativeFileDescriptor(int fd) + { + zfd = fd; + in.zfd = fd; + out.zfd = fd; + } + + /* + * Various socket options that are cached from calls to setOption() before + * the socket exists. + */ + private int _so_rcvtimeo; + private boolean _so_keepalive; + private int _so_sndbuf; + private int _so_rcvbuf; + private boolean _so_reuseaddr; + private int _so_linger; + private int _so_tos; + private boolean _so_nodelay; + + private void setCachedSocketOptions(int fd) + { + if (fd < 0) { + return; + } + + // If we previously received a setSoTimeout() call but were unable to process it due + // to the fact that the underlying socket didn't even exist yet, do so now. + int err = 0; + ZeroTierSocketOptionValue optval = new ZeroTierSocketOptionValue(); + + try { + // SO_TIMEOUT + if (_so_rcvtimeo > 0) { + optval.isInteger = true; + optval.isBoolean = false; + optval.integerValue = ((Integer)_so_rcvtimeo).intValue(); + if ((err = ZeroTier.setsockopt(fd, ZeroTier.SOL_SOCKET, ZeroTier.SO_RCVTIMEO, optval)) < 0) { + throw new IOException("socket("+fd+"), errno="+err+", unable to set previously cached SO_RCVTIMEO"); + } + } + // SO_KEEPALIVE + if (_so_keepalive == true) { + optval.isInteger = false; + optval.isBoolean = true; + optval.booleanValue = ((Boolean)_so_keepalive).booleanValue() ? true : false; + if ((err = ZeroTier.setsockopt(fd, ZeroTier.SOL_SOCKET, ZeroTier.SO_KEEPALIVE, optval)) < 0) { + throw new IOException("socket("+fd+"), errno="+err+", unable to set previously cached SO_KEEPALIVE"); + } + } + // SO_SNDBUF + if (_so_sndbuf > 0) { + optval.isInteger = true; + optval.isBoolean = false; + optval.integerValue = ((Integer)_so_sndbuf).intValue(); + if ((err = ZeroTier.setsockopt(fd, ZeroTier.SOL_SOCKET, ZeroTier.SO_SNDBUF, optval)) < 0) { + throw new IOException("socket("+fd+"), errno="+err+", unable to set previously cached SO_SNDBUF"); + } + } + // SO_RCVBUF + if (_so_rcvbuf > 0) { + optval.isInteger = true; + optval.isBoolean = false; + optval.integerValue = ((Integer)_so_rcvbuf).intValue(); + if ((err = ZeroTier.setsockopt(fd, ZeroTier.SOL_SOCKET, ZeroTier.SO_RCVBUF, optval)) < 0) { + throw new IOException("socket("+fd+"), errno="+err+", unable to set previously cached SO_RCVBUF"); + } + } + // SO_REUSEADDR + if (_so_reuseaddr == true) { + optval.isInteger = false; + optval.isBoolean = true; + optval.booleanValue = ((Boolean)_so_reuseaddr).booleanValue() ? true : false; + if ((err = ZeroTier.setsockopt(fd, ZeroTier.SOL_SOCKET, ZeroTier.SO_REUSEADDR, optval)) < 0) { + throw new IOException("socket("+fd+"), errno="+err+", unable to set previously cached SO_REUSEADDR"); + } + } + // SO_LINGER + if (_so_linger > 0) { + optval.isInteger = true; + optval.isBoolean = false; + optval.integerValue = ((Integer)_so_linger).intValue(); + if ((err = ZeroTier.setsockopt(fd, ZeroTier.SOL_SOCKET, ZeroTier.SO_LINGER, optval)) < 0) { + throw new IOException("socket("+fd+"), errno="+err+", unable to set previously cached SO_LINGER"); + } + } + // IP_TOS + if (_so_tos > 0) { + optval.isInteger = true; + optval.isBoolean = false; + optval.integerValue = ((Integer)_so_tos).intValue(); + if ((err = ZeroTier.setsockopt(fd, ZeroTier.SOL_SOCKET, ZeroTier.IP_TOS, optval)) < 0) { + throw new IOException("socket("+fd+"), errno="+err+", unable to set previously cached IP_TOS"); + } + } + // TCP_NODELAY + if (_so_nodelay == true) { + optval.isInteger = false; + optval.isBoolean = true; + optval.booleanValue = ((Boolean)_so_nodelay).booleanValue() ? true : false; + if ((err = ZeroTier.setsockopt(fd, ZeroTier.SOL_SOCKET, ZeroTier.TCP_NODELAY, optval)) < 0) { + throw new IOException("socket("+fd+"), errno="+err+", unable to set previously cached TCP_NODELAY"); + } + } + } + catch (Exception e) { + System.err.println(e); + } + } + + /* + * Constructor which creates a new ZeroTierSocketImpl + */ + public ZeroTierSocketImpl() + { + if ((zfd > -1) | (zfd4 > -1) | (zfd6 > -1)) { return; } + try { + create(true); + } catch (Exception x) { + System.err.println("error creating ZeroTierSocketImpl instance: " + x); + } + in.zfd = zfd; + out.zfd = zfd; + } + + /* + * Constructor to be called when an underlying ZeroTier socket already exists (does not create a new ZeroTierSocketImpl) + */ + public ZeroTierSocketImpl(int fd) { + setNativeFileDescriptor(fd); + } + + /* + * Creates a new ZeroTier socket in the native layer + */ + protected void create(boolean stream) + throws IOException + { + /* + * The native-layer socket is only created once a connect/bind call is made, this is due to the fact + * that beforehand we have no way to determine whether we should create an AF_INET or AF_INET6 socket, + * as a result, this method intentionally does nothing. + */ + } + + /* + * Creates the underlying libzt socket. + * + * This does the real work that can't be done in the constructor. This is because the socket address type + * isn't known until a connect() or bind() request is given. Additionally we cache the value provided by any + * setSoTimeout() calls and implement it immediately after creation. + */ + private void createAppropriateSocketImpl(InetAddress addr) + throws IOException + { + if ((zfd > -1) | (zfd4 > -1) | (zfd6 > -1)) { + return; // Socket already created + } + if(addr instanceof Inet4Address) { + if ((zfd4 = ZeroTier.socket(ZeroTier.AF_INET, ZeroTier.SOCK_STREAM, defaultProtocol)) < 0) { + throw new IOException("socket(), errno="+zfd4+", see: libzt/ext/lwip/src/include/errno.h"); + } + setCachedSocketOptions(zfd4); + } + /* + * Since Java creates sockets capable of handling IPV4 and IPV6, we must simulate this. We do this by + * creating two sockets (one of each type) + */ + if(addr instanceof Inet6Address) { + if ((zfd4 = ZeroTier.socket(ZeroTier.AF_INET, ZeroTier.SOCK_STREAM, defaultProtocol)) < 0) { + throw new IOException("socket(), errno="+zfd4+", see: libzt/ext/lwip/src/include/errno.h"); + } + if ((zfd6 = ZeroTier.socket(ZeroTier.AF_INET6, ZeroTier.SOCK_STREAM, defaultProtocol)) < 0) { + throw new IOException("socket(), errno="+zfd6+", see: libzt/ext/lwip/src/include/errno.h"); + } + setCachedSocketOptions(zfd4); + setCachedSocketOptions(zfd6); + } + } + + /* + * Return the remote address the socket is connected to + */ + protected InetAddress getInetAddress() + { + return address; + } + + /* + * Connects the socket to a remote address + */ + protected void connect(String host, int port) + throws IOException + { + // TODO: Refactor and consolidate the connect() logic for all three methods + createAppropriateSocketImpl(InetAddress.getByName(host)); + if ((zfd4 < 0) & (zfd6 < 0)) { + throw new IOException("invalid fd"); + } + int err; + InetAddress address = InetAddress.getByName(host); + ZeroTierSocketAddress zt_addr = new ZeroTierSocketAddress(host, port); + if (address instanceof Inet4Address) { + if ((err = ZeroTier.connect(zfd4, zt_addr)) < 0) { + throw new IOException("connect(), errno="+err+", see: libzt/ext/lwip/src/include/errno.h"); + } + setNativeFileDescriptor(zfd4); + } + if (address instanceof Inet6Address) { + if ((err = ZeroTier.connect(zfd6, zt_addr)) < 0) { + throw new IOException("connect(), errno="+err+", see: libzt/ext/lwip/src/include/errno.h"); + } + setNativeFileDescriptor(zfd6); + } + super.port = port; + } + + /* + * Connects the socket to a remote address + */ + protected void connect(InetAddress address, int port) + throws IOException + { + // TODO: Refactor and consolidate the connect() logic for all three methods + createAppropriateSocketImpl(address); + if ((zfd4 < 0) & (zfd6 < 0)) { + throw new IOException("invalid fd"); + } + int err; + ZeroTierSocketAddress zt_addr = new ZeroTierSocketAddress(address.getHostAddress(), port); + if (address instanceof Inet4Address) { + if ((err = ZeroTier.connect(zfd4, zt_addr)) < 0) { + throw new IOException("connect(), errno="+err+", see: libzt/ext/lwip/src/include/errno.h"); + } + setNativeFileDescriptor(zfd4); + } + if (address instanceof Inet6Address) { + if ((err = ZeroTier.connect(zfd6, zt_addr)) < 0) { + throw new IOException("connect(), errno="+err+", see: libzt/ext/lwip/src/include/errno.h"); + } + setNativeFileDescriptor(zfd6); + } + super.port = port; + } + + /* + * Connects the socket to a remote address + */ + protected void connect(SocketAddress address, int timeout) + throws IOException + { + // TODO: Refactor and consolidate the connect() logic for all three methods + //System.out.println("host="+((InetSocketAddress)address).getHostString()+", port="+((InetSocketAddress)address).getPort() + ", timeout="+timeout); + createAppropriateSocketImpl(((InetSocketAddress)address).getAddress()); + if ((zfd4 < 0) & (zfd6 < 0)) { + throw new IOException("invalid fd"); + } + ZeroTierSocketAddress zt_addr = null; + int err; + int port = ((InetSocketAddress)address).getPort(); + if (((InetSocketAddress)address).getAddress() instanceof Inet4Address) { + zt_addr = new ZeroTierSocketAddress(((InetSocketAddress)address).getHostString(), ((InetSocketAddress)address).getPort()); + if ((err = ZeroTier.connect(zfd4, zt_addr)) < 0) { + throw new IOException("connect("+zfd4+"), errno="+err+", see: libzt/ext/lwip/src/include/errno.h"); + } + setNativeFileDescriptor(zfd4); + } + if (((InetSocketAddress)address).getAddress() instanceof Inet6Address) { + zt_addr = new ZeroTierSocketAddress(((InetSocketAddress)address).getHostString(), ((InetSocketAddress)address).getPort()); + if ((err = ZeroTier.connect(zfd6, zt_addr)) < 0) { + throw new IOException("connect("+zfd6+"), errno="+err+", see: libzt/ext/lwip/src/include/errno.h"); + } + setNativeFileDescriptor(zfd6); + } + super.port = port; + } + + /* + * Binds the socket to a local address. + * + * If this gets a bind() request on [::] it will create a both an IPv4 and an IPv6 + * socket. This is because we might receive a subsequent listen() and accept() request + * and want to accept an IPv6 connection. (or) we may get a connect() request with + * an IPv4 address. In the latter case we must abandon the IPv6 socket and use the IPv4 + * socket exclusively. + */ + protected void bind(InetAddress host, int port) + throws IOException + { + createAppropriateSocketImpl(host); + /* + After this point we may have either a) created a single IPv4 socket, or b) created + an IPv4 and IPv6 socket in anticipation of either verion being used + */ + //System.out.println("host="+host.toString()+", port="+port); + int err; + if ((zfd < 0) & (zfd4 < 0) & (zfd6 < 0)) { + throw new IOException("invalid fd"); + } + ZeroTierSocketAddress zt_addr = new ZeroTierSocketAddress(host.getHostAddress(), port); + + if (zfd6 > -1) { + // Since an IPv6 socket and accept IPv4 connections we will only bind to this address + if ((err = ZeroTier.bind(zfd6, zt_addr)) < 0) { + throw new IOException("bind("+zfd6+"), errno="+err+", see: libzt/ext/lwip/src/include/errno.h"); + } + super.localport = port; + return; + } + if (zfd4 > -1) { + // Otherwise, just bind to the regular IPv4 address + if ((err = ZeroTier.bind(zfd4, zt_addr)) < 0) { + throw new IOException("bind("+zfd4+"), errno="+err+", see: libzt/ext/lwip/src/include/errno.h"); + } + super.localport = port; + return; + } + + } + + /* + * Puts the socket into a listening state. + * + * We listen on the IPv6 socket since it can listen for IPv4 connections + */ + protected void listen(int backlog) + throws IOException + { + int err; + if ((zfd6 < 0) | (backlog < 0)) { + throw new IOException("invalid fd and/or backlog"); + } + if ((err = ZeroTier.listen(zfd6, backlog)) < 0) { + throw new IOException("listen("+zfd6+"), errno="+err+", see: libzt/ext/lwip/src/include/errno.h"); + } + } + + /* + * Accepts an incoming connection. + * + * We accept on the IPv6 socket since it can accept IPv4 connections + */ + protected void accept(SocketImpl si) + throws IOException + { + if (zfd6 < 0) { + throw new IOException("invalid fd"); + } + int accetpedFd = -1; + ZeroTierSocketAddress addr = new ZeroTierSocketAddress(); + if ((accetpedFd = ZeroTier.accept(zfd6, addr)) < 0) { + throw new IOException("accept("+zfd6+"), errno="+accetpedFd+", see: libzt/ext/lwip/src/include/errno.h"); + } + // Give the new socket fd from the native layer to the new unconnected ZeroTierSocketImpl + ((ZeroTierSocketImpl)si).setFileDescriptor(accetpedFd); + } + + /* + * Returns the input stream for this socket + */ + protected ZeroTierInputStream getInputStream() + throws IOException + { + if (in == null) { + throw new IOException(); + } + return in; + } + + /* + * Returns the output stream for this socket + */ + protected ZeroTierOutputStream getOutputStream() + throws IOException + { + if (out == null) { + throw new IOException(); + } + return out; + } + + /* + * Returns the remote port to which this socket is connected + */ + protected int getPort() + { + return super.port; + } + + /* + * Returns the local port to which this socket is bound + */ + protected int getLocalPort() + { + return super.localport; + } + + /* + * Returns whether this socket implementation supports urgent data (hint: it doesn't) + */ + protected boolean supportsUrgentData() + { + return false; + } + + /* + * + */ + void setSocket(ZeroTierSocket soc) + { + this.socket = soc; + } + + /* + * + */ + Socket getSocket() + { + return socket; + } + + /* + * + */ + /* + void setServerSocket(ZeroTierServerSocket soc) + { + this.serverSocket = soc; + } + */ + + /* + * + */ + ServerSocket getServerSocket() + { + return serverSocket; + } + + /* + * Return the number of bytes that can be read from the socket without blocking + */ + protected int available() + throws IOException + { + // TODO + return 0; + } + + /* + * Closes the socket + */ + protected void close() + throws IOException + { + if (zfd > -1) { + ZeroTier.close(zfd); + } + if (zfd4 > -1) { + ZeroTier.close(zfd4); + } + if (zfd6 > -1) { + ZeroTier.close(zfd6); + } + } + + /* + * Send one byte of urgent data on the socket + */ + protected void sendUrgentData(int data) + throws IOException + { + System.err.println("sendUrgentData: ZeroTierSocketImpl does not currently support this feature"); + } + + /* + * Gets some specified socket option + */ + public Object getOption(int optID) + throws SocketException + { + // Call native layer + ZeroTierSocketOptionValue optval = new ZeroTierSocketOptionValue(); + int option = -1; + int level = -1; + + if (zfd < 0) { // If we haven't committed to a socket version yet, cache the value + if (optID == SocketOptions.SO_TIMEOUT || optID == ZeroTier.SO_RCVTIMEO) { + return Integer.valueOf(_so_rcvtimeo); + } + if (optID == SocketOptions.SO_KEEPALIVE || optID == ZeroTier.SO_KEEPALIVE) { + return Boolean.valueOf(_so_keepalive); + } + if (optID == SocketOptions.SO_SNDBUF || optID == ZeroTier.SO_SNDBUF) { + return Integer.valueOf(_so_sndbuf); + } + if (optID == SocketOptions.SO_RCVBUF || optID == ZeroTier.SO_RCVBUF) { + return Integer.valueOf(_so_rcvbuf); + } + if (optID == SocketOptions.SO_REUSEADDR || optID == ZeroTier.SO_REUSEADDR) { + return Boolean.valueOf(_so_reuseaddr); + } + if (optID == SocketOptions.SO_LINGER || optID == ZeroTier.SO_LINGER) { + return Integer.valueOf(_so_linger); + } + if (optID == SocketOptions.IP_TOS || optID == ZeroTier.IP_TOS) { + return Integer.valueOf(_so_tos); + } + if (optID == SocketOptions.TCP_NODELAY || optID == ZeroTier.TCP_NODELAY) { + return Boolean.valueOf(_so_nodelay); + } + } + else { + if (optID == SocketOptions.SO_TIMEOUT || optID == ZeroTier.SO_RCVTIMEO) { + option = ZeroTier.SO_RCVTIMEO; + level = ZeroTier.SOL_SOCKET; + } + if (optID == SocketOptions.SO_KEEPALIVE || optID == ZeroTier.SO_KEEPALIVE) { + option = ZeroTier.SO_KEEPALIVE; + level = ZeroTier.SOL_SOCKET; + } + if (optID == SocketOptions.SO_SNDBUF || optID == ZeroTier.SO_SNDBUF) { + option = ZeroTier.SO_SNDBUF; + level = ZeroTier.SOL_SOCKET; + } + if (optID == SocketOptions.SO_RCVBUF || optID == ZeroTier.SO_RCVBUF) { + option = ZeroTier.SO_RCVBUF; + level = ZeroTier.SOL_SOCKET; + } + if (optID == SocketOptions.SO_REUSEADDR || optID == ZeroTier.SO_REUSEADDR) { + option = ZeroTier.SO_REUSEADDR; + level = ZeroTier.SOL_SOCKET; + } + if (optID == SocketOptions.SO_LINGER || optID == ZeroTier.SO_LINGER) { + option = ZeroTier.SO_LINGER; + level = ZeroTier.SOL_SOCKET; + } + // IP + if (optID == SocketOptions.IP_TOS || optID == ZeroTier.IP_TOS) { + option = ZeroTier.IP_TOS; + level = ZeroTier.IPPROTO_IP; + } + // TCP + if (optID == SocketOptions.TCP_NODELAY || optID == ZeroTier.TCP_NODELAY) { + option = ZeroTier.TCP_NODELAY; + level = ZeroTier.IPPROTO_TCP; + } + ZeroTier.getsockopt(zfd, level, option, optval); + // Convert native layer's response into Java object of some sort + if (optval.isBoolean) { + return Boolean.valueOf(optval.booleanValue); + } + if (optval.isInteger) { + return Integer.valueOf(optval.integerValue); + } + } + return null; + } + + /* + * Sets a socket option to a specified value. This method should be able to handle SocketOptions values + * as well as native ZeroTier.* options + */ + public void setOption(int optID, Object value) + throws SocketException + { + if (value == null) { + throw new UnsupportedOperationException(); + } + + int option = -1; + int level = -1; + + ZeroTierSocketOptionValue optval = new ZeroTierSocketOptionValue(); + + if (zfd < 0) { // If we haven't committed to a socket version yet, cache the value + if (optID == SocketOptions.SO_TIMEOUT || optID == ZeroTier.SO_RCVTIMEO) { + _so_rcvtimeo = ((Integer)value).intValue(); return; + } + if (optID == SocketOptions.SO_KEEPALIVE || optID == ZeroTier.SO_KEEPALIVE) { + _so_keepalive = ((Boolean)value).booleanValue() ? true : false; return; + } + if (optID == SocketOptions.SO_SNDBUF || optID == ZeroTier.SO_SNDBUF) { + _so_sndbuf = ((Integer)value).intValue(); return; + } + if (optID == SocketOptions.SO_RCVBUF || optID == ZeroTier.SO_RCVBUF) { + _so_rcvbuf = ((Integer)value).intValue(); return; + } + if (optID == SocketOptions.SO_REUSEADDR || optID == ZeroTier.SO_REUSEADDR) { + _so_reuseaddr = ((Boolean)value).booleanValue() ? true : false; return; + } + if (optID == SocketOptions.SO_LINGER || optID == ZeroTier.SO_LINGER) { + _so_linger = ((Integer)value).intValue(); return; + } + if (optID == SocketOptions.IP_TOS || optID == ZeroTier.IP_TOS) { + _so_tos = ((Integer)value).intValue(); return; + } + if (optID == SocketOptions.TCP_NODELAY || optID == ZeroTier.TCP_NODELAY) { + _so_nodelay = ((Boolean)value).booleanValue() ? true : false; return; + } + } + else { + // SOL + if (optID == SocketOptions.SO_TIMEOUT || optID == ZeroTier.SO_RCVTIMEO) { + option = ZeroTier.SO_RCVTIMEO; + level = ZeroTier.SOL_SOCKET; + if (value instanceof Integer) { + optval.isInteger = true; + optval.integerValue = ((Integer)value).intValue(); + } + } + + if (optID == SocketOptions.SO_KEEPALIVE || optID == ZeroTier.SO_KEEPALIVE) { + option = ZeroTier.SO_KEEPALIVE; + level = ZeroTier.SOL_SOCKET; + if (value instanceof Integer) { + optval.isBoolean = true; + optval.booleanValue = ((Boolean)value).booleanValue() ? true : false; + } + } + if (optID == SocketOptions.SO_SNDBUF || optID == ZeroTier.SO_SNDBUF) { + option = ZeroTier.SO_SNDBUF; + level = ZeroTier.SOL_SOCKET; + if (value instanceof Integer) { + optval.isInteger = true; + optval.integerValue = ((Integer)value).intValue(); + } + } + if (optID == SocketOptions.SO_RCVBUF || optID == ZeroTier.SO_RCVBUF) { + option = ZeroTier.SO_RCVBUF; + level = ZeroTier.SOL_SOCKET; + if (value instanceof Integer) { + optval.isInteger = true; + optval.integerValue = ((Integer)value).intValue(); + } + } + if (optID == SocketOptions.SO_REUSEADDR || optID == ZeroTier.SO_REUSEADDR) { + option = ZeroTier.SO_REUSEADDR; + level = ZeroTier.SOL_SOCKET; + if (value instanceof Integer) { + optval.isBoolean = true; + optval.booleanValue = ((Boolean)value).booleanValue() ? true : false; + } + } + if (optID == SocketOptions.SO_LINGER || optID == ZeroTier.SO_LINGER) { + option = ZeroTier.SO_LINGER; + level = ZeroTier.SOL_SOCKET; + if (value instanceof Integer) { + optval.isInteger = true; + optval.integerValue = ((Integer)value).intValue(); + } + } + // IP + if (optID == SocketOptions.IP_TOS || optID == ZeroTier.IP_TOS) { + option = ZeroTier.IP_TOS; + level = ZeroTier.IPPROTO_IP; + if (value instanceof Integer) { + optval.isInteger = true; + optval.integerValue = ((Integer)value).intValue(); + } + } + // TCP + if (optID == SocketOptions.TCP_NODELAY || optID == ZeroTier.TCP_NODELAY) { + option = ZeroTier.TCP_NODELAY; + level = ZeroTier.IPPROTO_TCP; + if (value instanceof Integer) { + optval.isBoolean = true; + optval.booleanValue = ((Boolean)value).booleanValue() ? true : false; + } + } + + if (option < 0) { // No option was properly set + //throw new UnsupportedOperationException(); + } + ZeroTier.setsockopt(zfd, level, option, optval); + } + } + + /* + * Disables the input aspect of the socket + */ + public void shutdownInput() + { + ZeroTier.shutdown(zfd, ZeroTier.SHUT_RD); + // Alternatively: getInputStream().close(); + } + + /* + * Disables the output aspect of the socket + */ + public void shutdownOutput() + { + ZeroTier.shutdown(zfd, ZeroTier.SHUT_WR); + // Alternatively: getOutputStream().close(); + } + + /* + * Sets the file descriptor + */ + public void setFileDescriptor(int fd) + { + zfd = fd; + } + + /* + * Resets the socket + */ + void reset() + throws IOException + { + localport = 0; + address = null; + port = 0; + } + + public FileDescriptor getFileDescriptor() + { + // TODO: Should probably remove this for production + System.out.println("getFileDescriptor(), zfd="+zfd); + ParcelFileDescriptor pfd = ParcelFileDescriptor.adoptFd(zfd); + return pfd.getFileDescriptor(); + } +} \ No newline at end of file diff --git a/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSocketImplFactory.java b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSocketImplFactory.java new file mode 100644 index 0000000..f04bf95 --- /dev/null +++ b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSocketImplFactory.java @@ -0,0 +1,29 @@ +package com.zerotier.libzt; + +import com.zerotier.libzt.ZeroTier; +import com.zerotier.libzt.ZeroTierSocketImpl; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import javax.net.SocketFactory; +import java.net.SocketImplFactory; +import java.util.Objects; + + +public class ZeroTierSocketImplFactory implements SocketImplFactory +{ + /* + * Does nothing + */ + public ZeroTierSocketImplFactory() { } + + /* + * Produces a ZeroTierSocketImpl + */ + public ZeroTierSocketImpl createSocketImpl() + { + return new ZeroTierSocketImpl(); + } +} diff --git a/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSocketOptionValue.java b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSocketOptionValue.java new file mode 100644 index 0000000..15bbfef --- /dev/null +++ b/ports/android/app/src/main/java/com/zerotier/libzt/ZeroTierSocketOptionValue.java @@ -0,0 +1,14 @@ +package com.zerotier.libzt; + +public class ZeroTierSocketOptionValue +{ + public boolean isBoolean = false; + public boolean booleanValue; + + public boolean isInteger = false; + public int integerValue; + + public boolean isTimeval = false; + public int tv_sec; // seconds + public int tv_usec; // microseconds +} \ No newline at end of file diff --git a/ports/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/ports/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..c7bd21d --- /dev/null +++ b/ports/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/ports/android/app/src/main/res/drawable/ic_launcher_background.xml b/ports/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..d5fccc5 --- /dev/null +++ b/ports/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ports/android/app/src/main/res/layout/activity_main.xml b/ports/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..7d8d24e --- /dev/null +++ b/ports/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/ports/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/ports/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/ports/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/ports/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/ports/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/ports/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/ports/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/ports/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..a2f5908 Binary files /dev/null and b/ports/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/ports/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/ports/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..1b52399 Binary files /dev/null and b/ports/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/ports/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/ports/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..ff10afd Binary files /dev/null and b/ports/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/ports/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/ports/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..115a4c7 Binary files /dev/null and b/ports/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/ports/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/ports/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..dcd3cd8 Binary files /dev/null and b/ports/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/ports/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/ports/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..459ca60 Binary files /dev/null and b/ports/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/ports/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/ports/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..8ca12fe Binary files /dev/null and b/ports/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/ports/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/ports/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..8e19b41 Binary files /dev/null and b/ports/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/ports/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/ports/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..b824ebd Binary files /dev/null and b/ports/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/ports/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/ports/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..4c19a13 Binary files /dev/null and b/ports/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/ports/android/app/src/main/res/values/colors.xml b/ports/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..3ab3e9c --- /dev/null +++ b/ports/android/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/ports/android/app/src/main/res/values/strings.xml b/ports/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..87c6cc8 --- /dev/null +++ b/ports/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + ZeroTier + diff --git a/ports/android/app/src/main/res/values/styles.xml b/ports/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..5885930 --- /dev/null +++ b/ports/android/app/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/ports/android/app/src/test/java/com/example/zerotier/ExampleUnitTest.java b/ports/android/app/src/test/java/com/example/zerotier/ExampleUnitTest.java new file mode 100644 index 0000000..f0aee30 --- /dev/null +++ b/ports/android/app/src/test/java/com/example/zerotier/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.example.zerotier; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/ports/android/build.gradle b/ports/android/build.gradle new file mode 100644 index 0000000..43c0708 --- /dev/null +++ b/ports/android/build.gradle @@ -0,0 +1,27 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.1.3' + + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/ports/android/gradle.properties b/ports/android/gradle.properties new file mode 100644 index 0000000..b2b3bb2 --- /dev/null +++ b/ports/android/gradle.properties @@ -0,0 +1,13 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true \ No newline at end of file diff --git a/ports/android/gradle/wrapper/gradle-wrapper.jar b/ports/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7a3265e Binary files /dev/null and b/ports/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/ports/android/gradle/wrapper/gradle-wrapper.properties b/ports/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3705bc5 --- /dev/null +++ b/ports/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Jul 18 09:57:38 PDT 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/ports/android/gradlew b/ports/android/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/ports/android/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/ports/android/gradlew.bat b/ports/android/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/ports/android/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/ports/android/settings.gradle b/ports/android/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/ports/android/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/ports/clean.bat b/ports/clean.bat new file mode 100644 index 0000000..6e337a8 --- /dev/null +++ b/ports/clean.bat @@ -0,0 +1,4 @@ +rd /S /Q bin +rd /S /Q build +rd /S /Q WinBuild32 +rd /S /Q WinBuild64 \ No newline at end of file diff --git a/ports/clean.sh b/ports/clean.sh new file mode 100755 index 0000000..65e8421 --- /dev/null +++ b/ports/clean.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +rm -rf bin build products tmp +rm -f *.o *.s *.exp *.lib .depend* *.core core +rm -rf .depend +find . -type f \( -name '*.o' -o -name '*.o.d' -o -name \ + '*.out' -o -name '*.log' -o -name '*.dSYM' -o -name '*.class' \) -delete diff --git a/ports/dist.bat b/ports/dist.bat new file mode 100644 index 0000000..b82f883 --- /dev/null +++ b/ports/dist.bat @@ -0,0 +1,88 @@ +REM Build all target configurations and copy results into "prebuilt" + +set PrebuiltDebugWin32Dir=staging\debug\win32 +set PrebuiltDebugWin64Dir=staging\debug\win64 +set PrebuiltReleaseWin32Dir=staging\release\win32 +set PrebuiltReleaseWin64Dir=staging\release\win64 + +mkdir %PrebuiltDebugWin32Dir% +mkdir %PrebuiltDebugWin64Dir% +mkdir %PrebuiltReleaseWin32Dir% +mkdir %PrebuiltReleaseWin64Dir% + +set DebugWinBuildDir=bin\lib\Debug +set ReleaseWinBuildDir=bin\lib\Release + +mkdir WinBuild32 & pushd WinBuild32 +cmake -G "Visual Studio 15 2017" ../ +popd +mkdir WinBuild64 & pushd WinBuild64 +cmake -G "Visual Studio 15 2017 Win64" ../ +popd + +cmake --build WinBuild32 --config Release +cmake --build WinBuild32 --config Debug + +copy %DebugWinBuildDir%\zt-static.lib %PrebuiltDebugWin32Dir%\zt.lib +copy %DebugWinBuildDir%\zt-shared.dll %PrebuiltDebugWin32Dir%\zt.dll +copy %ReleaseWinBuildDir%\zt-static.lib %PrebuiltReleaseWin32Dir%\zt.lib +copy %ReleaseWinBuildDir%\zt-shared.dll %PrebuiltReleaseWin32Dir%\zt.dll + +cmake --build WinBuild64 --config Release +cmake --build WinBuild64 --config Debug + +copy %DebugWinBuildDir%\zt-static.lib %PrebuiltDebugWin64Dir%\zt.lib +copy %DebugWinBuildDir%\zt-shared.dll %PrebuiltDebugWin64Dir%\zt.dll +copy %ReleaseWinBuildDir%\zt-static.lib %PrebuiltReleaseWin64Dir%\zt.lib +copy %ReleaseWinBuildDir%\zt-shared.dll %PrebuiltReleaseWin64Dir%\zt.dll + +rd /S /Q bin + +# Build with JNI + +mkdir WinBuild32 & pushd WinBuild32 +cmake -D JNI:BOOL=ON -G "Visual Studio 15 2017" ../ +popd +mkdir WinBuild64 & pushd WinBuild64 +cmake -D JNI:BOOL=ON -G "Visual Studio 15 2017 Win64" ../ +popd + +cmake --build WinBuild32 --config Release +cmake --build WinBuild32 --config Debug + +REM Build JAR file +REM release variant +cd packages\java +del com/zerotier/libzt/*.class +move ..\..\%ReleaseWinBuildDir%\zt-shared.dll zt.dll +javac com/zerotier/libzt/*.java +jar cf zt.jar zt.dll com/zerotier/libzt/*.class +move zt.jar ..\..\%PrebuiltReleaseWin32Dir% +REM debug variant +del com/zerotier/libzt/*.class +move ..\..\%DebugWinBuildDir%\zt-shared.dll zt.dll +javac com/zerotier/libzt/*.java +jar cf zt.jar zt.dll com/zerotier/libzt/*.class +move zt.jar ..\..\%PrebuiltDebugWin32Dir% +popd +popd + +cmake --build WinBuild64 --config Release +cmake --build WinBuild64 --config Debug + +REM Build JAR file +REM release variant +cd packages\java +del com/zerotier/libzt/*.class +move ..\..\%ReleaseWinBuildDir%\zt-shared.dll zt.dll +javac com/zerotier/libzt/*.java +jar cf zt.jar zt.dll com/zerotier/libzt/*.class +move zt.jar ..\..\%PrebuiltReleaseWin64Dir% +REM debug variant +del com/zerotier/libzt/*.class +move ..\..\%DebugWinBuildDir%\zt-shared.dll zt.dll +javac com/zerotier/libzt/*.java +jar cf zt.jar zt.dll com/zerotier/libzt/*.class +move zt.jar ..\..\%PrebuiltDebugWin64Dir% +popd +popd \ No newline at end of file diff --git a/ports/dist.sh b/ports/dist.sh new file mode 100755 index 0000000..14fe9a1 --- /dev/null +++ b/ports/dist.sh @@ -0,0 +1,184 @@ +#!/bin/bash + +# Call this script from the root project directory via `make dist` +# - submodules will be recursively initialized and updated +# - patches will be applied to submodules if needed +# - this script will call CMake to generate library-building packages if necessary +# - once projects have been generated, this script will use their tooling to build the libraries/packages +# - when all products have been built and moved to `tmp`, they will be compressed and moved to `products` + +OSNAME=$(uname | tr '[A-Z]' '[a-z]') +BUILD_CONCURRENCY=4 +PROJROOT=$(pwd) +BUILD_PRODUCTS_DIR=$(pwd)/bin +LIB_PRODUCTS_DIR=$BUILD_PRODUCTS_DIR/lib +FINISHED_PRODUCTS_DIR=$(pwd)/products +STAGING_DIR=$(pwd)/staging +# Windows (previously built) +WIN_PREBUILT_DIR=$PROJROOT/staging/win +WIN_RELEASE_PRODUCTS_DIR=$WIN_PREBUILT_DIR/release +WIN_DEBUG_PRODUCTS_DIR=$WIN_PREBUILT_DIR/debug +WIN32_RELEASE_PRODUCTS_DIR=$WIN_RELEASE_PRODUCTS_DIR/win32 +WIN64_RELEASE_PRODUCTS_DIR=$WIN_RELEASE_PRODUCTS_DIR/win64 +WIN32_DEBUG_PRODUCTS_DIR=$WIN_DEBUG_PRODUCTS_DIR/win32 +WIN64_DEBUG_PRODUCTS_DIR=$WIN_DEBUG_PRODUCTS_DIR/win64 +# Linux +LINUX_PROD_DIR=$PROJROOT/staging/linux +# macOS +MACOS_PROD_DIR=$PROJROOT/staging/macos +MACOS_RELEASE_PROD_DIR=$MACOS_PROD_DIR/release +MACOS_DEBUG_PROD_DIR=$MACOS_PROD_DIR/debug +# iOS +IOS_PROD_DIR=$PROJROOT/staging/ios +# Android +ANDROID_PROJ_DIR=$(pwd)/"ports/android" +ANDROID_ARCHIVE_FILENAME="zt.aar" +# Xcode +XCODE_IOS_PROJ_DIR=$(pwd)/"ports/xcode_ios" +XCODE_MACOS_PROJ_DIR=$(pwd)/"ports/xcode_macos" + +mkdir $FINISHED_PRODUCTS_DIR +mkdir $STAGING_DIR + +# Check that projects exist, generate them and exit if they don't exist +generate_projects_if_necessary() +{ + if [[ $OSNAME = *"darwin"* ]]; then + # iOS + if [ ! -d "$XCODE_IOS_PROJ_DIR" ]; then + echo "BUILDING: iOS project" + should_exit=1 + mkdir -p $XCODE_IOS_PROJ_DIR + cd $XCODE_IOS_PROJ_DIR + cmake -G Xcode ../../ + # Bug in CMake requires us to manually replace architecture strings in project file + sed -i '' 's/x86_64/$(CURRENT_ARCH)/g' $PROJNAME.xcodeproj/project.pbxproj + cd - + fi + # macOS + if [ ! -d "$XCODE_MACOS_PROJ_DIR" ]; then + echo "BUILDING: macOS project" + should_exit=1 + mkdir -p $XCODE_MACOS_PROJ_DIR + cd $XCODE_MACOS_PROJ_DIR + cmake -G Xcode ../../ + cd - + fi + # android? + if [[ $should_exit = 1 ]]; then + echo "Generated projects. Perform necessary modifications and then re-run this script" + echo "Please place previously built windows binaries in $WIN_PREBUILT_DIR before running again." + exit 0 + else + echo "Projects detected, going to build stage next" + fi + fi +} + +build_all_products() +{ + CONFIG=$1 + UPPERCASE_CONFIG="$(tr '[:lower:]' '[:upper:]' <<< ${1:0:1})${1:1}" + + # Targets to build on and for darwin + if [[ $OSNAME = *"darwin"* ]]; then + # Xcode Frameworks --- Builds targets from a CMake-generated Xcode project + if false; then + if [[ $2 != *"JNI"* ]]; then + CURR_BUILD_PRODUCTS_DIR=$LIB_PRODUCTS_DIR/$UPPERCASE_CONFIG + # (iOS) + echo "BUILDING: iOS" + cd $XCODE_IOS_PROJ_DIR + xcodebuild -target zt -configuration "$UPPERCASE_CONFIG" -sdk "iphoneos" + xcodebuild -target zt-static -configuration "$UPPERCASE_CONFIG" -sdk "iphoneos" + cd - + CURR_ARCH="arm64" # spoof this architecture since HOSTTYPE is likely x86_64 + CURR_TMP_PRODUCT_DIR=$STAGING_DIR/$CONFIG/ios-$CURR_ARCH + mkdir -p $CURR_TMP_PRODUCT_DIR + mv $CURR_BUILD_PRODUCTS_DIR/*.framework $CURR_TMP_PRODUCT_DIR + mv $CURR_BUILD_PRODUCTS_DIR/libzt.* $CURR_TMP_PRODUCT_DIR + + # (macOS) + echo "BUILDING: macOS" + cd $XCODE_MACOS_PROJ_DIR + xcodebuild -target zt -configuration "$UPPERCASE_CONFIG" -sdk "macosx" + xcodebuild -target zt-static -configuration "$UPPERCASE_CONFIG" -sdk "macosx" + xcodebuild -target zt-shared -configuration "$UPPERCASE_CONFIG" -sdk "macosx" + cd - + CURR_TMP_PRODUCT_DIR=$STAGING_DIR/$CONFIG/macos-$(uname -m) + mkdir -p $CURR_TMP_PRODUCT_DIR + mv $CURR_BUILD_PRODUCTS_DIR/*.framework $CURR_TMP_PRODUCT_DIR + mv $CURR_BUILD_PRODUCTS_DIR/libzt.* $CURR_TMP_PRODUCT_DIR + fi + fi + # Android Archive (AAR) --- Executes a Gradle task + if true; then + CMAKE_FLAGS=$CMAKE_FLAGS" -DSDK_JNI=1" + CURR_ARCH="armeabi-v7a" # spoof this architecture since HOSTTYPE is likely x86_64 + CURR_TMP_PRODUCT_DIR=$STAGING_DIR/$CONFIG/android-$CURR_ARCH + mkdir -p $CURR_TMP_PRODUCT_DIR + echo "BUILDING: AAR" + cd $ANDROID_PROJ_DIR + ./gradlew assemble$UPPERCASE_CONFIG # e.g. assembleRelease + mv $ANDROID_PROJ_DIR/app/build/outputs/aar/app-$CONFIG.aar $CURR_TMP_PRODUCT_DIR/$ANDROID_ARCHIVE_FILENAME + cd - + fi + # Java Archive (JAR) + if false; then + CURR_BUILD_PRODUCTS_DIR=$LIB_PRODUCTS_DIR + CMAKE_FLAGS=$CMAKE_FLAGS" -DJNI=1" + CURR_TMP_PRODUCT_DIR=$STAGING_DIR/$CONFIG/macos-$(uname -m) + mkdir -p $CURR_TMP_PRODUCT_DIR + echo "BUILDING: JAR" + rm -rf $LIB_PRODUCTS_DIR # clean-lite + cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=$CONFIG "-DJNI=1 -DBUILD_TESTS=0" + cmake --build build + cd $PROJROOT/ports/java + cp $CURR_BUILD_PRODUCTS_DIR/libzt.dylib . + javac com/zerotier/libzt/*.java + jar cf zt.jar libzt.dylib com/zerotier/libzt/*.class + rm libzt.dylib + mv zt.jar $CURR_TMP_PRODUCT_DIR + cd - + fi + fi + # Linux targets + if [[ $OSNAME = *"linux"* ]]; then + CURR_BUILD_PRODUCTS_DIR=$LIB_PRODUCTS_DIR + # Ordinary libraries + if true; then + rm -rf $LIB_PRODUCTS_DIR + cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=$CONFIG "-DBUILD_TESTS=0" + cmake --build build + # -j $BUILD_CONCURRENCY + CURR_TMP_PRODUCT_DIR=$STAGING_DIR/$CONFIG/linux-$(uname -m) + mkdir -p $CURR_TMP_PRODUCT_DIR + mv $CURR_BUILD_PRODUCTS_DIR/libzt.* $CURR_TMP_PRODUCT_DIR + fi + # Java JAR file + if true; then + rm -rf $LIB_PRODUCTS_DIR + cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=$CONFIG "-DJNI=1 -DBUILD_TESTS=0" + cmake --build build + # -j $BUILD_CONCURRENCY + CURR_TMP_PRODUCT_DIR=$STAGING_DIR/$CONFIG/linux-$(uname -m) + mkdir -p $CURR_TMP_PRODUCT_DIR + cd $PROJROOT/ports/java + cp $CURR_BUILD_PRODUCTS_DIR/libzt.so . + javac com/zerotier/libzt/*.java + jar cf zt.jar libzt.dylib com/zerotier/libzt/*.class + rm libzt.dylib + mv zt.jar $CURR_TMP_PRODUCT_DIR + cd - + fi + fi +} + +main() +{ + #generate_projects_if_necessary + build_all_products "release" + build_all_products "debug" +} + +main "$@" \ No newline at end of file diff --git a/ports/module.modulemap b/ports/module.modulemap new file mode 100644 index 0000000..2fd95d7 --- /dev/null +++ b/ports/module.modulemap @@ -0,0 +1,6 @@ +framework module zt { + umbrella header "Xcode-Bridging-Header.h" + + export * + module * { export * } +} diff --git a/ports/package.sh b/ports/package.sh new file mode 100755 index 0000000..011c29b --- /dev/null +++ b/ports/package.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +PROJNAME="zt" +LIBNAME="lib"$PROJNAME +LIBZT_VERSION="1.2.0" +LIBZT_REVISION="1" +ZT_CORE_VERSION="1.2.12" +FILENAME_PREFIX=${LIBNAME} + +STAGING_DIR=$(pwd)/staging +STAGING_DEBUG_DIR=$(pwd)/staging/debug +STAGING_RELEASE_DIR=$(pwd)/staging/release +FINISHED_PRODUCTS_DIR=$(pwd)/products + +# Clean before zipping +find . -type f \( -name '*.DS_Store' -o -name 'thumbs.db' \) -delete + +# Emit a README file +echo $'* libzt version: '${LIBZT_VERSION}$'r'${LIBZT_REVISION}$'\n* Core ZeroTier version: '${ZT_CORE_VERSION}$'\n* Date: '$(date)$'\n\nZeroTier Manual: https://www.zerotier.com/manual.shtml\n +Other Downloads: https://www.zerotier.com/download.shtml +\nlibzt Repo: https://github.com/zerotier/libzt\n\nFor more assistance, visit https://my.zerotier.com and ask your question in our Community section' > ${STAGING_DIR}/README.md + +cp ${STAGING_DIR}/README.md ${STAGING_DIR}/debug/README.md +cp ${STAGING_DIR}/README.md ${STAGING_DIR}/release/README.md + +# Package everything together +# (debug) +PRODUCT_FILENAME=${FILENAME_PREFIX}-debug.tar.gz +echo "Making: " ${FINISHED_PRODUCTS_DIR}/${PRODUCT_FILENAME} +cd ${STAGING_DEBUG_DIR} +tar --exclude=${PRODUCT_FILENAME} -zcvf ${PRODUCT_FILENAME} . +md5 $PRODUCT_FILENAME +mv *.tar.gz ${FINISHED_PRODUCTS_DIR} +cd - + +# (release) +PRODUCT_FILENAME=${FILENAME_PREFIX}-release.tar.gz +echo "Making: " ${FINISHED_PRODUCTS_DIR}/${PRODUCT_FILENAME} +cd ${STAGING_RELEASE_DIR} +tar --exclude=${PRODUCT_FILENAME} -zcvf ${PRODUCT_FILENAME} . +md5 $PRODUCT_FILENAME +mv *.tar.gz ${FINISHED_PRODUCTS_DIR} +cd - \ No newline at end of file