diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml new file mode 100644 index 000000000..de77168d0 --- /dev/null +++ b/android/AndroidManifest.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + diff --git a/android/README.txt b/android/README.txt new file mode 100644 index 000000000..e8886a579 --- /dev/null +++ b/android/README.txt @@ -0,0 +1,25 @@ +These instructions are for the 1.5 SDK. +The build file is not compatible with the 1.1 SDK any more. + +#Unzip the android SDK in ../../ +#So then the android tools will be in ../../android-sdk-linux_x86-1.5_r2/tools/ + +#then build the android apk file: +ant debug + +# Create the android 1.5 virtual device +# (don't make a custom hardware profile) +../../android-sdk-linux_x86-1.5_r2/tools/android create avd --name i2p --target 2 + +#then run the emulator: +../../android-sdk-linux_x86-1.5_r2/tools/emulator -avd i2p & + +#then wait a couple minutes until the emulator is up +#then install the I2P app +ant install + +#then run the debugger +../../android-sdk-linux_x86-1.5_r2/tools/ddms & + +#to rebuild and reinstall to emulator: +ant reinstall diff --git a/android/build.xml b/android/build.xml new file mode 100644 index 000000000..2b8664dca --- /dev/null +++ b/android/build.xml @@ -0,0 +1,352 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Creating output directories if needed... + + + + + + + + + + Generating R.java / Manifest.java from the resources... + + + + + + + + + + + + + + + + + Compiling aidl files into Java classes... + + + + + + + + + + + + + + + + + + + + + + + + + + + Converting compiled files and external libraries into ${out-folder}/${dex-file}... + + + + + + + + + + + + + Packaging resources + + + + + + + + + + + + + + + + + + + + + + All generated packages need to be signed with jarsigner before they are published. + + + + + Installing ${out-debug-package} onto default emulator... + + + + + + + + Installing ${out-debug-package} onto default emulator... + + + + + + + + + + Uninstalling ${application-package} from the default emulator... + + + + + + + + + Android Ant Build. Available targets: + help: Displays this help. + debug: Builds the application and sign it with a debug key. + release: Builds the application. The generated apk file must be + signed before it is published. + install: Installs the debug package onto a running emulator or + device. This can only be used if the application has + not yet been installed. + reinstall: Installs the debug package on a running emulator or + device that already has the application. + The signatures must match. + uninstall: uninstall the application from a running emulator or + device. + + diff --git a/android/default.properties b/android/default.properties new file mode 100644 index 000000000..eba5c59ff --- /dev/null +++ b/android/default.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "build.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-2 diff --git a/android/res/layout/main.xml b/android/res/layout/main.xml new file mode 100644 index 000000000..d76411527 --- /dev/null +++ b/android/res/layout/main.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/android/res/raw/logger_config b/android/res/raw/logger_config new file mode 100644 index 000000000..2aeabb9f6 --- /dev/null +++ b/android/res/raw/logger_config @@ -0,0 +1,3 @@ +logger.defaultLevel=INFO +logger.record.net.i2p.router.transport.FIFOBandwidthRefiller=ERROR +logger.record.net.i2p.stat.Rate=ERROR diff --git a/android/res/raw/router_config b/android/res/raw/router_config new file mode 100644 index 000000000..cf63ed56a --- /dev/null +++ b/android/res/raw/router_config @@ -0,0 +1,16 @@ +# initial router.config +# temp directory +i2p.dir.temp=/data/data/net.i2p.router/files/tmp +i2p.dir.pid=/data/data/net.i2p.router/files/tmp +# save memory +router.prng.buffers=2 +router.decayingBloomFilterM=20 +stat.full=false +i2np.udp.maxConnections=30 +# no I2CP +i2p.dummyClientFacade=true +# for now +i2np.ntcp.enable=false +# not on android +i2np.upnp.enable=false +routerconsole.geoip.enable=false diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml new file mode 100644 index 000000000..983a304b9 --- /dev/null +++ b/android/res/values/strings.xml @@ -0,0 +1,4 @@ + + + I2PAndroid + diff --git a/android/src/net/i2p/router/I2PAndroid.java b/android/src/net/i2p/router/I2PAndroid.java new file mode 100644 index 000000000..ed626639c --- /dev/null +++ b/android/src/net/i2p/router/I2PAndroid.java @@ -0,0 +1,142 @@ +package net.i2p.router; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.Resources.NotFoundException; +import android.os.Bundle; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.IOException; +import java.util.List; + +import net.i2p.router.Router; +import net.i2p.router.RouterLaunch; +// import net.i2p.util.NativeBigInteger; + +public class I2PAndroid extends Activity +{ + static Context _context; + private static final String DIR = "/data/data/net.i2p.router/files"; + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + + _context = this; // Activity extends Context + debugStuff(); + initialize(); + // 300ms per run + // 5x slower than java on my server and 50x slower than native on my server + // NativeBigInteger.main(null); + } + + public void onRestart() + { + System.err.println("onRestart called"); + super.onRestart(); + } + + public void onStart() + { + System.err.println("onStart called"); + super.onStart(); + RouterLaunch.main(null); + System.err.println("Router.main finished"); + } + + public void onResume() + { + System.err.println("onResume called"); + super.onResume(); + } + + public void onPause() + { + System.err.println("onPause called"); + super.onPause(); + } + + public void onStop() + { + System.err.println("onStop called"); + super.onStop(); + + // from routerconsole ContextHelper + List contexts = RouterContext.listContexts(); + if ( (contexts == null) || (contexts.size() <= 0) ) + throw new IllegalStateException("No contexts. This is usually because the router is either starting up or shutting down."); + RouterContext ctx = (RouterContext)contexts.get(0); + + // shutdown() doesn't return so use shutdownGracefully() + ctx.router().shutdownGracefully(Router.EXIT_HARD); + System.err.println("shutdown complete"); + } + + public void onDestroy() + { + System.err.println("onDestroy called"); + super.onDestroy(); + } + + public static Context getContext() { + return _context; + } + + private void debugStuff() { + System.err.println("java.io.tmpdir" + ": " + System.getProperty("java.io.tmpdir")); + System.err.println("java.vendor" + ": " + System.getProperty("java.vendor")); + System.err.println("java.version" + ": " + System.getProperty("java.version")); + System.err.println("os.arch" + ": " + System.getProperty("os.arch")); + System.err.println("os.name" + ": " + System.getProperty("os.name")); + System.err.println("os.version" + ": " + System.getProperty("os.version")); + System.err.println("user.dir" + ": " + System.getProperty("user.dir")); + System.err.println("user.home" + ": " + System.getProperty("user.home")); + System.err.println("user.name" + ": " + System.getProperty("user.name")); + } + + private void initialize() { + // Until we can edit the router.config on the device, + // copy it from the resource every time. + // File f = new I2PFile("router.config"); + // if (!f.exists()) { + copyResourceToFile(R.raw.router_config, "router.config"); + copyResourceToFile(R.raw.logger_config, "logger.config"); + copyResourceToFile(R.raw.blocklist_txt, "blocklist.txt"); + // } + + // Set up the locations so Router and WorkingDir can find them + System.setProperty("i2p.dir.base", DIR); + System.setProperty("i2p.dir.config", DIR); + System.setProperty("wrapper.logfile", DIR + "/wrapper.log"); + } + + private void copyResourceToFile(int resID, String f) { + InputStream in = null; + FileOutputStream out = null; + + System.err.println("Creating file " + f + " from resource"); + byte buf[] = new byte[4096]; + try { + // Context methods + in = getResources().openRawResource(resID); + out = openFileOutput(f, 0); + + int read = 0; + while ( (read = in.read(buf)) != -1) + out.write(buf, 0, read); + + } catch (IOException ioe) { + } catch (Resources.NotFoundException nfe) { + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + if (out != null) try { out.close(); } catch (IOException ioe) {} + } + } + +} diff --git a/android/src/net/i2p/util/LogWriter.java b/android/src/net/i2p/util/LogWriter.java new file mode 100644 index 000000000..0babfab37 --- /dev/null +++ b/android/src/net/i2p/util/LogWriter.java @@ -0,0 +1,163 @@ +package net.i2p.util; + +/* + * public domain + * + */ + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; + +/** + * bridge to android logging + * + * @author zzz + */ +class LogWriter implements Runnable { + private final static long CONFIG_READ_ITERVAL = 10 * 1000; + private long _lastReadConfig = 0; + private long _numBytesInCurrentFile = 0; + private OutputStream _currentOut; // = System.out + private int _rotationNum = -1; + private String _logFilenamePattern; + private File _currentFile; + private LogManager _manager; + + private boolean _write; + + private LogWriter() { // nop + } + + public LogWriter(LogManager manager) { + _manager = manager; + } + + public void stopWriting() { + _write = false; + } + + public void run() { + _write = true; + try { + while (_write) { + flushRecords(); + rereadConfig(); + } + System.err.println("Done writing"); + } catch (Exception e) { + System.err.println("Error writing the logs: " + e.getMessage()); + e.printStackTrace(); + } + } + + public void flushRecords() { flushRecords(true); } + public void flushRecords(boolean shouldWait) { + try { + List records = _manager._removeAll(); + if (records == null) return; + for (int i = 0; i < records.size(); i++) { + LogRecord rec = (LogRecord) records.get(i); + writeRecord(rec); + } + } catch (Throwable t) { + t.printStackTrace(); + } finally { + if (shouldWait) { + try { + synchronized (this) { + this.wait(10*1000); + } + } catch (InterruptedException ie) { // nop + } + } + } + } + + public String currentFile() { + return _currentFile != null ? _currentFile.getAbsolutePath() : "uninitialized"; + } + + private void rereadConfig() { + long now = Clock.getInstance().now(); + if (now - _lastReadConfig > CONFIG_READ_ITERVAL) { + _manager.rereadConfig(); + _lastReadConfig = now; + } + } + + private void writeRecord(LogRecord rec) { + if (rec.getThrowable() == null) + log(rec.getPriority(), rec.getSource(), rec.getSourceName(), rec.getThreadName(), rec.getMessage()); + else + log(rec.getPriority(), rec.getSource(), rec.getSourceName(), rec.getThreadName(), rec.getMessage(), rec.getThrowable()); + } + + public void log(int priority, Class src, String name, String threadName, String msg) { + if (src != null) { + String tag = src.getName(); + int dot = tag.lastIndexOf("."); + if (dot >= 0) + tag = tag.substring(dot + 1); + android.util.Log.println(toAndroidLevel(priority), + tag, + '[' + threadName + "] " + msg); + } else if (name != null) + android.util.Log.println(toAndroidLevel(priority), + name, + '[' + threadName + "] " + msg); + else + android.util.Log.println(toAndroidLevel(priority), + threadName, msg); + } + + public void log(int priority, Class src, String name, String threadName, String msg, Throwable t) { + if (src != null) { + String tag = src.getName(); + int dot = tag.lastIndexOf("."); + if (dot >= 0) + tag = tag.substring(dot + 1); + android.util.Log.println(toAndroidLevel(priority), + tag, + '[' + threadName + "] " + msg + + ' ' + t.toString() + ' ' + android.util.Log.getStackTraceString(t)); + } else if (name != null) + android.util.Log.println(toAndroidLevel(priority), + name, + '[' + threadName + "] " + msg + + ' ' + t.toString() + ' ' + android.util.Log.getStackTraceString(t)); + else + android.util.Log.println(toAndroidLevel(priority), + threadName, + msg + ' ' + t.toString() + ' ' + android.util.Log.getStackTraceString(t)); + } + + private static int toAndroidLevel(int level) { + switch (level) { + case Log.DEBUG: + return android.util.Log.DEBUG; + case Log.INFO: + return android.util.Log.INFO; + case Log.WARN: + return android.util.Log.WARN; + case Log.ERROR: + case Log.CRIT: + default: + return android.util.Log.ERROR; + } + } + + private static final String replace(String pattern, int num) { + char c[] = pattern.toCharArray(); + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < c.length; i++) { + if ( (c[i] != '#') && (c[i] != '@') ) + buf.append(c[i]); + else + buf.append(num); + } + return buf.toString(); + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ContextHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ContextHelper.java index 0aa250654..7d9b28e89 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ContextHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ContextHelper.java @@ -5,7 +5,7 @@ import java.util.List; import net.i2p.data.Hash; import net.i2p.router.RouterContext; -class ContextHelper { +public class ContextHelper { public static RouterContext getContext(String contextId) { List contexts = RouterContext.listContexts(); if ( (contexts == null) || (contexts.size() <= 0) ) diff --git a/core/java/src/gnu/crypto/prng/AsyncFortunaStandalone.java b/core/java/src/gnu/crypto/prng/AsyncFortunaStandalone.java index 417d0fc72..504b87d76 100644 --- a/core/java/src/gnu/crypto/prng/AsyncFortunaStandalone.java +++ b/core/java/src/gnu/crypto/prng/AsyncFortunaStandalone.java @@ -12,10 +12,11 @@ import net.i2p.util.Log; * has been eaten) */ public class AsyncFortunaStandalone extends FortunaStandalone implements Runnable { - private static final int BUFFERS = 16; + private static final int DEFAULT_BUFFERS = 16; private static final int BUFSIZE = 256*1024; - private final byte asyncBuffers[][] = new byte[BUFFERS][BUFSIZE]; - private final int status[] = new int[BUFFERS]; + private int _bufferCount; + private final byte asyncBuffers[][]; + private final int status[]; private int nextBuf = 0; private I2PAppContext _context; private Log _log; @@ -27,7 +28,10 @@ public class AsyncFortunaStandalone extends FortunaStandalone implements Runnabl public AsyncFortunaStandalone(I2PAppContext context) { super(); - for (int i = 0; i < BUFFERS; i++) + _bufferCount = context.getProperty("router.prng.buffers", DEFAULT_BUFFERS); + asyncBuffers = new byte[_bufferCount][BUFSIZE]; + status = new int[_bufferCount]; + for (int i = 0; i < _bufferCount; i++) status[i] = STATUS_NEED_FILL; _context = context; context.statManager().createRateStat("prng.bufferWaitTime", "", "Encryption", new long[] { 60*1000, 10*60*1000, 60*60*1000 } ); @@ -80,11 +84,11 @@ public class AsyncFortunaStandalone extends FortunaStandalone implements Runnabl status[nextBuf] = STATUS_LIVE; int prev=nextBuf-1; if (prev<0) - prev = BUFFERS-1; + prev = _bufferCount-1; if (status[prev] == STATUS_LIVE) status[prev] = STATUS_NEED_FILL; nextBuf++; - if (nextBuf >= BUFFERS) + if (nextBuf >= _bufferCount) nextBuf = 0; asyncBuffers.notify(); } @@ -95,7 +99,7 @@ public class AsyncFortunaStandalone extends FortunaStandalone implements Runnabl int toFill = -1; try { synchronized (asyncBuffers) { - for (int i = 0; i < BUFFERS; i++) { + for (int i = 0; i < _bufferCount; i++) { if (status[i] == STATUS_NEED_FILL) { status[i] = STATUS_FILLING; toFill = i; diff --git a/core/java/src/net/i2p/crypto/HMAC256Generator.java b/core/java/src/net/i2p/crypto/HMAC256Generator.java index 2fcaa7b5e..0335d1e7e 100644 --- a/core/java/src/net/i2p/crypto/HMAC256Generator.java +++ b/core/java/src/net/i2p/crypto/HMAC256Generator.java @@ -7,7 +7,8 @@ import net.i2p.data.Hash; import net.i2p.data.SessionKey; import org.bouncycastle.crypto.Digest; -import org.bouncycastle.crypto.macs.HMac; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.macs.I2PHMac; /** * Calculate the HMAC-SHA256 of a key+message. All the good stuff occurs @@ -19,15 +20,15 @@ public class HMAC256Generator extends HMACGenerator { public HMAC256Generator(I2PAppContext context) { super(context); } @Override - protected HMac acquire() { + protected I2PHMac acquire() { synchronized (_available) { if (_available.size() > 0) - return (HMac)_available.remove(0); + return (I2PHMac)_available.remove(0); } // the HMAC is hardcoded to use SHA256 digest size // for backwards compatability. next time we have a backwards // incompatible change, we should update this by removing ", 32" - return new HMac(new Sha256ForMAC()); + return new I2PHMac(new Sha256ForMAC()); } private class Sha256ForMAC extends Sha256Standalone implements Digest { diff --git a/core/java/src/net/i2p/crypto/HMACGenerator.java b/core/java/src/net/i2p/crypto/HMACGenerator.java index 8388590a2..b549c8855 100644 --- a/core/java/src/net/i2p/crypto/HMACGenerator.java +++ b/core/java/src/net/i2p/crypto/HMACGenerator.java @@ -10,7 +10,8 @@ import net.i2p.data.Hash; import net.i2p.data.SessionKey; import org.bouncycastle.crypto.digests.MD5Digest; -import org.bouncycastle.crypto.macs.HMac; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.macs.I2PHMac; /** * Calculate the HMAC-MD5 of a key+message. All the good stuff occurs @@ -49,7 +50,7 @@ public class HMACGenerator { if ((key == null) || (key.getData() == null) || (data == null)) throw new NullPointerException("Null arguments for HMAC"); - HMac mac = acquire(); + I2PHMac mac = acquire(); mac.init(key.getData()); mac.update(data, offset, length); //byte rv[] = new byte[Hash.HASH_LENGTH]; @@ -73,7 +74,7 @@ public class HMACGenerator { if ((key == null) || (key.getData() == null) || (curData == null)) throw new NullPointerException("Null arguments for HMAC"); - HMac mac = acquire(); + I2PHMac mac = acquire(); mac.init(key.getData()); mac.update(curData, curOffset, curLength); byte rv[] = acquireTmp(); @@ -86,17 +87,17 @@ public class HMACGenerator { return eq; } - protected HMac acquire() { + protected I2PHMac acquire() { synchronized (_available) { if (_available.size() > 0) - return (HMac)_available.remove(0); + return (I2PHMac)_available.remove(0); } // the HMAC is hardcoded to use SHA256 digest size // for backwards compatability. next time we have a backwards // incompatible change, we should update this by removing ", 32" - return new HMac(new MD5Digest(), 32); + return new I2PHMac(new MD5Digest(), 32); } - private void release(HMac mac) { + private void release(Mac mac) { synchronized (_available) { if (_available.size() < 64) _available.add(mac); @@ -122,4 +123,4 @@ public class HMACGenerator { _availableTmp.add((Object)tmp); } } -} \ No newline at end of file +} diff --git a/core/java/src/net/i2p/util/DecayingBloomFilter.java b/core/java/src/net/i2p/util/DecayingBloomFilter.java index 164c8e453..8c375a66d 100644 --- a/core/java/src/net/i2p/util/DecayingBloomFilter.java +++ b/core/java/src/net/i2p/util/DecayingBloomFilter.java @@ -30,6 +30,7 @@ public class DecayingBloomFilter { private boolean _keepDecaying; private DecayEvent _decayEvent; + private static final int DEFAULT_M = 23; private static final boolean ALWAYS_MISS = false; /** @@ -44,8 +45,12 @@ public class DecayingBloomFilter { _context = context; _log = context.logManager().getLog(DecayingBloomFilter.class); _entryBytes = entryBytes; - _current = new BloomSHA1(23, 11); //new BloomSHA1(23, 11); - _previous = new BloomSHA1(23, 11); //new BloomSHA1(23, 11); + // this is instantiated in four different places, they may have different + // requirements, but for now use this as a gross method of memory reduction. + // m == 23 => 2MB each BloomSHA1 (8MB total) + int m = context.getProperty("router.decayingBloomFilterM", DEFAULT_M); + _current = new BloomSHA1(m, 11); //new BloomSHA1(23, 11); + _previous = new BloomSHA1(m, 11); //new BloomSHA1(23, 11); _durationMs = durationMs; int numExtenders = (32+ (entryBytes-1))/entryBytes - 1; if (numExtenders < 0) diff --git a/core/java/src/org/bouncycastle/crypto/macs/HMac.java b/core/java/src/org/bouncycastle/crypto/macs/I2PHMac.java similarity index 95% rename from core/java/src/org/bouncycastle/crypto/macs/HMac.java rename to core/java/src/org/bouncycastle/crypto/macs/I2PHMac.java index 7176c8aca..a566e8a79 100644 --- a/core/java/src/org/bouncycastle/crypto/macs/HMac.java +++ b/core/java/src/org/bouncycastle/crypto/macs/I2PHMac.java @@ -42,8 +42,12 @@ import org.bouncycastle.crypto.Mac; * a frequently used buffer (called on doFinal). changes released into the public * domain in 2005. * + * This is renamed from HMac because the constructor HMac(digest, sz) does not exist + * in the standard bouncycastle library, thus it conflicts in JVMs that contain the + * standard library (Android). + * */ -public class HMac +public class I2PHMac implements Mac { private final static int BLOCK_LENGTH = 64; @@ -56,12 +60,12 @@ implements Mac private byte[] inputPad = new byte[BLOCK_LENGTH]; private byte[] outputPad = new byte[BLOCK_LENGTH]; - public HMac( + public I2PHMac( Digest digest) { this(digest, digest.getDigestSize()); } - public HMac( + public I2PHMac( Digest digest, int sz) { this.digest = digest;