diff --git a/.gitignore b/.gitignore
index a129b62..1ba3b0c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,17 @@
.gradle/
.idea/
build/
+cjdns-src/
local.properties
*.iml
*.class
-src/main/assets/x86/
+src/main/libs/
+src/main/obj/
+src/main/assets/armeabi/
src/main/assets/armeabi-v7a/
-src/main/assets/cjdroute.conf
+src/main/assets/arm64-v8a/
+src/main/assets/x86/
+src/main/assets/x86_64/
+src/main/assets/mips/
+src/main/assets/mips64/
+src/main/assets/all/
diff --git a/.travis.yml b/.travis.yml
index dea662c..3c81db1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,14 +1,12 @@
language: android
+
android:
components:
- platform-tools
- tools
- # The BuildTools version used by your project
- - build-tools-23.0.2
-
- # The SDK version used to compile your project
- - android-23
+ - build-tools-25.0.1
+ - android-25
- extra-google-google_play_services
- extra-google-m2repository
@@ -18,4 +16,14 @@ android:
- 'android-sdk-license-.+'
- 'google-gdk-license-.+'
-script: ./gradlew assembleDebug
+jdk:
+ - oraclejdk8
+
+before_script:
+ - export NDK_VERSION=android-ndk-r11c
+ - curl -L https://dl.google.com/android/repository/${NDK_VERSION}-linux-x86_64.zip -O
+ - unzip -q ${NDK_VERSION}-linux-x86_64.zip
+ - export ANDROID_NDK_HOME=`pwd`/${NDK_VERSION}
+ - export PATH=${ANDROID_NDK_HOME}:${PATH}
+
+script: ./assemble_debug
diff --git a/README.md b/README.md
index 694104e..20f88a4 100644
--- a/README.md
+++ b/README.md
@@ -5,42 +5,26 @@ cjdns for Android
Meshnet is an Android app that lets you connect to cjdns networks, without the need for a rooted phone—thanks to the android.net.VpnService API introduced with Android 4.0 (Ice Cream Sandwich). Older versions still require root and routes through a TUN device.
-**Current state:** App starts and stops cjdroute for rooted devices with Android 4.4 (KitKat) and below. A public peer is added by default, so you should be able to browse websites and reach services on Hyperboria just by starting the cjdns service with the toggle. All other menus are only populated with mock data at the moment and you cannot add additional peers.
+**Current state:** App starts and stops cjdroute and sets up a VPN to access Hyperboria for non-rooted devices. Two public peers are added by default, so you should be able to browse websites and reach services on Hyperboria just by starting the cjdns service with the toggle. All other menus are only populated with mock data at the moment and you cannot add additional peers.
Installation
------------
-1. Install the [Android SDK](http://developer.android.com/sdk/index.html)
-2. Clone this application repo
-3. Clone [cjdns](https://github.com/hyperboria/cjdns) and build native binaries:
+1. Install the [Android SDK](http://developer.android.com/sdk/index.html)
+
+1. Clone this application repo
- ```
- ./android_do
- ```
+1. Optionally download the [Android NDK](https://developer.android.com/ndk/index.html) version r11c and set `ANDROID_NDK_HOME` to its path
-4. Copy built artifacts from **./build_android/** into the application repo such that corresponding **cjdroute** binaries are located as such:
+1. Build application and install on device by running `./install_debug`. This will also clone the [cjdns repo](https://github.com/cjdelisle/cjdns) and build the native artifacts for Android. If `ANDROID_NDK_HOME` is not set or the version is incorrect, the Android NDK will also be downloaded.
- ```
- ./src/main/assets/armeabi-v7a/cjdroute
- ./src/main/assets/x86/cjdroute
- ```
-
-5. Build application and install on device:
-
- ```
- ./gradlew installDebug
- ```
+**Note:** The cjdns repo is currently cloned from a fork until patches are merged into **cjdelisle/cjdns**.
Contact
-------
- Find out how to help by visiting our [issue tracker](https://github.com/hyperboria/android/issues)
-- IRC channel for this project: **#android on [HypeIRC](irc://irc.hypeirc.net)**
-
- ```
- fc13:6176:aaca:8c7f:9f55:924f:26b3:4b14
- fcbf:7bbc:32e4:0716:bd00:e936:c927:fc14
- ```
+- [Matrix](https://matrix.org) chat room for this project: [#android:tomesh.net](https://chat.tomesh.net/#/room/#android:tomesh.net)
Notes
-----
diff --git a/assemble_debug b/assemble_debug
new file mode 100755
index 0000000..c66c4c2
--- /dev/null
+++ b/assemble_debug
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+ndk-build NDK_DEBUG=true NDK_PROJECT_PATH=src/main/
+./gradlew assembleDebug
diff --git a/assemble_release b/assemble_release
new file mode 100755
index 0000000..0b12f75
--- /dev/null
+++ b/assemble_release
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+ndk-build NDK_DEBUG=false NDK_PROJECT_PATH=src/main/
+./gradlew assembleRelease
diff --git a/build.gradle b/build.gradle
index dc9962f..331b945 100644
--- a/build.gradle
+++ b/build.gradle
@@ -8,45 +8,93 @@ allprojects {
buildscript {
repositories {
jcenter()
+ mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.3.1'
+ classpath 'com.android.tools.build:gradle:2.2.2'
+ classpath 'org.ajoberstar:gradle-git:0.2.3'
}
}
+ext {
+ supportVersion = '25.0.1'
+}
+
+import org.ajoberstar.gradle.git.tasks.GitClone
+
+task cloneCjdns(type: GitClone) {
+ def destination = file("cjdns-src")
+
+ uri = "https://github.com/benhylau/cjdns"
+ // Use this repo until patch is merged in cjdelisle/cjdns
+ destinationPath = destination
+ bare = false
+ enabled = !destination.exists() // Clone only on first run
+}
+
+task buildCjdns(type: Exec) {
+ workingDir file("cjdns-src")
+ commandLine file("cjdns-src/android_do")
+}
+
+task copyNativeArtifacts(type: Copy) {
+ from 'cjdns-src/build_android/out/*'
+ into 'src/main/assets/'
+}
+
apply plugin: 'com.android.application'
apply plugin: 'checkstyle'
android {
- buildToolsVersion '23.0.2'
- compileSdkVersion 23
+ buildToolsVersion "25.0.1"
+ compileSdkVersion 25
+
defaultConfig {
minSdkVersion 9
- targetSdkVersion 23
+ targetSdkVersion 25
versionCode 1
versionName "1.0.0-SNAPSHOT"
}
+
+ sourceSets.main {
+ jni.srcDirs = []
+ jniLibs.srcDir 'src/main/libs'
+ }
+
signingConfigs {
release
}
+
buildTypes {
release {
signingConfig signingConfigs.release
}
}
+
lintOptions {
disable 'InvalidPackage'
}
}
+afterEvaluate {
+ if (COMPILE_CJDNS_NATIVE_ARTIFACTS.toBoolean()) {
+ android.applicationVariants.all { variant ->
+ variant.javaCompiler.dependsOn(cloneCjdns)
+ }
+ }
+}
+
+cloneCjdns.finalizedBy(buildCjdns)
+buildCjdns.finalizedBy(copyNativeArtifacts)
+
dependencies {
- compile 'com.android.support:support-v4:23.1.1'
- compile 'com.android.support:appcompat-v7:23.1.1'
- compile 'com.android.support:cardview-v7:23.1.1'
- compile 'com.android.support:recyclerview-v7:23.1.1'
- compile 'com.android.support:preference-v7:23.1.1'
- compile 'com.android.support:preference-v14:23.1.1'
+ compile "com.android.support:support-v4:$supportVersion"
+ compile "com.android.support:appcompat-v7:$supportVersion"
+ compile "com.android.support:cardview-v7:$supportVersion"
+ compile "com.android.support:recyclerview-v7:$supportVersion"
+ compile "com.android.support:preference-v7:$supportVersion"
+ compile "com.android.support:preference-v14:$supportVersion"
compile 'com.jakewharton:butterknife:6.0.0'
compile 'com.joanzapata.android:android-iconify:1.0.9'
compile('com.github.afollestad.material-dialogs:core:0.8.5.3@aar') {
@@ -57,8 +105,8 @@ dependencies {
compile 'io.reactivex:rxjava:1.0.7'
compile 'io.reactivex:rxandroid:0.24.0'
compile 'com.squareup:otto:1.3.5'
- compile 'com.squareup.dagger:dagger:1.2.2'
- provided 'com.squareup.dagger:dagger-compiler:1.2.2'
+ compile 'com.google.dagger:dagger:2.8'
+ provided 'com.google.dagger:dagger-compiler:2.8'
}
if (project.hasProperty('keyAlias')) {
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..e21e524
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1 @@
+COMPILE_CJDNS_NATIVE_ARTIFACTS=true
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 42cf951..a104d5d 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Tue Jan 06 21:17:32 EST 2015
+#Tue Nov 29 01:09:49 EST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
diff --git a/install_debug b/install_debug
new file mode 100755
index 0000000..b23237d
--- /dev/null
+++ b/install_debug
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+ndk-build NDK_DEBUG=true NDK_PROJECT_PATH=src/main/
+./gradlew installDebug
diff --git a/install_release b/install_release
new file mode 100755
index 0000000..d297eec
--- /dev/null
+++ b/install_release
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+ndk-build NDK_DEBUG=false NDK_PROJECT_PATH=src/main/
+./gradlew installRelease
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index b653535..e1d5afc 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -29,6 +29,9 @@
+
diff --git a/src/main/java/berlin/meshnet/cjdns/AdminApi.java b/src/main/java/berlin/meshnet/cjdns/AdminApi.java
index 0160122..f6bda3f 100644
--- a/src/main/java/berlin/meshnet/cjdns/AdminApi.java
+++ b/src/main/java/berlin/meshnet/cjdns/AdminApi.java
@@ -3,8 +3,6 @@
import android.util.Log;
import org.bitlet.wetorrent.bencode.Bencode;
-import org.json.JSONException;
-import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -13,94 +11,725 @@
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
import java.nio.ByteBuffer;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
import berlin.meshnet.cjdns.model.Node;
+import rx.Observable;
+import rx.Subscriber;
+/**
+ * API for administration of the cjdns node.
+ */
public class AdminApi {
- public static final int TIMEOUT = 5000;
- public static final int DGRAM_LENGTH = 4096;
- private InetAddress address;
- private int port;
- private byte[] password;
+ private static final String TAG = AdminApi.class.getSimpleName();
- static AdminApi from(JSONObject cjdrouteConf) throws IOException, JSONException {
- JSONObject admin = cjdrouteConf.getJSONObject("admin");
- String[] bind = admin.getString("bind").split(":");
+ /**
+ * Name of this class.
+ */
+ private static final String CLASS_NAME = AdminApi.class.getSimpleName();
- InetAddress address = InetAddress.getByName(bind[0]);
- int port = Integer.parseInt(bind[1]);
- byte[] password = admin.getString("password").getBytes();
+ /**
+ * UDP datagram socket timeout in milliseconds.
+ */
+ private static final int SOCKET_TIMEOUT = 30000;
- return new AdminApi(address, port, password);
+ /**
+ * UDP datagram length.
+ */
+ private static final int DATAGRAM_LENGTH = 4096;
+
+ /**
+ * Array used for HEX encoding.
+ */
+ private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
+
+ /**
+ * Request to {@link AdminApi} for authentication cookie.
+ */
+ private static final HashMap REQUEST_COOKIE = new LinkedHashMap() {{
+ put(wrapString("q"), wrapString("cookie"));
+ }};
+
+ /**
+ * The local IP address to bind the admin RPC server.
+ */
+ private static final String ADMIN_API_ADDRESS = "127.0.0.1";
+
+ /**
+ * The port to bind the admin RPC server.
+ */
+ private static final int ADMIN_API_PORT = 11234;
+
+ /**
+ * The password for authenticated requests.
+ */
+ private static final byte[] ADMIN_API_PASSWORD = "NONE".getBytes();
+
+ /**
+ * The local IP address to bind the admin RPC server, as an {@link InetAddress}.
+ */
+ private final InetAddress mAdminApiAddress;
+
+ /**
+ * Constructor.
+ */
+ public AdminApi() throws UnknownHostException {
+ mAdminApiAddress = InetAddress.getByName(ADMIN_API_ADDRESS);
}
- private AdminApi(InetAddress address, int port, byte[] password) {
- this.address = address;
- this.port = port;
- this.password = password;
+ public static class AdminLog {
+
+ public static Observable logMany(final AdminApi api) {
+ throw new UnsupportedOperationException("AdminLog_logMany is not implemented in " + CLASS_NAME);
+ }
+
+ public static Observable subscribe(final AdminApi api) {
+ throw new UnsupportedOperationException("AdminLog_subscribe is not implemented in " + CLASS_NAME);
+ }
+
+ public static Observable subscriptions(final AdminApi api) {
+ throw new UnsupportedOperationException("AdminLog_subscriptions is not implemented in " + CLASS_NAME);
+ }
+
+ public static Observable unsubscribe(final AdminApi api) {
+ throw new UnsupportedOperationException("AdminLog_unsubscribe is not implemented in " + CLASS_NAME);
+ }
}
- public String getBind() {
- return this.address.getHostAddress() + ":" + this.port;
+ public static class Admin {
+
+ public static Observable asyncEnabled(final AdminApi api) {
+ throw new UnsupportedOperationException("Admin_asyncEnabled is not implemented in " + CLASS_NAME);
+ }
+
+ public static Observable availableFunctions(final AdminApi api) {
+ throw new UnsupportedOperationException("Admin_availableFunctions is not implemented in " + CLASS_NAME);
+ }
}
- public int corePid() throws IOException {
- // try {
- HashMap request = new HashMap<>();
- request.put(ByteBuffer.wrap("q".getBytes()), ByteBuffer.wrap("Core_pid".getBytes()));
+ public static class Allocator {
- Map response = perform(request);
- Long pid = (Long) response.get(ByteBuffer.wrap("pid".getBytes()));
+ public static Observable bytesAllocated(final AdminApi api) {
+ throw new UnsupportedOperationException("Allocator_bytesAllocated is not implemented in " + CLASS_NAME);
+ }
- return pid.intValue();
- // } catch (IOException e) {
- // return 0;
- // }
+ public static Observable snapshot(final AdminApi api) {
+ throw new UnsupportedOperationException("Allocator_snapshot is not implemented in " + CLASS_NAME);
+ }
}
- public Node NodeStore_nodeForAddr() throws IOException {
- return new Node.Peer(0, "Some Peer Node", "foo.k", null);
+ public static class AuthorizedPasswords {
+
+ public static Observable add(final AdminApi api) {
+ throw new UnsupportedOperationException("AuthorizedPasswords_add is not implemented in " + CLASS_NAME);
+ }
+
+ public static Observable list(final AdminApi api) {
+ throw new UnsupportedOperationException("AuthorizedPasswords_list is not implemented in " + CLASS_NAME);
+ }
+
+ public static Observable remove(final AdminApi api) {
+ throw new UnsupportedOperationException("AuthorizedPasswords_remove is not implemented in " + CLASS_NAME);
+ }
}
- public Map perform(Map request) throws IOException {
- DatagramSocket socket = newSocket();
+ public static class Core {
- byte[] data = serialize(request);
- DatagramPacket dgram = new DatagramPacket(data, data.length, this.address, this.port);
- socket.send(dgram);
+ public static Observable exit(final AdminApi api) {
+ return Observable.create(new BaseOnSubscribe(api, new Request("Core_exit")) {
+ @Override
+ protected Boolean parseResult(final Map response) {
+ return Boolean.TRUE;
+ }
+ });
+ }
- DatagramPacket responseDgram = new DatagramPacket(new byte[DGRAM_LENGTH], DGRAM_LENGTH);
- socket.receive(responseDgram);
- socket.close();
+ public static Observable initTunfd(final AdminApi api, final Long tunfd, final Long type) {
+ return Observable.create(new BaseOnSubscribe(api, new Request("Core_initTunfd",
+ new LinkedHashMap() {{
+ put(wrapString("tunfd"), tunfd);
+ put(wrapString("type"), type);
+ }})) {
+ @Override
+ protected Boolean parseResult(final Map response) {
+ return Boolean.TRUE;
+ }
+ });
+ }
+
+ public static Observable initTunnel(final AdminApi api) {
+ throw new UnsupportedOperationException("Core_initTunnel is not implemented in " + CLASS_NAME);
+ }
+
+ public static Observable pid(final AdminApi api) {
+ return Observable.create(new BaseOnSubscribe(api, new Request("Core_pid")) {
+ @Override
+ protected Long parseResult(final Map response) {
+ return (Long) response.get(wrapString("pid"));
+ }
+ });
+ }
+ }
+
+ public static class EthInterface {
+
+ public static Observable beacon(final AdminApi api) {
+ throw new UnsupportedOperationException("ETHInterface_beacon is not implemented in " + CLASS_NAME);
+ }
+
+ public static Observable beginConnection(final AdminApi api) {
+ throw new UnsupportedOperationException("ETHInterface_beginConnection is not implemented in " + CLASS_NAME);
+ }
+
+ public static Observable listDevices(final AdminApi api) {
+ throw new UnsupportedOperationException("ETHInterface_listDevices is not implemented in " + CLASS_NAME);
+ }
+
+ public static Observable new0(final AdminApi api) {
+ throw new UnsupportedOperationException("ETHInterface_new is not implemented in " + CLASS_NAME);
+ }
+ }
+
+ public static class FileNo {
+
+ public static Observable