diff --git a/apps/BOB/Demos/echo/echoclient/build.xml b/apps/BOB/Demos/echo/echoclient/build.xml
new file mode 100644
index 000000000..4d233cee0
--- /dev/null
+++ b/apps/BOB/Demos/echo/echoclient/build.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+ Builds, tests, and runs the project echoclient.
+
+
+
diff --git a/apps/BOB/Demos/echo/echoclient/manifest.mf b/apps/BOB/Demos/echo/echoclient/manifest.mf
new file mode 100644
index 000000000..328e8e5bc
--- /dev/null
+++ b/apps/BOB/Demos/echo/echoclient/manifest.mf
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+X-COMMENT: Main-Class will be added automatically by build
+
diff --git a/apps/BOB/Demos/echo/echoclient/nbproject/build-impl.xml b/apps/BOB/Demos/echo/echoclient/nbproject/build-impl.xml
new file mode 100644
index 000000000..73d61d2fc
--- /dev/null
+++ b/apps/BOB/Demos/echo/echoclient/nbproject/build-impl.xml
@@ -0,0 +1,629 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set src.dir
+ Must set test.src.dir
+ Must set build.dir
+ Must set dist.dir
+ Must set build.classes.dir
+ Must set dist.javadoc.dir
+ Must set build.test.classes.dir
+ Must set build.test.results.dir
+ Must set build.classes.excludes
+ Must set dist.jar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ To run this application from the command line without Ant, try:
+
+
+
+
+
+
+ java -cp "${run.classpath.with.dist.jar}" ${main.class}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ To run this application from the command line without Ant, try:
+
+ java -jar "${dist.jar.resolved}"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set debug.class
+
+
+
+
+ Must set fix.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Some tests failed; see details above.
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set test.includes
+
+
+
+ Some tests failed; see details above.
+
+
+
+
+ Must select one file in the IDE or set test.class
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set applet.url
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set applet.url
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/BOB/Demos/echo/echoclient/nbproject/genfiles.properties b/apps/BOB/Demos/echo/echoclient/nbproject/genfiles.properties
new file mode 100644
index 000000000..50793d57a
--- /dev/null
+++ b/apps/BOB/Demos/echo/echoclient/nbproject/genfiles.properties
@@ -0,0 +1,8 @@
+build.xml.data.CRC32=8ce3cee9
+build.xml.script.CRC32=d1de2df3
+build.xml.stylesheet.CRC32=be360661
+# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
+# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
+nbproject/build-impl.xml.data.CRC32=8ce3cee9
+nbproject/build-impl.xml.script.CRC32=22d1fbbb
+nbproject/build-impl.xml.stylesheet.CRC32=487672f9
diff --git a/apps/BOB/Demos/echo/echoclient/nbproject/private/private.properties b/apps/BOB/Demos/echo/echoclient/nbproject/private/private.properties
new file mode 100644
index 000000000..d814cb447
--- /dev/null
+++ b/apps/BOB/Demos/echo/echoclient/nbproject/private/private.properties
@@ -0,0 +1,2 @@
+jaxws.endorsed.dir=/usr/local/netbeans-6.1/java2/modules/ext/jaxws21/api
+user.properties.file=/root/.netbeans/6.1/build.properties
diff --git a/apps/BOB/Demos/echo/echoclient/nbproject/project.properties b/apps/BOB/Demos/echo/echoclient/nbproject/project.properties
new file mode 100644
index 000000000..dfaeb3909
--- /dev/null
+++ b/apps/BOB/Demos/echo/echoclient/nbproject/project.properties
@@ -0,0 +1,60 @@
+build.classes.dir=${build.dir}/classes
+build.classes.excludes=**/*.java,**/*.form
+# This directory is removed when the project is cleaned:
+build.dir=build
+build.generated.dir=${build.dir}/generated
+# Only compile against the classpath explicitly listed here:
+build.sysclasspath=ignore
+build.test.classes.dir=${build.dir}/test/classes
+build.test.results.dir=${build.dir}/test/results
+debug.classpath=\
+ ${run.classpath}
+debug.test.classpath=\
+ ${run.test.classpath}
+# This directory is removed when the project is cleaned:
+dist.dir=dist
+dist.jar=${dist.dir}/echoclient.jar
+dist.javadoc.dir=${dist.dir}/javadoc
+excludes=
+file.reference.BOB.jar=../../../dist/BOB.jar
+includes=**
+jar.compress=false
+javac.classpath=
+# Space-separated list of extra javac options
+javac.compilerargs=
+javac.deprecation=false
+javac.source=1.5
+javac.target=1.5
+javac.test.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}:\
+ ${libs.junit.classpath}:\
+ ${libs.junit_4.classpath}
+javadoc.additionalparam=
+javadoc.author=false
+javadoc.encoding=${source.encoding}
+javadoc.noindex=false
+javadoc.nonavbar=false
+javadoc.notree=false
+javadoc.private=false
+javadoc.splitindex=true
+javadoc.use=true
+javadoc.version=false
+javadoc.windowtitle=
+main.class=net.i2p.BOB.Demos.echo.echoclient.Main
+manifest.file=manifest.mf
+meta.inf.dir=${src.dir}/META-INF
+platform.active=default_platform
+run.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}
+# Space-separated list of JVM arguments used when running the project
+# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value
+# or test-sys-prop.name=value to set system properties for unit tests):
+run.jvmargs=
+run.test.classpath=\
+ ${javac.test.classpath}:\
+ ${build.test.classes.dir}
+source.encoding=UTF-8
+src.dir=src
+test.src.dir=test
diff --git a/apps/BOB/Demos/echo/echoclient/nbproject/project.xml b/apps/BOB/Demos/echo/echoclient/nbproject/project.xml
new file mode 100644
index 000000000..9c6fff904
--- /dev/null
+++ b/apps/BOB/Demos/echo/echoclient/nbproject/project.xml
@@ -0,0 +1,16 @@
+
+
+ org.netbeans.modules.java.j2seproject
+
+
+ echoclient
+ 1.6.5
+
+
+
+
+
+
+
+
+
diff --git a/apps/BOB/build.xml b/apps/BOB/build.xml
new file mode 100644
index 000000000..f76222aea
--- /dev/null
+++ b/apps/BOB/build.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+ Builds, tests, and runs the project BOB.
+
+
+
diff --git a/apps/BOB/manifest.mf b/apps/BOB/manifest.mf
new file mode 100644
index 000000000..328e8e5bc
--- /dev/null
+++ b/apps/BOB/manifest.mf
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+X-COMMENT: Main-Class will be added automatically by build
+
diff --git a/apps/BOB/nbproject/build-impl.xml b/apps/BOB/nbproject/build-impl.xml
new file mode 100644
index 000000000..5dcf75563
--- /dev/null
+++ b/apps/BOB/nbproject/build-impl.xml
@@ -0,0 +1,629 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set src.dir
+ Must set test.src.dir
+ Must set build.dir
+ Must set dist.dir
+ Must set build.classes.dir
+ Must set dist.javadoc.dir
+ Must set build.test.classes.dir
+ Must set build.test.results.dir
+ Must set build.classes.excludes
+ Must set dist.jar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ To run this application from the command line without Ant, try:
+
+
+
+
+
+
+ java -cp "${run.classpath.with.dist.jar}" ${main.class}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ To run this application from the command line without Ant, try:
+
+ java -jar "${dist.jar.resolved}"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set debug.class
+
+
+
+
+ Must set fix.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Some tests failed; see details above.
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set test.includes
+
+
+
+ Some tests failed; see details above.
+
+
+
+
+ Must select one file in the IDE or set test.class
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set applet.url
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set applet.url
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/BOB/nbproject/genfiles.properties b/apps/BOB/nbproject/genfiles.properties
new file mode 100644
index 000000000..55b2caf31
--- /dev/null
+++ b/apps/BOB/nbproject/genfiles.properties
@@ -0,0 +1,8 @@
+build.xml.data.CRC32=209349b6
+build.xml.script.CRC32=05a1ffd9
+build.xml.stylesheet.CRC32=be360661
+# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
+# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
+nbproject/build-impl.xml.data.CRC32=209349b6
+nbproject/build-impl.xml.script.CRC32=eeeca375
+nbproject/build-impl.xml.stylesheet.CRC32=487672f9
diff --git a/apps/BOB/nbproject/private/config.properties b/apps/BOB/nbproject/private/config.properties
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/BOB/nbproject/private/private.properties b/apps/BOB/nbproject/private/private.properties
new file mode 100644
index 000000000..f11a1a7a7
--- /dev/null
+++ b/apps/BOB/nbproject/private/private.properties
@@ -0,0 +1,6 @@
+do.depend=false
+do.jar=true
+javac.debug=true
+javadoc.preview=true
+jaxws.endorsed.dir=/usr/local/netbeans-6.1/java2/modules/ext/jaxws21/api
+user.properties.file=/root/.netbeans/6.1/build.properties
diff --git a/apps/BOB/nbproject/private/private.xml b/apps/BOB/nbproject/private/private.xml
new file mode 100644
index 000000000..c1f155a78
--- /dev/null
+++ b/apps/BOB/nbproject/private/private.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/apps/BOB/nbproject/project.properties b/apps/BOB/nbproject/project.properties
new file mode 100644
index 000000000..89d7ad1da
--- /dev/null
+++ b/apps/BOB/nbproject/project.properties
@@ -0,0 +1,77 @@
+application.title=BOB
+application.vendor=root
+build.classes.dir=${build.dir}/classes
+build.classes.excludes=**/*.java,**/*.form
+# This directory is removed when the project is cleaned:
+build.dir=build
+build.generated.dir=${build.dir}/generated
+# Only compile against the classpath explicitly listed here:
+build.sysclasspath=ignore
+build.test.classes.dir=${build.dir}/test/classes
+build.test.results.dir=${build.dir}/test/results
+debug.classpath=\
+ ${run.classpath}
+debug.test.classpath=\
+ ${run.test.classpath}
+# This directory is removed when the project is cleaned:
+dist.dir=dist
+dist.jar=${dist.dir}/BOB.jar
+dist.javadoc.dir=${dist.dir}/javadoc
+excludes=
+file.reference.core.jar=../i2p.i2p/core/dist/core.jar
+file.reference.i2p.jar=../../bob/i2p/i2p.i2p/build/i2p.jar
+file.reference.i2p.jar-1=../../core/java/build/i2p.jar
+file.reference.i2p.jar-2=../i2p.i2p/core/java/build/i2p.jar
+file.reference.i2ptunnel.jar=../i2ptunnel/java/build/i2ptunnel.jar
+file.reference.java-src=../i2p.i2p/core/java/src/
+file.reference.jbigi.jar=../../bob/i2p/i2p.i2p/build/jbigi.jar
+file.reference.mstreaming.jar=../../bob/i2p/i2p.i2p/build/mstreaming.jar
+file.reference.mstreaming.jar-1=../ministreaming/java/build/mstreaming.jar
+file.reference.NetBeansProjects-i2p.i2p=../i2p.i2p/
+file.reference.streaming.jar=../../bob/i2p/i2p.i2p/build/streaming.jar
+file.reference.streaming.jar-1=../streaming/java/build/streaming.jar
+includes=**
+jar.compress=false
+javac.classpath=\
+ ${file.reference.i2p.jar-1}:\
+ ${file.reference.i2ptunnel.jar}:\
+ ${file.reference.mstreaming.jar-1}:\
+ ${file.reference.streaming.jar-1}
+# Space-separated list of extra javac options
+javac.compilerargs=
+javac.deprecation=false
+javac.source=1.5
+javac.target=1.5
+javac.test.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}:\
+ ${libs.junit.classpath}:\
+ ${libs.junit_4.classpath}
+javadoc.additionalparam=
+javadoc.author=false
+javadoc.encoding=${source.encoding}
+javadoc.noindex=false
+javadoc.nonavbar=false
+javadoc.notree=false
+javadoc.private=false
+javadoc.splitindex=true
+javadoc.use=true
+javadoc.version=false
+javadoc.windowtitle=
+main.class=net.i2p.BOB.Main
+manifest.file=manifest.mf
+meta.inf.dir=${src.dir}/META-INF
+platform.active=default_platform
+run.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}
+# Space-separated list of JVM arguments used when running the project
+# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value
+# or test-sys-prop.name=value to set system properties for unit tests):
+run.jvmargs=
+run.test.classpath=\
+ ${javac.test.classpath}:\
+ ${build.test.classes.dir}
+source.encoding=UTF-8
+src.dir=src
+test.src.dir=test
diff --git a/apps/BOB/nbproject/project.xml b/apps/BOB/nbproject/project.xml
new file mode 100644
index 000000000..44eb60a6d
--- /dev/null
+++ b/apps/BOB/nbproject/project.xml
@@ -0,0 +1,16 @@
+
+
+ org.netbeans.modules.java.j2seproject
+
+
+ BOB
+ 1.6.5
+
+
+
+
+
+
+
+
+
diff --git a/apps/BOB/src/net/i2p/BOB/BOB.java b/apps/BOB/src/net/i2p/BOB/BOB.java
new file mode 100644
index 000000000..ab29f6444
--- /dev/null
+++ b/apps/BOB/src/net/i2p/BOB/BOB.java
@@ -0,0 +1,157 @@
+/**
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * Version 2, December 2004
+ *
+ * Copyright (C) sponge
+ * Planet Earth
+ * Everyone is permitted to copy and distribute verbatim or modified
+ * copies of this license document, and changing it is allowed as long
+ * as the name is changed.
+ *
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+ *
+ * 0. You just DO WHAT THE FUCK YOU WANT TO.
+ *
+ * See...
+ *
+ * http://sam.zoy.org/wtfpl/
+ * and
+ * http://en.wikipedia.org/wiki/WTFPL
+ *
+ * ...for any additional details and liscense questions.
+ */
+package net.i2p.BOB;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.Properties;
+import net.i2p.client.I2PClient;
+import net.i2p.client.streaming.RetransmissionTimer;
+import net.i2p.util.Log;
+import net.i2p.util.SimpleTimer;
+
+/**
+ *
+ * BOB, main command socket listener, launches the command parser engine.
+ *
+ * @author sponge
+ */
+public class BOB {
+
+ private final static Log _log = new Log(BOB.class);
+ public final static String PROP_CONFIG_LOCATION = "BOB.config";
+ public final static String PROP_BOB_PORT = "BOB.port";
+ public final static String PROP_BOB_HOST = "BOB.host";
+ private static int maxConnections = 0;
+ private static nickname database;
+
+ /**
+ * Log a warning
+ *
+ * @param arg
+ */
+ public static void warn(String arg) {
+ System.out.println(arg);
+ _log.warn(arg);
+ }
+
+ /**
+ * Log an error
+ *
+ * @param arg
+ */
+ public static void error(String arg) {
+ System.out.println(arg);
+ _log.error(arg);
+ }
+
+ /**
+ * Listen for incoming connections and handle them
+ *
+ * @param args
+ */
+ public static void main(String[] args) {
+ database = new nickname();
+ int i = 0;
+ boolean save = false;
+ // Set up all defaults to be passed forward to other threads.
+ // Re-reading the config file in each thread is pretty damn stupid.
+ // I2PClient client = I2PClientFactory.createClient();
+ Properties props = new Properties();
+ String configLocation = System.getProperty(PROP_CONFIG_LOCATION, "bob.config");
+
+ // This is here just to ensure there is no interference with our threadgroups.
+ SimpleTimer Y = RetransmissionTimer.getInstance();
+ i = Y.hashCode();
+
+ try {
+ props.load(new FileInputStream(configLocation));
+ } catch(FileNotFoundException fnfe) {
+ warn("Unable to load up the BOB config file " + configLocation + ", Using defaults.");
+ warn(fnfe.toString());
+ save = true;
+ } catch(IOException ioe) {
+ warn("IOException on BOB config file " + configLocation + ", using defaults.");
+ warn(ioe.toString());
+ }
+ // Global router and client API configurations that are missing are set to defaults here.
+ if(!props.containsKey(I2PClient.PROP_TCP_HOST)) {
+ props.setProperty(I2PClient.PROP_TCP_HOST, "localhost");
+ }
+ if(!props.containsKey(I2PClient.PROP_TCP_PORT)) {
+ props.setProperty(I2PClient.PROP_TCP_PORT, "7654");
+ }
+ if(!props.containsKey(I2PClient.PROP_RELIABILITY)) {
+ props.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_BEST_EFFORT);
+ }
+ if(!props.containsKey(PROP_BOB_PORT)) {
+ props.setProperty(PROP_BOB_PORT, "2827"); // 0xB0B
+ }
+ if(!props.containsKey("inbound.length")) {
+ props.setProperty("inbound.length", "1");
+ }
+ if(!props.containsKey("outbound.length")) {
+ props.setProperty("outbound.length", "1");
+ }
+ if(!props.containsKey("inbound.lengthVariance")) {
+ props.setProperty("inbound.lengthVariance", "0");
+ }
+ if(!props.containsKey("outbound.lengthVariance")) {
+ props.setProperty("outbound.lengthVariance", "0");
+ }
+ if(!props.containsKey(PROP_BOB_HOST)) {
+ props.setProperty(PROP_BOB_HOST, "localhost");
+ }
+ if(save) {
+ try {
+ warn("Writing new defaults file " + configLocation);
+ props.store(new FileOutputStream(configLocation), configLocation);
+ } catch(IOException ioe) {
+ warn("IOException on BOB config file " + configLocation + ", " + ioe);
+ }
+ }
+
+ try {
+ ServerSocket listener = new ServerSocket(Integer.parseInt(props.getProperty(PROP_BOB_PORT)), 10, InetAddress.getByName(props.getProperty(PROP_BOB_HOST)));
+ Socket server;
+
+ while((i++ < maxConnections) || (maxConnections == 0)) {
+ //doCMDS connection;
+
+ server = listener.accept();
+ doCMDS conn_c = new doCMDS(server, props, database, _log);
+ Thread t = new Thread(conn_c);
+ t.start();
+ }
+ } catch(IOException ioe) {
+ warn("IOException on socket listen: " + ioe);
+ ioe.printStackTrace();
+ }
+ }
+}
diff --git a/apps/BOB/src/net/i2p/BOB/COPYING b/apps/BOB/src/net/i2p/BOB/COPYING
new file mode 100644
index 000000000..116db5958
--- /dev/null
+++ b/apps/BOB/src/net/i2p/BOB/COPYING
@@ -0,0 +1,21 @@
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ Version 2, December 2004
+
+ Copyright (C) sponge
+ Planet Earth
+ Everyone is permitted to copy and distribute verbatim or modified
+ copies of this license document, and changing it is allowed as long
+ as the name is changed.
+
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
+
+See...
+
+ http://sam.zoy.org/wtfpl/
+ and
+ http://en.wikipedia.org/wiki/WTFPL
+
+...for any additional details and license questions.
diff --git a/apps/BOB/src/net/i2p/BOB/I2Plistener.java b/apps/BOB/src/net/i2p/BOB/I2Plistener.java
new file mode 100644
index 000000000..f646466f6
--- /dev/null
+++ b/apps/BOB/src/net/i2p/BOB/I2Plistener.java
@@ -0,0 +1,128 @@
+/**
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * Version 2, December 2004
+ *
+ * Copyright (C) sponge
+ * Planet Earth
+ * Everyone is permitted to copy and distribute verbatim or modified
+ * copies of this license document, and changing it is allowed as long
+ * as the name is changed.
+ *
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+ *
+ * 0. You just DO WHAT THE FUCK YOU WANT TO.
+ *
+ * See...
+ *
+ * http://sam.zoy.org/wtfpl/
+ * and
+ * http://en.wikipedia.org/wiki/WTFPL
+ *
+ * ...for any additional details and liscense questions.
+ */
+package net.i2p.BOB;
+
+import java.net.ConnectException;
+import java.net.SocketTimeoutException;
+import net.i2p.I2PException;
+import net.i2p.client.I2PSession;
+import net.i2p.client.I2PSessionException;
+import net.i2p.client.streaming.I2PServerSocket;
+import net.i2p.client.streaming.I2PSocket;
+import net.i2p.client.streaming.I2PSocketManager;
+import net.i2p.util.Log;
+
+/**
+ * Listen on I2P and connect to TCP
+ *
+ * @author sponge
+ */
+public class I2Plistener implements Runnable {
+
+ private nickname info;
+ private Log _log;
+ private int tgwatch;
+ public I2PSocketManager socketManager;
+ public I2PServerSocket serverSocket;
+
+ /**
+ * Constructor
+ * @param S
+ * @param info
+ * @param _log
+ */
+ I2Plistener(I2PSocketManager S, nickname info, Log _log) {
+ this.info = info;
+ this._log = _log;
+ this.socketManager = S;
+ serverSocket = socketManager.getServerSocket();
+ tgwatch = 1;
+ }
+
+ /**
+ * Simply listen on I2P port, and thread connections
+ *
+ * @throws RuntimeException
+ */
+ public void run() throws RuntimeException {
+ boolean g = false;
+ I2PSocket sessSocket = null;
+
+ // needed to hack in this method :-/
+ serverSocket.setSoTimeout(1000);
+ if(info.exists("INPORT")) {
+ tgwatch = 2;
+ }
+ while(info.get("RUNNING").equals(true)) {
+ try {
+ try {
+ sessSocket = serverSocket.accept();
+ g = true;
+ } catch(ConnectException ce) {
+ g = false;
+ } catch (SocketTimeoutException ste) {
+ g = false;
+ }
+ if(g) {
+ g = false;
+ // toss the connection to a new thread.
+ I2PtoTCP conn_c = new I2PtoTCP(sessSocket, info);
+ Thread t = new Thread(conn_c, "BOBI2PtoTCP");
+ t.start();
+ }
+
+ } catch(I2PException e) {
+ System.out.println("Exception "+e);
+ }
+ }
+
+ try {
+ serverSocket.close();
+ } catch(I2PException e) {
+ // nop
+ }
+
+ while(Thread.activeCount() > tgwatch) { // wait for all threads in our threadgroup to finish
+// System.out.println("STOP Thread count " + Thread.activeCount());
+ try {
+ Thread.sleep(1000); //sleep for 1000 ms (One second)
+ } catch(Exception e) {
+ // nop
+ }
+ }
+// System.out.println("STOP Thread count " + Thread.activeCount());
+ // need to kill off the socket manager too.
+ I2PSession session = socketManager.getSession();
+ if(session != null) {
+ try {
+ session.destroySession();
+ } catch(I2PSessionException ex) {
+ // nop
+ }
+// System.out.println("destroySession Thread count " + Thread.activeCount());
+ }
+
+
+ }
+}
diff --git a/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java b/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java
new file mode 100644
index 000000000..58253542d
--- /dev/null
+++ b/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java
@@ -0,0 +1,106 @@
+/**
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * Version 2, December 2004
+ *
+ * Copyright (C) sponge
+ * Planet Earth
+ * Everyone is permitted to copy and distribute verbatim or modified
+ * copies of this license document, and changing it is allowed as long
+ * as the name is changed.
+ *
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+ *
+ * 0. You just DO WHAT THE FUCK YOU WANT TO.
+ *
+ * See...
+ *
+ * http://sam.zoy.org/wtfpl/
+ * and
+ * http://en.wikipedia.org/wiki/WTFPL
+ *
+ * ...for any additional details and liscense questions.
+ */
+package net.i2p.BOB;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import net.i2p.client.streaming.I2PSocket;
+
+/**
+ * Process I2P->TCP
+ *
+ * @author sponge
+ */
+public class I2PtoTCP implements Runnable {
+
+ private I2PSocket I2P;
+ private nickname info;
+ private Socket sock;
+
+ /**
+ * Constructor
+ *
+ * @param I2Psock
+ * @param db
+ */
+ I2PtoTCP(I2PSocket I2Psock, nickname db) {
+ this.I2P = I2Psock;
+ this.info = db;
+ }
+
+ /**
+ * I2P stream to TCP stream thread starter
+ *
+ */
+ public void run() {
+
+ try {
+ sock = new Socket(info.get("OUTHOST").toString(), Integer.parseInt(info.get("OUTPORT").toString()));
+ // make readers/writers
+ InputStream in = sock.getInputStream();
+ OutputStream out = sock.getOutputStream();
+ InputStream Iin = I2P.getInputStream();
+ OutputStream Iout = I2P.getOutputStream();
+ I2P.setReadTimeout(0); // temp bugfix, this *SHOULD* be the default
+
+ if(info.get("QUIET").equals(false)) {
+ // tell who is connecting
+ out.write(I2P.getPeerDestination().toBase64().getBytes());
+ out.write(10); // nl
+ out.flush(); // not really needed, but...
+ }
+ // setup to cross the streams
+ TCPio conn_c = new TCPio(in, Iout, info); // app -> I2P
+ TCPio conn_a = new TCPio(Iin, out, info); // I2P -> app
+ Thread t = new Thread(conn_c, "TCPioA");
+ Thread q = new Thread(conn_a, "TCPioB");
+ // Fire!
+ t.start();
+ q.start();
+ while(t.isAlive() && q.isAlive()) { // AND is used here to kill off the other thread
+ try {
+ Thread.sleep(10); //sleep for 10 ms
+ } catch(InterruptedException e) {
+ // nop
+ }
+ }
+
+ } catch(UnknownHostException ex) {
+ // OOPS!
+ } catch(IOException ex) {
+ // OOPS!
+ }
+ try {
+ I2P.close();
+ } catch(IOException ex) {
+ }
+ try {
+ sock.close();
+ } catch(IOException ex) {
+ }
+ }
+}
diff --git a/apps/BOB/src/net/i2p/BOB/MUXlisten.java b/apps/BOB/src/net/i2p/BOB/MUXlisten.java
new file mode 100644
index 000000000..680e3cd43
--- /dev/null
+++ b/apps/BOB/src/net/i2p/BOB/MUXlisten.java
@@ -0,0 +1,118 @@
+/**
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * Version 2, December 2004
+ *
+ * Copyright (C) sponge
+ * Planet Earth
+ * Everyone is permitted to copy and distribute verbatim or modified
+ * copies of this license document, and changing it is allowed as long
+ * as the name is changed.
+ *
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+ *
+ * 0. You just DO WHAT THE FUCK YOU WANT TO.
+ *
+ * See...
+ *
+ * http://sam.zoy.org/wtfpl/
+ * and
+ * http://en.wikipedia.org/wiki/WTFPL
+ *
+ * ...for any additional details and liscense questions.
+ */
+package net.i2p.BOB;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Properties;
+import net.i2p.I2PException;
+import net.i2p.client.streaming.I2PSocketManager;
+import net.i2p.client.streaming.I2PSocketManagerFactory;
+import net.i2p.util.Log;
+
+/**
+ *
+ * Multiplex listeners for TCP and I2P
+ *
+ * @author sponge
+ */
+public class MUXlisten implements Runnable {
+
+ private nickname info;
+ private Log _log;
+ private I2PSocketManager socketManager;
+ private ByteArrayInputStream prikey;
+ private ThreadGroup tg;
+ private String N;
+
+ /**
+ * Constructor
+ *
+ * @param info
+ * @param _log
+ * @throws net.i2p.I2PException
+ * @throws java.io.IOException
+ */
+ MUXlisten(nickname info, Log _log) throws I2PException, IOException {
+ this.info = info;
+ this._log = _log;
+ this.info.add("STARTING", true);
+
+ N = this.info.get("NICKNAME").toString();
+ prikey = new ByteArrayInputStream((byte[])info.get("KEYS"));
+ socketManager = I2PSocketManagerFactory.createManager(prikey, (Properties)info.get("PROPERTIES"));
+ }
+
+ /**
+ * MUX sockets, fire off a thread to connect, get destination info, and do I/O
+ *
+ */
+ public void run() {
+
+ tg = new ThreadGroup(N);
+ info.add("RUNNING", true);
+ info.add("STARTING", false);
+
+ // toss the connections to a new threads.
+ // will wrap with TCP and UDP when UDP works
+ if(info.exists("OUTPORT")) {
+ // I2P -> TCP
+ I2Plistener conn = new I2Plistener(socketManager, info, _log);
+ Thread t = new Thread(tg, conn, "BOBI2Plistener " + N);
+ t.start();
+ }
+ if(info.exists("INPORT")) {
+ // TCP -> I2P
+ TCPlistener conn = new TCPlistener(socketManager, info, _log);
+ Thread q = new Thread(tg, conn,"BOBTCPlistener" + N);
+ q.start();
+ }
+
+ while(info.get("STOPPING").equals(false)) {
+ try {
+ Thread.sleep(1000); //sleep for 1000 ms (One second)
+ } catch(InterruptedException e) {
+ // nop
+ }
+ }
+
+ info.add("RUNNING", false);
+ // wait for child threads and thread groups to die
+ while (tg.activeCount() + tg.activeGroupCount() != 0) {
+ try {
+ Thread.sleep(1000); //sleep for 1000 ms (One second)
+ } catch(InterruptedException ex) {
+ // nop
+ }
+ }
+
+ socketManager.destroySocketManager();
+ tg.destroy();
+ // Zap reference to the ThreadGroup so the JVM can GC it.
+ tg = null;
+ info.add("STOPPING", false);
+ info.add("STARTING", false);
+
+ }
+}
diff --git a/apps/BOB/src/net/i2p/BOB/Main.java b/apps/BOB/src/net/i2p/BOB/Main.java
new file mode 100644
index 000000000..3dfc9af21
--- /dev/null
+++ b/apps/BOB/src/net/i2p/BOB/Main.java
@@ -0,0 +1,48 @@
+/**
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * Version 2, December 2004
+ *
+ * Copyright (C) sponge
+ * Planet Earth
+ * Everyone is permitted to copy and distribute verbatim or modified
+ * copies of this license document, and changing it is allowed as long
+ * as the name is changed.
+ *
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+ *
+ * 0. You just DO WHAT THE FUCK YOU WANT TO.
+ *
+ * See...
+ *
+ * http://sam.zoy.org/wtfpl/
+ * and
+ * http://en.wikipedia.org/wiki/WTFPL
+ *
+ * ...for any additional details and liscense questions.
+ */
+
+package net.i2p.BOB;
+
+import net.i2p.client.streaming.RetransmissionTimer;
+import net.i2p.util.SimpleTimer;
+
+/**
+ * Start from command line
+ *
+ * @author sponge
+ *
+ */
+
+public class Main {
+
+ /**
+ * @param args the command line arguments, these are not used yet
+ */
+ public static void main(String[] args) {
+ // THINK THINK THINK THINK THINK THINK
+ SimpleTimer Y = RetransmissionTimer.getInstance();
+ BOB.main(args);
+ Y.removeSimpleTimer();
+ }
+}
diff --git a/apps/BOB/src/net/i2p/BOB/TCPio.java b/apps/BOB/src/net/i2p/BOB/TCPio.java
new file mode 100644
index 000000000..460f8aa58
--- /dev/null
+++ b/apps/BOB/src/net/i2p/BOB/TCPio.java
@@ -0,0 +1,93 @@
+/**
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * Version 2, December 2004
+ *
+ * Copyright (C) sponge
+ * Planet Earth
+ * Everyone is permitted to copy and distribute verbatim or modified
+ * copies of this license document, and changing it is allowed as long
+ * as the name is changed.
+ *
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+ *
+ * 0. You just DO WHAT THE FUCK YOU WANT TO.
+ *
+ * See...
+ *
+ * http://sam.zoy.org/wtfpl/
+ * and
+ * http://en.wikipedia.org/wiki/WTFPL
+ *
+ * ...for any additional details and liscense questions.
+ */
+package net.i2p.BOB;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Shove data from one stream to the other.
+ *
+ * @author sponge
+ */
+public class TCPio implements Runnable {
+
+ private InputStream Ain;
+ private OutputStream Aout;
+ private nickname info;
+
+ /**
+ * Constructor
+ *
+ * @param Ain
+ * @param Aout
+ * @param db
+ */
+ TCPio(InputStream Ain, OutputStream Aout, nickname db) {
+ this.Ain = Ain;
+ this.Aout = Aout;
+ this.info = db;
+ }
+
+ /**
+ * kill off the streams, to hopefully cause an IOException in the thread in order to kill it.
+ */
+ /**
+ * Copy from source to destination...
+ * and yes, we are totally OK to block here on writes,
+ * The OS has buffers, and I intend to use them.
+ *
+ */
+ public void run() {
+ int b;
+ byte a[] = new byte[1];
+ try {
+ while(info.get("RUNNING").equals(true)) {
+ b = Ain.read(a, 0, 1);
+ // System.out.println(info.get("NICKNAME").toString() + " " + b);
+ if(b > 0) {
+ Aout.write(a,0,1);
+ // Aout.flush(); too slow!
+ } else if(b == 0) {
+ try {
+ // Thread.yield();
+ Thread.sleep(10);
+ } catch(InterruptedException ex) {
+ }
+ } else {
+ /* according to the specs:
+ *
+ * The total number of bytes read into the buffer,
+ * or -1 is there is no more data because the end of
+ * the stream has been reached.
+ *
+ */
+ return;
+ }
+ }
+ } catch(IOException e) {
+ }
+ }
+}
diff --git a/apps/BOB/src/net/i2p/BOB/TCPlistener.java b/apps/BOB/src/net/i2p/BOB/TCPlistener.java
new file mode 100644
index 000000000..5c9f23def
--- /dev/null
+++ b/apps/BOB/src/net/i2p/BOB/TCPlistener.java
@@ -0,0 +1,123 @@
+/**
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * Version 2, December 2004
+ *
+ * Copyright (C) sponge
+ * Planet Earth
+ * Everyone is permitted to copy and distribute verbatim or modified
+ * copies of this license document, and changing it is allowed as long
+ * as the name is changed.
+ *
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+ *
+ * 0. You just DO WHAT THE FUCK YOU WANT TO.
+ *
+ * See...
+ *
+ * http://sam.zoy.org/wtfpl/
+ * and
+ * http://en.wikipedia.org/wiki/WTFPL
+ *
+ * ...for any additional details and liscense questions.
+ */
+package net.i2p.BOB;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import net.i2p.client.I2PSession;
+import net.i2p.client.I2PSessionException;
+import net.i2p.client.streaming.I2PServerSocket;
+import net.i2p.client.streaming.I2PSocketManager;
+import net.i2p.util.Log;
+
+/**
+ * Listen on TCP port and connect to I2P
+ *
+ * @author sponge
+ */
+public class TCPlistener implements Runnable {
+
+ private nickname info;
+ private Log _log;
+ private int tgwatch;
+ public I2PSocketManager socketManager;
+ public I2PServerSocket serverSocket;
+ private int backlog = 50; // should this be more? less?
+
+ /**
+ * Constructor
+ * @param S
+ * @param info
+ * @param _log
+ */
+ TCPlistener(I2PSocketManager S, nickname info, Log _log) {
+ this.info = info;
+ this._log = _log;
+ this.socketManager = S;
+ tgwatch = 1;
+ }
+
+ /**
+ * Simply listen on TCP port, and thread connections
+ * @throws java.lang.RuntimeException
+ */
+ public void run() throws RuntimeException {
+ boolean g = false;
+ if(info.exists("OUTPORT")) {
+ tgwatch = 2;
+ }
+ try {
+// System.out.println("Starting thread count " + Thread.activeCount());
+ ServerSocket listener = new ServerSocket(Integer.parseInt(info.get("INPORT").toString()), backlog, InetAddress.getByName(info.get("INHOST").toString()));
+ Socket server = new Socket();
+ listener.setSoTimeout(1000);
+ while(info.get("RUNNING").equals(true)) {
+// System.out.println("Thread count " + Thread.activeCount());
+ try {
+ server = listener.accept();
+ g = true;
+ } catch(SocketTimeoutException ste) {
+ g = false;
+ }
+ if(g) {
+ // toss the connection to a new thread.
+ TCPtoI2P conn_c = new TCPtoI2P(socketManager, server, info);
+ Thread t = new Thread(conn_c, "BOBTCPtoI2P");
+ t.start();
+ g = false;
+ }
+ }
+ listener.close();
+ } catch(IOException ioe) {
+ // throw new RuntimeException(ioe);
+ }
+
+//System.out.println("STOP!");
+
+ while(Thread.activeCount() > tgwatch) { // wait for all threads in our threadgroup to finish
+// System.out.println("STOP Thread count " + Thread.activeCount());
+ try {
+ Thread.sleep(1000); //sleep for 1000 ms (One second)
+ } catch(Exception e) {
+ // nop
+ }
+ }
+// System.out.println("STOP Thread count " + Thread.activeCount());
+ // need to kill off the socket manager too.
+ I2PSession session = socketManager.getSession();
+ if(session != null) {
+ try {
+ session.destroySession();
+ } catch(I2PSessionException ex) {
+ // nop
+ }
+// System.out.println("destroySession Thread count " + Thread.activeCount());
+ }
+ }
+}
+
+
diff --git a/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java b/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java
new file mode 100644
index 000000000..c1f1ba8e6
--- /dev/null
+++ b/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java
@@ -0,0 +1,186 @@
+/**
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * Version 2, December 2004
+ *
+ * Copyright (C) sponge
+ * Planet Earth
+ * Everyone is permitted to copy and distribute verbatim or modified
+ * copies of this license document, and changing it is allowed as long
+ * as the name is changed.
+ *
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+ *
+ * 0. You just DO WHAT THE FUCK YOU WANT TO.
+ *
+ * See...
+ *
+ * http://sam.zoy.org/wtfpl/
+ * and
+ * http://en.wikipedia.org/wiki/WTFPL
+ *
+ * ...for any additional details and liscense questions.
+ */
+package net.i2p.BOB;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.net.ConnectException;
+import java.net.NoRouteToHostException;
+import java.net.Socket;
+import net.i2p.I2PException;
+import net.i2p.client.streaming.I2PSocket;
+import net.i2p.client.streaming.I2PSocketManager;
+import net.i2p.data.DataFormatException;
+import net.i2p.data.Destination;
+import net.i2p.i2ptunnel.I2PTunnel;
+
+/**
+ *
+ * Process TCP->I2P
+ *
+ * @author sponge
+ */
+public class TCPtoI2P implements Runnable {
+
+ private I2PSocket I2P;
+ private nickname info;
+ private Socket sock;
+ private I2PSocketManager socketManager;
+
+ /**
+ * This is a more forgiving readline,
+ * it works on unbuffered streams
+ *
+ * @param in
+ * @return line of text as a String
+ * @throws java.io.IOException
+ */
+ public static String Lread(InputStream in) throws IOException {
+ String S;
+ int b;
+ char c;
+
+ S = new String();
+
+ while(true) {
+ b = in.read();
+ if(b == 13) {
+ //skip CR
+ continue;
+ }
+ if(b < 20 || b > 126) {
+ // exit on anything not legal
+ break;
+ }
+ c = (char)(b & 0x7f); // We only really give a fuck about ASCII
+ S = new String(S + c);
+ }
+ return S;
+ }
+
+ /**
+ * Constructor
+ * @param i2p
+ * @param socket
+ * @param db
+ */
+ TCPtoI2P(I2PSocketManager i2p, Socket socket, nickname db) {
+ this.sock = socket;
+ this.info = db;
+ this.socketManager = i2p;
+ }
+
+ /**
+ * Print an error message to out
+ *
+ * @param e
+ * @param out
+ * @throws java.io.IOException
+ */
+ private void Emsg(String e, OutputStream out) throws IOException {
+ System.out.println("ERROR TCPtoI2P: " + e);
+ out.write("ERROR".concat(e).getBytes());
+ out.write(13); // cr
+ out.flush();
+ sock.close();
+
+ }
+
+ /**
+ * TCP stream to I2P stream thread starter
+ */
+ public void run() {
+ String line, input;
+
+ try {
+
+ InputStream in = sock.getInputStream();
+ OutputStream out = sock.getOutputStream();
+ try {
+ line = Lread(in);
+ input = line.toLowerCase();
+ Destination dest = null;
+
+ if(input.endsWith(".i2p")) {
+ dest = I2PTunnel.destFromName(input);
+ line = dest.toBase64();
+ }
+ dest = new Destination();
+ dest.fromBase64(line);
+
+ try {
+ // get a client socket
+ I2P = socketManager.connect(dest);
+ I2P.setReadTimeout(0); // temp bugfix, this *SHOULD* be the default
+ // make readers/writers
+ InputStream Iin = I2P.getInputStream();
+ OutputStream Iout = I2P.getOutputStream();
+ // setup to cross the streams
+ TCPio conn_c = new TCPio(in, Iout, info); // app -> I2P
+ TCPio conn_a = new TCPio(Iin, out, info); // I2P -> app
+ Thread t = new Thread(conn_c, "TCPioA");
+ Thread q = new Thread(conn_a, "TCPioB");
+ // Fire!
+ t.start();
+ q.start();
+ while(t.isAlive() && q.isAlive()) { // AND is used here to kill off the other thread
+ try {
+ Thread.sleep(10); //sleep for 10 ms
+ } catch(InterruptedException e) {
+ // nop
+ }
+ }
+ } catch(I2PException e) {
+ Emsg("ERROR " + e.toString(), out);
+ } catch(ConnectException e) {
+ Emsg("ERROR " + e.toString(), out);
+ } catch(NoRouteToHostException e) {
+ Emsg("ERROR " + e.toString(), out);
+ } catch(InterruptedIOException e) {
+ Emsg("ERROR " + e.toString(), out);
+ }
+
+ } catch(DataFormatException e) {
+ Emsg("ERROR " + e.toString(), out);
+ } catch(NullPointerException e) {
+ Emsg("ERROR " + e.toString(), out);
+ }
+ } catch(IOException ioe) {
+ }
+ try {
+ I2P.close();
+ } catch(IOException ex) {
+ } catch(NullPointerException e) {
+ }
+
+ try {
+ sock.close();
+ } catch(IOException ex) {
+ } catch(NullPointerException e) {
+ }
+ }
+}
+
diff --git a/apps/BOB/src/net/i2p/BOB/UDPIOthread.java b/apps/BOB/src/net/i2p/BOB/UDPIOthread.java
new file mode 100644
index 000000000..deb22b223
--- /dev/null
+++ b/apps/BOB/src/net/i2p/BOB/UDPIOthread.java
@@ -0,0 +1,144 @@
+/**
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * Version 2, December 2004
+ *
+ * Copyright (C) sponge
+ * Planet Earth
+ * Everyone is permitted to copy and distribute verbatim or modified
+ * copies of this license document, and changing it is allowed as long
+ * as the name is changed.
+ *
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+ *
+ * 0. You just DO WHAT THE FUCK YOU WANT TO.
+ *
+ * See...
+ *
+ * http://sam.zoy.org/wtfpl/
+ * and
+ * http://en.wikipedia.org/wiki/WTFPL
+ *
+ * ...for any additional details and liscense questions.
+ */
+
+package net.i2p.BOB;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.Socket;
+import net.i2p.client.I2PSession;
+import net.i2p.client.I2PSessionException;
+import net.i2p.client.I2PSessionListener;
+import net.i2p.data.Destination;
+import net.i2p.util.Log;
+
+
+/**
+ * UDP IO on I2P
+ *
+ * FIX ME: Untested, and incomplete!
+ * I have no personal need to UDP yet,
+ * however alot of p2p apps pretty much demand it.
+ * The skeletal frame is here, just needs to be finished.
+ *
+ * @author sponge
+ */
+public class UDPIOthread implements I2PSessionListener, Runnable {
+
+ private nickname info;
+ private Log _log;
+ private Socket socket;
+ private DataInputStream in;
+ private DataOutputStream out;
+ private I2PSession _session;
+ private Destination _peerDestination;
+ private boolean up;
+
+/**
+ * Constructor
+ * @param info
+ * @param _log
+ * @param socket
+ * @param _session
+ */ UDPIOthread(nickname info, Log _log, Socket socket, I2PSession _session) {
+ this.info = info;
+ this._log = _log;
+ this.socket = socket;
+ this._session = _session;
+
+ }
+/**
+ *
+ */
+ public void run() {
+ byte data[] = new byte[1024];
+ up = true;
+ try {
+ in = new DataInputStream(socket.getInputStream());
+ out = new DataOutputStream(socket.getOutputStream());
+ while(up) {
+ int c = in.read(data);
+ // Note: could do a loopback test here with a wrapper.
+ boolean ok = _session.sendMessage(_peerDestination, data, 0, c);
+
+ if(!ok) {
+ up = false; // Is this the right thing to do??
+ }
+ }
+ } catch(IOException ioe) {
+ _log.error("Error running", ioe);
+ } catch(I2PSessionException ise) {
+ _log.error("Error communicating", ise);
+ // } catch(DataFormatException dfe) {
+ // _log.error("Peer destination file is not valid", dfe);
+ } finally {
+ if(_session != null) {
+ try {
+ _session.destroySession();
+ } catch(I2PSessionException ise) {
+ // ignored
+ }
+ }
+ }
+ }
+/**
+ *
+ * @param session
+ * @param msgId
+ * @param size
+ */
+ public void messageAvailable(I2PSession session, int msgId, long size) {
+// _log.debug("Message available: id = " + msgId + " size = " + size);
+ try {
+ byte msg[] = session.receiveMessage(msgId);
+ out.write(msg);
+ out.flush();
+ } catch(I2PSessionException ise) {
+ up = false;
+ } catch(IOException ioe) {
+ up = false;
+ }
+ }
+
+ // Great, can these be used to kill ourselves.
+
+ /** required by {@link I2PSessionListener I2PSessionListener} to notify of disconnect */
+ public void disconnected(I2PSession session) {
+ _log.debug("Disconnected");
+ // up = false;
+ }
+
+ /** required by {@link I2PSessionListener I2PSessionListener} to notify of error */
+ public void errorOccurred(I2PSession session, String message, Throwable error) {
+ _log.debug("Error occurred: " + message, error);
+ // up = false;
+ }
+
+ /** required by {@link I2PSessionListener I2PSessionListener} to notify of abuse */
+ public void reportAbuse(I2PSession session, int severity) {
+ _log.debug("Abuse reported of severity " + severity);
+ // up = false;
+ }
+}
diff --git a/apps/BOB/src/net/i2p/BOB/doCMDS.java b/apps/BOB/src/net/i2p/BOB/doCMDS.java
new file mode 100644
index 000000000..9e4cdce88
--- /dev/null
+++ b/apps/BOB/src/net/i2p/BOB/doCMDS.java
@@ -0,0 +1,568 @@
+/**
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * Version 2, December 2004
+ *
+ * Copyright (C) sponge
+ * Planet Earth
+ * Everyone is permitted to copy and distribute verbatim or modified
+ * copies of this license document, and changing it is allowed as long
+ * as the name is changed.
+ *
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+ *
+ * 0. You just DO WHAT THE FUCK YOU WANT TO.
+ *
+ * See...
+ *
+ * http://sam.zoy.org/wtfpl/
+ * and
+ * http://en.wikipedia.org/wiki/WTFPL
+ *
+ * ...for any additional details and liscense questions.
+ */
+package net.i2p.BOB;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.net.Socket;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import net.i2p.I2PException;
+import net.i2p.client.I2PClientFactory;
+import net.i2p.data.Destination;
+import net.i2p.util.Log;
+
+/**
+ * Simplistic command parser for BOB
+ *
+ * @author sponge
+ *
+ */
+public class doCMDS implements Runnable {
+
+ // FIX ME
+ // I need a better way to do versioning, but this will do for now.
+ public static final String BMAJ = "00", BMIN = "00", BREV = "01", BEXT = "-6";
+ public static final String BOBversion = BMAJ + "." + BMIN + "." + BREV + BEXT;
+ private Socket server;
+ private Properties props;
+ private nickname database;
+ private String line;
+ private Destination d;
+ private ByteArrayOutputStream prikey;
+ private boolean dk, ns, ip, op;
+ private nickname nickinfo;
+ private Log _log;
+ /* database strings */
+ private static final String P_INHOST = "INHOST";
+ private static final String P_INPORT = "INPORT";
+ private static final String P_KEYS = "KEYS";
+ private static final String P_NICKNAME = "NICKNAME";
+ private static final String P_OUTHOST = "OUTHOST";
+ private static final String P_OUTPORT = "OUTPORT";
+ private static final String P_PROPERTIES = "PROPERTIES";
+ private static final String P_QUIET = "QUIET";
+ private static final String P_RUNNING = "RUNNING";
+ private static final String P_STARTING = "STARTING";
+ private static final String P_STOPPING = "STOPPING";
+ /* command strings */
+ private static final String C_help = "help";
+ private static final String C_clear = "clear";
+ private static final String C_getkeys = "getkeys";
+ private static final String C_getnick = "getnick";
+ private static final String C_inhost = "inhost";
+ private static final String C_inport = "inport";
+ private static final String C_list = "list";
+ private static final String C_newkeys = "newkeys";
+ private static final String C_outhost = "outhost";
+ private static final String C_outport = "outport";
+ private static final String C_quiet = "quiet";
+ private static final String C_quit = "quit";
+ private static final String C_setkeys = "setkeys";
+ private static final String C_setnick = "setnick";
+ private static final String C_show = "show";
+ private static final String C_start = "start";
+ private static final String C_status = "status";
+ private static final String C_stop = "stop";
+
+ /* all the coomands available, plus description */
+ private static final String C_ALL[][] = {
+ {C_help, C_help + " * Get help on a command."},
+ {C_clear, C_clear + " * Clear the current nickname out of the list."},
+ {C_getkeys, C_getkeys + " * Return the keypair for the current nickname."},
+ {C_getnick, C_getnick + " tunnelname * Set the nickname from the database."},
+ {C_inhost, C_inhost + " hostname | IP * Set the inbound hostname or IP."},
+ {C_inport, C_inport + " port_number * Set the inbound port number nickname listens on."},
+ {C_list, C_list + " * List all tunnels."},
+ {C_newkeys, C_newkeys + " * Generate a new keypair for the current nickname."},
+ {C_outhost, C_outhost + " hostname | IP * Set the outbound hostname or IP."},
+ {C_outport, C_outport + " port_number * Set the outbound port that nickname contacts."},
+ {C_quiet, C_quiet + " *"},
+ {C_quit, C_quit + " * Quits this session with BOB."},
+ {C_setkeys, C_setkeys + " BASE64_keypair * Sets the keypair for the current nickname."},
+ {C_setnick, C_setnick + " nickname * Create a new nickname."},
+ {C_show, C_show + " * Display the status of the current nickname."},
+ {C_start, C_start + " * Start the current nickname tunnel."},
+ {C_status, C_status + " nickname * Display status of a nicknamed tunnel."},
+ {C_stop, C_stop + " * Stops the current nicknamed tunnel."},
+ {"", "COMMANDS: " + // this is ugly, but...
+ C_help + " " +
+ C_clear + " " +
+ C_getkeys + " " +
+ C_getnick + " " +
+ C_inhost + " " +
+ C_inport + " " +
+ C_list + " " +
+ C_newkeys + " " +
+ C_outhost + " " +
+ C_outport + " " +
+ C_quiet + " " +
+ C_quit + " " +
+ C_setkeys + " " +
+ C_setnick + " " +
+ C_show + " " +
+ C_start + " " +
+ C_status + " " +
+ C_stop
+ },
+ {" ", " "} // end of list
+ };
+
+ /**
+ *
+ * @param server
+ * @param props
+ * @param database
+ * @param _log
+ */
+ doCMDS(Socket server, Properties props, nickname database, Log _log) {
+ this.server = server;
+ this.props = props;
+ this.database = database;
+ this._log = _log;
+ }
+
+ /**
+ * Try to print info from the database
+ *
+ * @param out
+ * @param info
+ * @param key
+ */
+ public void trypnt(PrintStream out, nickname info, Object key) {
+ out.print(" " + key + ": ");
+ if(info.exists(key)) {
+ out.print(info.get(key));
+ } else {
+ out.print("not_set");
+ }
+ }
+
+ /**
+ * Print true or false if an object exists
+ *
+ * @param out
+ * @param info
+ * @param key
+ */
+ public void tfpnt(PrintStream out, nickname info, Object key) {
+ out.print(" " + key + ": ");
+ out.print(info.exists(key));
+ }
+
+ /**
+ * Print an error message
+ *
+ * @param out
+ */
+ public void nns(PrintStream out) {
+ out.println("ERROR no nickname has been set");
+ }
+
+ /**
+ * Dump various information from the database
+ *
+ * @param out
+ * @param info
+ */
+ public void nickprint(PrintStream out, nickname info) {
+ trypnt(out, info, P_NICKNAME);
+ trypnt(out, info, P_STARTING);
+ trypnt(out, info, P_RUNNING);
+ trypnt(out, info, P_STOPPING);
+ tfpnt(out, info, P_KEYS);
+ trypnt(out, info, P_QUIET);
+ trypnt(out, info, P_INPORT);
+ trypnt(out, info, P_INHOST);
+ trypnt(out, info, P_OUTPORT);
+ trypnt(out, info, P_OUTHOST);
+ out.println();
+
+ }
+
+ /**
+ * Print information on a specific record, indicated by nickname
+ * @param out
+ * @param database
+ * @param Arg
+ */
+ public void ttlpnt(PrintStream out, nickname database, Object Arg) {
+ if(database.exists(Arg)) {
+ out.print("DATA");
+ nickprint(out, (nickname)database.get(Arg));
+ }
+ }
+
+ /**
+ * Is this nickname's tunnel active?
+ *
+ * @param Arg
+ * @return true if the tunnel is active
+ */
+ public boolean tunnelactive(nickname Arg) {
+ return (Arg.get(P_STARTING).equals(true) ||
+ Arg.get(P_STOPPING).equals(true) ||
+ Arg.get(P_RUNNING).equals(true));
+
+ }
+
+ /**
+ * Does the base64 information look OK
+ *
+ * @param data
+ * @return
+ */
+ private boolean is64ok(String data) {
+ String dest = new String(data);
+ if(dest.replaceAll("[a-zA-Z0-9~-]", "").length() == 0) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * The actual parser.
+ * It probabbly needs a rewrite into functions, but I kind-of like inline code.
+ *
+ */
+ public void run() {
+ dk = ns = ip = op = false;
+
+ try {
+ // Get input from the client
+ BufferedReader in = new BufferedReader(new InputStreamReader(server.getInputStream()));
+ PrintStream out = new PrintStream(server.getOutputStream());
+ prikey = new ByteArrayOutputStream();
+ out.println("BOB " + BOBversion);
+ out.println("OK");
+ while((line = in.readLine()) != null) {
+ System.gc(); // yes, this does make a huge difference...
+ StringTokenizer token = new StringTokenizer(line, " "); // use a space as a delimiter
+ String Command = "";
+ String Arg = "";
+ nickname info;
+
+ if(token.countTokens() != 0) {
+ Command = token.nextToken();
+ Command = Command.toLowerCase();
+ if(token.countTokens() != 0) {
+ Arg = token.nextToken();
+ } else {
+ Arg = "";
+ }
+ // The rest of the tokens are considered junk,
+ // and discarded without any warnings.
+
+ if(Command.equals(C_help)) {
+ for(int i = 0; !C_ALL[i][0].equals(" "); i++) {
+ if(C_ALL[i][0].equalsIgnoreCase(Arg)) {
+ out.println("OK " + C_ALL[i][1]);
+ }
+ }
+
+ } else if(Command.equals(C_list)) {
+ // Produce a formatted list of all nicknames
+ for(int i = 0; i < database.getcount(); i++) {
+ try {
+ info = (nickname)database.getnext(i);
+ } catch(RuntimeException b) {
+ break; // something bad happened.
+ }
+
+ out.print("DATA");
+ nickprint(out, info);
+ }
+ out.println("OK Listing done");
+ } else if(Command.equals(C_quit)) {
+ // End the command session
+ break;
+ } else if(Command.equals(C_newkeys)) {
+ if(ns) {
+ if(tunnelactive(nickinfo)) {
+ out.println("ERROR tunnel is active");
+ } else {
+ try {
+ // Make a new PublicKey and PrivateKey
+ prikey = new ByteArrayOutputStream();
+ d = I2PClientFactory.createClient().createDestination(prikey);
+ dk = true;
+ nickinfo.add(P_KEYS, prikey.toByteArray());
+ // System.out.println(prikey.toByteArray().length);
+ out.println("OK " + d.toBase64());
+ } catch(IOException ioe) {
+ BOB.error("Error generating keys" + ioe);
+ out.println("ERROR generating keys");
+ } catch(I2PException ipe) {
+ BOB.error("Error generating keys" + ipe);
+ out.println("ERROR generating keys");
+ }
+ }
+ } else {
+ nns(out);
+ }
+ } else if(Command.equals(C_getkeys)) {
+ // Return public key
+ if(dk) {
+ prikey = new ByteArrayOutputStream();
+ prikey.write(((byte[])nickinfo.get(P_KEYS)));
+ out.println("OK " + net.i2p.data.Base64.encode(prikey.toByteArray()));
+ } else {
+ out.println("ERROR no public key has been set");
+ }
+ } else if(Command.equals(C_quiet)) {
+ if(ns) {
+ if(tunnelactive(nickinfo)) {
+ out.println("ERROR tunnel is active");
+ } else {
+ nickinfo.add(P_QUIET, (Boolean.parseBoolean(Arg) == true));
+ out.println("OK Quiet set");
+ }
+ } else {
+ nns(out);
+ }
+ } else if(Command.equals(C_setkeys)) {
+ // Set the nickname to a privatekey in BASE64 format
+ if(ns) {
+ if(tunnelactive(nickinfo)) {
+ out.println("ERROR tunnel is active");
+ } else {
+ prikey = new ByteArrayOutputStream();
+ prikey.write(net.i2p.data.Base64.decode(Arg));
+ if((Arg.length() == 884) && is64ok(Arg)) {
+ nickinfo.add(P_KEYS, prikey.toByteArray());
+ out.println("OK Keys set");
+ dk = true;
+ } else {
+ out.println("ERROR not in BASE64 format");
+ }
+ }
+ } else {
+ nns(out);
+ }
+ } else if(Command.equals(C_setnick)) {
+ ns = dk = ip = op = false;
+ try {
+ nickinfo = (nickname)database.get(Arg);
+ if(!tunnelactive(nickinfo)) {
+ nickinfo = null;
+ ns = true;
+ }
+ } catch(RuntimeException b) {
+ nickinfo = null;
+ ns = true;
+ }
+
+ // Clears and Sets the initial nickname structure to work with
+ if(ns) {
+ nickinfo = new nickname();
+ database.add(Arg, nickinfo);
+ nickinfo.add(P_NICKNAME, Arg);
+ nickinfo.add(P_STARTING, false);
+ nickinfo.add(P_RUNNING, false);
+ nickinfo.add(P_STOPPING, false);
+ nickinfo.add(P_QUIET, false);
+ nickinfo.add(P_INHOST, "localhost");
+ nickinfo.add(P_OUTHOST, "localhost");
+ Properties Q = props;
+ Q.setProperty("inbound.nickname", (String)nickinfo.get(P_NICKNAME));
+ Q.setProperty("outbound.nickname", (String)nickinfo.get(P_NICKNAME));
+ nickinfo.add(P_PROPERTIES, Q);
+ out.println("OK Nickname set to " + Arg);
+ } else {
+ out.println("ERROR tunnel is active");
+ }
+ } else if(Command.equals(C_getnick)) {
+ // Get the nickname to work with...
+ try {
+ nickinfo = (nickname)database.get(Arg);
+ ns = true;
+ } catch(RuntimeException b) {
+ nns(out);
+ }
+ if(ns) {
+ dk = nickinfo.exists(P_KEYS);
+ ip = nickinfo.exists(P_INPORT);
+ op = nickinfo.exists(P_OUTPORT);
+ // Finally say OK.
+ out.println("OK Nickname set to " + Arg);
+ }
+ } else if(Command.equals(C_inport)) {
+ // Set the nickname inbound TO the router port
+ // app --> BOB
+ if(ns) {
+ if(tunnelactive(nickinfo)) {
+ out.println("ERROR tunnel is active");
+ } else {
+ int prt;
+ nickinfo.kill(P_INPORT);
+ try {
+ prt = Integer.parseInt(Arg);
+ if(prt > 1 && prt < 65536) {
+ nickinfo.add(P_INPORT, prt);
+ }
+ } catch(NumberFormatException nfe) {
+ out.println("ERROR not a number");
+ }
+ ip = nickinfo.exists(P_INPORT);
+ if(ip) {
+ out.println("OK inbound port set");
+ } else {
+ out.println("ERROR port out of range");
+ }
+ }
+ } else {
+ nns(out);
+ }
+ } else if(Command.equals(C_outport)) {
+ // Set the nickname outbound FROM the router port
+ // BOB --> app
+ if(ns) {
+ if(tunnelactive(nickinfo)) {
+ out.println("ERROR tunnel is active");
+ } else {
+ int prt;
+ nickinfo.kill(P_OUTPORT);
+ try {
+ prt = Integer.parseInt(Arg);
+ if(prt > 1 && prt < 65536) {
+ nickinfo.add(P_OUTPORT, prt);
+ }
+ } catch(NumberFormatException nfe) {
+ out.println("ERROR not a number");
+ }
+ ip = nickinfo.exists(P_OUTPORT);
+ if(ip) {
+ out.println("OK outbound port set");
+ } else {
+ out.println("ERROR port out of range");
+ }
+ }
+ } else {
+ nns(out);
+ }
+ } else if(Command.equals(C_inhost)) {
+ if(ns) {
+ if(tunnelactive(nickinfo)) {
+ out.println("ERROR tunnel is active");
+ } else {
+ nickinfo.add(P_INHOST, Arg);
+ out.println("OK inhost set");
+ }
+ } else {
+ nns(out);
+ }
+ } else if(Command.equals(C_outhost)) {
+ if(ns) {
+ if(tunnelactive(nickinfo)) {
+ out.println("ERROR tunnel is active");
+ } else {
+ nickinfo.add(P_OUTHOST, Arg);
+ out.println("OK outhost set");
+ }
+ } else {
+ nns(out);
+ }
+ } else if(Command.equals(C_show)) {
+ // Get the current nickname properties
+ if(ns) {
+ out.print("OK");
+ nickprint(out, nickinfo);
+ } else {
+ nns(out);
+ }
+ } else if(Command.equals(C_start)) {
+ // Start the tunnel, if we have all the information
+ if(ns && dk && (ip || op)) {
+ if(tunnelactive(nickinfo)) {
+ out.println("ERROR tunnel is active");
+ } else {
+ MUXlisten tunnel;
+ try {
+ tunnel = new MUXlisten(nickinfo, _log);
+ Thread t = new Thread(tunnel);
+ t.start();
+ nickinfo.add(P_STARTING, true);
+ out.println("OK tunnel starting");
+ } catch(I2PException e) {
+ out.println("ERROR starting tunnel: " + e);
+ } catch(IOException e) {
+ out.println("ERROR starting tunnel: " + e);
+ }
+ }
+ } else {
+ out.println("ERROR tunnel settings incomplete");
+ }
+ } else if(Command.equals(C_stop)) {
+ // Stop the tunnel, if it is running
+ if(ns) {
+ if(nickinfo.get(P_RUNNING).equals(true) && nickinfo.get(P_STOPPING).equals(false)) {
+ nickinfo.add(P_STOPPING, true);
+ out.println("OK tunnel stopping");
+ } else {
+ out.println("ERROR tunnel is inactive");
+ }
+ } else {
+ nns(out);
+ }
+ } else if(Command.equals(C_clear)) {
+ // Clear use of the nickname if stopped
+ if(ns) {
+ if(tunnelactive(nickinfo)) {
+ out.println("ERROR tunnel is active");
+ } else {
+ database.kill(nickinfo.get(P_NICKNAME));
+ dk = ns = ip = op = false;
+ out.println("OK cleared");
+ }
+ } else {
+ nns(out);
+ }
+ } else if(Command.equals(C_status)) {
+ if(database.exists(Arg)) {
+ // Show status of a nickname
+ out.print("OK ");
+ ttlpnt(out, database, Arg);
+ } else {
+ nns(out);
+ }
+ } else {
+ out.println("ERROR UNKNOWN COMMAND! Try help");
+ }
+ }
+ }
+
+ // Say goodbye.
+
+ out.println("OK Bye!");
+
+ server.close();
+ } catch(IOException ioe) {
+ BOB.warn("IOException on socket listen: " + ioe);
+ ioe.printStackTrace();
+ }
+ }
+}
diff --git a/apps/BOB/src/net/i2p/BOB/nickname.java b/apps/BOB/src/net/i2p/BOB/nickname.java
new file mode 100644
index 000000000..b7b76b9ce
--- /dev/null
+++ b/apps/BOB/src/net/i2p/BOB/nickname.java
@@ -0,0 +1,165 @@
+/**
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * Version 2, December 2004
+ *
+ * Copyright (C) sponge
+ * Planet Earth
+ * Everyone is permitted to copy and distribute verbatim or modified
+ * copies of this license document, and changing it is allowed as long
+ * as the name is changed.
+ *
+ * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ * TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+ *
+ * 0. You just DO WHAT THE FUCK YOU WANT TO.
+ *
+ * See...
+ *
+ * http://sam.zoy.org/wtfpl/
+ * and
+ * http://en.wikipedia.org/wiki/WTFPL
+ *
+ * ...for any additional details and liscense questions.
+ */
+
+package net.i2p.BOB;
+
+/**
+ * Internal database to relate nicknames to options to values
+ *
+ * @author sponge
+ */
+public class nickname {
+
+ private Object[][] data;
+ private int index = 0;
+
+ /**
+ * make initial NULL object
+ *
+ */
+ public nickname() {
+ data = new Object[1][2];
+ }
+
+ /**
+ * Find objects in the array, returns it's index or throws exception
+ * @param key
+ * @return an objects index
+ */
+ public synchronized int idx(Object key) {
+ for(int i = 0; i < index; i++) {
+ if(key.equals(data[i][0])) {
+ return i;
+ }
+ }
+ throw new ArrayIndexOutOfBoundsException("Can't locate key for index");
+ }
+
+ /**
+ * Delete an object from array if it exists
+ *
+ * @param key
+ */
+ public synchronized void kill(Object key) {
+
+ int i, j, k, l;
+ Object[][] olddata;
+ int didsomething = 0;
+
+ try {
+ k = idx(key);
+ } catch(ArrayIndexOutOfBoundsException b) {
+ return;
+ }
+ olddata = new Object[index + 2][2];
+ // copy to olddata, skipping 'k'
+ for(i = 0 , l = 0; l < index; i++, l++) {
+ if(i == k) {
+ l++;
+ didsomething++;
+ }
+ for(j = 0; j < 2; j++) {
+ olddata[i][j] = data[l][j];
+ }
+ }
+ index -= didsomething;
+ data = olddata;
+
+ }
+
+ /**
+ * Add object to the array, deletes the old one if it exists
+ *
+ * @param key
+ * @param val
+ */
+ public synchronized void add(Object key, Object val) {
+ Object[][] olddata;
+ int i, j;
+ i = 0;
+ kill(key);
+
+ olddata = new Object[index + 2][2];
+ // copy to olddata
+ for(i = 0; i < index; i++) {
+ for(j = 0; j < 2; j++) {
+ olddata[i][j] = data[i][j];
+ }
+ }
+ data = olddata;
+ data[index++] = new Object[] {key, val};
+ }
+
+ /**
+ * Get the object, and return it, throws RuntimeException
+ *
+ * @param key
+ * @return Object
+ * @throws java.lang.RuntimeException
+ */
+ public synchronized Object get(Object key) throws RuntimeException {
+ for(int i = 0; i < index; i++) {
+ if(key.equals(data[i][0])) {
+ return data[i][1];
+ }
+ }
+ throw new RuntimeException("Key not found");
+ }
+
+ /**
+ * returns true if an object exists, else returns false
+ *
+ * @param key
+ * @return true if an object exists, else returns false
+ */
+ public synchronized boolean exists(Object key) {
+ for(int i = 0; i < index; i++) {
+ if(key.equals(data[i][0])) {
+ return true;
+ }
+ }
+ return false;
+
+ }
+
+ /**
+ *
+ * @param i index
+ * @return an indexed Object
+ * @throws java.lang.RuntimeException
+ */
+ public synchronized Object getnext(int i) throws RuntimeException {
+ if(i < index && i > -1) {
+ return data[i][1];
+ }
+ throw new RuntimeException("No more data");
+ }
+
+ /**
+ * @return the count of how many objects
+ */
+ public synchronized int getcount() {
+ return index;
+ }
+}
diff --git a/build.xml b/build.xml
index 57ad97f85..86e82078f 100644
--- a/build.xml
+++ b/build.xml
@@ -23,6 +23,7 @@
+
@@ -84,7 +85,7 @@
-
+
@@ -96,6 +97,7 @@
+
@@ -141,6 +143,7 @@
+
@@ -164,6 +167,7 @@
+
@@ -199,6 +203,7 @@
+
@@ -315,6 +320,7 @@
+