From d94ec3496058f6f78763dba9e9963750b4389aa7 Mon Sep 17 00:00:00 2001
From: KAAAsS
Date: Sat, 25 Mar 2023 01:04:55 +0800
Subject: [PATCH 01/13] feat: Update Zerotier SDK to latest
---
.../zerotier/sdk/DataStoreGetListener.java | 3 +-
.../zerotier/sdk/DataStorePutListener.java | 5 +-
app/src/main/java/com/zerotier/sdk/Event.java | 70 +++-
.../java/com/zerotier/sdk/EventListener.java | 8 +-
.../java/com/zerotier/sdk/NativeUtils.java | 93 ------
app/src/main/java/com/zerotier/sdk/Node.java | 135 ++++----
.../java/com/zerotier/sdk/NodeException.java | 7 +-
.../java/com/zerotier/sdk/NodeStatus.java | 59 ++--
.../java/com/zerotier/sdk/PacketSender.java | 5 +-
.../java/com/zerotier/sdk/PathChecker.java | 2 +
app/src/main/java/com/zerotier/sdk/Peer.java | 62 ++--
.../com/zerotier/sdk/PeerPhysicalPath.java | 52 +--
.../main/java/com/zerotier/sdk/PeerRole.java | 34 +-
.../java/com/zerotier/sdk/ResultCode.java | 50 ++-
.../main/java/com/zerotier/sdk/Version.java | 31 +-
.../zerotier/sdk/VirtualNetworkConfig.java | 308 ++++++++++++------
.../sdk/VirtualNetworkConfigListener.java | 8 +-
.../sdk/VirtualNetworkConfigOperation.java | 37 ++-
.../com/zerotier/sdk/VirtualNetworkDNS.java | 63 +++-
.../sdk/VirtualNetworkFrameListener.java | 7 +-
.../com/zerotier/sdk/VirtualNetworkRoute.java | 145 ++++++---
.../zerotier/sdk/VirtualNetworkStatus.java | 52 ++-
.../com/zerotier/sdk/VirtualNetworkType.java | 29 +-
.../com/zerotier/sdk/util/StringUtils.java | 52 +++
24 files changed, 882 insertions(+), 435 deletions(-)
delete mode 100644 app/src/main/java/com/zerotier/sdk/NativeUtils.java
create mode 100644 app/src/main/java/com/zerotier/sdk/util/StringUtils.java
diff --git a/app/src/main/java/com/zerotier/sdk/DataStoreGetListener.java b/app/src/main/java/com/zerotier/sdk/DataStoreGetListener.java
index 317511e..105b14c 100644
--- a/app/src/main/java/com/zerotier/sdk/DataStoreGetListener.java
+++ b/app/src/main/java/com/zerotier/sdk/DataStoreGetListener.java
@@ -24,6 +24,7 @@
* redistribute it in a modified binary form, please contact ZeroTier Networks
* LLC. Start here: http://www.zerotier.com/
*/
+
package com.zerotier.sdk;
public interface DataStoreGetListener {
@@ -48,7 +49,7 @@ public interface DataStoreGetListener {
* @param out_buffer buffer to put the object in
* @return size of the object
*/
- public long onDataStoreGet(
+ long onDataStoreGet(
String name,
byte[] out_buffer);
}
diff --git a/app/src/main/java/com/zerotier/sdk/DataStorePutListener.java b/app/src/main/java/com/zerotier/sdk/DataStorePutListener.java
index 77e5502..0fa8e19 100644
--- a/app/src/main/java/com/zerotier/sdk/DataStorePutListener.java
+++ b/app/src/main/java/com/zerotier/sdk/DataStorePutListener.java
@@ -24,6 +24,7 @@
* redistribute it in a modified binary form, please contact ZeroTier Networks
* LLC. Start here: http://www.zerotier.com/
*/
+
package com.zerotier.sdk;
public interface DataStorePutListener {
@@ -43,7 +44,7 @@ public interface DataStorePutListener {
* @param secure set to user read/write only.
* @return 0 on success.
*/
- public int onDataStorePut(
+ int onDataStorePut(
String name,
byte[] buffer,
boolean secure);
@@ -54,6 +55,6 @@ public int onDataStorePut(
* @param name Object name
* @return 0 on success.
*/
- public int onDelete(
+ int onDelete(
String name);
}
diff --git a/app/src/main/java/com/zerotier/sdk/Event.java b/app/src/main/java/com/zerotier/sdk/Event.java
index 22d350e..fbc016c 100644
--- a/app/src/main/java/com/zerotier/sdk/Event.java
+++ b/app/src/main/java/com/zerotier/sdk/Event.java
@@ -27,26 +27,32 @@
package com.zerotier.sdk;
+/**
+ * Status codes sent to status update callback when things happen
+ *
+ * Defined in ZeroTierOne.h as ZT_Event
+ */
public enum Event {
+
/**
* Node has been initialized
*
* This is the first event generated, and is always sent. It may occur
* before Node's constructor returns.
*/
- EVENT_UP,
+ EVENT_UP(0),
/**
* Node is offline -- network does not seem to be reachable by any available strategy
*/
- EVENT_OFFLINE,
+ EVENT_OFFLINE(1),
/**
* Node is online -- at least one upstream node appears reachable
*
* Meta-data: none
*/
- EVENT_ONLINE,
+ EVENT_ONLINE(2),
/**
* Node is shutting down
@@ -55,7 +61,7 @@ public enum Event {
* It's done for convenience, since cleaning up other state in the event
* handler may appear more idiomatic.
*/
- EVENT_DOWN,
+ EVENT_DOWN(3),
/**
* Your identity has collided with another node's ZeroTier address
@@ -85,7 +91,7 @@ public enum Event {
* condition is a good way to make sure it never arises. It's like how
* umbrellas prevent rain and smoke detectors prevent fires. They do, right?
*/
- EVENT_FATAL_ERROR_IDENTITY_COLLISION,
+ EVENT_FATAL_ERROR_IDENTITY_COLLISION(4),
/**
* Trace (debugging) message
@@ -94,5 +100,55 @@ public enum Event {
*
* Meta-data: {@link String}, TRACE message
*/
- EVENT_TRACE
-}
\ No newline at end of file
+ EVENT_TRACE(5),
+
+ /**
+ * VERB_USER_MESSAGE received
+ *
+ * These are generated when a VERB_USER_MESSAGE packet is received via
+ * ZeroTier VL1.
+ */
+ EVENT_USER_MESSAGE(6),
+
+ /**
+ * Remote trace received
+ *
+ * These are generated when a VERB_REMOTE_TRACE is received. Note
+ * that any node can fling one of these at us. It is your responsibility
+ * to filter and determine if it's worth paying attention to. If it's
+ * not just drop it. Most nodes that are not active controllers ignore
+ * these, and controllers only save them if they pertain to networks
+ * with remote tracing enabled.
+ */
+ EVENT_REMOTE_TRACE(7);
+
+ @SuppressWarnings({"FieldCanBeLocal", "unused"})
+ private final int id;
+
+ Event(int id) {
+ this.id = id;
+ }
+
+ public static Event fromInt(int id) {
+ switch (id) {
+ case 0:
+ return EVENT_UP;
+ case 1:
+ return EVENT_OFFLINE;
+ case 2:
+ return EVENT_ONLINE;
+ case 3:
+ return EVENT_DOWN;
+ case 4:
+ return EVENT_FATAL_ERROR_IDENTITY_COLLISION;
+ case 5:
+ return EVENT_TRACE;
+ case 6:
+ return EVENT_USER_MESSAGE;
+ case 7:
+ return EVENT_REMOTE_TRACE;
+ default:
+ throw new RuntimeException("Unhandled value: " + id);
+ }
+ }
+}
diff --git a/app/src/main/java/com/zerotier/sdk/EventListener.java b/app/src/main/java/com/zerotier/sdk/EventListener.java
index 91050aa..88fb8af 100644
--- a/app/src/main/java/com/zerotier/sdk/EventListener.java
+++ b/app/src/main/java/com/zerotier/sdk/EventListener.java
@@ -27,19 +27,17 @@
package com.zerotier.sdk;
-import java.net.InetSocketAddress;
-import java.lang.String;
-
/**
* Interface to handle callbacks for ZeroTier One events.
*/
public interface EventListener {
+
/**
* Callback for events with no other associated metadata
*
* @param event {@link Event} enum
*/
- public void onEvent(Event event);
+ void onEvent(Event event);
/**
* Trace messages
@@ -48,5 +46,5 @@ public interface EventListener {
*
* @param message the trace message
*/
- public void onTrace(String message);
+ void onTrace(String message);
}
diff --git a/app/src/main/java/com/zerotier/sdk/NativeUtils.java b/app/src/main/java/com/zerotier/sdk/NativeUtils.java
deleted file mode 100644
index 07e1ef5..0000000
--- a/app/src/main/java/com/zerotier/sdk/NativeUtils.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package com.zerotier.sdk;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * Simple library class for working with JNI (Java Native Interface)
- *
- * @see http://adamheinrich.com/2012/how-to-load-native-jni-library-from-jar
- *
- * @author Adam Heirnich , http://www.adamh.cz
- */
-public class NativeUtils {
-
- /**
- * Private constructor - this class will never be instanced
- */
- private NativeUtils() {
- }
-
- /**
- * Loads library from current JAR archive
- *
- * The file from JAR is copied into system temporary directory and then loaded. The temporary file is deleted after exiting.
- * Method uses String as filename because the pathname is "abstract", not system-dependent.
- *
- * @param filename The filename inside JAR as absolute path (beginning with '/'), e.g. /package/File.ext
- * @throws IOException If temporary file creation or read/write operation fails
- * @throws IllegalArgumentException If source file (param path) does not exist
- * @throws IllegalArgumentException If the path is not absolute or if the filename is shorter than three characters (restriction of {@see File#createTempFile(java.lang.String, java.lang.String)}).
- */
- public static void loadLibraryFromJar(String path) throws IOException {
-
- if (!path.startsWith("/")) {
- throw new IllegalArgumentException("The path has to be absolute (start with '/').");
- }
-
- // Obtain filename from path
- String[] parts = path.split("/");
- String filename = (parts.length > 1) ? parts[parts.length - 1] : null;
-
- // Split filename to prexif and suffix (extension)
- String prefix = "";
- String suffix = null;
- if (filename != null) {
- parts = filename.split("\\.", 2);
- prefix = parts[0];
- suffix = (parts.length > 1) ? "."+parts[parts.length - 1] : null; // Thanks, davs! :-)
- }
-
- // Check if the filename is okay
- if (filename == null || prefix.length() < 3) {
- throw new IllegalArgumentException("The filename has to be at least 3 characters long.");
- }
-
- // Prepare temporary file
- File temp = File.createTempFile(prefix, suffix);
- temp.deleteOnExit();
-
- if (!temp.exists()) {
- throw new FileNotFoundException("File " + temp.getAbsolutePath() + " does not exist.");
- }
-
- // Prepare buffer for data copying
- byte[] buffer = new byte[1024];
- int readBytes;
-
- // Open and check input stream
- InputStream is = NativeUtils.class.getResourceAsStream(path);
- if (is == null) {
- throw new FileNotFoundException("File " + path + " was not found inside JAR.");
- }
-
- // Open output stream and copy data between source file in JAR and the temporary file
- OutputStream os = new FileOutputStream(temp);
- try {
- while ((readBytes = is.read(buffer)) != -1) {
- os.write(buffer, 0, readBytes);
- }
- } finally {
- // If read/write fails, close streams safely before throwing an exception
- os.close();
- is.close();
- }
-
- // Finally, load the library
- System.load(temp.getAbsolutePath());
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/zerotier/sdk/Node.java b/app/src/main/java/com/zerotier/sdk/Node.java
index ef6ac9d..a3f3ab4 100644
--- a/app/src/main/java/com/zerotier/sdk/Node.java
+++ b/app/src/main/java/com/zerotier/sdk/Node.java
@@ -28,95 +28,72 @@
package com.zerotier.sdk;
import java.net.InetSocketAddress;
-import java.util.ArrayList;
-import java.io.IOException;
/**
* A ZeroTier One node
*/
public class Node {
- static {
- try {
- System.loadLibrary("ZeroTierOneJNI");
- } catch (UnsatisfiedLinkError e) {
- try {
- if(System.getProperty("os.name").startsWith("Windows")) {
- System.out.println("Arch: " + System.getProperty("sun.arch.data.model"));
- if(System.getProperty("sun.arch.data.model").equals("64")) {
- NativeUtils.loadLibraryFromJar("/lib/ZeroTierOneJNI_win64.dll");
- } else {
- NativeUtils.loadLibraryFromJar("/lib/ZeroTierOneJNI_win32.dll");
- }
- } else if(System.getProperty("os.name").startsWith("Mac")) {
- NativeUtils.loadLibraryFromJar("/lib/libZeroTierOneJNI.jnilib");
- } else {
- // TODO: Linux
- }
- } catch (IOException ioe) {
- ioe.printStackTrace();
- }
- }
- }
+ static {
+ System.loadLibrary("ZeroTierOneJNI");
+ }
private static final String TAG = "NODE";
/**
* Node ID for JNI purposes.
* Currently set to the now value passed in at the constructor
- *
- * -1 if the node has already been closed
*/
- private long nodeId;
-
- private final DataStoreGetListener getListener;
- private final DataStorePutListener putListener;
- private final PacketSender sender;
- private final EventListener eventListener;
- private final VirtualNetworkFrameListener frameListener;
- private final VirtualNetworkConfigListener configListener;
- private final PathChecker pathChecker;
+ private final long nodeId;
/**
* Create a new ZeroTier One node
*
+ * @param now Current clock in milliseconds
+ */
+ public Node(long now) {
+ this.nodeId = now;
+ }
+
+ /**
+ * Init a new ZeroTier One node
+ *
* Note that this can take a few seconds the first time it's called, as it
* will generate an identity.
*
- * @param now Current clock in milliseconds
* @param getListener User written instance of the {@link DataStoreGetListener} interface called to get objects from persistent storage. This instance must be unique per Node object.
- * @param putListener User written intstance of the {@link DataStorePutListener} interface called to put objects in persistent storage. This instance must be unique per Node object.
- * @param sender
+ * @param putListener User written instance of the {@link DataStorePutListener} interface called to put objects in persistent storage. This instance must be unique per Node object.
+ * @param sender User written instance of the {@link PacketSender} interface to send ZeroTier packets out over the wire.
* @param eventListener User written instance of the {@link EventListener} interface to receive status updates and non-fatal error notices. This instance must be unique per Node object.
- * @param frameListener
+ * @param frameListener User written instance of the {@link VirtualNetworkFrameListener} interface to send a frame out to a virtual network port.
* @param configListener User written instance of the {@link VirtualNetworkConfigListener} interface to be called when virtual LANs are created, deleted, or their config parameters change. This instance must be unique per Node object.
* @param pathChecker User written instance of the {@link PathChecker} interface. Not required and can be null.
*/
- public Node(long now,
- DataStoreGetListener getListener,
- DataStorePutListener putListener,
- PacketSender sender,
- EventListener eventListener,
- VirtualNetworkFrameListener frameListener,
- VirtualNetworkConfigListener configListener,
- PathChecker pathChecker) throws NodeException
- {
- this.nodeId = now;
-
- this.getListener = getListener;
- this.putListener = putListener;
- this.sender = sender;
- this.eventListener = eventListener;
- this.frameListener = frameListener;
- this.configListener = configListener;
- this.pathChecker = pathChecker;
-
- ResultCode rc = node_init(now);
- if(rc != ResultCode.RESULT_OK)
- {
- // TODO: Throw Exception
+ public ResultCode init(
+ DataStoreGetListener getListener,
+ DataStorePutListener putListener,
+ PacketSender sender,
+ EventListener eventListener,
+ VirtualNetworkFrameListener frameListener,
+ VirtualNetworkConfigListener configListener,
+ PathChecker pathChecker) throws NodeException {
+ ResultCode rc = node_init(
+ nodeId,
+ getListener,
+ putListener,
+ sender,
+ eventListener,
+ frameListener,
+ configListener,
+ pathChecker);
+ if(rc != ResultCode.RESULT_OK) {
throw new NodeException(rc.toString());
}
- }
+ return rc;
+ }
+
+ public boolean isInited() {
+ return node_isInited(nodeId);
+ }
/**
* Close this Node.
@@ -124,15 +101,12 @@ public Node(long now,
* The Node object can no longer be used once this method is called.
*/
public void close() {
- if(nodeId != -1) {
- node_delete(nodeId);
- nodeId = -1;
- }
+ node_delete(nodeId);
}
@Override
- protected void finalize() {
- close();
+ public String toString() {
+ return "Node(" + nodeId + ")";
}
/**
@@ -166,6 +140,7 @@ public ResultCode processVirtualNetworkFrame(
* Process a packet received from the physical wire
*
* @param now Current clock in milliseconds
+ * @param localSocket Local socket or -1
* @param remoteAddress Origin of packet
* @param packetData Packet data
* @param nextBackgroundTaskDeadline Value/result: set to deadline for next call to processBackgroundTasks()
@@ -197,7 +172,7 @@ public ResultCode processBackgroundTasks(long now, long[] nextBackgroundTaskDead
* Join a network
*
* This may generate calls to the port config callback before it returns,
- * or these may be deffered if a netconf is not available yet.
+ * or these may be deferred if a netconf is not available yet.
*
* If we are already a member of the network, nothing is done and OK is
* returned.
@@ -392,8 +367,8 @@ public VirtualNetworkConfig networkConfig(long nwid) {
*
* @return List of networks or NULL on failure
*/
- public VirtualNetworkConfig[] networks() {
- return networks(nodeId);
+ public VirtualNetworkConfig[] networkConfigs() {
+ return networkConfigs(nodeId);
}
/**
@@ -408,7 +383,17 @@ public Version getVersion() {
//
// function declarations for JNI
//
- private native ResultCode node_init(long now);
+ private native ResultCode node_init(
+ long nodeId,
+ DataStoreGetListener dataStoreGetListener,
+ DataStorePutListener dataStorePutListener,
+ PacketSender packetSender,
+ EventListener eventListener,
+ VirtualNetworkFrameListener virtualNetworkFrameListener,
+ VirtualNetworkConfigListener virtualNetworkConfigListener,
+ PathChecker pathChecker);
+
+ private native boolean node_isInited(long nodeId);
private native void node_delete(long nodeId);
@@ -471,5 +456,5 @@ private native ResultCode deorbit(
private native Peer[] peers(long nodeId);
- private native VirtualNetworkConfig[] networks(long nodeId);
-}
\ No newline at end of file
+ private native VirtualNetworkConfig[] networkConfigs(long nodeId);
+}
diff --git a/app/src/main/java/com/zerotier/sdk/NodeException.java b/app/src/main/java/com/zerotier/sdk/NodeException.java
index 1fdef72..beeb060 100644
--- a/app/src/main/java/com/zerotier/sdk/NodeException.java
+++ b/app/src/main/java/com/zerotier/sdk/NodeException.java
@@ -27,10 +27,11 @@
package com.zerotier.sdk;
-import java.lang.RuntimeException;
+public class NodeException extends Exception {
-public class NodeException extends RuntimeException {
+ private static final long serialVersionUID = 6268040509883125819L;
+
public NodeException(String message) {
super(message);
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/zerotier/sdk/NodeStatus.java b/app/src/main/java/com/zerotier/sdk/NodeStatus.java
index 94376d8..1172650 100644
--- a/app/src/main/java/com/zerotier/sdk/NodeStatus.java
+++ b/app/src/main/java/com/zerotier/sdk/NodeStatus.java
@@ -27,43 +27,64 @@
package com.zerotier.sdk;
-public final class NodeStatus {
- private long address;
- private String publicIdentity;
- private String secretIdentity;
- private boolean online;
+import com.zerotier.sdk.util.StringUtils;
- private NodeStatus() {}
+/**
+ * Current node status
+ *
+ * Defined in ZeroTierOne.h as ZT_NodeStatus
+ */
+public class NodeStatus {
+
+ private final long address;
+
+ private final String publicIdentity;
+
+ private final String secretIdentity;
+
+ private final boolean online;
+
+ public NodeStatus(long address, String publicIdentity, String secretIdentity, boolean online) {
+ this.address = address;
+ this.publicIdentity = publicIdentity;
+ this.secretIdentity = secretIdentity;
+ this.online = online;
+ }
+
+ @Override
+ public String toString() {
+ return "NodeStatus(" + StringUtils.addressToString(address) + ", " + publicIdentity + ", " + secretIdentity + ", " + online + ")";
+ }
/**
* 40-bit ZeroTier address of this node
*/
- public final long getAddres() {
- return address;
- }
+ public long getAddress() {
+ return address;
+ }
/**
* Public identity in string-serialized form (safe to send to others)
*
* This identity will remain valid as long as the node exists.
*/
- public final String getPublicIdentity() {
- return publicIdentity;
- }
+ public String getPublicIdentity() {
+ return publicIdentity;
+ }
/**
* Full identity including secret key in string-serialized form
*
* This identity will remain valid as long as the node exists.
*/
- public final String getSecretIdentity() {
- return secretIdentity;
- }
+ public String getSecretIdentity() {
+ return secretIdentity;
+ }
/**
* True if some kind of connectivity appears available
*/
- public final boolean isOnline() {
- return online;
- }
-}
\ No newline at end of file
+ public boolean isOnline() {
+ return online;
+ }
+}
diff --git a/app/src/main/java/com/zerotier/sdk/PacketSender.java b/app/src/main/java/com/zerotier/sdk/PacketSender.java
index 06ec01b..893824a 100644
--- a/app/src/main/java/com/zerotier/sdk/PacketSender.java
+++ b/app/src/main/java/com/zerotier/sdk/PacketSender.java
@@ -24,12 +24,14 @@
* redistribute it in a modified binary form, please contact ZeroTier Networks
* LLC. Start here: http://www.zerotier.com/
*/
+
package com.zerotier.sdk;
import java.net.InetSocketAddress;
public interface PacketSender {
+
/**
* Function to send a ZeroTier packet out over the wire
*
@@ -40,9 +42,10 @@ public interface PacketSender {
* @param localSocket socket file descriptor to send from. Set to -1 if not specified.
* @param remoteAddr {@link InetSocketAddress} to send to
* @param packetData data to send
+ * @param ttl TTL is ignored
* @return 0 on success, any error code on failure.
*/
- public int onSendPacketRequested(
+ int onSendPacketRequested(
long localSocket,
InetSocketAddress remoteAddr,
byte[] packetData,
diff --git a/app/src/main/java/com/zerotier/sdk/PathChecker.java b/app/src/main/java/com/zerotier/sdk/PathChecker.java
index 6bf31df..cfc97d6 100644
--- a/app/src/main/java/com/zerotier/sdk/PathChecker.java
+++ b/app/src/main/java/com/zerotier/sdk/PathChecker.java
@@ -8,6 +8,7 @@
import java.net.InetSocketAddress;
public interface PathChecker {
+
/**
* Callback to check whether a path should be used for ZeroTier traffic
*
@@ -28,6 +29,7 @@ public interface PathChecker {
* @param ztAddress ZeroTier address or 0 for none/any
* @param localSocket Local interface socket. -1 if unspecified
* @param remoteAddress remote address
+ * @return true if the path should be used
*/
boolean onPathCheck(long ztAddress, long localSocket, InetSocketAddress remoteAddress);
diff --git a/app/src/main/java/com/zerotier/sdk/Peer.java b/app/src/main/java/com/zerotier/sdk/Peer.java
index eb3d713..e3d5443 100644
--- a/app/src/main/java/com/zerotier/sdk/Peer.java
+++ b/app/src/main/java/com/zerotier/sdk/Peer.java
@@ -27,68 +27,92 @@
package com.zerotier.sdk;
-import java.util.ArrayList;
+import com.zerotier.sdk.util.StringUtils;
+
+import java.util.Arrays;
/**
- * Peer status result
+ * Peer status result buffer
+ *
+ * Defined in ZeroTierOne.h as ZT_Peer
*/
-public final class Peer {
- private long address;
- private int versionMajor;
- private int versionMinor;
- private int versionRev;
- private int latency;
- private PeerRole role;
- private PeerPhysicalPath[] paths;
+public class Peer {
+
+ private final long address;
+
+ private final int versionMajor;
+
+ private final int versionMinor;
+
+ private final int versionRev;
- private Peer() {}
+ private final int latency;
+
+ private final PeerRole role;
+
+ private final PeerPhysicalPath[] paths;
+
+ public Peer(long address, int versionMajor, int versionMinor, int versionRev, int latency, PeerRole role, PeerPhysicalPath[] paths) {
+ this.address = address;
+ this.versionMajor = versionMajor;
+ this.versionMinor = versionMinor;
+ this.versionRev = versionRev;
+ this.latency = latency;
+ this.role = role;
+ this.paths = paths;
+ }
+
+ @Override
+ public String toString() {
+ return "Peer(" + StringUtils.addressToString(address) + ", " + versionMajor + ", " + versionMinor + ", " + versionRev + ", " + latency + ", " + role + ", " + Arrays.toString(paths) + ")";
+ }
/**
* ZeroTier address (40 bits)
*/
- public final long address() {
+ public long getAddress() {
return address;
}
/**
* Remote major version or -1 if not known
*/
- public final int versionMajor() {
+ public int getVersionMajor() {
return versionMajor;
}
/**
* Remote minor version or -1 if not known
*/
- public final int versionMinor() {
+ public int getVersionMinor() {
return versionMinor;
}
/**
* Remote revision or -1 if not known
*/
- public final int versionRev() {
+ public int getVersionRev() {
return versionRev;
}
/**
* Last measured latency in milliseconds or zero if unknown
*/
- public final int latency() {
+ public int getLatency() {
return latency;
}
/**
* What trust hierarchy role does this device have?
*/
- public final PeerRole role() {
+ public PeerRole getRole() {
return role;
}
/**
* Known network paths to peer
*/
- public final PeerPhysicalPath[] paths() {
+ public PeerPhysicalPath[] getPaths() {
return paths;
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/zerotier/sdk/PeerPhysicalPath.java b/app/src/main/java/com/zerotier/sdk/PeerPhysicalPath.java
index 3f9a861..f6d3264 100644
--- a/app/src/main/java/com/zerotier/sdk/PeerPhysicalPath.java
+++ b/app/src/main/java/com/zerotier/sdk/PeerPhysicalPath.java
@@ -31,48 +31,62 @@
/**
* Physical network path to a peer
+ *
+ * Defined in ZeroTierOne.h as ZT_PeerPhysicalPath
*/
-public final class PeerPhysicalPath {
- private InetSocketAddress address;
- private long lastSend;
- private long lastReceive;
- private boolean fixed;
- private boolean preferred;
+public class PeerPhysicalPath {
+
+ private final InetSocketAddress address;
+
+ private final long lastSend;
+
+ private final long lastReceive;
+
+ private final boolean preferred;
+
+ public PeerPhysicalPath(InetSocketAddress address, long lastSend, long lastReceive, boolean preferred) {
+ this.address = address;
+ if (lastSend < 0) {
+ throw new RuntimeException("lastSend < 0: " + lastSend);
+ }
+ this.lastSend = lastSend;
+ if (lastReceive < 0) {
+ throw new RuntimeException("lastReceive < 0: " + lastReceive);
+ }
+ this.lastReceive = lastReceive;
+ this.preferred = preferred;
+ }
- private PeerPhysicalPath() {}
+ @Override
+ public String toString() {
+ return "PeerPhysicalPath(" + address + ", " + lastSend + ", " + lastReceive + ", " + preferred + ")";
+ }
/**
* Address of endpoint
*/
- public final InetSocketAddress address() {
+ public InetSocketAddress getAddress() {
return address;
}
/**
* Time of last send in milliseconds or 0 for never
*/
- public final long lastSend() {
+ public long getLastSend() {
return lastSend;
}
/**
* Time of last receive in milliseconds or 0 for never
*/
- public final long lastReceive() {
+ public long getLastReceive() {
return lastReceive;
}
- /**
- * Is path fixed? (i.e. not learned, static)
- */
- public final boolean isFixed() {
- return fixed;
- }
-
/**
* Is path preferred?
*/
- public final boolean isPreferred() {
+ public boolean isPreferred() {
return preferred;
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/zerotier/sdk/PeerRole.java b/app/src/main/java/com/zerotier/sdk/PeerRole.java
index fce183d..d69a1f1 100644
--- a/app/src/main/java/com/zerotier/sdk/PeerRole.java
+++ b/app/src/main/java/com/zerotier/sdk/PeerRole.java
@@ -27,19 +27,45 @@
package com.zerotier.sdk;
+/**
+ * What trust hierarchy role does this peer have?
+ *
+ * Defined in ZeroTierOne.h as ZT_PeerRole
+ */
public enum PeerRole {
+
/**
* An ordinary node
*/
- PEER_ROLE_LEAF,
+ PEER_ROLE_LEAF(0),
/**
* moon root
*/
- PEER_ROLE_MOON,
+ PEER_ROLE_MOON(1),
/**
* planetary root
*/
- PEER_ROLE_PLANET
-}
\ No newline at end of file
+ PEER_ROLE_PLANET(2);
+
+ @SuppressWarnings({"FieldCanBeLocal", "unused"})
+ private final int id;
+
+ PeerRole(int id) {
+ this.id = id;
+ }
+
+ public static PeerRole fromInt(int id) {
+ switch (id) {
+ case 0:
+ return PEER_ROLE_LEAF;
+ case 1:
+ return PEER_ROLE_MOON;
+ case 2:
+ return PEER_ROLE_PLANET;
+ default:
+ throw new RuntimeException("Unhandled value: " + id);
+ }
+ }
+}
diff --git a/app/src/main/java/com/zerotier/sdk/ResultCode.java b/app/src/main/java/com/zerotier/sdk/ResultCode.java
index 66f5756..dc8a901 100644
--- a/app/src/main/java/com/zerotier/sdk/ResultCode.java
+++ b/app/src/main/java/com/zerotier/sdk/ResultCode.java
@@ -34,14 +34,22 @@
* occurs, the node should be considered to not be working correctly. These
* indicate serious problems like an inaccessible data store or a compile
* problem.
+ *
+ * Defined in ZeroTierOne.h as ZT_ResultCode
*/
public enum ResultCode {
+
/**
* Operation completed normally
*/
- RESULT_OK(0),
+ RESULT_OK(0),
+
+ /**
+ * Call produced no error but no action was taken
+ */
+ RESULT_OK_IGNORED(1),
- // Fatal errors (> 0, < 1000)
+ // Fatal errors (>=100, <1000)
/**
* Ran out of memory
*/
@@ -68,12 +76,36 @@ public enum ResultCode {
RESULT_ERROR_BAD_PARAMETER(1002);
-
- private final int id;
- ResultCode(int id) { this.id = id; }
- public int getValue() { return id; }
+ private final int id;
+
+ ResultCode(int id) {
+ this.id = id;
+ }
+
+ public static ResultCode fromInt(int id) {
+ switch (id) {
+ case 0:
+ return RESULT_OK;
+ case 1:
+ return RESULT_OK_IGNORED;
+ case 100:
+ return RESULT_FATAL_ERROR_OUT_OF_MEMORY;
+ case 101:
+ return RESULT_FATAL_ERROR_DATA_STORE_FAILED;
+ case 102:
+ return RESULT_FATAL_ERROR_INTERNAL;
+ case 1000:
+ return RESULT_ERROR_NETWORK_NOT_FOUND;
+ case 1001:
+ return RESULT_ERROR_UNSUPPORTED_OPERATION;
+ case 1002:
+ return RESULT_ERROR_BAD_PARAMETER;
+ default:
+ throw new RuntimeException("Unhandled value: " + id);
+ }
+ }
- public boolean isFatal(int id) {
- return (id > 100 && id < 1000);
+ public boolean isFatal() {
+ return (id >= 100 && id < 1000);
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/zerotier/sdk/Version.java b/app/src/main/java/com/zerotier/sdk/Version.java
index c93c259..0dbe1d2 100644
--- a/app/src/main/java/com/zerotier/sdk/Version.java
+++ b/app/src/main/java/com/zerotier/sdk/Version.java
@@ -27,10 +27,27 @@
package com.zerotier.sdk;
-public final class Version {
- private Version() {}
-
- public int major = 0;
- public int minor = 0;
- public int revision = 0;
-}
\ No newline at end of file
+public class Version {
+
+ private final int major;
+ private final int minor;
+ private final int revision;
+
+ public Version(int major, int minor, int revision) {
+ this.major = major;
+ this.minor = minor;
+ this.revision = revision;
+ }
+
+ public int getMajor() {
+ return major;
+ }
+
+ public int getMinor() {
+ return minor;
+ }
+
+ public int getRevision() {
+ return revision;
+ }
+}
diff --git a/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfig.java b/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfig.java
index c7b48d5..bcf6485 100644
--- a/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfig.java
+++ b/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfig.java
@@ -29,197 +29,302 @@
import android.util.Log;
-import java.lang.Comparable;
-import java.lang.Override;
-import java.lang.String;
-import java.util.ArrayList;
+import com.zerotier.sdk.util.StringUtils;
+
import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
-public final class VirtualNetworkConfig implements Comparable {
+/**
+ * Virtual network configuration
+ *
+ * Defined in ZeroTierOne.h as ZT_VirtualNetworkConfig
+ */
+public class VirtualNetworkConfig implements Comparable {
+
private final static String TAG = "VirtualNetworkConfig";
public static final int MAX_MULTICAST_SUBSCRIPTIONS = 4096;
public static final int ZT_MAX_ZT_ASSIGNED_ADDRESSES = 16;
- private long nwid;
- private long mac;
- private String name;
- private VirtualNetworkStatus status;
- private VirtualNetworkType type;
- private int mtu;
- private boolean dhcp;
- private boolean bridge;
- private boolean broadcastEnabled;
- private int portError;
- private boolean enabled;
- private long netconfRevision;
- private InetSocketAddress[] assignedAddresses;
- private VirtualNetworkRoute[] routes;
- private VirtualNetworkDNS dns;
-
- private VirtualNetworkConfig() {
+ private final long nwid;
- }
+ private final long mac;
- public boolean equals(VirtualNetworkConfig cfg) {
- ArrayList aaCurrent = new ArrayList<>();
- ArrayList aaNew = new ArrayList<>();
- for (InetSocketAddress s : assignedAddresses) {
- aaCurrent.add(s.toString());
- }
- for (InetSocketAddress s : cfg.assignedAddresses) {
- aaNew.add(s.toString());
+ private final String name;
+
+ private final VirtualNetworkStatus status;
+
+ private final VirtualNetworkType type;
+
+ private final int mtu;
+
+ private final boolean dhcp;
+
+ private final boolean bridge;
+
+ private final boolean broadcastEnabled;
+
+ private final int portError;
+
+ private final long netconfRevision;
+
+ private final InetSocketAddress[] assignedAddresses;
+
+ private final VirtualNetworkRoute[] routes;
+
+ private final VirtualNetworkDNS dns;
+
+ public VirtualNetworkConfig(long nwid, long mac, String name, VirtualNetworkStatus status, VirtualNetworkType type, int mtu, boolean dhcp, boolean bridge, boolean broadcastEnabled, int portError, long netconfRevision, InetSocketAddress[] assignedAddresses, VirtualNetworkRoute[] routes, VirtualNetworkDNS dns) {
+ this.nwid = nwid;
+ this.mac = mac;
+ this.name = name;
+ this.status = status;
+ this.type = type;
+ if (mtu < 0) {
+ throw new RuntimeException("mtu < 0: " + mtu);
}
- Collections.sort(aaCurrent);
- Collections.sort(aaNew);
- boolean aaEqual = aaCurrent.equals(aaNew);
-
- ArrayList rCurrent = new ArrayList<>();
- ArrayList rNew = new ArrayList<>();
- for (VirtualNetworkRoute r : routes) {
- rCurrent.add(r.toString());
+ this.mtu = mtu;
+ this.dhcp = dhcp;
+ this.bridge = bridge;
+ this.broadcastEnabled = broadcastEnabled;
+ this.portError = portError;
+ if (netconfRevision < 0) {
+ throw new RuntimeException("netconfRevision < 0: " + netconfRevision);
}
- for (VirtualNetworkRoute r : cfg.routes) {
- rNew.add(r.toString());
+ this.netconfRevision = netconfRevision;
+ this.assignedAddresses = assignedAddresses;
+ this.routes = routes;
+ this.dns = dns;
+ }
+
+ @Override
+ public String toString() {
+ return "VirtualNetworkConfig(" + StringUtils.networkIdToString(nwid) + ", " + StringUtils.macAddressToString(mac) + ", " + name + ", " + status + ", " + type + ", " + mtu + ", " + dhcp + ", " + bridge + ", " + broadcastEnabled + ", " + portError + ", " + netconfRevision + ", " + Arrays.toString(assignedAddresses) + ", " + Arrays.toString(routes) + ", " + dns + ")";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+
+ if (!(o instanceof VirtualNetworkConfig)) {
+ return false;
}
- Collections.sort(rCurrent);
- Collections.sort(rNew);
- boolean routesEqual = rCurrent.equals(rNew);
+
+ VirtualNetworkConfig cfg = (VirtualNetworkConfig) o;
if (this.nwid != cfg.nwid) {
- Log.i(TAG, "nwid Changed. Old: " + Long.toHexString(this.nwid) + " (" + Long.toString(this.nwid) + "), " +
- "New: " + Long.toHexString(cfg.nwid) + " (" + Long.toString(cfg.nwid) + ")");
+ Log.i(TAG, "NetworkID Changed. Old: " + StringUtils.networkIdToString(this.nwid) + " (" + this.nwid + "), " +
+ "New: " + StringUtils.networkIdToString(cfg.nwid) + " (" + cfg.nwid + ")");
+
+ return false;
}
+
if (this.mac != cfg.mac) {
- Log.i(TAG, "MAC Changed. Old: " + Long.toHexString(this.mac) + ", New: " + Long.toHexString(cfg.mac));
+ Log.i(TAG, "MAC Changed. Old: " + StringUtils.macAddressToString(this.mac) + ", New: " + StringUtils.macAddressToString(cfg.mac));
+
+ return false;
}
if (!this.name.equals(cfg.name)) {
- Log.i(TAG, "Name Changed. Old: " + this.name + " New: "+ cfg.name);
+ Log.i(TAG, "Name Changed. Old: " + this.name + ", New: " + cfg.name);
+
+ return false;
+ }
+
+ if (this.status != cfg.status) {
+ Log.i(TAG, "Status Changed. Old: " + this.status + ", New: " + cfg.status);
+
+ return false;
}
- if (!this.type.equals(cfg.type)) {
- Log.i(TAG, "TYPE changed. Old " + this.type + ", New: " + cfg.type);
+ if (this.type != cfg.type) {
+ Log.i(TAG, "Type changed. Old " + this.type + ", New: " + cfg.type);
+
+ return false;
}
if (this.mtu != cfg.mtu) {
- Log.i(TAG, "MTU Changed. Old: " + this.mtu + ", New: " + cfg.mtu);
+ Log.i(TAG, "MTU Changed. Old: " + this.mtu + ", New: " + cfg.mtu);
+
+ return false;
}
if (this.dhcp != cfg.dhcp) {
Log.i(TAG, "DHCP Flag Changed. Old: " + this.dhcp + ", New: " + cfg.dhcp);
+
+ return false;
}
if (this.bridge != cfg.bridge) {
Log.i(TAG, "Bridge Flag Changed. Old: " + this.bridge + ", New: " + cfg.bridge);
+
+ return false;
}
if (this.broadcastEnabled != cfg.broadcastEnabled) {
- Log.i(TAG, "Broadcast Flag Changed. Old: "+ this.broadcastEnabled +", New: " + this.broadcastEnabled);
+ Log.i(TAG, "Broadcast Flag Changed. Old: "+ this.broadcastEnabled + ", New: " + cfg.broadcastEnabled);
+
+ return false;
}
if (this.portError != cfg.portError) {
- Log.i(TAG, "Port Error Changed. Old: " + this.portError + ", New: " + this.portError);
+ Log.i(TAG, "Port Error Changed. Old: " + this.portError + ", New: " + cfg.portError);
+
+ return false;
}
- if (this.enabled != cfg.enabled) {
- Log.i(TAG, "Enabled Changed. Old: " + this.enabled + ", New: " + this.enabled);
+ if (this.netconfRevision != cfg.netconfRevision) {
+ Log.i(TAG, "NetConfRevision Changed. Old: " + this.netconfRevision + ", New: " + cfg.netconfRevision);
+
+ return false;
}
- if (!aaEqual) {
+ if (!Arrays.equals(assignedAddresses, cfg.assignedAddresses)) {
+
+ ArrayList aaCurrent = new ArrayList<>();
+ ArrayList aaNew = new ArrayList<>();
+ for (InetSocketAddress s : assignedAddresses) {
+ aaCurrent.add(s.toString());
+ }
+ for (InetSocketAddress s : cfg.assignedAddresses) {
+ aaNew.add(s.toString());
+ }
+ Collections.sort(aaCurrent);
+ Collections.sort(aaNew);
+
Log.i(TAG, "Assigned Addresses Changed");
Log.i(TAG, "Old:");
for (String s : aaCurrent) {
Log.i(TAG, " " + s);
}
+ Log.i(TAG, "");
Log.i(TAG, "New:");
for (String s : aaNew) {
Log.i(TAG, " " +s);
}
+ Log.i(TAG, "");
+
+ return false;
}
- if (!routesEqual) {
+ if (!Arrays.equals(routes, cfg.routes)) {
+
+ ArrayList rCurrent = new ArrayList<>();
+ ArrayList rNew = new ArrayList<>();
+ for (VirtualNetworkRoute r : routes) {
+ rCurrent.add(r.toString());
+ }
+ for (VirtualNetworkRoute r : cfg.routes) {
+ rNew.add(r.toString());
+ }
+ Collections.sort(rCurrent);
+ Collections.sort(rNew);
+
Log.i(TAG, "Managed Routes Changed");
Log.i(TAG, "Old:");
for (String s : rCurrent) {
Log.i(TAG, " " + s);
}
+ Log.i(TAG, "");
Log.i(TAG, "New:");
for (String s : rNew) {
Log.i(TAG, " " + s);
}
+ Log.i(TAG, "");
+
+ return false;
+ }
+
+ boolean dnsEquals;
+ if (this.dns == null) {
+ //noinspection RedundantIfStatement
+ if (cfg.dns == null) {
+ dnsEquals = true;
+ } else {
+ dnsEquals = false;
+ }
+ } else {
+ if (cfg.dns == null) {
+ dnsEquals = false;
+ } else {
+ dnsEquals = this.dns.equals(cfg.dns);
+ }
}
- boolean dnsEquals = false;
- if (this.dns == null || cfg.dns == null) {
- dnsEquals = true;
- } else if (this.dns != null) {
- dnsEquals = this.dns.equals(cfg.dns);
+ if (!dnsEquals) {
+ return false;
}
- return this.nwid == cfg.nwid &&
- this.mac == cfg.mac &&
- this.name.equals(cfg.name) &&
- this.status.equals(cfg.status) &&
- this.type.equals(cfg.type) &&
- this.mtu == cfg.mtu &&
- this.dhcp == cfg.dhcp &&
- this.bridge == cfg.bridge &&
- this.broadcastEnabled == cfg.broadcastEnabled &&
- this.portError == cfg.portError &&
- this.enabled == cfg.enabled &&
- dnsEquals &&
- aaEqual && routesEqual;
+ return true;
}
+ @Override
public int compareTo(VirtualNetworkConfig cfg) {
- if(cfg.nwid == this.nwid) {
- return 0;
- } else {
- return this.nwid > cfg.nwid ? 1 : -1;
- }
+ return Long.compare(this.nwid, cfg.nwid);
+ }
+
+ @Override
+ public int hashCode() {
+
+ int result = 17;
+ result = 37 * result + (int) (nwid ^ (nwid >>> 32));
+ result = 37 * result + (int) (mac ^ (mac >>> 32));
+ result = 37 * result + name.hashCode();
+ result = 37 * result + status.hashCode();
+ result = 37 * result + type.hashCode();
+ result = 37 * result + mtu;
+ result = 37 * result + (dhcp ? 1 : 0);
+ result = 37 * result + (bridge ? 1 : 0);
+ result = 37 * result + (broadcastEnabled ? 1 : 0);
+ result = 37 * result + portError;
+ result = 37 * result + (int) (netconfRevision ^ (netconfRevision >>> 32));
+ result = 37 * result + Arrays.hashCode(assignedAddresses);
+ result = 37 * result + Arrays.hashCode(routes);
+ result = 37 * result + (dns == null ? 0 : dns.hashCode());
+
+ return result;
}
/**
* 64-bit ZeroTier network ID
*/
- public final long networkId() {
+ public long getNwid() {
return nwid;
}
/**
- * Ethernet MAC (40 bits) that should be assigned to port
+ * Ethernet MAC (48 bits) that should be assigned to port
*/
- public final long macAddress() {
+ public long getMac() {
return mac;
}
/**
* Network name (from network configuration master)
*/
- public final String name() {
+ public String getName() {
return name;
}
/**
* Network configuration request status
*/
- public final VirtualNetworkStatus networkStatus() {
+ public VirtualNetworkStatus getStatus() {
return status;
}
/**
* Network type
*/
- public final VirtualNetworkType networkType() {
+ public VirtualNetworkType getType() {
return type;
}
/**
* Maximum interface MTU
*/
- public final int mtu() {
+ public int getMtu() {
return mtu;
}
@@ -230,7 +335,7 @@ public final int mtu() {
* for security or other reasons. This is simply a netconf parameter that
* means 'DHCP is available on this network.'
*/
- public final boolean isDhcpAvailable() {
+ public boolean isDhcp() {
return dhcp;
}
@@ -240,21 +345,21 @@ public final boolean isDhcpAvailable() {
* This is informational. If this is false, bridged packets will simply
* be dropped and bridging won't work.
*/
- public final boolean isBridgeEnabled() {
+ public boolean isBridge() {
return bridge;
}
/**
* If true, this network supports and allows broadcast (ff:ff:ff:ff:ff:ff) traffic
*/
- public final boolean broadcastEnabled() {
+ public boolean isBroadcastEnabled() {
return broadcastEnabled;
}
/**
* If the network is in PORT_ERROR state, this is the error most recently returned by the port config callback
*/
- public final int portError() {
+ public int getPortError() {
return portError;
}
@@ -263,12 +368,12 @@ public final int portError() {
*
* If this is zero, it means we're still waiting for our netconf.
*/
- public final long netconfRevision() {
+ public long getNetconfRevision() {
return netconfRevision;
}
/**
- * ZeroTier-assigned addresses (in {@link java.net.InetSocketAddress} objects)
+ * ZeroTier-assigned addresses (in {@link InetSocketAddress} objects)
*
* For IP, the port number of the sockaddr_XX structure contains the number
* of bits in the address netmask. Only the IP address and port are used.
@@ -277,16 +382,21 @@ public final long netconfRevision() {
* This is only used for ZeroTier-managed address assignments sent by the
* virtual network's configuration master.
*/
- public final InetSocketAddress[] assignedAddresses() {
+ public InetSocketAddress[] getAssignedAddresses() {
return assignedAddresses;
}
/**
- * ZeroTier-assigned routes (in {@link com.zerotier.sdk.VirtualNetworkRoute} objects)
- *
- * @return
+ * ZeroTier-assigned routes (in {@link VirtualNetworkRoute} objects)
*/
- public final VirtualNetworkRoute[] routes() { return routes; }
+ public VirtualNetworkRoute[] getRoutes() {
+ return routes;
+ }
- public final VirtualNetworkDNS dns() { return dns; }
+ /**
+ * Network specific DNS configuration
+ */
+ public VirtualNetworkDNS getDns() {
+ return dns;
+ }
}
diff --git a/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfigListener.java b/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfigListener.java
index 15ae301..ce91e79 100644
--- a/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfigListener.java
+++ b/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfigListener.java
@@ -25,11 +25,11 @@
* LLC. Start here: http://www.zerotier.com/
*/
-
package com.zerotier.sdk;
public interface VirtualNetworkConfigListener {
+
/**
* Callback called to update virtual network port configuration
*
@@ -40,7 +40,7 @@ public interface VirtualNetworkConfigListener {
* This in turn should be used by the underlying implementation to create
* and configure tap devices at the OS (or virtual network stack) layer.
*
- * This should not call {@link Node#multicastSubscribe} or other network-modifying
+ * This should not call {@link Node#multicastSubscribe(long, long)} or other network-modifying
* methods, as this could cause a deadlock in multithreaded or interrupt
* driven environments.
*
@@ -53,8 +53,8 @@ public interface VirtualNetworkConfigListener {
* @param config {@link VirtualNetworkConfig} object with the new configuration
* @return 0 on success
*/
- public int onNetworkConfigurationUpdated(
+ int onNetworkConfigurationUpdated(
long nwid,
VirtualNetworkConfigOperation op,
VirtualNetworkConfig config);
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfigOperation.java b/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfigOperation.java
index b70eb47..a1981bd 100644
--- a/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfigOperation.java
+++ b/app/src/main/java/com/zerotier/sdk/VirtualNetworkConfigOperation.java
@@ -24,26 +24,55 @@
* redistribute it in a modified binary form, please contact ZeroTier Networks
* LLC. Start here: http://www.zerotier.com/
*/
+
package com.zerotier.sdk;
+/**
+ * Virtual network configuration update type
+ *
+ * Defined in ZeroTierOne.h as ZT_VirtualNetworkConfigOperation
+ */
public enum VirtualNetworkConfigOperation {
+
/**
* Network is coming up (either for the first time or after service restart)
*/
- VIRTUAL_NETWORK_CONFIG_OPERATION_UP,
+ VIRTUAL_NETWORK_CONFIG_OPERATION_UP(1),
/**
* Network configuration has been updated
*/
- VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE,
+ VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE(2),
/**
* Network is going down (not permanently)
*/
- VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN,
+ VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN(3),
/**
* Network is going down permanently (leave/delete)
*/
- VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY
+ VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY(4);
+
+ @SuppressWarnings({"FieldCanBeLocal", "unused"})
+ private final int id;
+
+ VirtualNetworkConfigOperation(int id) {
+ this.id = id;
+ }
+
+ public static VirtualNetworkConfigOperation fromInt(int id) {
+ switch (id) {
+ case 1:
+ return VIRTUAL_NETWORK_CONFIG_OPERATION_UP;
+ case 2:
+ return VIRTUAL_NETWORK_CONFIG_OPERATION_CONFIG_UPDATE;
+ case 3:
+ return VIRTUAL_NETWORK_CONFIG_OPERATION_DOWN;
+ case 4:
+ return VIRTUAL_NETWORK_CONFIG_OPERATION_DESTROY;
+ default:
+ throw new RuntimeException("Unhandled value: " + id);
+ }
+ }
}
diff --git a/app/src/main/java/com/zerotier/sdk/VirtualNetworkDNS.java b/app/src/main/java/com/zerotier/sdk/VirtualNetworkDNS.java
index 7046fd4..6e4bb3d 100644
--- a/app/src/main/java/com/zerotier/sdk/VirtualNetworkDNS.java
+++ b/app/src/main/java/com/zerotier/sdk/VirtualNetworkDNS.java
@@ -8,15 +8,48 @@
import java.net.InetSocketAddress;
import java.util.ArrayList;
+/**
+ * DNS configuration to be pushed on a virtual network
+ *
+ * Defined in ZeroTierOne.h as ZT_VirtualNetworkDNS
+ */
public class VirtualNetworkDNS implements Comparable {
- private String domain;
- private ArrayList servers;
- public VirtualNetworkDNS() {}
+ private final String domain;
+ private final ArrayList servers;
+
+ public VirtualNetworkDNS(String domain, ArrayList servers) {
+ this.domain = domain;
+ this.servers = servers;
+ }
+
+ @Override
+ public String toString() {
+ return "VirtualNetworkDNS(" + domain + ", " + servers + ")";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+
+ if (o == null) {
+ return false;
+ }
+
+ if (!(o instanceof VirtualNetworkDNS)) {
+ return false;
+ }
- public boolean equals(VirtualNetworkDNS o) {
- if (o == null) return false;
- return domain.equals(o.domain) && servers.equals(o.servers);
+ VirtualNetworkDNS d = (VirtualNetworkDNS) o;
+
+ if (!domain.equals(d.domain)) {
+ return false;
+ }
+
+ if (!servers.equals(d.servers)) {
+ return false;
+ }
+
+ return true;
}
@Override
@@ -24,7 +57,21 @@ public int compareTo(VirtualNetworkDNS o) {
return domain.compareTo(o.domain);
}
- public String getSearchDomain() { return domain; }
+ @Override
+ public int hashCode() {
+
+ int result = 17;
+ result = 37 * result + domain.hashCode();
+ result = 37 * result + servers.hashCode();
+
+ return result;
+ }
+
+ public String getDomain() {
+ return domain;
+ }
- public ArrayList getServers() { return servers; }
+ public ArrayList getServers() {
+ return servers;
+ }
}
diff --git a/app/src/main/java/com/zerotier/sdk/VirtualNetworkFrameListener.java b/app/src/main/java/com/zerotier/sdk/VirtualNetworkFrameListener.java
index 9ad3228..650c9ce 100644
--- a/app/src/main/java/com/zerotier/sdk/VirtualNetworkFrameListener.java
+++ b/app/src/main/java/com/zerotier/sdk/VirtualNetworkFrameListener.java
@@ -28,17 +28,18 @@
package com.zerotier.sdk;
public interface VirtualNetworkFrameListener {
+
/**
* Function to send a frame out to a virtual network port
*
* @param nwid ZeroTier One network ID
* @param srcMac source MAC address
* @param destMac destination MAC address
- * @param ethertype
- * @param vlanId
+ * @param etherType EtherType
+ * @param vlanId VLAN ID
* @param frameData data to send
*/
- public void onVirtualNetworkFrame(
+ void onVirtualNetworkFrame(
long nwid,
long srcMac,
long destMac,
diff --git a/app/src/main/java/com/zerotier/sdk/VirtualNetworkRoute.java b/app/src/main/java/com/zerotier/sdk/VirtualNetworkRoute.java
index 8dd700c..afd9ee4 100644
--- a/app/src/main/java/com/zerotier/sdk/VirtualNetworkRoute.java
+++ b/app/src/main/java/com/zerotier/sdk/VirtualNetworkRoute.java
@@ -29,80 +29,135 @@
import java.net.InetSocketAddress;
-public final class VirtualNetworkRoute implements Comparable
+/**
+ * A route to be pushed on a virtual network
+ *
+ * Defined in ZeroTierOne.h as ZT_VirtualNetworkRoute
+ */
+public class VirtualNetworkRoute implements Comparable
{
- private VirtualNetworkRoute() {
- target = null;
- via = null;
- flags = 0;
- metric = 0;
- }
-
/**
* Target network / netmask bits (in port field) or NULL or 0.0.0.0/0 for default
*/
- public InetSocketAddress target;
-
+ private final InetSocketAddress target;
+
/**
* Gateway IP address (port ignored) or NULL (family == 0) for LAN-local (no gateway)
*/
- public InetSocketAddress via;
+ private final InetSocketAddress via;
/**
* Route flags
*/
- public int flags;
+ private final int flags;
/**
* Route metric (not currently used)
*/
- public int metric;
+ private final int metric;
- @Override
+ public VirtualNetworkRoute(InetSocketAddress target, InetSocketAddress via, int flags, int metric) {
+ this.target = target;
+ this.via = via;
+ this.flags = flags;
+ this.metric = metric;
+ }
+
+ @Override
public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append(target.toString());
- if (via != null) {
- sb.append(via.toString());
- }
- return sb.toString();
+ return "VirtualNetworkRoute(" + target + ", " + via + ", " + flags + ", " + metric + ")";
}
@Override
- public int compareTo(VirtualNetworkRoute other) {
- return this.toString().compareTo(other.toString());
- }
-
- public boolean equals(VirtualNetworkRoute other) {
- boolean targetEquals = false;
- if (target == null && other.target == null) {
- targetEquals = true;
- }
- else if (target == null && other.target != null) {
- targetEquals = false;
- }
- else if (target != null && other.target == null) {
- targetEquals = false;
+ public int compareTo(VirtualNetworkRoute other) {
+ throw new RuntimeException("Unimplemented");
+ }
+
+ @Override
+ public boolean equals(Object o) {
+
+ if (!(o instanceof VirtualNetworkRoute)) {
+ return false;
}
- else {
- targetEquals = target.toString().equals(other.target.toString());
+
+ VirtualNetworkRoute other = (VirtualNetworkRoute) o;
+
+ boolean targetEquals;
+ if (target == null) {
+ //noinspection RedundantIfStatement
+ if (other.target == null) {
+ targetEquals = true;
+ } else {
+ targetEquals = false;
+ }
+ } else {
+ if (other.target == null) {
+ targetEquals = false;
+ } else {
+ targetEquals = target.equals(other.target);
+ }
}
+ if (!targetEquals) {
+ return false;
+ }
boolean viaEquals;
- if (via == null && other.via == null) {
- viaEquals = true;
+ if (via == null) {
+ //noinspection RedundantIfStatement
+ if (other.via == null) {
+ viaEquals = true;
+ } else {
+ viaEquals = false;
+ }
+ } else {
+ if (other.via == null) {
+ viaEquals = false;
+ } else {
+ viaEquals = via.equals(other.via);
+ }
}
- else if (via == null && other.via != null) {
- viaEquals = false;
+
+ if (!viaEquals) {
+ return false;
}
- else if (via != null && other.via == null) {
- viaEquals = false;
+
+ if (flags != other.flags) {
+ return false;
}
- else {
- viaEquals = via.toString().equals(other.via.toString());
+
+ if (metric != other.metric) {
+ return false;
}
- return viaEquals && targetEquals;
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+
+ int result = 17;
+ result = 37 * result + (target == null ? 0 : target.hashCode());
+ result = 37 * result + (via == null ? 0 : via.hashCode());
+ result = 37 * result + flags;
+ result = 37 * result + metric;
+
+ return result;
+ }
+
+ public InetSocketAddress getTarget() {
+ return target;
+ }
+
+ public InetSocketAddress getVia() {
+ return via;
+ }
+
+ public int getFlags() {
+ return flags;
+ }
+
+ public int getMetric() {
+ return metric;
}
}
diff --git a/app/src/main/java/com/zerotier/sdk/VirtualNetworkStatus.java b/app/src/main/java/com/zerotier/sdk/VirtualNetworkStatus.java
index 2d00561..8a32ba6 100644
--- a/app/src/main/java/com/zerotier/sdk/VirtualNetworkStatus.java
+++ b/app/src/main/java/com/zerotier/sdk/VirtualNetworkStatus.java
@@ -24,36 +24,76 @@
* redistribute it in a modified binary form, please contact ZeroTier Networks
* LLC. Start here: http://www.zerotier.com/
*/
+
package com.zerotier.sdk;
+/**
+ * Virtual network status codes
+ *
+ * Defined in ZeroTierOne.h as ZT_VirtualNetworkStatus
+ */
public enum VirtualNetworkStatus {
+
/**
* Waiting for network configuration (also means revision == 0)
*/
- NETWORK_STATUS_REQUESTING_CONFIGURATION,
+ NETWORK_STATUS_REQUESTING_CONFIGURATION(0),
/**
* Configuration received and we are authorized
*/
- NETWORK_STATUS_OK,
+ NETWORK_STATUS_OK(1),
/**
* Netconf master told us 'nope'
*/
- NETWORK_STATUS_ACCESS_DENIED,
+ NETWORK_STATUS_ACCESS_DENIED(2),
/**
* Netconf master exists, but this virtual network does not
*/
- NETWORK_STATUS_NOT_FOUND,
+ NETWORK_STATUS_NOT_FOUND(3),
/**
* Initialization of network failed or other internal error
*/
- NETWORK_STATUS_PORT_ERROR,
+ NETWORK_STATUS_PORT_ERROR(4),
/**
* ZeroTier One version too old
*/
- NETWORK_STATUS_CLIENT_TOO_OLD
+ NETWORK_STATUS_CLIENT_TOO_OLD(5),
+
+ /**
+ * External authentication is required (e.g. SSO)
+ */
+ NETWORK_STATUS_AUTHENTICATION_REQUIRED(6);
+
+ @SuppressWarnings({"FieldCanBeLocal", "unused"})
+ private final int id;
+
+ VirtualNetworkStatus(int id) {
+ this.id = id;
+ }
+
+ public static VirtualNetworkStatus fromInt(int id) {
+ switch (id) {
+ case 0:
+ return NETWORK_STATUS_REQUESTING_CONFIGURATION;
+ case 1:
+ return NETWORK_STATUS_OK;
+ case 2:
+ return NETWORK_STATUS_ACCESS_DENIED;
+ case 3:
+ return NETWORK_STATUS_NOT_FOUND;
+ case 4:
+ return NETWORK_STATUS_PORT_ERROR;
+ case 5:
+ return NETWORK_STATUS_CLIENT_TOO_OLD;
+ case 6:
+ return NETWORK_STATUS_AUTHENTICATION_REQUIRED;
+ default:
+ throw new RuntimeException("Unhandled value: " + id);
+ }
+ }
}
diff --git a/app/src/main/java/com/zerotier/sdk/VirtualNetworkType.java b/app/src/main/java/com/zerotier/sdk/VirtualNetworkType.java
index ab1f4e0..44be886 100644
--- a/app/src/main/java/com/zerotier/sdk/VirtualNetworkType.java
+++ b/app/src/main/java/com/zerotier/sdk/VirtualNetworkType.java
@@ -24,16 +24,41 @@
* redistribute it in a modified binary form, please contact ZeroTier Networks
* LLC. Start here: http://www.zerotier.com/
*/
+
package com.zerotier.sdk;
+/**
+ * Virtual network type codes
+ *
+ * Defined in ZeroTierOne.h as ZT_VirtualNetworkType
+ */
public enum VirtualNetworkType {
+
/**
* Private networks are authorized via certificates of membership
*/
- NETWORK_TYPE_PRIVATE,
+ NETWORK_TYPE_PRIVATE(0),
/**
* Public networks have no access control -- they'll always be AUTHORIZED
*/
- NETWORK_TYPE_PUBLIC
+ NETWORK_TYPE_PUBLIC(1);
+
+ @SuppressWarnings({"FieldCanBeLocal", "unused"})
+ private final int id;
+
+ VirtualNetworkType(int id) {
+ this.id = id;
+ }
+
+ public static VirtualNetworkType fromInt(int id) {
+ switch (id) {
+ case 0:
+ return NETWORK_TYPE_PRIVATE;
+ case 1:
+ return NETWORK_TYPE_PUBLIC;
+ default:
+ throw new RuntimeException("Unhandled value: " + id);
+ }
+ }
}
diff --git a/app/src/main/java/com/zerotier/sdk/util/StringUtils.java b/app/src/main/java/com/zerotier/sdk/util/StringUtils.java
new file mode 100644
index 0000000..c7bcc5d
--- /dev/null
+++ b/app/src/main/java/com/zerotier/sdk/util/StringUtils.java
@@ -0,0 +1,52 @@
+/*
+ * ZeroTier One - Network Virtualization Everywhere
+ * Copyright (C) 2011-2023 ZeroTier, Inc. https://www.zerotier.com/
+ */
+
+package com.zerotier.sdk.util;
+
+public class StringUtils {
+
+ /**
+ * Convert mac address to string.
+ *
+ * @param mac MAC address
+ * @return string in XX:XX:XX:XX:XX:XX format
+ */
+ public static String macAddressToString(long mac) {
+
+ int[] macChars = new int[6];
+ for (int i = 0; i < 6; i++) {
+ macChars[i] = (int) (mac % 256);
+ mac >>= 8;
+ }
+
+ return String.format("%02x:%02x:%02x:%02x:%02x:%02x", macChars[5], macChars[4], macChars[3], macChars[2], macChars[1], macChars[0]);
+ }
+
+ /**
+ * Convert long to hex string.
+ *
+ * @param networkId long
+ * @return string with 0 padding
+ */
+ public static String networkIdToString(long networkId) {
+ return String.format("%016x", networkId);
+ }
+
+ /**
+ * Convert node address to string.
+ *
+ * Node addresses are 40 bits, so print 10 hex characters.
+ *
+ * @param address Node address
+ * @return formatted string
+ */
+ public static String addressToString(long address) {
+ return String.format("%010x", address);
+ }
+
+ public static String etherTypeToString(long etherType) {
+ return String.format("%04x", etherType);
+ }
+}
From f3ac86ef86f67b0fc6dc317e2f80b10adb178cc1 Mon Sep 17 00:00:00 2001
From: KAAAsS
Date: Sat, 25 Mar 2023 20:38:40 +0800
Subject: [PATCH 02/13] feat: Update Zerotier prebuilt core to 1.10.5
---
.../jniLibs/arm64-v8a/libZeroTierOneJNI.so | Bin 1177896 -> 1086896 bytes
.../jniLibs/armeabi-v7a/libZeroTierOneJNI.so | Bin 870160 -> 818972 bytes
app/src/main/jniLibs/x86/libZeroTierOneJNI.so | Bin 1398556 -> 1297596 bytes
.../main/jniLibs/x86_64/libZeroTierOneJNI.so | Bin 1395256 -> 1237528 bytes
4 files changed, 0 insertions(+), 0 deletions(-)
diff --git a/app/src/main/jniLibs/arm64-v8a/libZeroTierOneJNI.so b/app/src/main/jniLibs/arm64-v8a/libZeroTierOneJNI.so
index cca00431a6b51b833a0387e48ce69fe3d5544a41..55391115d954caf7bddd96e51f60b6d28562e3a3 100644
GIT binary patch
literal 1086896
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