diff --git a/apps/BOB/src/net/i2p/BOB/DoCMDS.java b/apps/BOB/src/net/i2p/BOB/DoCMDS.java
index 16da28ce9c019e2b588cacf7d83d0983e260e6e0..4a13844cb400e0d09b2261844cbd0cdae369a5c7 100644
--- a/apps/BOB/src/net/i2p/BOB/DoCMDS.java
+++ b/apps/BOB/src/net/i2p/BOB/DoCMDS.java
@@ -46,7 +46,7 @@ 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 = "05",  BEXT = "";
+	public static final String BMAJ = "00",  BMIN = "00",  BREV = "06",  BEXT = "";
 	public static final String BOBversion = BMAJ + "." + BMIN + "." + BREV + BEXT;
 	private Socket server;
 	private Properties props;
@@ -1263,11 +1263,11 @@ public class DoCMDS implements Runnable {
 												tunnel = new MUXlisten(database, nickinfo, _log);
 												Thread t = new Thread(tunnel);
 												t.start();
-												try {
-													Thread.sleep(1000 * 10); // Slow down the startup.
-												} catch(InterruptedException ie) {
-													// ignore it
-												}
+												// try {
+												//	Thread.sleep(1000 * 10); // Slow down the startup.
+												// } catch(InterruptedException ie) {
+												//	// ignore it
+												// }
 												out.println("OK tunnel starting");
 											} catch (I2PException e) {
 												out.println("ERROR starting tunnel: " + e);
diff --git a/apps/BOB/src/net/i2p/BOB/I2Plistener.java b/apps/BOB/src/net/i2p/BOB/I2Plistener.java
index a8115893db8ca2f650bf976407356e647ecb0354..caaadc76d515aa31503bd41dc892fdb077f52cab 100644
--- a/apps/BOB/src/net/i2p/BOB/I2Plistener.java
+++ b/apps/BOB/src/net/i2p/BOB/I2Plistener.java
@@ -25,6 +25,8 @@ package net.i2p.BOB;
 
 import java.net.ConnectException;
 import java.net.SocketTimeoutException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import net.i2p.I2PException;
 import net.i2p.client.streaming.I2PServerSocket;
 import net.i2p.client.streaming.I2PSocket;
@@ -78,91 +80,59 @@ public class I2Plistener implements Runnable {
 	public void run() {
 		boolean g = false;
 		I2PSocket sessSocket = null;
+		int conn = 0;
+		try {
+			die:
+			{
 
-die:		{
+				serverSocket.setSoTimeout(50);
+				boolean spin = true;
+				while (spin) {
 
-			serverSocket.setSoTimeout(50);
-//			try {
-//				if (info.exists("INPORT")) {
-//					tgwatch = 2;
-//				}
-//			} catch (Exception e) {
-//				try {
-//					runlock();
-//				} catch (Exception e2) {
-//					break die;
-//				}
-//				break die;
-//			}
-			boolean spin = true;
-			while (spin) {
-
-				try {
-					rlock();
-				} catch (Exception e) {
-					break die;
-				}
-				try {
-					spin = info.get("RUNNING").equals(Boolean.TRUE);
-				} catch (Exception e) {
 					try {
-						runlock();
-					} catch (Exception e2) {
+						rlock();
+					} catch (Exception e) {
 						break die;
 					}
-					break die;
-				}
-				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, database);
-						Thread t = new Thread(conn_c, "BOBI2PtoTCP");
-						t.start();
+						spin = info.get("RUNNING").equals(Boolean.TRUE);
+					} catch (Exception e) {
+						try {
+							runlock();
+						} catch (Exception e2) {
+							break die;
+						}
+						break die;
 					}
+					try {
+						try {
+							sessSocket = serverSocket.accept();
+							g = true;
+						} catch (ConnectException ce) {
+							g = false;
+						} catch (SocketTimeoutException ste) {
+							g = false;
+						}
+						if (g) {
+							g = false;
+							conn++;
+							// toss the connection to a new thread.
+							I2PtoTCP conn_c = new I2PtoTCP(sessSocket, info, database);
+							Thread t = new Thread(conn_c, Thread.currentThread().getName() + " I2PtoTCP " + conn);
+							t.start();
+						}
 
-				} catch (I2PException e) {
-					//	System.out.println("Exception " + e);
+					} catch (Exception e) {
+						//	System.out.println("Exception " + e);
+					}
 				}
 			}
-		}
+		} finally {
+			try {
+				serverSocket.close();
+			} catch (I2PException ex) {
+			}
 		// System.out.println("I2Plistener: Close");
-
-
-		// Previous level does this cleanup now.
-		//
-		// try {
-		//	serverSocket.close();
-		// } catch (I2PException e) {
-			// nop
-		//}
-		// need to kill off the socket manager too.
-		// I2PSession session = socketManager.getSession();
-		// if (session != null) {
-			// System.out.println("I2Plistener: destroySession");
-		//	try {
-		//		session.destroySession();
-		//	} catch (I2PSessionException ex) {
-				// nop
-		//	}
-		//}
-		// System.out.println("I2Plistener: Waiting for children");
-		// while (Thread.activeCount() > tgwatch) { // wait for all threads in our threadgroup to finish
-		//	try {
-		//		Thread.sleep(100); //sleep for 100 ms (One tenth second)
-		//	} catch (Exception e) {
-				// nop
-		//	}
-		//}
-
-	// System.out.println("I2Plistener: Done.");
+		}
 	}
 }
diff --git a/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java b/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java
index 0984823b6e631ee19f8ff5a73e185fa3cdae808d..73e936c61b3cf7b77970820a63dbcdd9c8eb01bf 100644
--- a/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java
+++ b/apps/BOB/src/net/i2p/BOB/I2PtoTCP.java
@@ -23,7 +23,6 @@
  */
 package net.i2p.BOB;
 
-import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.Socket;
@@ -71,90 +70,99 @@ public class I2PtoTCP implements Runnable {
 		String host;
 		int port;
 		boolean tell;
-die:            {
-			try {
-				try {
-					rlock();
-				} catch(Exception e) {
-					break die;
-				}
-				try {
-					host = info.get("OUTHOST").toString();
-					port = Integer.parseInt(info.get("OUTPORT").toString());
-					tell = info.get("QUIET").equals(Boolean.FALSE);
-				} catch(Exception e) {
-					runlock();
-					break die;
-				}
+		InputStream in = null;
+		OutputStream out = null;
+		InputStream Iin = null;
+		OutputStream Iout = null;
+		try {
+			die:
+			{
 				try {
-					runlock();
-				} catch(Exception e) {
-					break die;
-				}
-				sock = new Socket(host, port);
-				// 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(tell) {
-					// 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, database */ ); // app -> I2P
-				TCPio conn_a = new TCPio(Iin, out /* , info, database */); // 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) {
-						try {
-							in.close();
-						} catch(Exception ex) {
-						}
-						try {
-							out.close();
-						} catch(Exception ex) {
-						}
-						try {
-							Iin.close();
-						} catch(Exception ex) {
-						}
+						rlock();
+					} catch (Exception e) {
+						break die;
+					}
+					try {
+						host = info.get("OUTHOST").toString();
+						port = Integer.parseInt(info.get("OUTPORT").toString());
+						tell = info.get("QUIET").equals(Boolean.FALSE);
+					} catch (Exception e) {
+						runlock();
+						break die;
+					}
+					try {
+						runlock();
+					} catch (Exception e) {
+						break die;
+					}
+					sock = new Socket(host, port);
+					// make readers/writers
+					in = sock.getInputStream();
+					out = sock.getOutputStream();
+					Iin = I2P.getInputStream();
+					Iout = I2P.getOutputStream();
+					I2P.setReadTimeout(0); // temp bugfix, this *SHOULD* be the default
+
+					if (tell) {
+						// 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, database */); // app -> I2P
+					TCPio conn_a = new TCPio(Iin, out /* , info, database */); // I2P -> app
+					Thread t = new Thread(conn_c, Thread.currentThread().getName() + " TCPioA");
+					Thread q = new Thread(conn_a, Thread.currentThread().getName() + " TCPioB");
+					// Fire!
+					t.start();
+					q.start();
+					while (t.isAlive() && q.isAlive()) { // AND is used here to kill off the other thread
 						try {
-							Iout.close();
-						} catch(Exception ex) {
+							Thread.sleep(10); //sleep for 10 ms
+						} catch (InterruptedException e) {
+							break die;
 						}
 					}
-				}
 				// System.out.println("I2PtoTCP: Going away...");
-			} catch(Exception e) {
-				// System.out.println("I2PtoTCP: Owch! damn!");
-				break die;
+				} catch (Exception e) {
+					// System.out.println("I2PtoTCP: Owch! damn!");
+					break die;
+				}
+			} // die
+		} finally {
+			try {
+				in.close();
+			} catch (Exception ex) {
+			}
+			try {
+				out.close();
+			} catch (Exception ex) {
+			}
+			try {
+				Iin.close();
+			} catch (Exception ex) {
+			}
+			try {
+				Iout.close();
+			} catch (Exception ex) {
+			}
+			try {
+				// System.out.println("I2PtoTCP: Close I2P");
+				I2P.close();
+			} catch (Exception e) {
+				tell = false;
+			}
+			//System.out.println("I2PtoTCP: Closed I2P");
+			try {
+				// System.out.println("I2PtoTCP: Close sock");
+				sock.close();
+			} catch (Exception e) {
+				tell = false;
 			}
-		} // die
-		try {
-			// System.out.println("I2PtoTCP: Close I2P");
-			I2P.close();
-		} catch(Exception e) {
-			tell = false;
-		}
-		//System.out.println("I2PtoTCP: Closed I2P");
-		try {
-			// System.out.println("I2PtoTCP: Close sock");
-			sock.close();
-		} catch(Exception e) {
-			tell = false;
-		}
 		// System.out.println("I2PtoTCP: Done");
 
+		}
 	}
 }
diff --git a/apps/BOB/src/net/i2p/BOB/MUXlisten.java b/apps/BOB/src/net/i2p/BOB/MUXlisten.java
index dc30c5445d0d31ca3e668e5b1cf14a491f21b112..f77d5bc82d887e15f0c92357a5695f56aaa3d372 100644
--- a/apps/BOB/src/net/i2p/BOB/MUXlisten.java
+++ b/apps/BOB/src/net/i2p/BOB/MUXlisten.java
@@ -29,8 +29,6 @@ import java.net.InetAddress;
 import java.net.ServerSocket;
 import java.util.Properties;
 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.I2PSocketManager;
 import net.i2p.client.streaming.I2PSocketManagerFactory;
@@ -50,7 +48,7 @@ public class MUXlisten implements Runnable {
 	private ByteArrayInputStream prikey;
 	private ThreadGroup tg;
 	private String N;
-	private ServerSocket listener;
+	private ServerSocket listener = null;
 	private int backlog = 50; // should this be more? less?
 	boolean go_out;
 	boolean come_in;
@@ -133,234 +131,195 @@ public class MUXlisten implements Runnable {
 	 */
 	public void run() {
 		I2PServerSocket SS = null;
-		int ticks = 1200; // Allow 120 seconds, no more.
+		Thread t = null;
+		Thread q = null;
 		try {
-			wlock();
 			try {
-				info.add("RUNNING", new Boolean(true));
+				wlock();
+				try {
+					info.add("RUNNING", new Boolean(true));
+				} catch (Exception e) {
+					wunlock();
+					return;
+				}
 			} catch (Exception e) {
+				return;
+			}
+			try {
 				wunlock();
+			} catch (Exception e) {
 				return;
 			}
-		} catch (Exception e) {
-			return;
-		}
-		try {
-			wunlock();
-		} catch (Exception e) {
-			return;
-		}
 //		socketManager.addDisconnectListener(new DisconnectListener());
 
-quit:
-		{
-			try {
-				tg = new ThreadGroup(N);
-die:
-				{
-					// toss the connections to a new threads.
-					// will wrap with TCP and UDP when UDP works
+			quit:
+			{
+				try {
+					tg = new ThreadGroup(N);
+					die:
+					{
+						// toss the connections to a new threads.
+						// will wrap with TCP and UDP when UDP works
 
-					if (go_out) {
-						// I2P -> TCP
-						SS = socketManager.getServerSocket();
-						I2Plistener conn = new I2Plistener(SS, socketManager, info, database, _log);
-						Thread t = new Thread(tg, conn, "BOBI2Plistener " + N);
-						t.start();
-					}
+						if (go_out) {
+							// I2P -> TCP
+							SS = socketManager.getServerSocket();
+							I2Plistener conn = new I2Plistener(SS, socketManager, info, database, _log);
+							t = new Thread(tg, conn, "BOBI2Plistener " + N);
+							t.start();
+						}
 
-					if (come_in) {
-						// TCP -> I2P
-						TCPlistener conn = new TCPlistener(listener, socketManager, info, database, _log);
-						Thread q = new Thread(tg, conn, "BOBTCPlistener" + N);
-						q.start();
-					}
+						if (come_in) {
+							// TCP -> I2P
+							TCPlistener conn = new TCPlistener(listener, socketManager, info, database, _log);
+							q = new Thread(tg, conn, "BOBTCPlistener " + N);
+							q.start();
+						}
 
-					try {
-						wlock();
 						try {
-							info.add("STARTING", new Boolean(false));
+							wlock();
+							try {
+								info.add("STARTING", new Boolean(false));
+							} catch (Exception e) {
+								wunlock();
+								break die;
+							}
 						} catch (Exception e) {
-							wunlock();
 							break die;
 						}
-					} catch (Exception e) {
-						break die;
-					}
-					try {
-						wunlock();
-					} catch (Exception e) {
-						break die;
-					}
-					boolean spin = true;
-					while (spin) {
 						try {
-							Thread.sleep(1000); //sleep for 1 second
-						} catch (InterruptedException e) {
+							wunlock();
+						} catch (Exception e) {
 							break die;
 						}
-						try {
-							rlock();
+						boolean spin = true;
+						while (spin) {
+							try {
+								Thread.sleep(1000); //sleep for 1 second
+							} catch (InterruptedException e) {
+								break die;
+							}
 							try {
-								spin = info.get("STOPPING").equals(Boolean.FALSE);
+								rlock();
+								try {
+									spin = info.get("STOPPING").equals(Boolean.FALSE);
+								} catch (Exception e) {
+									runlock();
+									break die;
+								}
 							} catch (Exception e) {
+								break die;
+							}
+							try {
 								runlock();
+							} catch (Exception e) {
 								break die;
 							}
-						} catch (Exception e) {
-							break die;
 						}
+
 						try {
-							runlock();
+							wlock();
+							try {
+								info.add("RUNNING", new Boolean(false));
+							} catch (Exception e) {
+								wunlock();
+								break die;
+							}
 						} catch (Exception e) {
 							break die;
 						}
-					}
-
-					try {
-						wlock();
 						try {
-							info.add("RUNNING", new Boolean(false));
-						} catch (Exception e) {
 							wunlock();
+						} catch (Exception e) {
 							break die;
 						}
-					} catch (Exception e) {
-						break die;
-					}
-					try {
-						wunlock();
-					} catch (Exception e) {
-						break die;
-					}
-				} // die
-
-				if (SS != null) {
-					try {
-						SS.close();
-					} catch (I2PException ex) {
-						//Logger.getLogger(MUXlisten.class.getName()).log(Level.SEVERE, null, ex);
-					}
-				}
-				if (this.come_in) {
-					try {
-						listener.close();
-					} catch (IOException e) {
-					}
-				}
+					} // die
 
-				I2PSession session = socketManager.getSession();
-				if (session != null) {
-					// System.out.println("I2Plistener: destroySession");
-					try {
-						session.destroySession();
-					} catch (I2PSessionException ex) {
-					// nop
-					}
+				} catch (Exception e) {
+					// System.out.println("MUXlisten: Caught an exception" + e);
+					break quit;
 				}
+			} // quit
+		} finally {
+			// Start cleanup. Allow threads above this one to catch the stop signal.
+			try {
+				Thread.sleep(250);
+			} catch (InterruptedException ex) {
+			}
+			// zero out everything.
+			try {
+				wlock();
 				try {
-					socketManager.destroySocketManager();
+					info.add("STARTING", new Boolean(false));
+					info.add("STOPPING", new Boolean(false));
+					info.add("RUNNING", new Boolean(false));
 				} catch (Exception e) {
-					// nop
+					wunlock();
+					return;
 				}
-				// Wait for child threads and thread groups to die
-				// System.out.println("MUXlisten: waiting for children");
-				if (tg.activeCount() + tg.activeGroupCount() != 0) {
-					while ((tg.activeCount() + tg.activeGroupCount() != 0) && ticks != 0) {
-						tg.interrupt(); // unwedge any blocking threads.
-						ticks--;
-						try {
-							Thread.sleep(100); //sleep for 100 ms (One tenth second)
-						} catch (InterruptedException ex) {
-							break quit;
-						}
-					}
-					if (tg.activeCount() + tg.activeGroupCount() != 0) {
-						break quit; // Uh-oh.
-					}
-				}
-				tg.destroy();
-				// Zap reference to the ThreadGroup so the JVM can GC it.
-				tg = null;
+				wunlock();
 			} catch (Exception e) {
-				// System.out.println("MUXlisten: Caught an exception" + e);
-				break quit;
 			}
-		} // quit
 
-		// This is here to catch when something fucks up REALLY bad.
-		if (tg != null) {
+
 			if (SS != null) {
 				try {
 					SS.close();
 				} catch (I2PException ex) {
-					//Logger.getLogger(MUXlisten.class.getName()).log(Level.SEVERE, null, ex);
 				}
 			}
-			if (this.come_in) {
+			if (listener != null) {
 				try {
 					listener.close();
 				} catch (IOException e) {
 				}
 			}
+
 			try {
 				socketManager.destroySocketManager();
 			} catch (Exception e) {
 				// nop
-			}
-			ticks = 600; // 60 seconds
-			if (tg.activeCount() + tg.activeGroupCount() != 0) {
-				while ((tg.activeCount() + tg.activeGroupCount() != 0) && ticks != 0) {
-					tg.interrupt(); // unwedge any blocking threads.
-					ticks--;
-					try {
-						Thread.sleep(100); //sleep for 100 ms (One tenth second)
-					} catch (InterruptedException ex) {
-						// nop
+				}
+			// Wait around till all threads are collected.
+			if (tg != null) {
+				String boner = tg.getName();
+				_log.warn("BOB: MUXlisten: Starting thread collection for: " + boner);
+				// tg.interrupt(); // give my stuff a small smack again.
+				if (tg.activeCount() + tg.activeGroupCount() != 0) {
+					int foo = tg.activeCount() + tg.activeGroupCount();
+					// hopefully no longer needed!
+					// int bar = foo;
+					// System.out.println("BOB: MUXlisten: Waiting on threads for " + boner);
+					// System.out.println("\nBOB: MUXlisten: ThreadGroup dump BEGIN " + boner);
+					// visit(tg, 0, boner);
+					// System.out.println("BOB: MUXlisten: ThreadGroup dump END " + boner + "\n");
+					// Happily spin forever :-(
+					while (foo != 0) {
+						foo = tg.activeCount() + tg.activeGroupCount();
+						//	if (foo != bar) {
+						//		System.out.println("\nBOB: MUXlisten: ThreadGroup dump BEGIN " + boner);
+						//		visit(tg, 0, boner);
+						//		System.out.println("BOB: MUXlisten: ThreadGroup dump END " + boner + "\n");
+						//	}
+						// bar = foo;
+						try {
+							Thread.sleep(100); //sleep for 100 ms (One tenth second)
+						} catch (InterruptedException ex) {
+							// nop
+						}
 					}
 				}
-			}
-			if (tg.activeCount() + tg.activeGroupCount() == 0) {
+				_log.warn("BOB: MUXlisten: Threads went away. Success: " + boner);
 				tg.destroy();
 				// Zap reference to the ThreadGroup so the JVM can GC it.
 				tg = null;
-			} else {
-				System.out.println("BOB: MUXlisten: Can't kill threads. Please send the following dump to sponge@mail.i2p");
-				System.out.println("\n\nBOB: MUXlisten: ThreadGroup dump BEGIN");
-				visit(tg, 0);
-				System.out.println("BOB: MUXlisten: ThreadGroup dump END\n\n");
-			}
-		}
-
-		// This is here to catch when something fucks up REALLY bad.
-//		if (tg != null) {
-//			System.out.println("BOB: MUXlisten: Something fucked up REALLY bad!");
-//			System.out.println("BOB: MUXlisten: Please email the following dump to sponge@mail.i2p");
-//			WrapperManager.requestThreadDump();
-//			System.out.println("BOB: MUXlisten: Something fucked up REALLY bad!");
-//			System.out.println("BOB: MUXlisten: Please email the above dump to sponge@mail.i2p");
-//		}
-		// zero out everything.
-		try {
-			wlock();
-			try {
-				info.add("STARTING", new Boolean(false));
-				info.add("STOPPING", new Boolean(false));
-				info.add("RUNNING", new Boolean(false));
-			} catch (Exception e) {
-				wunlock();
-				return;
 			}
-			wunlock();
-		} catch (Exception e) {
 		}
-
 	}
 
 
-	// Debugging...
-
-	/** 
+	// Debugging... None of this is normally used.
+	/**
 	 *	Find the root thread group and print them all.
 	 *
 	 */
@@ -371,7 +330,7 @@ die:
 		}
 
 		// Visit each thread group
-		visit(root, 0);
+		visit(root, 0, root.getName());
 	}
 
 	/**
@@ -379,7 +338,7 @@ die:
 	 * @param group ThreadGroup to visit
 	 * @param level Current level
 	 */
-	private static void visit(ThreadGroup group, int level) {
+	private static void visit(ThreadGroup group, int level, String tn) {
 		// Get threads in `group'
 		int numThreads = group.activeCount();
 		Thread[] threads = new Thread[numThreads * 2];
@@ -389,7 +348,36 @@ die:
 		for (int i = 0; i < numThreads; i++) {
 			// Get thread
 			Thread thread = threads[i];
-			System.out.println("BOB: MUXlisten: " + indent + thread.toString());
+			System.out.println("BOB: MUXlisten: " + tn + ": " + indent + thread.toString());
+		}
+
+		// Get thread subgroups of `group'
+		int numGroups = group.activeGroupCount();
+		ThreadGroup[] groups = new ThreadGroup[numGroups * 2];
+		numGroups = group.enumerate(groups, false);
+
+		// Recursively visit each subgroup
+		for (int i = 0; i < numGroups; i++) {
+			visit(groups[i], level + 1, groups[i].getName());
+		}
+	}
+
+	private static void nuke(ThreadGroup group, int level) {
+		// Get threads in `group'
+		int numThreads = group.activeCount();
+		Thread[] threads = new Thread[numThreads * 2];
+		numThreads = group.enumerate(threads, false);
+		// Enumerate each thread in `group' and stop it.
+		for (int i = 0; i < numThreads; i++) {
+			// Get thread
+			Thread thread = threads[i];
+			try {
+				if (thread.isAlive()) {
+					thread.stop();
+				}
+			} catch (SecurityException se) {
+				//nop
+			}
 		}
 
 		// Get thread subgroups of `group'
@@ -399,7 +387,14 @@ die:
 
 		// Recursively visit each subgroup
 		for (int i = 0; i < numGroups; i++) {
-			visit(groups[i], level + 1);
+			nuke(groups[i], level + 1);
+		}
+		try {
+			group.destroy();
+		} catch (IllegalThreadStateException IE) {
+			//nop
+		} catch (SecurityException se) {
+			//nop
 		}
 	}
 }
diff --git a/apps/BOB/src/net/i2p/BOB/TCPio.java b/apps/BOB/src/net/i2p/BOB/TCPio.java
index d4b353c5498053592e88e8531c919ae2123d24bd..d92a5cef07f8d0025f184a7f0c9d125e8e56fe80 100644
--- a/apps/BOB/src/net/i2p/BOB/TCPio.java
+++ b/apps/BOB/src/net/i2p/BOB/TCPio.java
@@ -87,23 +87,13 @@ public class TCPio implements Runnable {
 		boolean spin = true;
 		try {
 			while(spin) {
-				// database.getReadLock();
-				// info.getReadLock();
-				// spin = info.get("RUNNING").equals(Boolean.TRUE);
-				// info.releaseReadLock();
-				// database.releaseReadLock();
 				b = Ain.read(a, 0, 1);
-				// System.out.println(info.get("NICKNAME").toString() + " " + b);
 				if(b > 0) {
 					Aout.write(a, 0, b);
 				} else if(b == 0) {
 					Thread.yield(); // this should act like a mini sleep.
 					if(Ain.available() == 0) {
-//						try {
-							// Thread.yield();
 							Thread.sleep(10);
-//						} catch(InterruptedException ex) {
-//						}
 					}
 				} else {
 					/* according to the specs:
@@ -119,19 +109,16 @@ public class TCPio implements Runnable {
 					return;
 				}
 			}
-			// System.out.println("TCPio: RUNNING = false");
 		} catch(Exception e) {
 			// Eject!!! Eject!!!
 			//System.out.println("TCPio: Caught an exception " + e);
 			try {
 				Ain.close();
 			} catch (IOException ex) {
-//				Logger.getLogger(TCPio.class.getName()).log(Level.SEVERE, null, ex);
 			}
 			try {
 				Aout.close();
 			} catch (IOException ex) {
-//				Logger.getLogger(TCPio.class.getName()).log(Level.SEVERE, null, ex);
 			}
 			return;
 		}
diff --git a/apps/BOB/src/net/i2p/BOB/TCPlistener.java b/apps/BOB/src/net/i2p/BOB/TCPlistener.java
index 78155eb787057f4e00e4e1beeed61fc57b9218e7..0ac67d277b8919d685ed335f2126c438260b6e19 100644
--- a/apps/BOB/src/net/i2p/BOB/TCPlistener.java
+++ b/apps/BOB/src/net/i2p/BOB/TCPlistener.java
@@ -29,6 +29,8 @@ import java.net.Socket;
 import java.net.SocketTimeoutException;
 // import net.i2p.client.I2PSession;
 // import net.i2p.client.I2PSessionException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import net.i2p.client.streaming.I2PServerSocket;
 import net.i2p.client.streaming.I2PSocketManager;
 import net.i2p.util.Log;
@@ -73,16 +75,6 @@ public class TCPlistener implements Runnable {
 		info.releaseReadLock();
 	}
 
-	private void wlock() throws Exception {
-		database.getWriteLock();
-		info.getWriteLock();
-	}
-
-	private void wunlock() throws Exception {
-		info.releaseWriteLock();
-		database.releaseWriteLock();
-	}
-
 	/**
 	 * Simply listen on TCP port, and thread connections
 	 *
@@ -90,124 +82,80 @@ public class TCPlistener implements Runnable {
 	public void run() {
 		boolean g = false;
 		boolean spin = true;
-
-die:		{
-			try {
-				rlock();
-			} catch (Exception e) {
-				break die;
-			}
-			try {
-				if (info.exists("OUTPORT")) {
-					tgwatch = 2;
-				}
-			} catch (Exception e) {
+		int conn = 0;
+		try {
+			die:
+			{
 				try {
-					runlock();
-				} catch (Exception e2) {
+					rlock();
+				} catch (Exception e) {
 					break die;
 				}
-				break die;
-			}
-			try {
-				runlock();
-			} catch (Exception e) {
-				break die;
-			}
-			try {
-				Socket server = new Socket();
-				listener.setSoTimeout(50); // Half of the expected time from MUXlisten
-				while (spin) {
-					try {
-						rlock();
-					} catch (Exception e) {
-						break die;
+				try {
+					if (info.exists("OUTPORT")) {
+						tgwatch = 2;
 					}
+				} catch (Exception e) {
 					try {
-						spin = info.get("RUNNING").equals(Boolean.TRUE);
-					} catch (Exception e) {
-						try {
-							runlock();
-						} catch (Exception e2) {
-							break die;
-						}
+						runlock();
+					} catch (Exception e2) {
 						break die;
 					}
-					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, database */);
-						Thread t = new Thread(conn_c, "BOBTCPtoI2P");
-						t.start();
-						g = false;
-					}
-				}
-				//System.out.println("TCPlistener: destroySession");
-				listener.close();
-			} catch (IOException ioe) {
-				try {
-					listener.close();
-				} catch (IOException e) {
+					break die;
 				}
-				// Fatal failure, cause a stop event
 				try {
-					rlock();
-					try {
-						spin = info.get("RUNNING").equals(Boolean.TRUE);
-					} catch (Exception e) {
-						runlock();
-						break die;
-					}
+					runlock();
 				} catch (Exception e) {
 					break die;
 				}
-				if (spin) {
-					try {
-						wlock();
+				try {
+					Socket server = new Socket();
+					listener.setSoTimeout(50); // We don't block, we cycle and check.
+					while (spin) {
 						try {
-							info.add("STOPPING", new Boolean(true));
-							info.add("RUNNING", new Boolean(false));
+							rlock();
 						} catch (Exception e) {
-							wunlock();
 							break die;
 						}
-					} catch (Exception e) {
-						break die;
+						try {
+							spin = info.get("RUNNING").equals(Boolean.TRUE);
+						} catch (Exception e) {
+							try {
+								runlock();
+							} catch (Exception e2) {
+								break die;
+							}
+							break die;
+						}
+						try {
+							server = listener.accept();
+							g = true;
+						} catch (SocketTimeoutException ste) {
+							g = false;
+						}
+						if (g) {
+							conn++;
+							// toss the connection to a new thread.
+							TCPtoI2P conn_c = new TCPtoI2P(socketManager, server);
+							Thread t = new Thread(conn_c, Thread.currentThread().getName() + " TCPtoI2P " + conn);
+							t.start();
+							g = false;
+						}
 					}
+					listener.close();
+				} catch (IOException ioe) {
 					try {
-						wunlock();
-					} catch (Exception e) {
-						break die;
+						listener.close();
+					} catch (IOException e) {
 					}
 				}
 			}
+		} finally {
+			try {
+				listener.close();
+			} catch (IOException ex) {
+			}
+		//System.out.println("TCPlistener: " + Thread.currentThread().getName() +  "Done.");
 		}
-		// Previous level does this cleanup now.
-		//
-		// 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("TCPlistener: Waiting for children");
-		//while (Thread.activeCount() > tgwatch) { // wait for all threads in our threadgroup to finish
-		//	try {
-		//		Thread.sleep(100); //sleep for 100 ms (One tenth second)
-		//	} catch (Exception e) {
-		//		// nop
-		//	}
-		//}
-	//System.out.println("TCPlistener: Done.");
 	}
 }
-
-
diff --git a/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java b/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java
index c376e16fe1bf8af9dd1e8f5ac25cd5d3a745936c..f3f2c74456ed3e3d1d6e4ccc7b07d99505ce0942 100644
--- a/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java
+++ b/apps/BOB/src/net/i2p/BOB/TCPtoI2P.java
@@ -64,17 +64,17 @@ public class TCPtoI2P implements Runnable {
 
 		S = new String();
 
-		while(true) {
+		while (true) {
 			b = in.read();
-			if(b == 13) {
+			if (b == 13) {
 				//skip CR
 				continue;
 			}
-			if(b < 20 || b > 126) {
+			if (b < 20 || b > 126) {
 				// exit on anything not legal
 				break;
 			}
-			c = (char)(b & 0x7f); // We only really give a fuck about ASCII
+			c = (char) (b & 0x7f); // We only really give a fuck about ASCII
 			S = new String(S + c);
 		}
 		return S;
@@ -118,90 +118,87 @@ public class TCPtoI2P implements Runnable {
 		OutputStream Iout = null;
 		InputStream in = null;
 		OutputStream out = null;
-
 		try {
-
-			in = sock.getInputStream();
-			out = sock.getOutputStream();
 			try {
-				line = lnRead(in);
-				input = line.toLowerCase();
-				Destination dest = null;
-
-				if(input.endsWith(".i2p")) {
-					dest = I2PTunnel.destFromName(input);
-					line = dest.toBase64();
-				}
-				dest = new Destination();
-				dest.fromBase64(line);
 
+				in = sock.getInputStream();
+				out = sock.getOutputStream();
 				try {
-					// get a client socket
-					I2P = socketManager.connect(dest);
-					I2P.setReadTimeout(0); // temp bugfix, this *SHOULD* be the default
-					// make readers/writers
-					Iin = I2P.getInputStream();
-					Iout = I2P.getOutputStream();
-					// setup to cross the streams
-					TCPio conn_c = new TCPio(in, Iout /*, info, database */); // app -> I2P
-					TCPio conn_a = new TCPio(Iin, out /*, info, database */); // 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 {
+					line = lnRead(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
+						Iin = I2P.getInputStream();
+						Iout = I2P.getOutputStream();
+						// setup to cross the streams
+						TCPio conn_c = new TCPio(in, Iout /*, info, database */); // app -> I2P
+						TCPio conn_a = new TCPio(Iin, out /*, info, database */); // I2P -> app
+						Thread t = new Thread(conn_c, Thread.currentThread().getName() + " TCPioA");
+						Thread q = new Thread(conn_a, Thread.currentThread().getName() + " TCPioB");
+						// Fire!
+						t.start();
+						q.start();
+						while (t.isAlive() && q.isAlive()) { // AND is used here to kill off the other thread
 							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) {
+						// We're breaking away.
 					}
-					// System.out.println("TCPtoI2P: Going away...");
 
-				} 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) {
+				} catch (Exception e) {
 					Emsg("ERROR " + e.toString(), out);
 				}
-
-			} catch(Exception e) {
-				Emsg("ERROR " + e.toString(), out);
+			} catch (Exception e) {
+				// bail on anything else
+			}
+		} finally {
+			try {
+				in.close();
+			} catch (Exception e) {
+			}
+			try {
+				out.close();
+			} catch (Exception e) {
+			}
+			try {
+				Iin.close();
+			} catch (Exception e) {
+			}
+			try {
+				Iout.close();
+			} catch (Exception e) {
+			}
+			try {
+				// System.out.println("TCPtoI2P: Close I2P");
+				I2P.close();
+			} catch (Exception e) {
 			}
-		} catch(Exception e) {
-			// bail on anything else
-		}
-		try {
-			in.close();
-		} catch(Exception e) {
-		}
-		try {
-			out.close();
-		} catch(Exception e) {
-		}
-		try {
-			Iin.close();
-		} catch(Exception e) {
-		}
-		try {
-			Iout.close();
-		} catch(Exception e) {
-		}
-		try {
-			// System.out.println("TCPtoI2P: Close I2P");
-			I2P.close();
-		} catch(Exception e) {
-		}
 
-		try {
-			// System.out.println("TCPtoI2P: Close sock");
-			sock.close();
-		} catch(Exception e) {
+			try {
+				// System.out.println("TCPtoI2P: Close sock");
+				sock.close();
+			} catch (Exception e) {
+			}
 		}
-		// System.out.println("TCPtoI2P: Done.");
+	// System.out.println("TCPtoI2P: Done.");
 	}
 }
diff --git a/apps/desktopgui/nbproject/build-impl.xml b/apps/desktopgui/nbproject/build-impl.xml
index f8fea458d1f346442380fd849ea37ea18712ff34..039f8788f1ca19268562c37f54c28030b53894f8 100644
--- a/apps/desktopgui/nbproject/build-impl.xml
+++ b/apps/desktopgui/nbproject/build-impl.xml
@@ -152,7 +152,7 @@ is divided into following sections:
             <attribute default="${includes}" name="includes"/>
             <attribute default="${excludes}" name="excludes"/>
             <attribute default="${javac.debug}" name="debug"/>
-            <attribute default="" name="sourcepath"/>
+            <attribute default="/does/not/exist" name="sourcepath"/>
             <element name="customize" optional="true"/>
             <sequential>
                 <javac debug="@{debug}" deprecation="${javac.deprecation}" destdir="@{destdir}" encoding="${source.encoding}" excludes="@{excludes}" includeantruntime="false" includes="@{includes}" source="${javac.source}" sourcepath="@{sourcepath}" srcdir="@{srcdir}" target="${javac.target}">
@@ -218,13 +218,13 @@ is divided into following sections:
             </sequential>
         </macrodef>
     </target>
-    <target name="-init-macrodef-nbjpda">
+    <target depends="-init-debug-args" name="-init-macrodef-nbjpda">
         <macrodef name="nbjpdastart" uri="http://www.netbeans.org/ns/j2se-project/1">
             <attribute default="${main.class}" name="name"/>
             <attribute default="${debug.classpath}" name="classpath"/>
             <attribute default="" name="stopclassname"/>
             <sequential>
-                <nbjpdastart addressproperty="jpda.address" name="@{name}" stopclassname="@{stopclassname}" transport="dt_socket">
+                <nbjpdastart addressproperty="jpda.address" name="@{name}" stopclassname="@{stopclassname}" transport="${debug-transport}">
                     <classpath>
                         <path path="@{classpath}"/>
                     </classpath>
@@ -255,6 +255,12 @@ is divided into following sections:
         <condition else="-Xdebug" property="debug-args-line" value="-Xdebug -Xnoagent -Djava.compiler=none">
             <istrue value="${have-jdk-older-than-1.4}"/>
         </condition>
+        <condition else="dt_socket" property="debug-transport-by-os" value="dt_shmem">
+            <os family="windows"/>
+        </condition>
+        <condition else="${debug-transport-by-os}" property="debug-transport" value="${debug.transport}">
+            <isset property="debug.transport"/>
+        </condition>
     </target>
     <target depends="-init-debug-args" name="-init-macrodef-debug">
         <macrodef name="debug" uri="http://www.netbeans.org/ns/j2se-project/3">
@@ -264,7 +270,7 @@ is divided into following sections:
             <sequential>
                 <java classname="@{classname}" dir="${work.dir}" fork="true">
                     <jvmarg line="${debug-args-line}"/>
-                    <jvmarg value="-Xrunjdwp:transport=dt_socket,address=${jpda.address}"/>
+                    <jvmarg value="-Xrunjdwp:transport=${debug-transport},address=${jpda.address}"/>
                     <jvmarg line="${run.jvmargs}"/>
                     <classpath>
                         <path path="@{classpath}"/>
@@ -311,6 +317,13 @@ is divided into following sections:
                 ===================
             -->
     <target depends="init" name="deps-jar" unless="no.deps"/>
+    <target depends="init,-check-automatic-build,-clean-after-automatic-build" name="-verify-automatic-build"/>
+    <target depends="init" name="-check-automatic-build">
+        <available file="${build.classes.dir}/.netbeans_automatic_build" property="netbeans.automatic.build"/>
+    </target>
+    <target depends="init" if="netbeans.automatic.build" name="-clean-after-automatic-build">
+        <antcall target="clean"/>
+    </target>
     <target depends="init,deps-jar" name="-pre-pre-compile">
         <mkdir dir="${build.classes.dir}"/>
     </target>
@@ -331,7 +344,7 @@ is divided into following sections:
         <!-- Empty placeholder for easier customization. -->
         <!-- You can override this target in the ../build.xml file. -->
     </target>
-    <target depends="init,deps-jar,-pre-pre-compile,-pre-compile,-do-compile,-post-compile" description="Compile project." name="compile"/>
+    <target depends="init,deps-jar,-verify-automatic-build,-pre-pre-compile,-pre-compile,-do-compile,-post-compile" description="Compile project." name="compile"/>
     <target name="-pre-compile-single">
         <!-- Empty placeholder for easier customization. -->
         <!-- You can override this target in the ../build.xml file. -->
@@ -345,7 +358,7 @@ is divided into following sections:
         <!-- Empty placeholder for easier customization. -->
         <!-- You can override this target in the ../build.xml file. -->
     </target>
-    <target depends="init,deps-jar,-pre-pre-compile,-pre-compile-single,-do-compile-single,-post-compile-single" name="compile-single"/>
+    <target depends="init,deps-jar,-verify-automatic-build,-pre-pre-compile,-pre-compile-single,-do-compile-single,-post-compile-single" name="compile-single"/>
     <!--
                 ====================
                 JAR BUILDING SECTION
diff --git a/apps/desktopgui/nbproject/genfiles.properties b/apps/desktopgui/nbproject/genfiles.properties
index 1b326007c72e7a85df4b7923826955a2cbbe1fd3..f6b0f837bcfabb640d6b0fd4fb85db89aa0b70da 100644
--- a/apps/desktopgui/nbproject/genfiles.properties
+++ b/apps/desktopgui/nbproject/genfiles.properties
@@ -3,6 +3,6 @@ build.xml.script.CRC32=9785bb9a
 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=c4b345cd
-nbproject/build-impl.xml.script.CRC32=74d3fda2
-nbproject/build-impl.xml.stylesheet.CRC32=487672f9
+nbproject/build-impl.xml.data.CRC32=f630f8db
+nbproject/build-impl.xml.script.CRC32=8c02c081
+nbproject/build-impl.xml.stylesheet.CRC32=65b8de21
diff --git a/apps/desktopgui/nbproject/project.properties b/apps/desktopgui/nbproject/project.properties
index b7a05f8ed6df77b4543a58141526bd4976695be3..e9f5d1a2a159f3695a72d31d03a2e8aeb743ce61 100644
--- a/apps/desktopgui/nbproject/project.properties
+++ b/apps/desktopgui/nbproject/project.properties
@@ -58,7 +58,7 @@ jnlp.codebase.url=file:/home/mathias/Documenten/Programmeren/i2p_monotone/repo/i
 jnlp.enabled=false
 jnlp.offline-allowed=false
 jnlp.signed=false
-main.class=desktopgui.Main
+main.class=net.i2p.desktopgui.desktopgui.Main
 manifest.file=manifest.mf
 meta.inf.dir=${src.dir}/META-INF
 platform.active=default_platform
diff --git a/apps/desktopgui/nbproject/project.xml b/apps/desktopgui/nbproject/project.xml
index 09409a64cda6d6b494561a5fccf74c9d90b73245..9dba9493c19d106655401d5e0fabc02381858ef8 100644
--- a/apps/desktopgui/nbproject/project.xml
+++ b/apps/desktopgui/nbproject/project.xml
@@ -13,7 +13,7 @@
             </test-roots>
         </data>
         <swingapp xmlns="http://www.netbeans.org/ns/form-swingapp/1">
-            <application-class name="desktopgui.Main"/>
+            <application-class name="net.i2p.desktopgui.desktopgui.Main"/>
         </swingapp>
     </configuration>
 </project>
diff --git a/apps/desktopgui/src/gui/GeneralConfiguration.java b/apps/desktopgui/src/gui/GeneralConfiguration.java
deleted file mode 100644
index 912026bf12d218bb5703a1b850180df7772355f3..0000000000000000000000000000000000000000
--- a/apps/desktopgui/src/gui/GeneralConfiguration.java
+++ /dev/null
@@ -1,376 +0,0 @@
-/*
- * GeneralConfiguration.java
- *
- * Created on 10 april 2009, 19:04
- */
-
-package gui;
-
-/**
- *
- * @author  mathias
- */
-public class GeneralConfiguration extends javax.swing.JFrame {
-
-    /** Creates new form GeneralConfiguration */
-    public GeneralConfiguration() {
-        initComponents();
-        this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
-        this.setVisible(true);
-    }
-
-    /** This method is called from within the constructor to
-     * initialize the form.
-     * WARNING: Do NOT modify this code. The content of this method is
-     * always regenerated by the Form Editor.
-     */
-    @SuppressWarnings("unchecked")
-    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
-    private void initComponents() {
-
-        jPanel1 = new javax.swing.JPanel();
-        cancel = new javax.swing.JToggleButton();
-        ok = new javax.swing.JToggleButton();
-        jTabbedPane1 = new javax.swing.JTabbedPane();
-        jPanel2 = new javax.swing.JPanel();
-        jLabel1 = new javax.swing.JLabel();
-        jLabel2 = new javax.swing.JLabel();
-        jTextField1 = new javax.swing.JTextField();
-        jTextField2 = new javax.swing.JTextField();
-        jComboBox1 = new javax.swing.JComboBox();
-        jComboBox2 = new javax.swing.JComboBox();
-        jLabel3 = new javax.swing.JLabel();
-        jLabel4 = new javax.swing.JLabel();
-        jTextField3 = new javax.swing.JTextField();
-        jTextField4 = new javax.swing.JTextField();
-        jLabel5 = new javax.swing.JLabel();
-        jLabel6 = new javax.swing.JLabel();
-        jLabel7 = new javax.swing.JLabel();
-        jPanel3 = new javax.swing.JPanel();
-        jLabel8 = new javax.swing.JLabel();
-        jRadioButton1 = new javax.swing.JRadioButton();
-        jRadioButton2 = new javax.swing.JRadioButton();
-        jRadioButton3 = new javax.swing.JRadioButton();
-        jToggleButton1 = new javax.swing.JToggleButton();
-        jToggleButton2 = new javax.swing.JToggleButton();
-        jToggleButton3 = new javax.swing.JToggleButton();
-        jPanel4 = new javax.swing.JPanel();
-        jScrollPane1 = new javax.swing.JScrollPane();
-        jScrollPane2 = new javax.swing.JScrollPane();
-        jLabel9 = new javax.swing.JLabel();
-        jLabel10 = new javax.swing.JLabel();
-        jLabel11 = new javax.swing.JLabel();
-        jPanel5 = new javax.swing.JPanel();
-        jPanel6 = new javax.swing.JPanel();
-
-        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
-        setName("Form"); // NOI18N
-
-        jPanel1.setName("jPanel1"); // NOI18N
-
-        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(GeneralConfiguration.class);
-        cancel.setText(resourceMap.getString("cancel.text")); // NOI18N
-        cancel.setName("cancel"); // NOI18N
-
-        ok.setText(resourceMap.getString("ok.text")); // NOI18N
-        ok.setName("ok"); // NOI18N
-
-        javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
-        jPanel1.setLayout(jPanel1Layout);
-        jPanel1Layout.setHorizontalGroup(
-            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
-                .addContainerGap(475, Short.MAX_VALUE)
-                .addComponent(ok)
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(cancel)
-                .addContainerGap())
-        );
-        jPanel1Layout.setVerticalGroup(
-            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGroup(jPanel1Layout.createSequentialGroup()
-                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
-                    .addComponent(cancel)
-                    .addComponent(ok))
-                .addContainerGap(14, Short.MAX_VALUE))
-        );
-
-        jTabbedPane1.setName("jTabbedPane1"); // NOI18N
-
-        jPanel2.setName("jPanel2"); // NOI18N
-        jPanel2.setLayout(null);
-
-        jLabel1.setText(resourceMap.getString("jLabel1.text")); // NOI18N
-        jLabel1.setName("jLabel1"); // NOI18N
-        jPanel2.add(jLabel1);
-        jLabel1.setBounds(20, 20, 140, 30);
-
-        jLabel2.setText(resourceMap.getString("jLabel2.text")); // NOI18N
-        jLabel2.setName("jLabel2"); // NOI18N
-        jPanel2.add(jLabel2);
-        jLabel2.setBounds(20, 60, 140, 30);
-
-        jTextField1.setText(resourceMap.getString("jTextField1.text")); // NOI18N
-        jTextField1.setName("jTextField1"); // NOI18N
-        jPanel2.add(jTextField1);
-        jTextField1.setBounds(160, 20, 77, 27);
-
-        jTextField2.setText(resourceMap.getString("jTextField2.text")); // NOI18N
-        jTextField2.setName("jTextField2"); // NOI18N
-        jPanel2.add(jTextField2);
-        jTextField2.setBounds(160, 60, 77, 27);
-
-        jComboBox1.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
-        jComboBox1.setName("jComboBox1"); // NOI18N
-        jPanel2.add(jComboBox1);
-        jComboBox1.setBounds(240, 20, 78, 27);
-
-        jComboBox2.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
-        jComboBox2.setName("jComboBox2"); // NOI18N
-        jPanel2.add(jComboBox2);
-        jComboBox2.setBounds(240, 60, 78, 27);
-
-        jLabel3.setText(resourceMap.getString("jLabel3.text")); // NOI18N
-        jLabel3.setName("jLabel3"); // NOI18N
-        jPanel2.add(jLabel3);
-        jLabel3.setBounds(330, 20, 97, 30);
-
-        jLabel4.setText(resourceMap.getString("jLabel4.text")); // NOI18N
-        jLabel4.setName("jLabel4"); // NOI18N
-        jPanel2.add(jLabel4);
-        jLabel4.setBounds(330, 60, 97, 30);
-
-        jTextField3.setText(resourceMap.getString("jTextField3.text")); // NOI18N
-        jTextField3.setName("jTextField3"); // NOI18N
-        jPanel2.add(jTextField3);
-        jTextField3.setBounds(440, 20, 60, 27);
-
-        jTextField4.setText(resourceMap.getString("jTextField4.text")); // NOI18N
-        jTextField4.setName("jTextField4"); // NOI18N
-        jPanel2.add(jTextField4);
-        jTextField4.setBounds(440, 60, 60, 27);
-
-        jLabel5.setText(resourceMap.getString("jLabel5.text")); // NOI18N
-        jLabel5.setName("jLabel5"); // NOI18N
-        jPanel2.add(jLabel5);
-        jLabel5.setBounds(510, 20, 19, 30);
-
-        jLabel6.setText(resourceMap.getString("jLabel6.text")); // NOI18N
-        jLabel6.setName("jLabel6"); // NOI18N
-        jPanel2.add(jLabel6);
-        jLabel6.setBounds(510, 60, 19, 30);
-
-        jLabel7.setText(resourceMap.getString("jLabel7.text")); // NOI18N
-        jLabel7.setName("jLabel7"); // NOI18N
-        jPanel2.add(jLabel7);
-        jLabel7.setBounds(20, 100, 520, 70);
-
-        jTabbedPane1.addTab(resourceMap.getString("jPanel2.TabConstraints.tabTitle"), jPanel2); // NOI18N
-
-        jPanel3.setName("jPanel3"); // NOI18N
-
-        jLabel8.setText(resourceMap.getString("jLabel8.text")); // NOI18N
-        jLabel8.setName("jLabel8"); // NOI18N
-
-        jRadioButton1.setText(resourceMap.getString("jRadioButton1.text")); // NOI18N
-        jRadioButton1.setName("jRadioButton1"); // NOI18N
-
-        jRadioButton2.setText(resourceMap.getString("jRadioButton2.text")); // NOI18N
-        jRadioButton2.setName("jRadioButton2"); // NOI18N
-
-        jRadioButton3.setText(resourceMap.getString("jRadioButton3.text")); // NOI18N
-        jRadioButton3.setName("jRadioButton3"); // NOI18N
-
-        jToggleButton1.setText(resourceMap.getString("jToggleButton1.text")); // NOI18N
-        jToggleButton1.setName("jToggleButton1"); // NOI18N
-
-        jToggleButton2.setText(resourceMap.getString("jToggleButton2.text")); // NOI18N
-        jToggleButton2.setName("jToggleButton2"); // NOI18N
-
-        jToggleButton3.setText(resourceMap.getString("jToggleButton3.text")); // NOI18N
-        jToggleButton3.setName("jToggleButton3"); // NOI18N
-
-        javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3);
-        jPanel3.setLayout(jPanel3Layout);
-        jPanel3Layout.setHorizontalGroup(
-            jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGroup(jPanel3Layout.createSequentialGroup()
-                .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-                    .addGroup(jPanel3Layout.createSequentialGroup()
-                        .addGap(20, 20, 20)
-                        .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-                            .addComponent(jLabel8)
-                            .addGroup(jPanel3Layout.createSequentialGroup()
-                                .addComponent(jToggleButton1)
-                                .addGap(18, 18, 18)
-                                .addComponent(jToggleButton2))))
-                    .addGroup(jPanel3Layout.createSequentialGroup()
-                        .addGap(40, 40, 40)
-                        .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-                            .addComponent(jRadioButton2)
-                            .addComponent(jRadioButton1)
-                            .addComponent(jRadioButton3))))
-                .addGap(9, 9, 9))
-            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel3Layout.createSequentialGroup()
-                .addContainerGap(339, Short.MAX_VALUE)
-                .addComponent(jToggleButton3)
-                .addContainerGap())
-        );
-        jPanel3Layout.setVerticalGroup(
-            jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGroup(jPanel3Layout.createSequentialGroup()
-                .addContainerGap()
-                .addComponent(jLabel8)
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
-                .addComponent(jRadioButton1)
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(jRadioButton2)
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(jRadioButton3)
-                .addGap(18, 18, 18)
-                .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
-                    .addComponent(jToggleButton1)
-                    .addComponent(jToggleButton2))
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 181, Short.MAX_VALUE)
-                .addComponent(jToggleButton3)
-                .addContainerGap())
-        );
-
-        jTabbedPane1.addTab(resourceMap.getString("jPanel3.TabConstraints.tabTitle"), jPanel3); // NOI18N
-
-        jPanel4.setName("jPanel4"); // NOI18N
-
-        jScrollPane1.setName("jScrollPane1"); // NOI18N
-
-        jScrollPane2.setName("jScrollPane2"); // NOI18N
-
-        jLabel9.setText(resourceMap.getString("jLabel9.text")); // NOI18N
-        jLabel9.setName("jLabel9"); // NOI18N
-
-        jLabel10.setText(resourceMap.getString("jLabel10.text")); // NOI18N
-        jLabel10.setName("jLabel10"); // NOI18N
-
-        jLabel11.setText(resourceMap.getString("jLabel11.text")); // NOI18N
-        jLabel11.setName("jLabel11"); // NOI18N
-
-        javax.swing.GroupLayout jPanel4Layout = new javax.swing.GroupLayout(jPanel4);
-        jPanel4.setLayout(jPanel4Layout);
-        jPanel4Layout.setHorizontalGroup(
-            jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGroup(jPanel4Layout.createSequentialGroup()
-                .addContainerGap()
-                .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-                    .addComponent(jScrollPane2, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 538, Short.MAX_VALUE)
-                    .addComponent(jLabel9, javax.swing.GroupLayout.DEFAULT_SIZE, 538, Short.MAX_VALUE)
-                    .addComponent(jLabel10)
-                    .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 538, Short.MAX_VALUE)
-                    .addComponent(jLabel11))
-                .addContainerGap())
-        );
-        jPanel4Layout.setVerticalGroup(
-            jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGroup(jPanel4Layout.createSequentialGroup()
-                .addContainerGap()
-                .addComponent(jLabel10)
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 119, Short.MAX_VALUE)
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(jLabel11)
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 122, Short.MAX_VALUE)
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(jLabel9, javax.swing.GroupLayout.PREFERRED_SIZE, 65, javax.swing.GroupLayout.PREFERRED_SIZE)
-                .addContainerGap())
-        );
-
-        jTabbedPane1.addTab(resourceMap.getString("jPanel4.TabConstraints.tabTitle"), jPanel4); // NOI18N
-
-        jPanel5.setName("jPanel5"); // NOI18N
-
-        javax.swing.GroupLayout jPanel5Layout = new javax.swing.GroupLayout(jPanel5);
-        jPanel5.setLayout(jPanel5Layout);
-        jPanel5Layout.setHorizontalGroup(
-            jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGap(0, 562, Short.MAX_VALUE)
-        );
-        jPanel5Layout.setVerticalGroup(
-            jPanel5Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGap(0, 388, Short.MAX_VALUE)
-        );
-
-        jTabbedPane1.addTab(resourceMap.getString("jPanel5.TabConstraints.tabTitle"), jPanel5); // NOI18N
-
-        jPanel6.setName("jPanel6"); // NOI18N
-
-        javax.swing.GroupLayout jPanel6Layout = new javax.swing.GroupLayout(jPanel6);
-        jPanel6.setLayout(jPanel6Layout);
-        jPanel6Layout.setHorizontalGroup(
-            jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGap(0, 562, Short.MAX_VALUE)
-        );
-        jPanel6Layout.setVerticalGroup(
-            jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGap(0, 388, Short.MAX_VALUE)
-        );
-
-        jTabbedPane1.addTab(resourceMap.getString("jPanel6.TabConstraints.tabTitle"), jPanel6); // NOI18N
-
-        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
-        getContentPane().setLayout(layout);
-        layout.setHorizontalGroup(
-            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
-            .addGroup(layout.createSequentialGroup()
-                .addGap(12, 12, 12)
-                .addComponent(jTabbedPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 566, Short.MAX_VALUE))
-        );
-        layout.setVerticalGroup(
-            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
-                .addComponent(jTabbedPane1)
-                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
-                .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
-        );
-
-        pack();
-    }// </editor-fold>//GEN-END:initComponents
-
-
-    // Variables declaration - do not modify//GEN-BEGIN:variables
-    private javax.swing.JToggleButton cancel;
-    private javax.swing.JComboBox jComboBox1;
-    private javax.swing.JComboBox jComboBox2;
-    private javax.swing.JLabel jLabel1;
-    private javax.swing.JLabel jLabel10;
-    private javax.swing.JLabel jLabel11;
-    private javax.swing.JLabel jLabel2;
-    private javax.swing.JLabel jLabel3;
-    private javax.swing.JLabel jLabel4;
-    private javax.swing.JLabel jLabel5;
-    private javax.swing.JLabel jLabel6;
-    private javax.swing.JLabel jLabel7;
-    private javax.swing.JLabel jLabel8;
-    private javax.swing.JLabel jLabel9;
-    private javax.swing.JPanel jPanel1;
-    private javax.swing.JPanel jPanel2;
-    private javax.swing.JPanel jPanel3;
-    private javax.swing.JPanel jPanel4;
-    private javax.swing.JPanel jPanel5;
-    private javax.swing.JPanel jPanel6;
-    private javax.swing.JRadioButton jRadioButton1;
-    private javax.swing.JRadioButton jRadioButton2;
-    private javax.swing.JRadioButton jRadioButton3;
-    private javax.swing.JScrollPane jScrollPane1;
-    private javax.swing.JScrollPane jScrollPane2;
-    private javax.swing.JTabbedPane jTabbedPane1;
-    private javax.swing.JTextField jTextField1;
-    private javax.swing.JTextField jTextField2;
-    private javax.swing.JTextField jTextField3;
-    private javax.swing.JTextField jTextField4;
-    private javax.swing.JToggleButton jToggleButton1;
-    private javax.swing.JToggleButton jToggleButton2;
-    private javax.swing.JToggleButton jToggleButton3;
-    private javax.swing.JToggleButton ok;
-    // End of variables declaration//GEN-END:variables
-
-}
diff --git a/apps/desktopgui/src/gui/resources/GeneralConfiguration.properties b/apps/desktopgui/src/gui/resources/GeneralConfiguration.properties
deleted file mode 100644
index 3dc629a9715ac76d59515d7e02df719a4cb41375..0000000000000000000000000000000000000000
--- a/apps/desktopgui/src/gui/resources/GeneralConfiguration.properties
+++ /dev/null
@@ -1,29 +0,0 @@
-
-jPanel2.TabConstraints.tabTitle=Speed
-jPanel3.TabConstraints.tabTitle=Updates
-jPanel4.TabConstraints.tabTitle=Tunnels/Services
-jPanel5.TabConstraints.tabTitle=Network
-jPanel6.TabConstraints.tabTitle=Advanced
-cancel.text=Cancel
-ok.text=OK
-jLabel1.text=Upload speed:
-jLabel2.text=Download speed:
-jTextField1.text=jTextField1
-jTextField2.text=jTextField2
-jLabel3.text=Monthly usage:
-jLabel4.text=Monthly usage:
-jTextField3.text=jTextField3
-jTextField4.text=jTextField4
-jLabel5.text=GB
-jLabel6.text=GB
-jLabel7.text=Explanation ...
-jLabel8.text=What is your preferred automatic update setting?
-jRadioButton1.text=Only inform about updates
-jRadioButton2.text=Download and verify update file, do not restart
-jRadioButton3.text=Download, verify and restart
-jToggleButton1.text=Check for updates now
-jToggleButton2.text=Update available: update now
-jToggleButton3.text=Advanced update configuration
-jLabel9.text=Tunnel explanation
-jLabel10.text=Client tunnels:
-jLabel11.text=Server tunnels:
diff --git a/apps/desktopgui/src/desktopgui/GUIVersion.java b/apps/desktopgui/src/net/i2p/desktopgui/desktopgui/GUIVersion.java
similarity index 63%
rename from apps/desktopgui/src/desktopgui/GUIVersion.java
rename to apps/desktopgui/src/net/i2p/desktopgui/desktopgui/GUIVersion.java
index 7bde544de8d05d1b0dbaecbf613f2d3667ce97c5..f4e948e52c05df8d2c848d9cb5221d77609120c7 100644
--- a/apps/desktopgui/src/desktopgui/GUIVersion.java
+++ b/apps/desktopgui/src/net/i2p/desktopgui/desktopgui/GUIVersion.java
@@ -3,12 +3,12 @@
  * and open the template in the editor.
  */
 
-package desktopgui;
+package net.i2p.desktopgui.desktopgui;
 
 /**
  *
  * @author mathias
  */
 public class GUIVersion {
-    public static final String VERSION = "0.0.1.1";
+    public static final String VERSION = "0.0.1.2";
 }
diff --git a/apps/desktopgui/src/desktopgui/Main.java b/apps/desktopgui/src/net/i2p/desktopgui/desktopgui/Main.java
similarity index 95%
rename from apps/desktopgui/src/desktopgui/Main.java
rename to apps/desktopgui/src/net/i2p/desktopgui/desktopgui/Main.java
index 9d9708c8dd61b9a32ee9317e2aef273a6ae1a019..c3078163d4c64ff29d6c58927c3c3e71ddf3efd3 100644
--- a/apps/desktopgui/src/desktopgui/Main.java
+++ b/apps/desktopgui/src/net/i2p/desktopgui/desktopgui/Main.java
@@ -1,4 +1,4 @@
-package desktopgui;
+package net.i2p.desktopgui.desktopgui;
 
 /*
  * Main.java
@@ -6,8 +6,8 @@ package desktopgui;
 
 
 
-import gui.Tray;
-import gui.SpeedSelector;
+import net.i2p.desktopgui.gui.Tray;
+import net.i2p.desktopgui.gui.SpeedSelector;
 import java.awt.SystemTray;
 import java.util.Properties;
 import java.util.logging.Level;
@@ -16,7 +16,7 @@ import javax.swing.UIManager;
 import javax.swing.UnsupportedLookAndFeelException;
 import org.jdesktop.application.Application;
 import org.jdesktop.application.SingleFrameApplication;
-import persistence.PropertyManager;
+import net.i2p.desktopgui.persistence.PropertyManager;
 
 /**
  * The main class of the application.
diff --git a/apps/desktopgui/src/desktopgui/resources/Main.properties b/apps/desktopgui/src/net/i2p/desktopgui/desktopgui/resources/Main.properties
similarity index 100%
rename from apps/desktopgui/src/desktopgui/resources/Main.properties
rename to apps/desktopgui/src/net/i2p/desktopgui/desktopgui/resources/Main.properties
diff --git a/apps/desktopgui/src/desktopgui/resources/Main_nl_BE.properties b/apps/desktopgui/src/net/i2p/desktopgui/desktopgui/resources/Main_nl_BE.properties
similarity index 100%
rename from apps/desktopgui/src/desktopgui/resources/Main_nl_BE.properties
rename to apps/desktopgui/src/net/i2p/desktopgui/desktopgui/resources/Main_nl_BE.properties
diff --git a/apps/desktopgui/src/gui/GeneralConfiguration.form b/apps/desktopgui/src/net/i2p/desktopgui/gui/GeneralConfiguration.form
similarity index 71%
rename from apps/desktopgui/src/gui/GeneralConfiguration.form
rename to apps/desktopgui/src/net/i2p/desktopgui/gui/GeneralConfiguration.form
index df02e3d967a24a23edea625e7fb33f3f4bc76984..7c0fc8edcdf41c794e426e3b8937ded3d2782e88 100644
--- a/apps/desktopgui/src/gui/GeneralConfiguration.form
+++ b/apps/desktopgui/src/net/i2p/desktopgui/gui/GeneralConfiguration.form
@@ -1,8 +1,13 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 
 <Form version="1.5" maxVersion="1.6" type="org.netbeans.modules.form.forminfo.JFrameFormInfo">
+  <NonVisualComponents>
+    <Component class="javax.swing.ButtonGroup" name="buttonGroup1">
+    </Component>
+  </NonVisualComponents>
   <Properties>
     <Property name="defaultCloseOperation" type="int" value="3"/>
+    <Property name="title" type="java.lang.String" resourceKey="Form.title"/>
     <Property name="name" type="java.lang.String" value="Form" noResource="true"/>
   </Properties>
   <SyntheticProperties>
@@ -11,6 +16,7 @@
   <AuxValues>
     <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="2"/>
     <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
     <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
     <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
     <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
@@ -22,27 +28,27 @@
   <Layout>
     <DimensionLayout dim="0">
       <Group type="103" groupAlignment="0" attributes="0">
-          <Component id="jPanel1" alignment="0" max="32767" attributes="0"/>
+          <Component id="applyPanel" alignment="0" max="32767" attributes="0"/>
           <Group type="102" attributes="0">
               <EmptySpace min="12" pref="12" max="12" attributes="0"/>
-              <Component id="jTabbedPane1" pref="566" max="32767" attributes="0"/>
+              <Component id="settingsPanel" pref="566" max="32767" attributes="0"/>
           </Group>
       </Group>
     </DimensionLayout>
     <DimensionLayout dim="1">
       <Group type="103" groupAlignment="0" attributes="0">
           <Group type="102" alignment="1" attributes="0">
-              <Component id="jTabbedPane1" max="32767" attributes="0"/>
+              <Component id="settingsPanel" max="32767" attributes="0"/>
               <EmptySpace max="-2" attributes="0"/>
-              <Component id="jPanel1" min="-2" max="-2" attributes="0"/>
+              <Component id="applyPanel" min="-2" max="-2" attributes="0"/>
           </Group>
       </Group>
     </DimensionLayout>
   </Layout>
   <SubComponents>
-    <Container class="javax.swing.JPanel" name="jPanel1">
+    <Container class="javax.swing.JPanel" name="applyPanel">
       <Properties>
-        <Property name="name" type="java.lang.String" value="jPanel1" noResource="true"/>
+        <Property name="name" type="java.lang.String" value="applyPanel" noResource="true"/>
       </Properties>
 
       <Layout>
@@ -84,21 +90,21 @@
         </Component>
       </SubComponents>
     </Container>
-    <Container class="javax.swing.JTabbedPane" name="jTabbedPane1">
+    <Container class="javax.swing.JTabbedPane" name="settingsPanel">
       <Properties>
-        <Property name="name" type="java.lang.String" value="jTabbedPane1" noResource="true"/>
+        <Property name="name" type="java.lang.String" value="settingsPanel" noResource="true"/>
       </Properties>
 
       <Layout class="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout"/>
       <SubComponents>
-        <Container class="javax.swing.JPanel" name="jPanel2">
+        <Container class="javax.swing.JPanel" name="speedPanel">
           <Properties>
-            <Property name="name" type="java.lang.String" value="jPanel2" noResource="true"/>
+            <Property name="name" type="java.lang.String" value="speedPanel" noResource="true"/>
           </Properties>
           <Constraints>
             <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
               <JTabbedPaneConstraints tabName="Speed">
-                <Property name="tabTitle" type="java.lang.String" resourceKey="jPanel2.TabConstraints.tabTitle"/>
+                <Property name="tabTitle" type="java.lang.String" resourceKey="speedPanel.TabConstraints.tabTitle"/>
               </JTabbedPaneConstraints>
             </Constraint>
           </Constraints>
@@ -107,10 +113,10 @@
             <Property name="useNullLayout" type="boolean" value="true"/>
           </Layout>
           <SubComponents>
-            <Component class="javax.swing.JLabel" name="jLabel1">
+            <Component class="javax.swing.JLabel" name="uploadSpeedLabel">
               <Properties>
-                <Property name="text" type="java.lang.String" resourceKey="jLabel1.text"/>
-                <Property name="name" type="java.lang.String" value="jLabel1" noResource="true"/>
+                <Property name="text" type="java.lang.String" resourceKey="uploadSpeedLabel.text"/>
+                <Property name="name" type="java.lang.String" value="uploadSpeedLabel" noResource="true"/>
               </Properties>
               <Constraints>
                 <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout" value="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout$AbsoluteConstraintsDescription">
@@ -118,10 +124,10 @@
                 </Constraint>
               </Constraints>
             </Component>
-            <Component class="javax.swing.JLabel" name="jLabel2">
+            <Component class="javax.swing.JLabel" name="downloadSpeedLabel">
               <Properties>
-                <Property name="text" type="java.lang.String" resourceKey="jLabel2.text"/>
-                <Property name="name" type="java.lang.String" value="jLabel2" noResource="true"/>
+                <Property name="text" type="java.lang.String" resourceKey="downloadSpeedLabel.text"/>
+                <Property name="name" type="java.lang.String" value="downloadSpeedLabel" noResource="true"/>
               </Properties>
               <Constraints>
                 <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout" value="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout$AbsoluteConstraintsDescription">
@@ -129,58 +135,66 @@
                 </Constraint>
               </Constraints>
             </Component>
-            <Component class="javax.swing.JTextField" name="jTextField1">
+            <Component class="javax.swing.JTextField" name="uploadspeed">
               <Properties>
-                <Property name="text" type="java.lang.String" resourceKey="jTextField1.text"/>
-                <Property name="name" type="java.lang.String" value="jTextField1" noResource="true"/>
+                <Property name="text" type="java.lang.String" resourceKey="uploadspeed.text"/>
+                <Property name="name" type="java.lang.String" value="uploadspeed" noResource="true"/>
               </Properties>
+              <Events>
+                <EventHandler event="keyTyped" listener="java.awt.event.KeyListener" parameters="java.awt.event.KeyEvent" handler="speedKeyTyped"/>
+              </Events>
               <Constraints>
                 <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout" value="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout$AbsoluteConstraintsDescription">
                   <AbsoluteConstraints x="160" y="20" width="-1" height="-1"/>
                 </Constraint>
               </Constraints>
             </Component>
-            <Component class="javax.swing.JTextField" name="jTextField2">
+            <Component class="javax.swing.JTextField" name="downloadspeed">
               <Properties>
-                <Property name="text" type="java.lang.String" resourceKey="jTextField2.text"/>
-                <Property name="name" type="java.lang.String" value="jTextField2" noResource="true"/>
+                <Property name="text" type="java.lang.String" resourceKey="downloadspeed.text"/>
+                <Property name="name" type="java.lang.String" value="downloadspeed" noResource="true"/>
               </Properties>
+              <Events>
+                <EventHandler event="keyTyped" listener="java.awt.event.KeyListener" parameters="java.awt.event.KeyEvent" handler="speedKeyTyped"/>
+              </Events>
               <Constraints>
                 <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout" value="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout$AbsoluteConstraintsDescription">
                   <AbsoluteConstraints x="160" y="60" width="-1" height="-1"/>
                 </Constraint>
               </Constraints>
             </Component>
-            <Component class="javax.swing.JComboBox" name="jComboBox1">
+            <Component class="javax.swing.JComboBox" name="uploadkbps">
               <Properties>
                 <Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
-                  <StringArray count="4">
-                    <StringItem index="0" value="Item 1"/>
-                    <StringItem index="1" value="Item 2"/>
-                    <StringItem index="2" value="Item 3"/>
-                    <StringItem index="3" value="Item 4"/>
+                  <StringArray count="2">
+                    <StringItem index="0" value="kbps"/>
+                    <StringItem index="1" value="kBps"/>
                   </StringArray>
                 </Property>
-                <Property name="name" type="java.lang.String" value="jComboBox1" noResource="true"/>
+                <Property name="name" type="java.lang.String" value="uploadkbps" noResource="true"/>
               </Properties>
+              <Events>
+                <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="uploadkbpsActionPerformed"/>
+              </Events>
               <Constraints>
                 <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout" value="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout$AbsoluteConstraintsDescription">
                   <AbsoluteConstraints x="240" y="20" width="-1" height="-1"/>
                 </Constraint>
               </Constraints>
             </Component>
-            <Component class="javax.swing.JComboBox" name="jComboBox2">
+            <Component class="javax.swing.JComboBox" name="downloadkbps">
               <Properties>
                 <Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
-                  <StringArray count="4">
-                    <StringItem index="0" value="Item 1"/>
-                    <StringItem index="1" value="Item 2"/>
-                    <StringItem index="2" value="Item 3"/>
-                    <StringItem index="3" value="Item 4"/>
+                  <StringArray count="2">
+                    <StringItem index="0" value="kbps"/>
+                    <StringItem index="1" value="kBps"/>
                   </StringArray>
                 </Property>
-                <Property name="name" type="java.lang.String" value="jComboBox2" noResource="true"/>
+                <Property name="name" type="java.lang.String" value="downloadkbps" noResource="true"/>
               </Properties>
+              <Events>
+                <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="downloadkbpsActionPerformed"/>
+              </Events>
               <Constraints>
                 <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout" value="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout$AbsoluteConstraintsDescription">
                   <AbsoluteConstraints x="240" y="60" width="-1" height="-1"/>
@@ -209,22 +223,28 @@
                 </Constraint>
               </Constraints>
             </Component>
-            <Component class="javax.swing.JTextField" name="jTextField3">
+            <Component class="javax.swing.JTextField" name="uploadgb">
               <Properties>
-                <Property name="text" type="java.lang.String" resourceKey="jTextField3.text"/>
-                <Property name="name" type="java.lang.String" value="jTextField3" noResource="true"/>
+                <Property name="text" type="java.lang.String" resourceKey="uploadgb.text"/>
+                <Property name="name" type="java.lang.String" value="uploadgb" noResource="true"/>
               </Properties>
+              <Events>
+                <EventHandler event="keyTyped" listener="java.awt.event.KeyListener" parameters="java.awt.event.KeyEvent" handler="uploadgbKeyTyped"/>
+              </Events>
               <Constraints>
                 <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout" value="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout$AbsoluteConstraintsDescription">
                   <AbsoluteConstraints x="440" y="20" width="60" height="-1"/>
                 </Constraint>
               </Constraints>
             </Component>
-            <Component class="javax.swing.JTextField" name="jTextField4">
+            <Component class="javax.swing.JTextField" name="downloadgb">
               <Properties>
-                <Property name="text" type="java.lang.String" resourceKey="jTextField4.text"/>
-                <Property name="name" type="java.lang.String" value="jTextField4" noResource="true"/>
+                <Property name="text" type="java.lang.String" resourceKey="downloadgb.text"/>
+                <Property name="name" type="java.lang.String" value="downloadgb" noResource="true"/>
               </Properties>
+              <Events>
+                <EventHandler event="keyTyped" listener="java.awt.event.KeyListener" parameters="java.awt.event.KeyEvent" handler="downloadgbKeyTyped"/>
+              </Events>
               <Constraints>
                 <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout" value="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout$AbsoluteConstraintsDescription">
                   <AbsoluteConstraints x="440" y="60" width="60" height="-1"/>
@@ -266,14 +286,14 @@
             </Component>
           </SubComponents>
         </Container>
-        <Container class="javax.swing.JPanel" name="jPanel3">
+        <Container class="javax.swing.JPanel" name="updatesPanel">
           <Properties>
-            <Property name="name" type="java.lang.String" value="jPanel3" noResource="true"/>
+            <Property name="name" type="java.lang.String" value="updatesPanel" noResource="true"/>
           </Properties>
           <Constraints>
             <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
               <JTabbedPaneConstraints tabName="Updates">
-                <Property name="tabTitle" type="java.lang.String" resourceKey="jPanel3.TabConstraints.tabTitle"/>
+                <Property name="tabTitle" type="java.lang.String" resourceKey="updatesPanel.TabConstraints.tabTitle"/>
               </JTabbedPaneConstraints>
             </Constraint>
           </Constraints>
@@ -286,20 +306,20 @@
                           <Group type="102" alignment="0" attributes="0">
                               <EmptySpace min="-2" pref="20" max="-2" attributes="0"/>
                               <Group type="103" groupAlignment="0" attributes="0">
-                                  <Component id="jLabel8" alignment="0" min="-2" max="-2" attributes="0"/>
+                                  <Component id="updateMethod" alignment="0" min="-2" max="-2" attributes="0"/>
                                   <Group type="102" alignment="0" attributes="0">
-                                      <Component id="jToggleButton1" min="-2" max="-2" attributes="0"/>
+                                      <Component id="checkUpdates" min="-2" max="-2" attributes="0"/>
                                       <EmptySpace type="separate" max="-2" attributes="0"/>
-                                      <Component id="jToggleButton2" min="-2" max="-2" attributes="0"/>
+                                      <Component id="updateNow" min="-2" max="-2" attributes="0"/>
                                   </Group>
                               </Group>
                           </Group>
                           <Group type="102" alignment="0" attributes="0">
                               <EmptySpace min="40" pref="40" max="40" attributes="0"/>
                               <Group type="103" groupAlignment="0" attributes="0">
-                                  <Component id="jRadioButton2" alignment="0" min="-2" max="-2" attributes="0"/>
-                                  <Component id="jRadioButton1" alignment="0" min="-2" max="-2" attributes="0"/>
-                                  <Component id="jRadioButton3" alignment="0" min="-2" max="-2" attributes="0"/>
+                                  <Component id="updateDownload" alignment="0" min="-2" max="-2" attributes="0"/>
+                                  <Component id="updateInform" alignment="0" min="-2" max="-2" attributes="0"/>
+                                  <Component id="updateDownloadRestart" alignment="0" min="-2" max="-2" attributes="0"/>
                               </Group>
                           </Group>
                       </Group>
@@ -307,7 +327,7 @@
                   </Group>
                   <Group type="102" alignment="1" attributes="0">
                       <EmptySpace pref="339" max="32767" attributes="0"/>
-                      <Component id="jToggleButton3" min="-2" max="-2" attributes="0"/>
+                      <Component id="advancedUpdateConfig" min="-2" max="-2" attributes="0"/>
                       <EmptySpace max="-2" attributes="0"/>
                   </Group>
               </Group>
@@ -316,78 +336,87 @@
               <Group type="103" groupAlignment="0" attributes="0">
                   <Group type="102" attributes="0">
                       <EmptySpace max="-2" attributes="0"/>
-                      <Component id="jLabel8" min="-2" max="-2" attributes="0"/>
+                      <Component id="updateMethod" min="-2" max="-2" attributes="0"/>
                       <EmptySpace type="unrelated" max="-2" attributes="0"/>
-                      <Component id="jRadioButton1" min="-2" max="-2" attributes="0"/>
+                      <Component id="updateInform" min="-2" max="-2" attributes="0"/>
                       <EmptySpace max="-2" attributes="0"/>
-                      <Component id="jRadioButton2" min="-2" max="-2" attributes="0"/>
+                      <Component id="updateDownload" min="-2" max="-2" attributes="0"/>
                       <EmptySpace max="-2" attributes="0"/>
-                      <Component id="jRadioButton3" min="-2" max="-2" attributes="0"/>
+                      <Component id="updateDownloadRestart" min="-2" max="-2" attributes="0"/>
                       <EmptySpace type="separate" max="-2" attributes="0"/>
                       <Group type="103" groupAlignment="3" attributes="0">
-                          <Component id="jToggleButton1" alignment="3" min="-2" max="-2" attributes="0"/>
-                          <Component id="jToggleButton2" alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="checkUpdates" alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="updateNow" alignment="3" min="-2" max="-2" attributes="0"/>
                       </Group>
                       <EmptySpace pref="181" max="32767" attributes="0"/>
-                      <Component id="jToggleButton3" min="-2" max="-2" attributes="0"/>
+                      <Component id="advancedUpdateConfig" min="-2" max="-2" attributes="0"/>
                       <EmptySpace max="-2" attributes="0"/>
                   </Group>
               </Group>
             </DimensionLayout>
           </Layout>
           <SubComponents>
-            <Component class="javax.swing.JLabel" name="jLabel8">
+            <Component class="javax.swing.JLabel" name="updateMethod">
               <Properties>
-                <Property name="text" type="java.lang.String" resourceKey="jLabel8.text"/>
-                <Property name="name" type="java.lang.String" value="jLabel8" noResource="true"/>
+                <Property name="text" type="java.lang.String" resourceKey="updateMethod.text"/>
+                <Property name="name" type="java.lang.String" value="updateMethod" noResource="true"/>
               </Properties>
             </Component>
-            <Component class="javax.swing.JRadioButton" name="jRadioButton1">
+            <Component class="javax.swing.JRadioButton" name="updateInform">
               <Properties>
-                <Property name="text" type="java.lang.String" resourceKey="jRadioButton1.text"/>
-                <Property name="name" type="java.lang.String" value="jRadioButton1" noResource="true"/>
+                <Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
+                  <ComponentRef name="buttonGroup1"/>
+                </Property>
+                <Property name="text" type="java.lang.String" resourceKey="updateInform.text"/>
+                <Property name="name" type="java.lang.String" value="updateInform" noResource="true"/>
               </Properties>
             </Component>
-            <Component class="javax.swing.JRadioButton" name="jRadioButton2">
+            <Component class="javax.swing.JRadioButton" name="updateDownload">
               <Properties>
-                <Property name="text" type="java.lang.String" resourceKey="jRadioButton2.text"/>
-                <Property name="name" type="java.lang.String" value="jRadioButton2" noResource="true"/>
+                <Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
+                  <ComponentRef name="buttonGroup1"/>
+                </Property>
+                <Property name="text" type="java.lang.String" resourceKey="updateDownload.text"/>
+                <Property name="name" type="java.lang.String" value="updateDownload" noResource="true"/>
               </Properties>
             </Component>
-            <Component class="javax.swing.JRadioButton" name="jRadioButton3">
+            <Component class="javax.swing.JRadioButton" name="updateDownloadRestart">
               <Properties>
-                <Property name="text" type="java.lang.String" resourceKey="jRadioButton3.text"/>
-                <Property name="name" type="java.lang.String" value="jRadioButton3" noResource="true"/>
+                <Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
+                  <ComponentRef name="buttonGroup1"/>
+                </Property>
+                <Property name="text" type="java.lang.String" resourceKey="updateDownloadRestart.text"/>
+                <Property name="name" type="java.lang.String" value="updateDownloadRestart" noResource="true"/>
               </Properties>
             </Component>
-            <Component class="javax.swing.JToggleButton" name="jToggleButton1">
+            <Component class="javax.swing.JToggleButton" name="checkUpdates">
               <Properties>
-                <Property name="text" type="java.lang.String" resourceKey="jToggleButton1.text"/>
-                <Property name="name" type="java.lang.String" value="jToggleButton1" noResource="true"/>
+                <Property name="text" type="java.lang.String" resourceKey="checkUpdates.text"/>
+                <Property name="name" type="java.lang.String" value="checkUpdates" noResource="true"/>
               </Properties>
             </Component>
-            <Component class="javax.swing.JToggleButton" name="jToggleButton2">
+            <Component class="javax.swing.JToggleButton" name="updateNow">
               <Properties>
-                <Property name="text" type="java.lang.String" resourceKey="jToggleButton2.text"/>
-                <Property name="name" type="java.lang.String" value="jToggleButton2" noResource="true"/>
+                <Property name="text" type="java.lang.String" resourceKey="updateNow.text"/>
+                <Property name="name" type="java.lang.String" value="updateNow" noResource="true"/>
               </Properties>
             </Component>
-            <Component class="javax.swing.JToggleButton" name="jToggleButton3">
+            <Component class="javax.swing.JToggleButton" name="advancedUpdateConfig">
               <Properties>
-                <Property name="text" type="java.lang.String" resourceKey="jToggleButton3.text"/>
-                <Property name="name" type="java.lang.String" value="jToggleButton3" noResource="true"/>
+                <Property name="text" type="java.lang.String" resourceKey="advancedUpdateConfig.text"/>
+                <Property name="name" type="java.lang.String" value="advancedUpdateConfig" noResource="true"/>
               </Properties>
             </Component>
           </SubComponents>
         </Container>
-        <Container class="javax.swing.JPanel" name="jPanel4">
+        <Container class="javax.swing.JPanel" name="tunnelPanel">
           <Properties>
-            <Property name="name" type="java.lang.String" value="jPanel4" noResource="true"/>
+            <Property name="name" type="java.lang.String" value="tunnelPanel" noResource="true"/>
           </Properties>
           <Constraints>
             <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
               <JTabbedPaneConstraints tabName="Tunnels/Services">
-                <Property name="tabTitle" type="java.lang.String" resourceKey="jPanel4.TabConstraints.tabTitle"/>
+                <Property name="tabTitle" type="java.lang.String" resourceKey="tunnelPanel.TabConstraints.tabTitle"/>
               </JTabbedPaneConstraints>
             </Constraint>
           </Constraints>
@@ -398,11 +427,11 @@
                   <Group type="102" alignment="0" attributes="0">
                       <EmptySpace max="-2" attributes="0"/>
                       <Group type="103" groupAlignment="0" attributes="0">
-                          <Component id="jScrollPane2" alignment="1" pref="538" max="32767" attributes="0"/>
-                          <Component id="jLabel9" alignment="0" pref="538" max="32767" attributes="0"/>
-                          <Component id="jLabel10" alignment="0" min="-2" max="-2" attributes="0"/>
-                          <Component id="jScrollPane1" alignment="0" pref="538" max="32767" attributes="0"/>
-                          <Component id="jLabel11" alignment="0" min="-2" max="-2" attributes="0"/>
+                          <Component id="serverFrame" alignment="1" pref="538" max="32767" attributes="0"/>
+                          <Component id="tunnelsExplanation" alignment="0" pref="538" max="32767" attributes="0"/>
+                          <Component id="clientTunnelLabel" alignment="0" min="-2" max="-2" attributes="0"/>
+                          <Component id="clientFrame" alignment="0" pref="538" max="32767" attributes="0"/>
+                          <Component id="serverTunnelLabel" alignment="0" min="-2" max="-2" attributes="0"/>
                       </Group>
                       <EmptySpace max="-2" attributes="0"/>
                   </Group>
@@ -412,63 +441,63 @@
               <Group type="103" groupAlignment="0" attributes="0">
                   <Group type="102" alignment="0" attributes="0">
                       <EmptySpace min="-2" max="-2" attributes="0"/>
-                      <Component id="jLabel10" min="-2" max="-2" attributes="0"/>
+                      <Component id="clientTunnelLabel" min="-2" max="-2" attributes="0"/>
                       <EmptySpace min="-2" max="-2" attributes="0"/>
-                      <Component id="jScrollPane1" pref="119" max="32767" attributes="0"/>
+                      <Component id="clientFrame" pref="119" max="32767" attributes="0"/>
                       <EmptySpace min="-2" max="-2" attributes="0"/>
-                      <Component id="jLabel11" min="-2" max="-2" attributes="0"/>
+                      <Component id="serverTunnelLabel" min="-2" max="-2" attributes="0"/>
                       <EmptySpace min="-2" max="-2" attributes="0"/>
-                      <Component id="jScrollPane2" pref="122" max="32767" attributes="0"/>
+                      <Component id="serverFrame" pref="122" max="32767" attributes="0"/>
                       <EmptySpace min="-2" max="-2" attributes="0"/>
-                      <Component id="jLabel9" min="-2" pref="65" max="-2" attributes="0"/>
+                      <Component id="tunnelsExplanation" min="-2" pref="65" max="-2" attributes="0"/>
                       <EmptySpace min="-2" max="-2" attributes="0"/>
                   </Group>
               </Group>
             </DimensionLayout>
           </Layout>
           <SubComponents>
-            <Container class="javax.swing.JScrollPane" name="jScrollPane1">
+            <Container class="javax.swing.JScrollPane" name="clientFrame">
               <Properties>
-                <Property name="name" type="java.lang.String" value="jScrollPane1" noResource="true"/>
+                <Property name="name" type="java.lang.String" value="clientFrame" noResource="true"/>
               </Properties>
 
               <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
             </Container>
-            <Container class="javax.swing.JScrollPane" name="jScrollPane2">
+            <Container class="javax.swing.JScrollPane" name="serverFrame">
               <Properties>
-                <Property name="name" type="java.lang.String" value="jScrollPane2" noResource="true"/>
+                <Property name="name" type="java.lang.String" value="serverFrame" noResource="true"/>
               </Properties>
 
               <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
             </Container>
-            <Component class="javax.swing.JLabel" name="jLabel9">
+            <Component class="javax.swing.JLabel" name="tunnelsExplanation">
               <Properties>
-                <Property name="text" type="java.lang.String" resourceKey="jLabel9.text"/>
-                <Property name="name" type="java.lang.String" value="jLabel9" noResource="true"/>
+                <Property name="text" type="java.lang.String" resourceKey="tunnelsExplanation.text"/>
+                <Property name="name" type="java.lang.String" value="tunnelsExplanation" noResource="true"/>
               </Properties>
             </Component>
-            <Component class="javax.swing.JLabel" name="jLabel10">
+            <Component class="javax.swing.JLabel" name="clientTunnelLabel">
               <Properties>
-                <Property name="text" type="java.lang.String" resourceKey="jLabel10.text"/>
-                <Property name="name" type="java.lang.String" value="jLabel10" noResource="true"/>
+                <Property name="text" type="java.lang.String" resourceKey="clientTunnelLabel.text"/>
+                <Property name="name" type="java.lang.String" value="clientTunnelLabel" noResource="true"/>
               </Properties>
             </Component>
-            <Component class="javax.swing.JLabel" name="jLabel11">
+            <Component class="javax.swing.JLabel" name="serverTunnelLabel">
               <Properties>
-                <Property name="text" type="java.lang.String" resourceKey="jLabel11.text"/>
-                <Property name="name" type="java.lang.String" value="jLabel11" noResource="true"/>
+                <Property name="text" type="java.lang.String" resourceKey="serverTunnelLabel.text"/>
+                <Property name="name" type="java.lang.String" value="serverTunnelLabel" noResource="true"/>
               </Properties>
             </Component>
           </SubComponents>
         </Container>
-        <Container class="javax.swing.JPanel" name="jPanel5">
+        <Container class="javax.swing.JPanel" name="networkPanel">
           <Properties>
-            <Property name="name" type="java.lang.String" value="jPanel5" noResource="true"/>
+            <Property name="name" type="java.lang.String" value="networkPanel" noResource="true"/>
           </Properties>
           <Constraints>
             <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
               <JTabbedPaneConstraints tabName="Network">
-                <Property name="tabTitle" type="java.lang.String" resourceKey="jPanel5.TabConstraints.tabTitle"/>
+                <Property name="tabTitle" type="java.lang.String" resourceKey="networkPanel.TabConstraints.tabTitle"/>
               </JTabbedPaneConstraints>
             </Constraint>
           </Constraints>
@@ -486,14 +515,14 @@
             </DimensionLayout>
           </Layout>
         </Container>
-        <Container class="javax.swing.JPanel" name="jPanel6">
+        <Container class="javax.swing.JPanel" name="advancedPanel">
           <Properties>
-            <Property name="name" type="java.lang.String" value="jPanel6" noResource="true"/>
+            <Property name="name" type="java.lang.String" value="advancedPanel" noResource="true"/>
           </Properties>
           <Constraints>
             <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
               <JTabbedPaneConstraints tabName="Advanced">
-                <Property name="tabTitle" type="java.lang.String" resourceKey="jPanel6.TabConstraints.tabTitle"/>
+                <Property name="tabTitle" type="java.lang.String" resourceKey="advancedPanel.TabConstraints.tabTitle"/>
               </JTabbedPaneConstraints>
             </Constraint>
           </Constraints>
diff --git a/apps/desktopgui/src/net/i2p/desktopgui/gui/GeneralConfiguration.java b/apps/desktopgui/src/net/i2p/desktopgui/gui/GeneralConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..910ca21fb5eeee8fec14984eaa501704f7c2f9e6
--- /dev/null
+++ b/apps/desktopgui/src/net/i2p/desktopgui/gui/GeneralConfiguration.java
@@ -0,0 +1,461 @@
+/*
+ * GeneralConfiguration.java
+ *
+ * Created on 10 april 2009, 19:04
+ */
+
+package net.i2p.desktopgui.gui;
+
+import net.i2p.desktopgui.router.configuration.SpeedHelper;
+
+/**
+ *
+ * @author  mathias
+ */
+public class GeneralConfiguration extends javax.swing.JFrame {
+
+    /** Creates new form GeneralConfiguration */
+    public GeneralConfiguration() {
+        initComponents();
+        extraInitComponents();
+        this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+        this.setVisible(true);
+    }
+    
+    private void extraInitComponents() {
+        downloadspeed.setText(SpeedHelper.getInboundBandwidth());
+    }
+
+    /** This method is called from within the constructor to
+     * initialize the form.
+     * WARNING: Do NOT modify this code. The content of this method is
+     * always regenerated by the Form Editor.
+     */
+    @SuppressWarnings("unchecked")
+    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
+    private void initComponents() {
+
+        buttonGroup1 = new javax.swing.ButtonGroup();
+        applyPanel = new javax.swing.JPanel();
+        cancel = new javax.swing.JToggleButton();
+        ok = new javax.swing.JToggleButton();
+        settingsPanel = new javax.swing.JTabbedPane();
+        speedPanel = new javax.swing.JPanel();
+        uploadSpeedLabel = new javax.swing.JLabel();
+        downloadSpeedLabel = new javax.swing.JLabel();
+        uploadspeed = new javax.swing.JTextField();
+        downloadspeed = new javax.swing.JTextField();
+        uploadkbps = new javax.swing.JComboBox();
+        downloadkbps = new javax.swing.JComboBox();
+        jLabel3 = new javax.swing.JLabel();
+        jLabel4 = new javax.swing.JLabel();
+        uploadgb = new javax.swing.JTextField();
+        downloadgb = new javax.swing.JTextField();
+        jLabel5 = new javax.swing.JLabel();
+        jLabel6 = new javax.swing.JLabel();
+        jLabel7 = new javax.swing.JLabel();
+        updatesPanel = new javax.swing.JPanel();
+        updateMethod = new javax.swing.JLabel();
+        updateInform = new javax.swing.JRadioButton();
+        updateDownload = new javax.swing.JRadioButton();
+        updateDownloadRestart = new javax.swing.JRadioButton();
+        checkUpdates = new javax.swing.JToggleButton();
+        updateNow = new javax.swing.JToggleButton();
+        advancedUpdateConfig = new javax.swing.JToggleButton();
+        tunnelPanel = new javax.swing.JPanel();
+        clientFrame = new javax.swing.JScrollPane();
+        serverFrame = new javax.swing.JScrollPane();
+        tunnelsExplanation = new javax.swing.JLabel();
+        clientTunnelLabel = new javax.swing.JLabel();
+        serverTunnelLabel = new javax.swing.JLabel();
+        networkPanel = new javax.swing.JPanel();
+        advancedPanel = new javax.swing.JPanel();
+
+        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
+        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(net.i2p.desktopgui.desktopgui.Main.class).getContext().getResourceMap(GeneralConfiguration.class);
+        setTitle(resourceMap.getString("Form.title")); // NOI18N
+        setName("Form"); // NOI18N
+
+        applyPanel.setName("applyPanel"); // NOI18N
+
+        cancel.setText(resourceMap.getString("cancel.text")); // NOI18N
+        cancel.setName("cancel"); // NOI18N
+
+        ok.setText(resourceMap.getString("ok.text")); // NOI18N
+        ok.setName("ok"); // NOI18N
+
+        javax.swing.GroupLayout applyPanelLayout = new javax.swing.GroupLayout(applyPanel);
+        applyPanel.setLayout(applyPanelLayout);
+        applyPanelLayout.setHorizontalGroup(
+            applyPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, applyPanelLayout.createSequentialGroup()
+                .addContainerGap(475, Short.MAX_VALUE)
+                .addComponent(ok)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(cancel)
+                .addContainerGap())
+        );
+        applyPanelLayout.setVerticalGroup(
+            applyPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(applyPanelLayout.createSequentialGroup()
+                .addGroup(applyPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                    .addComponent(cancel)
+                    .addComponent(ok))
+                .addContainerGap(14, Short.MAX_VALUE))
+        );
+
+        settingsPanel.setName("settingsPanel"); // NOI18N
+
+        speedPanel.setName("speedPanel"); // NOI18N
+        speedPanel.setLayout(null);
+
+        uploadSpeedLabel.setText(resourceMap.getString("uploadSpeedLabel.text")); // NOI18N
+        uploadSpeedLabel.setName("uploadSpeedLabel"); // NOI18N
+        speedPanel.add(uploadSpeedLabel);
+        uploadSpeedLabel.setBounds(20, 20, 140, 30);
+
+        downloadSpeedLabel.setText(resourceMap.getString("downloadSpeedLabel.text")); // NOI18N
+        downloadSpeedLabel.setName("downloadSpeedLabel"); // NOI18N
+        speedPanel.add(downloadSpeedLabel);
+        downloadSpeedLabel.setBounds(20, 60, 140, 30);
+
+        uploadspeed.setText(resourceMap.getString("uploadspeed.text")); // NOI18N
+        uploadspeed.setName("uploadspeed"); // NOI18N
+        uploadspeed.addKeyListener(new java.awt.event.KeyAdapter() {
+            public void keyTyped(java.awt.event.KeyEvent evt) {
+                speedKeyTyped(evt);
+            }
+        });
+        speedPanel.add(uploadspeed);
+        uploadspeed.setBounds(160, 20, 77, 27);
+
+        downloadspeed.setText(resourceMap.getString("downloadspeed.text")); // NOI18N
+        downloadspeed.setName("downloadspeed"); // NOI18N
+        downloadspeed.addKeyListener(new java.awt.event.KeyAdapter() {
+            public void keyTyped(java.awt.event.KeyEvent evt) {
+                speedKeyTyped(evt);
+            }
+        });
+        speedPanel.add(downloadspeed);
+        downloadspeed.setBounds(160, 60, 77, 27);
+
+        uploadkbps.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "kbps", "kBps" }));
+        uploadkbps.setName("uploadkbps"); // NOI18N
+        uploadkbps.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                uploadkbpsActionPerformed(evt);
+            }
+        });
+        speedPanel.add(uploadkbps);
+        uploadkbps.setBounds(240, 20, 68, 27);
+
+        downloadkbps.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "kbps", "kBps" }));
+        downloadkbps.setName("downloadkbps"); // NOI18N
+        downloadkbps.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                downloadkbpsActionPerformed(evt);
+            }
+        });
+        speedPanel.add(downloadkbps);
+        downloadkbps.setBounds(240, 60, 68, 27);
+
+        jLabel3.setText(resourceMap.getString("jLabel3.text")); // NOI18N
+        jLabel3.setName("jLabel3"); // NOI18N
+        speedPanel.add(jLabel3);
+        jLabel3.setBounds(330, 20, 97, 30);
+
+        jLabel4.setText(resourceMap.getString("jLabel4.text")); // NOI18N
+        jLabel4.setName("jLabel4"); // NOI18N
+        speedPanel.add(jLabel4);
+        jLabel4.setBounds(330, 60, 97, 30);
+
+        uploadgb.setText(resourceMap.getString("uploadgb.text")); // NOI18N
+        uploadgb.setName("uploadgb"); // NOI18N
+        uploadgb.addKeyListener(new java.awt.event.KeyAdapter() {
+            public void keyTyped(java.awt.event.KeyEvent evt) {
+                uploadgbKeyTyped(evt);
+            }
+        });
+        speedPanel.add(uploadgb);
+        uploadgb.setBounds(440, 20, 60, 27);
+
+        downloadgb.setText(resourceMap.getString("downloadgb.text")); // NOI18N
+        downloadgb.setName("downloadgb"); // NOI18N
+        downloadgb.addKeyListener(new java.awt.event.KeyAdapter() {
+            public void keyTyped(java.awt.event.KeyEvent evt) {
+                downloadgbKeyTyped(evt);
+            }
+        });
+        speedPanel.add(downloadgb);
+        downloadgb.setBounds(440, 60, 60, 27);
+
+        jLabel5.setText(resourceMap.getString("jLabel5.text")); // NOI18N
+        jLabel5.setName("jLabel5"); // NOI18N
+        speedPanel.add(jLabel5);
+        jLabel5.setBounds(510, 20, 19, 30);
+
+        jLabel6.setText(resourceMap.getString("jLabel6.text")); // NOI18N
+        jLabel6.setName("jLabel6"); // NOI18N
+        speedPanel.add(jLabel6);
+        jLabel6.setBounds(510, 60, 19, 30);
+
+        jLabel7.setText(resourceMap.getString("jLabel7.text")); // NOI18N
+        jLabel7.setName("jLabel7"); // NOI18N
+        speedPanel.add(jLabel7);
+        jLabel7.setBounds(20, 100, 520, 70);
+
+        settingsPanel.addTab(resourceMap.getString("speedPanel.TabConstraints.tabTitle"), speedPanel); // NOI18N
+
+        updatesPanel.setName("updatesPanel"); // NOI18N
+
+        updateMethod.setText(resourceMap.getString("updateMethod.text")); // NOI18N
+        updateMethod.setName("updateMethod"); // NOI18N
+
+        buttonGroup1.add(updateInform);
+        updateInform.setText(resourceMap.getString("updateInform.text")); // NOI18N
+        updateInform.setName("updateInform"); // NOI18N
+
+        buttonGroup1.add(updateDownload);
+        updateDownload.setText(resourceMap.getString("updateDownload.text")); // NOI18N
+        updateDownload.setName("updateDownload"); // NOI18N
+
+        buttonGroup1.add(updateDownloadRestart);
+        updateDownloadRestart.setText(resourceMap.getString("updateDownloadRestart.text")); // NOI18N
+        updateDownloadRestart.setName("updateDownloadRestart"); // NOI18N
+
+        checkUpdates.setText(resourceMap.getString("checkUpdates.text")); // NOI18N
+        checkUpdates.setName("checkUpdates"); // NOI18N
+
+        updateNow.setText(resourceMap.getString("updateNow.text")); // NOI18N
+        updateNow.setName("updateNow"); // NOI18N
+
+        advancedUpdateConfig.setText(resourceMap.getString("advancedUpdateConfig.text")); // NOI18N
+        advancedUpdateConfig.setName("advancedUpdateConfig"); // NOI18N
+
+        javax.swing.GroupLayout updatesPanelLayout = new javax.swing.GroupLayout(updatesPanel);
+        updatesPanel.setLayout(updatesPanelLayout);
+        updatesPanelLayout.setHorizontalGroup(
+            updatesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(updatesPanelLayout.createSequentialGroup()
+                .addGroup(updatesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addGroup(updatesPanelLayout.createSequentialGroup()
+                        .addGap(20, 20, 20)
+                        .addGroup(updatesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                            .addComponent(updateMethod)
+                            .addGroup(updatesPanelLayout.createSequentialGroup()
+                                .addComponent(checkUpdates)
+                                .addGap(18, 18, 18)
+                                .addComponent(updateNow))))
+                    .addGroup(updatesPanelLayout.createSequentialGroup()
+                        .addGap(40, 40, 40)
+                        .addGroup(updatesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                            .addComponent(updateDownload)
+                            .addComponent(updateInform)
+                            .addComponent(updateDownloadRestart))))
+                .addGap(9, 9, 9))
+            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, updatesPanelLayout.createSequentialGroup()
+                .addContainerGap(339, Short.MAX_VALUE)
+                .addComponent(advancedUpdateConfig)
+                .addContainerGap())
+        );
+        updatesPanelLayout.setVerticalGroup(
+            updatesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(updatesPanelLayout.createSequentialGroup()
+                .addContainerGap()
+                .addComponent(updateMethod)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+                .addComponent(updateInform)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(updateDownload)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(updateDownloadRestart)
+                .addGap(18, 18, 18)
+                .addGroup(updatesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+                    .addComponent(checkUpdates)
+                    .addComponent(updateNow))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 181, Short.MAX_VALUE)
+                .addComponent(advancedUpdateConfig)
+                .addContainerGap())
+        );
+
+        settingsPanel.addTab(resourceMap.getString("updatesPanel.TabConstraints.tabTitle"), updatesPanel); // NOI18N
+
+        tunnelPanel.setName("tunnelPanel"); // NOI18N
+
+        clientFrame.setName("clientFrame"); // NOI18N
+
+        serverFrame.setName("serverFrame"); // NOI18N
+
+        tunnelsExplanation.setText(resourceMap.getString("tunnelsExplanation.text")); // NOI18N
+        tunnelsExplanation.setName("tunnelsExplanation"); // NOI18N
+
+        clientTunnelLabel.setText(resourceMap.getString("clientTunnelLabel.text")); // NOI18N
+        clientTunnelLabel.setName("clientTunnelLabel"); // NOI18N
+
+        serverTunnelLabel.setText(resourceMap.getString("serverTunnelLabel.text")); // NOI18N
+        serverTunnelLabel.setName("serverTunnelLabel"); // NOI18N
+
+        javax.swing.GroupLayout tunnelPanelLayout = new javax.swing.GroupLayout(tunnelPanel);
+        tunnelPanel.setLayout(tunnelPanelLayout);
+        tunnelPanelLayout.setHorizontalGroup(
+            tunnelPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(tunnelPanelLayout.createSequentialGroup()
+                .addContainerGap()
+                .addGroup(tunnelPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                    .addComponent(serverFrame, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 538, Short.MAX_VALUE)
+                    .addComponent(tunnelsExplanation, javax.swing.GroupLayout.DEFAULT_SIZE, 538, Short.MAX_VALUE)
+                    .addComponent(clientTunnelLabel)
+                    .addComponent(clientFrame, javax.swing.GroupLayout.DEFAULT_SIZE, 538, Short.MAX_VALUE)
+                    .addComponent(serverTunnelLabel))
+                .addContainerGap())
+        );
+        tunnelPanelLayout.setVerticalGroup(
+            tunnelPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(tunnelPanelLayout.createSequentialGroup()
+                .addContainerGap()
+                .addComponent(clientTunnelLabel)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(clientFrame, javax.swing.GroupLayout.DEFAULT_SIZE, 119, Short.MAX_VALUE)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(serverTunnelLabel)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(serverFrame, javax.swing.GroupLayout.DEFAULT_SIZE, 122, Short.MAX_VALUE)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(tunnelsExplanation, javax.swing.GroupLayout.PREFERRED_SIZE, 65, javax.swing.GroupLayout.PREFERRED_SIZE)
+                .addContainerGap())
+        );
+
+        settingsPanel.addTab(resourceMap.getString("tunnelPanel.TabConstraints.tabTitle"), tunnelPanel); // NOI18N
+
+        networkPanel.setName("networkPanel"); // NOI18N
+
+        javax.swing.GroupLayout networkPanelLayout = new javax.swing.GroupLayout(networkPanel);
+        networkPanel.setLayout(networkPanelLayout);
+        networkPanelLayout.setHorizontalGroup(
+            networkPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGap(0, 562, Short.MAX_VALUE)
+        );
+        networkPanelLayout.setVerticalGroup(
+            networkPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGap(0, 388, Short.MAX_VALUE)
+        );
+
+        settingsPanel.addTab(resourceMap.getString("networkPanel.TabConstraints.tabTitle"), networkPanel); // NOI18N
+
+        advancedPanel.setName("advancedPanel"); // NOI18N
+
+        javax.swing.GroupLayout advancedPanelLayout = new javax.swing.GroupLayout(advancedPanel);
+        advancedPanel.setLayout(advancedPanelLayout);
+        advancedPanelLayout.setHorizontalGroup(
+            advancedPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGap(0, 562, Short.MAX_VALUE)
+        );
+        advancedPanelLayout.setVerticalGroup(
+            advancedPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGap(0, 388, Short.MAX_VALUE)
+        );
+
+        settingsPanel.addTab(resourceMap.getString("advancedPanel.TabConstraints.tabTitle"), advancedPanel); // NOI18N
+
+        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
+        getContentPane().setLayout(layout);
+        layout.setHorizontalGroup(
+            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addComponent(applyPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+            .addGroup(layout.createSequentialGroup()
+                .addGap(12, 12, 12)
+                .addComponent(settingsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 566, Short.MAX_VALUE))
+        );
+        layout.setVerticalGroup(
+            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+                .addComponent(settingsPanel)
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(applyPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+        );
+
+        pack();
+    }// </editor-fold>//GEN-END:initComponents
+
+private void speedKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_speedKeyTyped
+    try {
+        String upload = "";
+        if(uploadkbps.getSelectedIndex() == KILOBIT)
+            upload = "" + Integer.parseInt(uploadspeed.getText())/8;
+        else
+            upload = uploadspeed.getText();
+        String download = "";
+        if(downloadkbps.getSelectedIndex() == KILOBIT)
+            download = "" + Integer.parseInt(downloadspeed.getText())/8;
+        else
+            download = downloadspeed.getText();
+        initUsage(upload, download);
+    }
+    catch(NumberFormatException e) {
+        e.printStackTrace();
+        return;
+    }
+}//GEN-LAST:event_speedKeyTyped
+
+private void uploadkbpsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_uploadkbpsActionPerformed
+    // TODO add your handling code here:
+}//GEN-LAST:event_uploadkbpsActionPerformed
+
+private void downloadkbpsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_downloadkbpsActionPerformed
+    // TODO add your handling code here:
+}//GEN-LAST:event_downloadkbpsActionPerformed
+
+private void uploadgbKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_uploadgbKeyTyped
+    // TODO add your handling code here:
+}//GEN-LAST:event_uploadgbKeyTyped
+
+private void downloadgbKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_downloadgbKeyTyped
+    // TODO add your handling code here:
+}//GEN-LAST:event_downloadgbKeyTyped
+
+    protected void initUsage(String upload, String download) {
+        uploadgb.setText("" + SpeedHelper.calculateMonthlyUsage(Integer.parseInt(upload)));
+        downloadgb.setText("" + SpeedHelper.calculateMonthlyUsage(Integer.parseInt(download)));
+    }
+
+    // Variables declaration - do not modify//GEN-BEGIN:variables
+    private javax.swing.JPanel advancedPanel;
+    private javax.swing.JToggleButton advancedUpdateConfig;
+    private javax.swing.JPanel applyPanel;
+    private javax.swing.ButtonGroup buttonGroup1;
+    private javax.swing.JToggleButton cancel;
+    private javax.swing.JToggleButton checkUpdates;
+    private javax.swing.JScrollPane clientFrame;
+    private javax.swing.JLabel clientTunnelLabel;
+    private javax.swing.JLabel downloadSpeedLabel;
+    private javax.swing.JTextField downloadgb;
+    private javax.swing.JComboBox downloadkbps;
+    private javax.swing.JTextField downloadspeed;
+    private javax.swing.JLabel jLabel3;
+    private javax.swing.JLabel jLabel4;
+    private javax.swing.JLabel jLabel5;
+    private javax.swing.JLabel jLabel6;
+    private javax.swing.JLabel jLabel7;
+    private javax.swing.JPanel networkPanel;
+    private javax.swing.JToggleButton ok;
+    private javax.swing.JScrollPane serverFrame;
+    private javax.swing.JLabel serverTunnelLabel;
+    private javax.swing.JTabbedPane settingsPanel;
+    private javax.swing.JPanel speedPanel;
+    private javax.swing.JPanel tunnelPanel;
+    private javax.swing.JLabel tunnelsExplanation;
+    private javax.swing.JRadioButton updateDownload;
+    private javax.swing.JRadioButton updateDownloadRestart;
+    private javax.swing.JRadioButton updateInform;
+    private javax.swing.JLabel updateMethod;
+    private javax.swing.JToggleButton updateNow;
+    private javax.swing.JPanel updatesPanel;
+    private javax.swing.JLabel uploadSpeedLabel;
+    private javax.swing.JTextField uploadgb;
+    private javax.swing.JComboBox uploadkbps;
+    private javax.swing.JTextField uploadspeed;
+    // End of variables declaration//GEN-END:variables
+
+    public static final int KILOBIT = 0;
+    public static final int KILOBYTE = 1;
+}
diff --git a/apps/desktopgui/src/gui/JPopupTrayIcon.java b/apps/desktopgui/src/net/i2p/desktopgui/gui/JPopupTrayIcon.java
similarity index 83%
rename from apps/desktopgui/src/gui/JPopupTrayIcon.java
rename to apps/desktopgui/src/net/i2p/desktopgui/gui/JPopupTrayIcon.java
index 83872e4a51958c1b9b5ca094800ec731392574a3..1f62df2c27099c0863608ec181204dbe1d3473d2 100644
--- a/apps/desktopgui/src/gui/JPopupTrayIcon.java
+++ b/apps/desktopgui/src/net/i2p/desktopgui/gui/JPopupTrayIcon.java
@@ -24,7 +24,7 @@
  * 
  */
 
-package gui;
+package net.i2p.desktopgui.gui;
 
 import java.awt.Dimension;
 import java.awt.Frame;
@@ -37,13 +37,12 @@ import java.awt.Window;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import javax.swing.JDialog;
-import javax.swing.JFrame;
-import javax.swing.JPanel;
 import javax.swing.JPopupMenu;
 import javax.swing.JWindow;
 import javax.swing.RootPaneContainer;
 import javax.swing.event.PopupMenuEvent;
 import javax.swing.event.PopupMenuListener;
+import java.util.Date;
 
 
 
@@ -63,6 +62,10 @@ public class JPopupTrayIcon extends TrayIcon {
     
     private final static boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase().contains("windows");
 
+    private static MouseEvent previous = null;
+    private static Date previousTime = new Date();
+    private static Date time = new Date();
+
     public JPopupTrayIcon(Image image) {
         super(image);
         init();
@@ -92,12 +95,12 @@ public class JPopupTrayIcon extends TrayIcon {
 
             @Override
             public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
-//                System.out.println("popupMenuWillBecomeVisible");
+                //System.out.println("popupMenuWillBecomeVisible");
             }
 
             @Override
             public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
-//                System.out.println("popupMenuWillBecomeInvisible");
+                //System.out.println("popupMenuWillBecomeInvisible");
                 if(window != null) {
                     window.dispose();
                     window = null;
@@ -117,21 +120,27 @@ public class JPopupTrayIcon extends TrayIcon {
         addMouseListener(new MouseAdapter() {
             @Override
             public void mousePressed(MouseEvent e) {
-//                System.out.println(e.getPoint());
-                showJPopupMenu(e);
+                //System.out.println("Pressed " + e.getPoint());
+                showJPopupMenu(e, previous);
+                previous = e;
+                previousTime = time;
+                time = new Date();
             }
 
             @Override
             public void mouseReleased(MouseEvent e) {
-//                System.out.println(e.getPoint());
-                showJPopupMenu(e);
+                //System.out.println("Released " + e.getPoint());
+                showJPopupMenu(e, previous);
+                previous = e;
+                previousTime = time;
+                time = new Date();
             }
         });
 
     }
 
-    private final void showJPopupMenu(MouseEvent e) {
-        if(e.isPopupTrigger() && menu != null) {
+    private final void showJPopupMenu(MouseEvent e, MouseEvent previous) {
+        if((e.isPopupTrigger() || previous.isPopupTrigger()) && (time.getTime() - previousTime.getTime() < 1000) && menu != null) {
             if (window == null) {
 
                 if(IS_WINDOWS) {
diff --git a/apps/desktopgui/src/gui/LogViewer.form b/apps/desktopgui/src/net/i2p/desktopgui/gui/LogViewer.form
similarity index 96%
rename from apps/desktopgui/src/gui/LogViewer.form
rename to apps/desktopgui/src/net/i2p/desktopgui/gui/LogViewer.form
index b53b410bac2305d27ef38ff74014877dace14fdc..ed2441319e9e731b289d75c220c71dbf87c906db 100644
--- a/apps/desktopgui/src/gui/LogViewer.form
+++ b/apps/desktopgui/src/net/i2p/desktopgui/gui/LogViewer.form
@@ -3,6 +3,7 @@
 <Form version="1.5" maxVersion="1.6" type="org.netbeans.modules.form.forminfo.JFrameFormInfo">
   <Properties>
     <Property name="defaultCloseOperation" type="int" value="3"/>
+    <Property name="title" type="java.lang.String" resourceKey="Form.title"/>
     <Property name="name" type="java.lang.String" value="Form" noResource="true"/>
   </Properties>
   <SyntheticProperties>
@@ -11,6 +12,7 @@
   <AuxValues>
     <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="2"/>
     <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
     <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
     <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
     <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
diff --git a/apps/desktopgui/src/gui/LogViewer.java b/apps/desktopgui/src/net/i2p/desktopgui/gui/LogViewer.java
similarity index 96%
rename from apps/desktopgui/src/gui/LogViewer.java
rename to apps/desktopgui/src/net/i2p/desktopgui/gui/LogViewer.java
index 2dad70c1fc242ecabc02401c3ac10c83908dbddf..1edf85b3bf0d169647fceacd52a33e671f90d025 100644
--- a/apps/desktopgui/src/gui/LogViewer.java
+++ b/apps/desktopgui/src/net/i2p/desktopgui/gui/LogViewer.java
@@ -4,7 +4,7 @@
  * Created on 10 april 2009, 19:17
  */
 
-package gui;
+package net.i2p.desktopgui.gui;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -73,6 +73,8 @@ public class LogViewer extends javax.swing.JFrame {
         clearButton = new javax.swing.JButton();
 
         setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
+        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(net.i2p.desktopgui.desktopgui.Main.class).getContext().getResourceMap(LogViewer.class);
+        setTitle(resourceMap.getString("Form.title")); // NOI18N
         setName("Form"); // NOI18N
 
         textScroll.setName("textScroll"); // NOI18N
@@ -82,7 +84,6 @@ public class LogViewer extends javax.swing.JFrame {
         logText.setName("logText"); // NOI18N
         textScroll.setViewportView(logText);
 
-        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(LogViewer.class);
         explanationText.setText(resourceMap.getString("explanationText.text")); // NOI18N
         explanationText.setName("explanationText"); // NOI18N
 
diff --git a/apps/desktopgui/src/gui/SpeedSelector.form b/apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelector.form
similarity index 99%
rename from apps/desktopgui/src/gui/SpeedSelector.form
rename to apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelector.form
index 79d25ae9650187e565486e5823d843a5a52a4727..5ee7b94ac85c39d02589ebb8741493ec98026232 100644
--- a/apps/desktopgui/src/gui/SpeedSelector.form
+++ b/apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelector.form
@@ -16,6 +16,7 @@
   <AuxValues>
     <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="2"/>
     <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
     <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
     <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
     <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
diff --git a/apps/desktopgui/src/gui/SpeedSelector.java b/apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelector.java
similarity index 96%
rename from apps/desktopgui/src/gui/SpeedSelector.java
rename to apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelector.java
index bce18621f2919beeb77c9d8e9702ca903a63e980..335812e2a99a1ce4dcdd5dd7f58eb3d1e3d25b02 100644
--- a/apps/desktopgui/src/gui/SpeedSelector.java
+++ b/apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelector.java
@@ -4,15 +4,14 @@
  * Created on 3 april 2009, 13:57
  */
 
-package gui;
+package net.i2p.desktopgui.gui;
 
-import java.awt.Dimension;
 import java.awt.Point;
 import java.util.Properties;
 import javax.swing.JComboBox;
 import javax.swing.JTextField;
-import persistence.PropertyManager;
-import util.IntegerVerifier;
+import net.i2p.desktopgui.persistence.PropertyManager;
+import net.i2p.desktopgui.util.IntegerVerifier;
 
 /**
  *
@@ -60,7 +59,7 @@ public class SpeedSelector extends javax.swing.JFrame {
         downloadkbps = new javax.swing.JComboBox();
 
         setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
-        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(SpeedSelector.class);
+        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(net.i2p.desktopgui.desktopgui.Main.class).getContext().getResourceMap(SpeedSelector.class);
         setTitle(resourceMap.getString("Form.title")); // NOI18N
         setMinimumSize(new java.awt.Dimension(610, 330));
         setName("Form"); // NOI18N
diff --git a/apps/desktopgui/src/gui/SpeedSelector2.form b/apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelector2.form
similarity index 98%
rename from apps/desktopgui/src/gui/SpeedSelector2.form
rename to apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelector2.form
index 5c305de19b450dfc4a0d812173e600d01612449d..54f5fa7262dc3a23f793f4180dc0d1acb83f6cb4 100644
--- a/apps/desktopgui/src/gui/SpeedSelector2.form
+++ b/apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelector2.form
@@ -20,6 +20,7 @@
   <AuxValues>
     <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="2"/>
     <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
     <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
     <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
     <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
diff --git a/apps/desktopgui/src/gui/SpeedSelector2.java b/apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelector2.java
similarity index 96%
rename from apps/desktopgui/src/gui/SpeedSelector2.java
rename to apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelector2.java
index 1177b5725817f5aead7062dc8dae196af3fc67e7..772d16918a58a6edf6d65601f708dc8768c35c01 100644
--- a/apps/desktopgui/src/gui/SpeedSelector2.java
+++ b/apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelector2.java
@@ -4,14 +4,13 @@
  * Created on 3 april 2009, 14:36
  */
 
-package gui;
+package net.i2p.desktopgui.gui;
 
-import java.awt.Dimension;
 import java.awt.Point;
 import java.util.Enumeration;
 import java.util.Properties;
 import javax.swing.AbstractButton;
-import persistence.PropertyManager;
+import net.i2p.desktopgui.persistence.PropertyManager;
 
 /**
  *
@@ -48,7 +47,7 @@ public class SpeedSelector2 extends javax.swing.JFrame {
         jLabel1 = new javax.swing.JLabel();
 
         setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
-        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(SpeedSelector2.class);
+        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(net.i2p.desktopgui.desktopgui.Main.class).getContext().getResourceMap(SpeedSelector2.class);
         setTitle(resourceMap.getString("Form.title")); // NOI18N
         setMinimumSize(new java.awt.Dimension(610, 330));
         setName("Form"); // NOI18N
diff --git a/apps/desktopgui/src/gui/SpeedSelector3.form b/apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelector3.form
similarity index 99%
rename from apps/desktopgui/src/gui/SpeedSelector3.form
rename to apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelector3.form
index b295c45938c70d6d1a1a54dba4a00d3e55e3f747..8e4aa5b4e8d19b460b4eb3a21575ac3a309fa496 100644
--- a/apps/desktopgui/src/gui/SpeedSelector3.form
+++ b/apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelector3.form
@@ -16,6 +16,7 @@
   <AuxValues>
     <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="2"/>
     <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
     <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
     <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
     <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
@@ -318,7 +319,7 @@
       </Properties>
       <Constraints>
         <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout" value="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout$AbsoluteConstraintsDescription">
-          <AbsoluteConstraints x="580" y="150" width="-1" height="30"/>
+          <AbsoluteConstraints x="580" y="150" width="40" height="30"/>
         </Constraint>
       </Constraints>
     </Component>
diff --git a/apps/desktopgui/src/gui/SpeedSelector3.java b/apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelector3.java
similarity index 98%
rename from apps/desktopgui/src/gui/SpeedSelector3.java
rename to apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelector3.java
index 8c6407f629d55dea655d5e63961420f40b39ab8c..933ad3ff2f9489714d1db00c73be96c466d86580 100644
--- a/apps/desktopgui/src/gui/SpeedSelector3.java
+++ b/apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelector3.java
@@ -4,16 +4,16 @@
  * Created on 3 april 2009, 15:17
  */
 
-package gui;
+package net.i2p.desktopgui.gui;
 
 import java.awt.Dimension;
 import java.awt.Point;
 import java.util.Properties;
 import javax.swing.JComboBox;
 import javax.swing.JTextField;
-import persistence.PropertyManager;
-import router.configuration.SpeedHandler;
-import router.configuration.SpeedHelper;
+import net.i2p.desktopgui.persistence.PropertyManager;
+import net.i2p.desktopgui.router.configuration.SpeedHandler;
+import net.i2p.desktopgui.router.configuration.SpeedHelper;
 
 /**
  *
@@ -67,7 +67,7 @@ public class SpeedSelector3 extends javax.swing.JFrame {
         explanation = new javax.swing.JLabel();
 
         setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
-        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(SpeedSelector3.class);
+        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(net.i2p.desktopgui.desktopgui.Main.class).getContext().getResourceMap(SpeedSelector3.class);
         setTitle(resourceMap.getString("Form.title")); // NOI18N
         setMinimumSize(new java.awt.Dimension(670, 330));
         setName("Form"); // NOI18N
@@ -231,7 +231,7 @@ public class SpeedSelector3 extends javax.swing.JFrame {
         downloadGB.setText(resourceMap.getString("downloadUsageLabel.text")); // NOI18N
         downloadGB.setName("downloadUsageLabel"); // NOI18N
         getContentPane().add(downloadGB);
-        downloadGB.setBounds(580, 150, 19, 30);
+        downloadGB.setBounds(580, 150, 40, 30);
 
         explanation.setText(resourceMap.getString("explanation.text")); // NOI18N
         explanation.setName("explanation"); // NOI18N
diff --git a/apps/desktopgui/src/gui/SpeedSelectorConstants.java b/apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelectorConstants.java
similarity index 96%
rename from apps/desktopgui/src/gui/SpeedSelectorConstants.java
rename to apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelectorConstants.java
index ea5e32427b56eb9d86e5cc263787de07fe1648bc..1088f6957f27bdba770c02a26ecb3f0fee19c1a1 100644
--- a/apps/desktopgui/src/gui/SpeedSelectorConstants.java
+++ b/apps/desktopgui/src/net/i2p/desktopgui/gui/SpeedSelectorConstants.java
@@ -1,4 +1,4 @@
-package gui;
+package net.i2p.desktopgui.gui;
 
 /**
  *
diff --git a/apps/desktopgui/src/gui/Tray.java b/apps/desktopgui/src/net/i2p/desktopgui/gui/Tray.java
similarity index 97%
rename from apps/desktopgui/src/gui/Tray.java
rename to apps/desktopgui/src/net/i2p/desktopgui/gui/Tray.java
index 7c95cd6eb3ef740bf518141d130b6d7d5079129a..b20a850f6d744075da74d2e9cd51ae82deb37799 100644
--- a/apps/desktopgui/src/gui/Tray.java
+++ b/apps/desktopgui/src/net/i2p/desktopgui/gui/Tray.java
@@ -3,9 +3,9 @@
  * and open the template in the editor.
  */
 
-package gui;
+package net.i2p.desktopgui.gui;
 
-import desktopgui.*;
+import net.i2p.desktopgui.desktopgui.*;
 import java.awt.AWTException;
 import java.awt.Desktop;
 import java.awt.Image;
@@ -23,9 +23,9 @@ import java.util.logging.Logger;
 import javax.swing.JMenu;
 import javax.swing.JMenuItem;
 import javax.swing.JPopupMenu;
-import router.RouterHandler;
-import router.RouterHelper;
-import router.configuration.PeerHelper;
+import net.i2p.desktopgui.router.RouterHandler;
+import net.i2p.desktopgui.router.RouterHelper;
+import net.i2p.desktopgui.router.configuration.PeerHelper;
 
 /**
  *
diff --git a/apps/desktopgui/src/gui/Version.form b/apps/desktopgui/src/net/i2p/desktopgui/gui/Version.form
similarity index 79%
rename from apps/desktopgui/src/gui/Version.form
rename to apps/desktopgui/src/net/i2p/desktopgui/gui/Version.form
index 20df8621a0c244ba59e834ca5543fcf3731bf219..32ab90b311d119e3fa4034aa8480e47ccbc3cdec 100644
--- a/apps/desktopgui/src/gui/Version.form
+++ b/apps/desktopgui/src/net/i2p/desktopgui/gui/Version.form
@@ -3,6 +3,7 @@
 <Form version="1.5" maxVersion="1.6" type="org.netbeans.modules.form.forminfo.JDialogFormInfo">
   <Properties>
     <Property name="defaultCloseOperation" type="int" value="2"/>
+    <Property name="title" type="java.lang.String" resourceKey="Form.title"/>
     <Property name="name" type="java.lang.String" value="Form" noResource="true"/>
   </Properties>
   <SyntheticProperties>
@@ -11,6 +12,7 @@
   <AuxValues>
     <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="2"/>
     <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="true"/>
+    <AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
     <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
     <AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
     <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
@@ -22,24 +24,24 @@
   <Layout>
     <DimensionLayout dim="0">
       <Group type="103" groupAlignment="0" attributes="0">
-          <Group type="102" alignment="0" attributes="0">
+          <Group type="102" attributes="0">
               <EmptySpace max="-2" attributes="0"/>
-              <Group type="103" groupAlignment="1" max="-2" attributes="0">
-                  <Component id="I2Plabel" alignment="0" max="32767" attributes="0"/>
-                  <Component id="GUILabel" alignment="0" max="32767" attributes="1"/>
-              </Group>
-              <EmptySpace type="separate" max="-2" attributes="0"/>
               <Group type="103" groupAlignment="0" attributes="0">
-                  <Component id="I2PVersion" pref="126" max="32767" attributes="0"/>
-                  <Component id="GUIVersion" pref="126" max="32767" attributes="0"/>
+                  <Group type="102" alignment="0" attributes="0">
+                      <Group type="103" groupAlignment="1" max="-2" attributes="0">
+                          <Component id="I2Plabel" alignment="0" max="32767" attributes="0"/>
+                          <Component id="GUILabel" alignment="0" max="32767" attributes="1"/>
+                      </Group>
+                      <EmptySpace type="separate" max="-2" attributes="0"/>
+                      <Group type="103" groupAlignment="0" attributes="0">
+                          <Component id="I2PVersion" pref="126" max="32767" attributes="0"/>
+                          <Component id="GUIVersion" pref="126" max="32767" attributes="0"/>
+                      </Group>
+                  </Group>
+                  <Component id="okButton" alignment="1" min="-2" max="-2" attributes="0"/>
               </Group>
               <EmptySpace max="-2" attributes="0"/>
           </Group>
-          <Group type="102" alignment="1" attributes="0">
-              <EmptySpace pref="294" max="32767" attributes="0"/>
-              <Component id="okButton" min="-2" max="-2" attributes="0"/>
-              <EmptySpace max="-2" attributes="0"/>
-          </Group>
       </Group>
     </DimensionLayout>
     <DimensionLayout dim="1">
diff --git a/apps/desktopgui/src/gui/Version.java b/apps/desktopgui/src/net/i2p/desktopgui/gui/Version.java
similarity index 75%
rename from apps/desktopgui/src/gui/Version.java
rename to apps/desktopgui/src/net/i2p/desktopgui/gui/Version.java
index 3e5a478c59a15f5c5ef6576e5f5261830f2a05f1..25a38d6b8ffa7785db79aee870680e9ed9fcd818 100644
--- a/apps/desktopgui/src/gui/Version.java
+++ b/apps/desktopgui/src/net/i2p/desktopgui/gui/Version.java
@@ -4,10 +4,10 @@
  * Created on 13 april 2009, 13:48
  */
 
-package gui;
+package net.i2p.desktopgui.gui;
 
 import javax.swing.JFrame;
-import router.RouterHelper;
+import net.i2p.desktopgui.router.RouterHelper;
 
 /**
  *
@@ -24,7 +24,7 @@ public class Version extends javax.swing.JDialog {
         super(parent, modal);
         initComponents();
         String i2pVersion = RouterHelper.getVersion();
-        String guiVersion = desktopgui.GUIVersion.VERSION;
+        String guiVersion = net.i2p.desktopgui.desktopgui.GUIVersion.VERSION;
         this.I2PVersion.setText("<html><h1>" + i2pVersion + "</h1></html>");
         this.GUIVersion.setText("<html><h1>" + guiVersion + "</h1></html>");
         this.setVisible(true);
@@ -46,9 +46,10 @@ public class Version extends javax.swing.JDialog {
         GUIVersion = new javax.swing.JLabel();
 
         setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
+        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(net.i2p.desktopgui.desktopgui.Main.class).getContext().getResourceMap(Version.class);
+        setTitle(resourceMap.getString("Form.title")); // NOI18N
         setName("Form"); // NOI18N
 
-        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopgui.Main.class).getContext().getResourceMap(Version.class);
         okButton.setText(resourceMap.getString("okButton.text")); // NOI18N
         okButton.setName("okButton"); // NOI18N
         okButton.addActionListener(new java.awt.event.ActionListener() {
@@ -75,17 +76,16 @@ public class Version extends javax.swing.JDialog {
             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
             .addGroup(layout.createSequentialGroup()
                 .addContainerGap()
-                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
-                    .addComponent(I2Plabel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
-                    .addComponent(GUILabel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
-                .addGap(18, 18, 18)
                 .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
-                    .addComponent(I2PVersion, javax.swing.GroupLayout.DEFAULT_SIZE, 126, Short.MAX_VALUE)
-                    .addComponent(GUIVersion, javax.swing.GroupLayout.DEFAULT_SIZE, 126, Short.MAX_VALUE))
-                .addContainerGap())
-            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
-                .addContainerGap(294, Short.MAX_VALUE)
-                .addComponent(okButton)
+                    .addGroup(layout.createSequentialGroup()
+                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
+                            .addComponent(I2Plabel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+                            .addComponent(GUILabel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+                        .addGap(18, 18, 18)
+                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+                            .addComponent(I2PVersion, javax.swing.GroupLayout.DEFAULT_SIZE, 126, Short.MAX_VALUE)
+                            .addComponent(GUIVersion, javax.swing.GroupLayout.DEFAULT_SIZE, 126, Short.MAX_VALUE)))
+                    .addComponent(okButton, javax.swing.GroupLayout.Alignment.TRAILING))
                 .addContainerGap())
         );
         layout.setVerticalGroup(
diff --git a/apps/desktopgui/src/net/i2p/desktopgui/gui/resources/GeneralConfiguration.properties b/apps/desktopgui/src/net/i2p/desktopgui/gui/resources/GeneralConfiguration.properties
new file mode 100644
index 0000000000000000000000000000000000000000..e1c16a08cb12f393dacb7b3190ecbe899c11a529
--- /dev/null
+++ b/apps/desktopgui/src/net/i2p/desktopgui/gui/resources/GeneralConfiguration.properties
@@ -0,0 +1,29 @@
+cancel.text=Cancel
+ok.text=OK
+jLabel3.text=Monthly usage:
+jLabel4.text=Monthly usage:
+jLabel5.text=GB
+jLabel6.text=GB
+jLabel7.text=Explanation ...
+Form.title=General Configuration
+speedPanel.TabConstraints.tabTitle=Speed
+updatesPanel.TabConstraints.tabTitle=Updates
+tunnelPanel.TabConstraints.tabTitle=Tunnels/Services
+networkPanel.TabConstraints.tabTitle=Network
+advancedPanel.TabConstraints.tabTitle=Advanced
+uploadSpeedLabel.text=Upload speed:
+downloadSpeedLabel.text=Download speed:
+uploadspeed.text=jTextField1
+downloadspeed.text=jTextField2
+uploadgb.text=jTextField3
+downloadgb.text=jTextField4
+updateMethod.text=What is your preferred automatic update setting?
+updateInform.text=Only inform about updates
+updateDownload.text=Download and verify update file, do not restart
+updateDownloadRestart.text=Download, verify and restart
+checkUpdates.text=Check for updates now
+updateNow.text=Update available: update now
+advancedUpdateConfig.text=Advanced update configuration
+clientTunnelLabel.text=Client tunnels:
+serverTunnelLabel.text=Server tunnels:
+tunnelsExplanation.text=Tunnel explanation
diff --git a/apps/desktopgui/src/gui/resources/LogViewer.properties b/apps/desktopgui/src/net/i2p/desktopgui/gui/resources/LogViewer.properties
similarity index 80%
rename from apps/desktopgui/src/gui/resources/LogViewer.properties
rename to apps/desktopgui/src/net/i2p/desktopgui/gui/resources/LogViewer.properties
index 8cb0e620913951f63269300844bf5f732f5f550f..d4ef5bf0e0c6c495848c2cd8f8988133c684121e 100644
--- a/apps/desktopgui/src/gui/resources/LogViewer.properties
+++ b/apps/desktopgui/src/net/i2p/desktopgui/gui/resources/LogViewer.properties
@@ -1,3 +1,4 @@
 refreshButton.text=Refresh
 clearButton.text=Clear
 explanationText.text=Explanation ...
+Form.title=View Logs
diff --git a/apps/desktopgui/src/gui/resources/SpeedSelector.properties b/apps/desktopgui/src/net/i2p/desktopgui/gui/resources/SpeedSelector.properties
similarity index 100%
rename from apps/desktopgui/src/gui/resources/SpeedSelector.properties
rename to apps/desktopgui/src/net/i2p/desktopgui/gui/resources/SpeedSelector.properties
diff --git a/apps/desktopgui/src/gui/resources/SpeedSelector2.properties b/apps/desktopgui/src/net/i2p/desktopgui/gui/resources/SpeedSelector2.properties
similarity index 100%
rename from apps/desktopgui/src/gui/resources/SpeedSelector2.properties
rename to apps/desktopgui/src/net/i2p/desktopgui/gui/resources/SpeedSelector2.properties
diff --git a/apps/desktopgui/src/gui/resources/SpeedSelector3.properties b/apps/desktopgui/src/net/i2p/desktopgui/gui/resources/SpeedSelector3.properties
similarity index 92%
rename from apps/desktopgui/src/gui/resources/SpeedSelector3.properties
rename to apps/desktopgui/src/net/i2p/desktopgui/gui/resources/SpeedSelector3.properties
index 5b338348cf2e778a6a37fc3109aac5a169747a9e..6dca3ca51603dd9a6667aea2588bda0812aecd5f 100644
--- a/apps/desktopgui/src/gui/resources/SpeedSelector3.properties
+++ b/apps/desktopgui/src/net/i2p/desktopgui/gui/resources/SpeedSelector3.properties
@@ -5,8 +5,8 @@ uploadLabel.text=Upload Speed:
 uploadBurstLabel.text=Burst Upload Speed:
 downloadLabel.text=Download Speed:
 downloadBurstLabel.text=Burst Download Speed:
-uploadUsageLabel.text=Monthly usage:
-downloadUsageLabel.text=Monthly usage:
+uploadUsageLabel.text=GB
+downloadUsageLabel.text=GB
 uploadField.text=jTextField1
 uploadBurstField.text=jTextField2
 downloadField.text=jTextField4
diff --git a/apps/desktopgui/src/gui/resources/Version.properties b/apps/desktopgui/src/net/i2p/desktopgui/gui/resources/Version.properties
similarity index 89%
rename from apps/desktopgui/src/gui/resources/Version.properties
rename to apps/desktopgui/src/net/i2p/desktopgui/gui/resources/Version.properties
index 91ee786ad12716094d21e405f0619bf11676ea82..c2030bb2d838b6859ded31c3fee5ef30beeba8f0 100644
--- a/apps/desktopgui/src/gui/resources/Version.properties
+++ b/apps/desktopgui/src/net/i2p/desktopgui/gui/resources/Version.properties
@@ -3,3 +3,4 @@ I2Plabel.text=<html><h1>I2P Version:</h1></html>
 GUILabel.text=<html><h1>GUI Version:</h1></html>
 I2PVersion.text=jLabel3
 GUIVersion.text=jLabel4
+Form.title=Version
diff --git a/apps/desktopgui/src/persistence/PropertyManager.java b/apps/desktopgui/src/net/i2p/desktopgui/persistence/PropertyManager.java
similarity index 98%
rename from apps/desktopgui/src/persistence/PropertyManager.java
rename to apps/desktopgui/src/net/i2p/desktopgui/persistence/PropertyManager.java
index bacbf348ac4b0d645ab2d7913f9c0b32dd7df989..770bd09570f16b90522e0042c183f792ccd58737 100644
--- a/apps/desktopgui/src/persistence/PropertyManager.java
+++ b/apps/desktopgui/src/net/i2p/desktopgui/persistence/PropertyManager.java
@@ -1,4 +1,4 @@
-package persistence;
+package net.i2p.desktopgui.persistence;
 
 import java.io.File;
 import java.io.FileInputStream;
diff --git a/apps/desktopgui/src/router/RouterHandler.java b/apps/desktopgui/src/net/i2p/desktopgui/router/RouterHandler.java
similarity index 97%
rename from apps/desktopgui/src/router/RouterHandler.java
rename to apps/desktopgui/src/net/i2p/desktopgui/router/RouterHandler.java
index 0752f877b516cc618a72b71ab14e49be3c619157..022c5c8149659087f48433ee05b7ced2a9dfa7ef 100644
--- a/apps/desktopgui/src/router/RouterHandler.java
+++ b/apps/desktopgui/src/net/i2p/desktopgui/router/RouterHandler.java
@@ -3,7 +3,7 @@
  * and open the template in the editor.
  */
 
-package router;
+package net.i2p.desktopgui.router;
 
 import java.util.logging.Level;
 import java.util.logging.Logger;
diff --git a/apps/desktopgui/src/router/RouterHelper.java b/apps/desktopgui/src/net/i2p/desktopgui/router/RouterHelper.java
similarity index 93%
rename from apps/desktopgui/src/router/RouterHelper.java
rename to apps/desktopgui/src/net/i2p/desktopgui/router/RouterHelper.java
index 49a7c8b29a2fe8632ba1dd4d935db9332f8497be..2da95f0c6a86d41f4e975532d689fc438922fa0a 100644
--- a/apps/desktopgui/src/router/RouterHelper.java
+++ b/apps/desktopgui/src/net/i2p/desktopgui/router/RouterHelper.java
@@ -1,4 +1,4 @@
-package router;
+package net.i2p.desktopgui.router;
 
 import net.i2p.router.RouterContext;
 import net.i2p.router.RouterVersion;
diff --git a/apps/desktopgui/src/router/configuration/PeerHelper.java b/apps/desktopgui/src/net/i2p/desktopgui/router/configuration/PeerHelper.java
similarity index 98%
rename from apps/desktopgui/src/router/configuration/PeerHelper.java
rename to apps/desktopgui/src/net/i2p/desktopgui/router/configuration/PeerHelper.java
index 76ffeef8777077fcf1b131d8943da125d29521f6..74df2b3d99ec0186ebda188743feb25bdbafa195 100644
--- a/apps/desktopgui/src/router/configuration/PeerHelper.java
+++ b/apps/desktopgui/src/net/i2p/desktopgui/router/configuration/PeerHelper.java
@@ -1,4 +1,4 @@
-package router.configuration;
+package net.i2p.desktopgui.router.configuration;
 
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
@@ -11,7 +11,7 @@ import net.i2p.router.CommSystemFacade;
 import net.i2p.router.RouterContext;
 import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
 import net.i2p.router.transport.ntcp.NTCPAddress;
-import router.RouterHelper;
+import net.i2p.desktopgui.router.RouterHelper;
 
 /**
  * Part of the code imported and adapted from the I2P Router Console (which is licensed as public domain)
diff --git a/apps/desktopgui/src/router/configuration/SpeedHandler.java b/apps/desktopgui/src/net/i2p/desktopgui/router/configuration/SpeedHandler.java
similarity index 91%
rename from apps/desktopgui/src/router/configuration/SpeedHandler.java
rename to apps/desktopgui/src/net/i2p/desktopgui/router/configuration/SpeedHandler.java
index 235790792ac539e7e6120efa2220026b1b34548a..8fd67565787d35322c96c2eb82207ff0e1769118 100644
--- a/apps/desktopgui/src/router/configuration/SpeedHandler.java
+++ b/apps/desktopgui/src/net/i2p/desktopgui/router/configuration/SpeedHandler.java
@@ -1,8 +1,8 @@
-package router.configuration;
+package net.i2p.desktopgui.router.configuration;
 
 import net.i2p.router.RouterContext;
 import net.i2p.router.transport.FIFOBandwidthRefiller;
-import router.RouterHelper;
+import net.i2p.desktopgui.router.RouterHelper;
 
 /**
  *
diff --git a/apps/desktopgui/src/router/configuration/SpeedHelper.java b/apps/desktopgui/src/net/i2p/desktopgui/router/configuration/SpeedHelper.java
similarity index 77%
rename from apps/desktopgui/src/router/configuration/SpeedHelper.java
rename to apps/desktopgui/src/net/i2p/desktopgui/router/configuration/SpeedHelper.java
index acad2adb146d65403c3c497b9b7364c139f3dc09..5d2074de2c76b3ce893caee53947a32646787d84 100644
--- a/apps/desktopgui/src/router/configuration/SpeedHelper.java
+++ b/apps/desktopgui/src/net/i2p/desktopgui/router/configuration/SpeedHelper.java
@@ -1,4 +1,7 @@
-package router.configuration;
+package net.i2p.desktopgui.router.configuration;
+
+import net.i2p.router.transport.FIFOBandwidthRefiller;
+import net.i2p.desktopgui.router.RouterHelper;
 
 /**
  *
@@ -29,4 +32,8 @@ public class SpeedHelper {
     public static int calculateSpeed(int gigabytes) {
         return (int) (((long)gigabytes)*1000000/31/24/3600);
     }
+    
+    public static String getInboundBandwidth() {
+        return RouterHelper.getContext().router().getConfigSetting(FIFOBandwidthRefiller.PROP_INBOUND_BANDWIDTH);
+    }
 }
diff --git a/apps/desktopgui/src/util/IntegerVerifier.java b/apps/desktopgui/src/net/i2p/desktopgui/util/IntegerVerifier.java
similarity index 95%
rename from apps/desktopgui/src/util/IntegerVerifier.java
rename to apps/desktopgui/src/net/i2p/desktopgui/util/IntegerVerifier.java
index 74f87961daa02897cff605f36aadcba95314f9a0..7cc0fb46ae5ccd8b676a5f33137909e3d5c36dd9 100644
--- a/apps/desktopgui/src/util/IntegerVerifier.java
+++ b/apps/desktopgui/src/net/i2p/desktopgui/util/IntegerVerifier.java
@@ -1,4 +1,4 @@
-package util;
+package net.i2p.desktopgui.util;
 
 import javax.swing.InputVerifier;
 import javax.swing.JComponent;
diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
index 515594219e378dcd246b52d4415c76b254d5e5ba..3923484a80901882de9735eff80905314df77516 100644
--- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
+++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java
@@ -587,6 +587,8 @@ public class I2PSnarkServlet extends HttpServlet {
                     client = "I2PSnarkXL";
                 else if ("ZV".equals(ch.substring(2,4)))
                     client = "Robert";
+                else if ("VUZP".equals(ch))
+                    client = "Robert";
                 else
                     client = "Unknown (" + ch + ')';
                 out.write("<font size=-1>" + client + "</font>&nbsp;&nbsp;<tt>" + peer.toString().substring(5, 9) + "</tt>");
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
index 015f5c9c5811571cb320cc981dcef5b1727de7ab..8e76273098154ca34df4fd706588654cc23c5847 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnel.java
@@ -92,7 +92,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
     private final List tasks = new ArrayList();
     private int next_task_id = 1;
 
-    private Set listeners = new HashSet();
+    private final Set listeners = new HashSet();
 
     public static void main(String[] args) throws IOException {
         new I2PTunnel(args);
@@ -668,9 +668,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
      */
     public void runConnectClient(String args[], Logging l) {
         if (args.length >= 1 && args.length <= 3) {
-            int port = -1;
+            int _port = -1;
             try {
-                port = Integer.parseInt(args[0]);
+                _port = Integer.parseInt(args[0]);
             } catch (NumberFormatException nfe) {
                 _log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
                 return;
@@ -702,10 +702,10 @@ public class I2PTunnel implements Logging, EventDispatcher {
             I2PTunnelTask task;
             ownDest = !isShared;
             try {
-                task = new I2PTunnelConnectClient(port, l, ownDest, proxy, (EventDispatcher) this, this);
+                task = new I2PTunnelConnectClient(_port, l, ownDest, proxy, (EventDispatcher) this, this);
                 addtask(task);
             } catch (IllegalArgumentException iae) {
-                _log.error(getPrefix() + "Invalid I2PTunnel config to create an httpclient [" + host + ":"+ port + "]", iae);
+                _log.error(getPrefix() + "Invalid I2PTunnel config to create an httpclient [" + host + ":"+ _port + "]", iae);
             }
         } else {
             l.log("connectclient <port> [<sharedClient>] [<proxy>]");
@@ -728,9 +728,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
      */
     public void runIrcClient(String args[], Logging l) {
         if (args.length >= 2) {
-            int port = -1;
+            int _port = -1;
             try {
-                port = Integer.parseInt(args[0]);
+                _port = Integer.parseInt(args[0]);
             } catch (NumberFormatException nfe) {
                 l.log("invalid port");
                 _log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
@@ -757,12 +757,12 @@ public class I2PTunnel implements Logging, EventDispatcher {
                 String privateKeyFile = null;
                 if (args.length >= 4)
                     privateKeyFile = args[3];
-                task = new I2PTunnelIRCClient(port, args[1], l, ownDest, (EventDispatcher) this, this, privateKeyFile);
+                task = new I2PTunnelIRCClient(_port, args[1], l, ownDest, (EventDispatcher) this, this, privateKeyFile);
                 addtask(task);
                 notifyEvent("ircclientTaskId", Integer.valueOf(task.getId()));
             } catch (IllegalArgumentException iae) {
-                _log.error(getPrefix() + "Invalid I2PTunnel config to create an ircclient [" + host + ":"+ port + "]", iae);
-                l.log("Invalid I2PTunnel configuration [" + host + ":" + port + "]");
+                _log.error(getPrefix() + "Invalid I2PTunnel config to create an ircclient [" + host + ":"+ _port + "]", iae);
+                l.log("Invalid I2PTunnel configuration [" + host + ":" + _port + "]");
                 notifyEvent("ircclientTaskId", Integer.valueOf(-1));
             }
         } else {
@@ -786,9 +786,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
      */
     public void runSOCKSTunnel(String args[], Logging l) {
         if (args.length >= 1 && args.length <= 2) {
-            int port = -1;
+            int _port = -1;
             try {
-                port = Integer.parseInt(args[0]);
+                _port = Integer.parseInt(args[0]);
             } catch (NumberFormatException nfe) {
                 l.log("invalid port");
                 _log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
@@ -802,7 +802,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
 
             ownDest = !isShared;
             I2PTunnelTask task;
-            task = new I2PSOCKSTunnel(port, l, ownDest, (EventDispatcher) this, this);
+            task = new I2PSOCKSTunnel(_port, l, ownDest, (EventDispatcher) this, this);
             addtask(task);
             notifyEvent("sockstunnelTaskId", Integer.valueOf(task.getId()));
         } else {
@@ -820,9 +820,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
      */
     public void runStreamrClient(String args[], Logging l) {
         if (args.length == 3) {
-            InetAddress host;
+            InetAddress _host;
             try {
-                host = InetAddress.getByName(args[0]);
+                _host = InetAddress.getByName(args[0]);
             } catch (UnknownHostException uhe) {
                 l.log("unknown host");
                 _log.error(getPrefix() + "Error resolving " + args[0], uhe);
@@ -830,9 +830,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
                 return;
             }
 
-            int port = -1;
+            int _port = -1;
             try {
-                port = Integer.parseInt(args[1]);
+                _port = Integer.parseInt(args[1]);
             } catch (NumberFormatException nfe) {
                 l.log("invalid port");
                 _log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
@@ -840,7 +840,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
                 return;
             }
 
-            StreamrConsumer task = new StreamrConsumer(host, port, args[2], l, (EventDispatcher) this, this);
+            StreamrConsumer task = new StreamrConsumer(_host, _port, args[2], l, (EventDispatcher) this, this);
             task.startRunning();
             addtask(task);
             notifyEvent("streamrtunnelTaskId", Integer.valueOf(task.getId()));
@@ -859,9 +859,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
      */
     public void runStreamrServer(String args[], Logging l) {
         if (args.length == 2) {
-            int port = -1;
+            int _port = -1;
             try {
-                port = Integer.parseInt(args[0]);
+                _port = Integer.parseInt(args[0]);
             } catch (NumberFormatException nfe) {
                 l.log("invalid port");
                 _log.error(getPrefix() + "Port specified is not valid: " + args[0], nfe);
@@ -877,7 +877,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
                 return;
             }
 
-            StreamrProducer task = new StreamrProducer(port, privKeyFile, args[1], l, (EventDispatcher) this, this);
+            StreamrProducer task = new StreamrProducer(_port, privKeyFile, args[1], l, (EventDispatcher) this, this);
             task.startRunning();
             addtask(task);
             notifyEvent("streamrtunnelTaskId", Integer.valueOf(task.getId()));
diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocketImpl.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocketImpl.java
index 93db8595b7024d2fbbccdffa58dfb83de52ee1d9..662fd9e5721eb40de7446f84a59006ab473711af 100644
--- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocketImpl.java
+++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PServerSocketImpl.java
@@ -20,7 +20,7 @@ class I2PServerSocketImpl implements I2PServerSocket {
     private final static Log _log = new Log(I2PServerSocketImpl.class);
     private I2PSocketManager mgr;
     /** list of sockets waiting for the client to accept them */
-    private List pendingSockets = Collections.synchronizedList(new ArrayList(4));
+    private List<I2PSocket> pendingSockets = Collections.synchronizedList(new ArrayList<I2PSocket>(4));
     
     /** have we been closed */
     private volatile boolean closing = false;
diff --git a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerImpl.java b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerImpl.java
index 406f718474e934677b583b869873e9a5b16365f6..b40b9091f79e79caa8dae5faa44b713742b63b91 100644
--- a/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerImpl.java
+++ b/apps/ministreaming/java/src/net/i2p/client/streaming/I2PSocketManagerImpl.java
@@ -43,12 +43,12 @@ class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListener {
     private I2PSession _session;
     private I2PServerSocketImpl _serverSocket = null;
     private Object lock = new Object(); // for locking socket lists
-    private HashMap _outSockets;
-    private HashMap _inSockets;
+    private HashMap<String,I2PSocket> _outSockets;
+    private HashMap<String,I2PSocket> _inSockets;
     private I2PSocketOptions _defaultOptions;
     private long _acceptTimeout;
     private String _name;
-    private List _listeners;
+    private List<DisconnectListener> _listeners;
     private static int __managerId = 0;
     
     public static final short ACK = 0x51;
@@ -76,10 +76,10 @@ class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListener {
         _name = name;
         _context = context;
         _log = _context.logManager().getLog(I2PSocketManager.class);
-        _inSockets = new HashMap(16);
-        _outSockets = new HashMap(16);
+        _inSockets = new HashMap<String,I2PSocket>(16);
+        _outSockets = new HashMap<String,I2PSocket>(16);
         _acceptTimeout = ACCEPT_TIMEOUT_DEFAULT;
-        _listeners = new ArrayList(1);
+        _listeners = new ArrayList<DisconnectListener>(1);
         setSession(session);
         setDefaultOptions(buildOptions(opts));
         _context.statManager().createRateStat("streaming.lifetime", "How long before the socket is closed?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
@@ -113,9 +113,9 @@ class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListener {
     public void disconnected(I2PSession session) {
         _log.info(getName() + ": Disconnected from the session");
         destroySocketManager();
-        List listeners = null;
+        List<DisconnectListener> listeners = null;
         synchronized (_listeners) {
-            listeners = new ArrayList(_listeners);
+            listeners = new ArrayList<DisconnectListener>(_listeners);
             _listeners.clear();
         }
         for (int i = 0; i < listeners.size(); i++) {
@@ -660,7 +660,7 @@ class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListener {
      *
      */
     public Set listSockets() {
-        Set sockets = new HashSet(8);
+        Set<I2PSocket> sockets = new HashSet<I2PSocket>(8);
         synchronized (lock) {
             sockets.addAll(_inSockets.values());
             sockets.addAll(_outSockets.values());
diff --git a/apps/sam/Demos/datagramTests/README.txt b/apps/sam/Demos/datagramTests/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8e79434f9541f302533b6e343c5bcd3cf2139fa4
--- /dev/null
+++ b/apps/sam/Demos/datagramTests/README.txt
@@ -0,0 +1,15 @@
+# test example
+
+#in a first terminal, launch :
+ ./samIn.py inTest
+
+#in a second terminal, launch :
+ ./samForward.py 25000 forward
+
+#in a third terminal, launch :
+l=0
+while [ $l -lt 1000 ]
+do 
+   l=$((l+1))
+   ./samOut.py forward this is message n. $l
+done
diff --git a/apps/sam/Demos/datagramTests/samForward.py b/apps/sam/Demos/datagramTests/samForward.py
new file mode 100755
index 0000000000000000000000000000000000000000..a1fd5b3e3bc16adb6c6d3c64993d75829a50330a
--- /dev/null
+++ b/apps/sam/Demos/datagramTests/samForward.py
@@ -0,0 +1,35 @@
+#!/usr/bin/python
+
+import socket
+import sys
+
+# create a forward style SAM datagram session
+# that forwards messages on specified port (default port : 25000)
+# creates a standard datagram server that listens on this port forever
+# usage : ./samForward.py [port [SAM session name]]
+
+if len(sys.argv)>=2 :
+	port = eval(sys.argv[1])
+else :
+	port = 25000
+
+if len(sys.argv)==3 :
+	name = sys.argv[2]
+else :
+	name = "datagramSamForward"
+
+sess = socket.socket(
+    socket.AF_INET, socket.SOCK_STREAM)
+sess.connect(("127.0.0.1",7656));
+sess.send("HELLO VERSION MIN=3.0 MAX=3.0\n")
+sys.stdout.write(sess.recv(1000))
+sess.send("SESSION CREATE STYLE=DATAGRAM PORT="+str(port)+" ID="+name+" DESTINATION=EYUpJFeW9tiubXR0aOjvCJ~ndj3xN0Wn-ljuGdbpOEttPg7nj0VCTOQDJ~FAolzn9FIDdmR3VjM0OFFDT46Q5HN4vShXFE2VNC8e3~GjzxJfaJhijRC2R9oIOzsNlzKtInD2o9lh0PxPioNMCigwmgWuqlQHs4tjWeaYRAtooHxbrtuoCIhIdGfyVV-nAcPiyYbouKq3leETXE~4kBXm-LfWfyPtrv6OuDk3GBVVcthv19GYBmnl2YI8HpJjc-G-TvNkgYishjzIJyEW-Xrpy43R4ZBXlyQqnheGLlbOEY8NLDbyNHLRMMOGbcr~67SVE3Iw3RqQ3Dhrkq2FCaQwcDucfIUCCbOfCZgu0hlnCkS42xsUvegQeiwMxbdI~h9v7vcR3yFFOrHX6WQvIZSbFLKNGArGJcfmOJVLqw1wTC4AgYXjk3csVDPd-QWbMXOuodyBgrg27Ds2BBYTsVXWskoo6ASsMIQZ6jMfL7PkY9dPLCRParIyzb9aPmf~MntNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHNqwgkhJnBW4ymaRsdVmITAha-ff0UiALfKSlznqp5HcSewgMHbzQ0I01TQytFnW outbound.nickname="+name+" inbound.nickname="+name+" outbound.length=0\n")
+sys.stdout.write(sess.recv(10000))
+
+s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+s.bind(("", port))
+print "waiting on port:", port
+while 1:
+    data, addr = s.recvfrom(40000)
+    print data, " received from ", addr, "length=", len(data)
+
diff --git a/apps/sam/Demos/datagramTests/samIn.py b/apps/sam/Demos/datagramTests/samIn.py
new file mode 100755
index 0000000000000000000000000000000000000000..c2c0589f30e2629966e600af50bc057d6dad29b8
--- /dev/null
+++ b/apps/sam/Demos/datagramTests/samIn.py
@@ -0,0 +1,29 @@
+#!/usr/bin/python
+
+
+# create a SAM datagram session that writes incoming messages on its master session stream
+# and a listen forever
+# usage : ./samIn.py [session name]
+
+import socket
+import sys
+
+if len(sys.argv)==2 :
+	name = sys.argv[1]
+else :
+	name = "datagramSamIn"
+
+
+sess = socket.socket(
+    socket.AF_INET, socket.SOCK_STREAM)
+sess.connect(("127.0.0.1",7656));
+sess.send("HELLO VERSION MIN=3.0 MAX=3.0\n")
+sys.stdout.write(sess.recv(1000))
+sess.send("SESSION CREATE STYLE=DATAGRAM ID="+name+" DESTINATION=tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABngJSS8xMyF4t82otZmCDhrKjbm-QLMtOLoumwR28ebDHEd4clF6O7aRa3d3yRH7p\n")
+sys.stdout.write(sess.recv(1000))
+while 1 :
+    chunk = sess.recv(10000)
+    sys.stdout.write(chunk+'\n')
+    if not chunk : break
+print
+
diff --git a/apps/sam/Demos/datagramTests/samOut.py b/apps/sam/Demos/datagramTests/samOut.py
new file mode 100755
index 0000000000000000000000000000000000000000..1a521334f9759e68ad6bef05b0d807a818835ec9
--- /dev/null
+++ b/apps/sam/Demos/datagramTests/samOut.py
@@ -0,0 +1,31 @@
+#!/usr/bin/python
+
+# sends a message to datagram destinations opened by samForward.py and samIn.py, using specified sending session name
+# at least samForward.py should be running for results to be seen
+# usage : ./samOut.py [ sendingSessionName [ message ... ] ]
+#             sendingSessionName : default = datagramSamForward
+#             message : default = "this is nice message"
+
+import socket
+import sys
+import time
+
+if len(sys.argv)>=2 :
+	name = sys.argv[1]
+else :
+	name = "datagramSamForward"
+
+if len(sys.argv)>2 :
+	message = ''.join([s+' ' for s in sys.argv[2:]]).strip()
+else :
+	message = "This is a nice message"
+
+
+# client.py
+port = 7655
+host = "localhost"
+s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+s.bind(("", 0))
+s.sendto("3.0 "+name+" tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAA\n"+message, (host, port))
+s.sendto("3.0 "+name+" EYUpJFeW9tiubXR0aOjvCJ~ndj3xN0Wn-ljuGdbpOEttPg7nj0VCTOQDJ~FAolzn9FIDdmR3VjM0OFFDT46Q5HN4vShXFE2VNC8e3~GjzxJfaJhijRC2R9oIOzsNlzKtInD2o9lh0PxPioNMCigwmgWuqlQHs4tjWeaYRAtooHxbrtuoCIhIdGfyVV-nAcPiyYbouKq3leETXE~4kBXm-LfWfyPtrv6OuDk3GBVVcthv19GYBmnl2YI8HpJjc-G-TvNkgYishjzIJyEW-Xrpy43R4ZBXlyQqnheGLlbOEY8NLDbyNHLRMMOGbcr~67SVE3Iw3RqQ3Dhrkq2FCaQwcDucfIUCCbOfCZgu0hlnCkS42xsUvegQeiwMxbdI~h9v7vcR3yFFOrHX6WQvIZSbFLKNGArGJcfmOJVLqw1wTC4AgYXjk3csVDPd-QWbMXOuodyBgrg27Ds2BBYTsVXWskoo6ASsMIQZ6jMfL7PkY9dPLCRParIyzb9aPmf~MntNAAAA\n"+message, (host, port))
+
diff --git a/apps/sam/Demos/rawTests/README.txt b/apps/sam/Demos/rawTests/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8e79434f9541f302533b6e343c5bcd3cf2139fa4
--- /dev/null
+++ b/apps/sam/Demos/rawTests/README.txt
@@ -0,0 +1,15 @@
+# test example
+
+#in a first terminal, launch :
+ ./samIn.py inTest
+
+#in a second terminal, launch :
+ ./samForward.py 25000 forward
+
+#in a third terminal, launch :
+l=0
+while [ $l -lt 1000 ]
+do 
+   l=$((l+1))
+   ./samOut.py forward this is message n. $l
+done
diff --git a/apps/sam/Demos/rawTests/samForward.py b/apps/sam/Demos/rawTests/samForward.py
new file mode 100755
index 0000000000000000000000000000000000000000..5a65b6614afaafb0f3f48a43a7b5bde1f4285343
--- /dev/null
+++ b/apps/sam/Demos/rawTests/samForward.py
@@ -0,0 +1,36 @@
+#!/usr/bin/python
+
+import socket
+import sys
+
+# create a forward style SAM raw datagram session
+# that forwards messages on specified port (default port : 25000)
+# creates a standard datagram server that listens on this port forever
+# usage : ./samForward.py [port [SAM session name]]
+
+if len(sys.argv)>=2 :
+	port = eval(sys.argv[1])
+else :
+	port = 25000
+
+if len(sys.argv)==3 :
+	name = sys.argv[2]
+else :
+	name = "rawSamForward"
+
+sess = socket.socket(
+    socket.AF_INET, socket.SOCK_STREAM)
+sess.connect(("127.0.0.1",7656));
+sess.send("HELLO VERSION MIN=3.0 MAX=3.0\n")
+sys.stdout.write(sess.recv(1000))
+sess.send("SESSION CREATE STYLE=RAW PORT="+str(port)+" ID="+name+" DESTINATION=EYUpJFeW9tiubXR0aOjvCJ~ndj3xN0Wn-ljuGdbpOEttPg7nj0VCTOQDJ~FAolzn9FIDdmR3VjM0OFFDT46Q5HN4vShXFE2VNC8e3~GjzxJfaJhijRC2R9oIOzsNlzKtInD2o9lh0PxPioNMCigwmgWuqlQHs4tjWeaYRAtooHxbrtuoCIhIdGfyVV-nAcPiyYbouKq3leETXE~4kBXm-LfWfyPtrv6OuDk3GBVVcthv19GYBmnl2YI8HpJjc-G-TvNkgYishjzIJyEW-Xrpy43R4ZBXlyQqnheGLlbOEY8NLDbyNHLRMMOGbcr~67SVE3Iw3RqQ3Dhrkq2FCaQwcDucfIUCCbOfCZgu0hlnCkS42xsUvegQeiwMxbdI~h9v7vcR3yFFOrHX6WQvIZSbFLKNGArGJcfmOJVLqw1wTC4AgYXjk3csVDPd-QWbMXOuodyBgrg27Ds2BBYTsVXWskoo6ASsMIQZ6jMfL7PkY9dPLCRParIyzb9aPmf~MntNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHNqwgkhJnBW4ymaRsdVmITAha-ff0UiALfKSlznqp5HcSewgMHbzQ0I01TQytFnW\n")
+sys.stdout.write(sess.recv(10000))
+
+# listening server
+s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+s.bind(("", port))
+print "waiting on port:", port
+while 1:
+    data, addr = s.recvfrom(40000)
+    print data, " received from ", addr, "length=", len(data)
+
diff --git a/apps/sam/Demos/rawTests/samIn.py b/apps/sam/Demos/rawTests/samIn.py
new file mode 100755
index 0000000000000000000000000000000000000000..ee2c3cbc8b8abb8f043cc6d7a1066962a42f81f2
--- /dev/null
+++ b/apps/sam/Demos/rawTests/samIn.py
@@ -0,0 +1,31 @@
+#!/usr/bin/python
+
+
+# create a SAM datagram session that writes incoming messages on its master session stream
+# and a listen forever
+# usage : ./samIn.py [session name]
+
+import socket
+import sys
+
+if len(sys.argv)==2 :
+	name = sys.argv[1]
+else :
+	name = "rawSamIn"
+
+
+sess = socket.socket(
+    socket.AF_INET, socket.SOCK_STREAM)
+sess.connect(("127.0.0.1",7656));
+sess.send("HELLO VERSION MIN=3.0 MAX=3.0\n")
+sys.stdout.write(sess.recv(1000))
+sess.send("SESSION CREATE STYLE=RAW ID="+name+" DESTINATION=tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABngJSS8xMyF4t82otZmCDhrKjbm-QLMtOLoumwR28ebDHEd4clF6O7aRa3d3yRH7p\n")
+sys.stdout.write(sess.recv(1000))
+
+# listen incoming messages
+while 1 :
+    chunk = sess.recv(10000)
+    sys.stdout.write(chunk+'\n')
+    if not chunk : break
+print
+
diff --git a/apps/sam/Demos/rawTests/samOut.py b/apps/sam/Demos/rawTests/samOut.py
new file mode 100755
index 0000000000000000000000000000000000000000..b035d8997e7e9a0526de9dc41f4d6dacad74c188
--- /dev/null
+++ b/apps/sam/Demos/rawTests/samOut.py
@@ -0,0 +1,31 @@
+#!/usr/bin/python
+
+# sends a message to datagram destinations opened by samForward.py and samIn.py, using specified sending session name
+# at least samForward.py should be running for results to be seen
+# usage : ./samOut.py [ sendingSessionName [ message ... ] ]
+#             sendingSessionName : default = datagramSamForward
+#             message : default = "this is nice message"
+
+import socket
+import sys
+import time
+
+if len(sys.argv)>=2 :
+	name = sys.argv[1]
+else :
+	name = "rawSamForward"
+
+if len(sys.argv)>2 :
+	message = ''.join([s+' ' for s in sys.argv[2:]]).strip()
+else :
+	message = "This is a nice message"
+
+
+# client.py
+port = 7655
+host = "localhost"
+s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+s.bind(("", 0))
+s.sendto("3.0 "+name+" tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAA\n"+message, (host, port))
+s.sendto("3.0 "+name+" EYUpJFeW9tiubXR0aOjvCJ~ndj3xN0Wn-ljuGdbpOEttPg7nj0VCTOQDJ~FAolzn9FIDdmR3VjM0OFFDT46Q5HN4vShXFE2VNC8e3~GjzxJfaJhijRC2R9oIOzsNlzKtInD2o9lh0PxPioNMCigwmgWuqlQHs4tjWeaYRAtooHxbrtuoCIhIdGfyVV-nAcPiyYbouKq3leETXE~4kBXm-LfWfyPtrv6OuDk3GBVVcthv19GYBmnl2YI8HpJjc-G-TvNkgYishjzIJyEW-Xrpy43R4ZBXlyQqnheGLlbOEY8NLDbyNHLRMMOGbcr~67SVE3Iw3RqQ3Dhrkq2FCaQwcDucfIUCCbOfCZgu0hlnCkS42xsUvegQeiwMxbdI~h9v7vcR3yFFOrHX6WQvIZSbFLKNGArGJcfmOJVLqw1wTC4AgYXjk3csVDPd-QWbMXOuodyBgrg27Ds2BBYTsVXWskoo6ASsMIQZ6jMfL7PkY9dPLCRParIyzb9aPmf~MntNAAAA\n"+message, (host, port))
+
diff --git a/apps/sam/Demos/streamTests/README.txt b/apps/sam/Demos/streamTests/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6ce7ae57b53131aacb0bafac2736f46ea30b33de
--- /dev/null
+++ b/apps/sam/Demos/streamTests/README.txt
@@ -0,0 +1,24 @@
+# test example
+
+#in a first terminal, launch :
+ ./samIn.py inTest
+
+#in a second terminal, launch :
+ ./samOut.py
+
+#and again
+ ./samOut.py
+
+##########
+
+# test example n°2
+
+#in a first terminal, launch :
+ ./samForward.py inTest
+
+#in a second terminal, launch :
+ ./server.py
+
+#in a third terminal, launch :
+ ./samOut.py
+
diff --git a/apps/sam/Demos/streamTests/samForward.py b/apps/sam/Demos/streamTests/samForward.py
new file mode 100755
index 0000000000000000000000000000000000000000..687b265f6d6f26d6d8a1718419376948903cf012
--- /dev/null
+++ b/apps/sam/Demos/streamTests/samForward.py
@@ -0,0 +1,64 @@
+#!/usr/bin/python
+
+import socket
+import sys
+
+# create a master SAM stream session that opens a destination in I2P world
+# then open another session that tells SAM to forward incoming connections
+#  to the specified address
+#
+# usage :
+#        ./samForward.py [ silent [ port [ sessionName [ host ] ] ] ]
+#
+#           silent : should the first line of incoming socket contain the peer destination (true or false)
+#           port : port to which connections are forwarded (default : 25000)
+#           sessionName : session id (default : "forward")
+#           host : host to which connections are forwarded (default : this host)
+
+if len(sys.argv)>=2 :
+	silent = " SILENT="+sys.argv[1]
+else :  silent = " SILENT=false"
+
+if len(sys.argv)>=3 :
+	port = " PORT="+sys.argv[2]
+else :  port = " PORT=25000"
+
+if len(sys.argv)>=4 :
+	name = " ID="+sys.argv[3]
+else :  name = " ID=forward"
+
+if len(sys.argv)>=5 :
+	host = " HOST="+sys.argv[4]
+else :  host = ""
+
+
+
+
+sess = socket.socket(
+    socket.AF_INET, socket.SOCK_STREAM)
+sess.connect(("127.0.0.1",7656));
+sess.send("HELLO VERSION MIN=3.0 MAX=3.0\n")
+sys.stdout.write(sess.recv(1000))
+sess.send("SESSION CREATE STYLE=STREAM"+name+" DESTINATION=tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABngJSS8xMyF4t82otZmCDhrKjbm-QLMtOLoumwR28ebDHEd4clF6O7aRa3d3yRH7p\n")
+sys.stdout.write(sess.recv(1000))
+
+sock =  socket.socket(
+    socket.AF_INET, socket.SOCK_STREAM)
+sock.connect(("127.0.0.1",7656));
+sock.send("HELLO VERSION MIN=3.0 MAX=3.0\n")
+sys.stdout.write(sock.recv(1000))
+sock.send("STREAM FORWARD" + name + host + port + silent + "\n")
+sys.stdout.write(sock.recv(1000))
+
+l=0
+while 1 :
+    chunk = sock.recv(100)
+    sys.stdout.write(chunk)
+    if not chunk : break
+print "Forward socket closed"
+l=0
+while 1 :
+    chunk = sess.recv(100)
+    sys.stdout.write(chunk)
+    if not chunk : break
+
diff --git a/apps/sam/Demos/streamTests/samIn.py b/apps/sam/Demos/streamTests/samIn.py
new file mode 100755
index 0000000000000000000000000000000000000000..ab2e3fa5252ff0f5046d39ae06cb9c4788dd0ecc
--- /dev/null
+++ b/apps/sam/Demos/streamTests/samIn.py
@@ -0,0 +1,91 @@
+#!/usr/bin/python
+
+
+# create an stream session
+# then an "accept" stream connected to this session
+# then another "accept" stream from the same session
+# then listen from the first stream and then listen from the second
+# usage : ./samIn.py [ silent [ name ] ]
+#            name : the session id ( defaults to InTest )
+#            silent : true or false : tells wether we want to receive the incoming stream destination
+#                       as first line
+
+import socket
+import sys
+import time
+
+if len(sys.argv)>=2 :
+	silent = " SILENT="+sys.argv[1]
+else :  silent = " SILENT=false"
+
+if len(sys.argv)>=3 :
+	name = sys.argv[2]
+else :  name = "inTest"
+
+
+
+
+sess = socket.socket(
+    socket.AF_INET, socket.SOCK_STREAM)
+sess.connect(("127.0.0.1",7656));
+sess.send("HELLO VERSION MIN=3.0 MAX=3.0\n")
+sys.stdout.write(sess.recv(1000))
+sess.send("SESSION CREATE STYLE=STREAM ID="+name+" DESTINATION=tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABngJSS8xMyF4t82otZmCDhrKjbm-QLMtOLoumwR28ebDHEd4clF6O7aRa3d3yRH7p\n")
+sys.stdout.write(sess.recv(1000))
+
+
+
+
+
+def accept() :
+	sock = socket.socket(
+		socket.AF_INET, socket.SOCK_STREAM)
+	sock.connect(("127.0.0.1",7656));
+	sock.send("HELLO VERSION MIN=3.0 MAX=3.0\n")
+	sys.stdout.write(sock.recv(1000))
+	sock.send("STREAM ACCEPT ID=" + name + silent+"\n")
+	print "STREAM ACCEPT ID="+name+silent
+	if (silent==" SILENT=false") :
+		sys.stdout.write( sock.recv(100) )
+	return sock
+
+def echo( sock, lines ) :
+	l = 0
+	while lines==-1 or l<lines :
+	    chunk = sock.recv(1000)
+	    if lines!=-1 : l = l + 1
+	    if not chunk : break
+	    print chunk
+	    sock.send(chunk)
+	print
+
+
+
+sock1 = accept()
+time.sleep(1)
+
+sock2 = accept()
+
+print "Second listening session"
+try :
+	echo(sock2, -1)
+except :
+	print sock2
+
+if silent == " SILENT=false" :
+	sys.stdout.write(sock1.recv(1000))
+else :
+	# we know sock1 is accepted if it receives a byte
+	sock1.recv(1)
+
+sock3 = accept()
+
+
+print "First listening session"
+echo(sock1, 2)
+sock1.close()
+
+print "Third listening session"
+echo(sock3, -1)
+
+
diff --git a/apps/sam/Demos/streamTests/samOut.py b/apps/sam/Demos/streamTests/samOut.py
new file mode 100755
index 0000000000000000000000000000000000000000..7e132f2c4ba79b30ffc4d9e9d9706634694cc2dc
--- /dev/null
+++ b/apps/sam/Demos/streamTests/samOut.py
@@ -0,0 +1,52 @@
+#!/usr/bin/python
+
+
+# open a I2P stream destination
+# then open another stream that connects to the destination created by samForward.py or samIn.py
+# then send bytes through the stream
+# usage :
+#        ./samOut.py [ silent [ sessionName ] ]
+#
+#           silent : should the first incoming after the connection request contain the connection status message (true or false)
+#           sessionName : session id (default : "forward")
+
+import socket
+import sys
+import time
+
+if len(sys.argv)>=2 :
+	silent = " SILENT="+sys.argv[1]
+else :  silent = " SILENT=false"
+
+if len(sys.argv)>=3 :
+	name = " ID="+sys.argv[2]
+else :  name = " ID=testOutStream"
+
+sess = socket.socket(
+    socket.AF_INET, socket.SOCK_STREAM)
+sess.connect(("127.0.0.1",7656));
+sess.send("HELLO VERSION MIN=3.0 MAX=3.0\n")
+sys.stdout.write(sess.recv(1000))
+sess.send("SESSION CREATE STYLE=STREAM"+name+" DESTINATION=EYUpJFeW9tiubXR0aOjvCJ~ndj3xN0Wn-ljuGdbpOEttPg7nj0VCTOQDJ~FAolzn9FIDdmR3VjM0OFFDT46Q5HN4vShXFE2VNC8e3~GjzxJfaJhijRC2R9oIOzsNlzKtInD2o9lh0PxPioNMCigwmgWuqlQHs4tjWeaYRAtooHxbrtuoCIhIdGfyVV-nAcPiyYbouKq3leETXE~4kBXm-LfWfyPtrv6OuDk3GBVVcthv19GYBmnl2YI8HpJjc-G-TvNkgYishjzIJyEW-Xrpy43R4ZBXlyQqnheGLlbOEY8NLDbyNHLRMMOGbcr~67SVE3Iw3RqQ3Dhrkq2FCaQwcDucfIUCCbOfCZgu0hlnCkS42xsUvegQeiwMxbdI~h9v7vcR3yFFOrHX6WQvIZSbFLKNGArGJcfmOJVLqw1wTC4AgYXjk3csVDPd-QWbMXOuodyBgrg27Ds2BBYTsVXWskoo6ASsMIQZ6jMfL7PkY9dPLCRParIyzb9aPmf~MntNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHNqwgkhJnBW4ymaRsdVmITAha-ff0UiALfKSlznqp5HcSewgMHbzQ0I01TQytFnW\n")
+sys.stdout.write(sess.recv(1000))
+
+sock =  socket.socket(
+    socket.AF_INET, socket.SOCK_STREAM)
+sock.connect(("127.0.0.1",7656));
+sock.send("HELLO VERSION MIN=3.0 MAX=3.0\n")
+sys.stdout.write(sock.recv(1000))
+sock.send("STREAM CONNECT"+name+" DESTINATION=tYhjbFlFL38WFuO5eCzTvE0UBr4RfaqWMKlekGeMoB-Ouz7nYaWfiS-9j3jMiZT7FH~pwdmoSREOs2ZbXK84sR59P~pPfeCMxnJrk57f3U9uKzXkesjkKWYco3YAGs-G8sw8Fu2FBx0Do57yBdA9~j8Zq6pMjmgPBXCLuXG3vo0Z8zUWCjApJyFY6OXYopHck9Fz9vKy7YhC6zXFHfEuNHVkAooduiLd~aCoGij0TW3lH2rTVU-lx-DUdi6edxQ5-RvDNkXfikvytoCpRkivbNVytjCJLk~7RNU4FpBD20wTZWNJmEG3OY3cjNjawJVFdNjtgczh9K7gZ7ad-NjVjZVhXEj1lU8mk~vAH-2QE5om8dstWUwWoNDwmVDlvIJNKzQmahG~VrpFexFHXO0n3fKIXcSgWGOHDExM8w9neCt7AxUjxPDtXXuYNW~bRwcfiL-C9~z4K9rmwiTPZX0lmsToSXTF28l7WAoj~TMT9kZAjQeFRRWU5oW5oxVuonVvAAAA"+silent+"\n")
+
+# wait for acknowledgement before sending data, if we asked for it
+if (silent==" SILENT=false") :
+	sys.stdout.write(sock.recv(1000))
+
+for i in range(1,11) :
+    sock.send(str(i)+'\n')
+    buf=sock.recv(1000)
+    sys.stdout.write(str(i)+' '+buf)
+    if not buf : break
+
+print
+
+
diff --git a/apps/sam/Demos/streamTests/samOutWithNaming.py b/apps/sam/Demos/streamTests/samOutWithNaming.py
new file mode 100755
index 0000000000000000000000000000000000000000..6aa6476bc2ef55563d3a697f9279795086e7295c
--- /dev/null
+++ b/apps/sam/Demos/streamTests/samOutWithNaming.py
@@ -0,0 +1,51 @@
+#!/usr/bin/python
+
+
+# open a I2P stream destination
+# then open another stream that connects to the destination created by samForward.py or samIn.py
+# then send bytes through the stream
+# usage :
+#        ./samOut.py [ silent [ sessionName ] ]
+#
+#           silent : should the first incoming after the connection request contain the connection status message (true or false)
+#           sessionName : session id (default : "forward")
+
+import socket
+import sys
+import time
+
+if len(sys.argv)>=2 :
+	silent = " SILENT="+sys.argv[1]
+else :  silent = " SILENT=false"
+
+if len(sys.argv)>=3 :
+	name = " ID="+sys.argv[2]
+else :  name = " ID=testOutStream"
+
+sess = socket.socket(
+    socket.AF_INET, socket.SOCK_STREAM)
+sess.connect(("127.0.0.1",7656));
+sess.send("HELLO VERSION MIN=3.0 MAX=3.0\n")
+sys.stdout.write(sess.recv(1000))
+sess.send("SESSION CREATE STYLE=STREAM"+name+" DESTINATION=EYUpJFeW9tiubXR0aOjvCJ~ndj3xN0Wn-ljuGdbpOEttPg7nj0VCTOQDJ~FAolzn9FIDdmR3VjM0OFFDT46Q5HN4vShXFE2VNC8e3~GjzxJfaJhijRC2R9oIOzsNlzKtInD2o9lh0PxPioNMCigwmgWuqlQHs4tjWeaYRAtooHxbrtuoCIhIdGfyVV-nAcPiyYbouKq3leETXE~4kBXm-LfWfyPtrv6OuDk3GBVVcthv19GYBmnl2YI8HpJjc-G-TvNkgYishjzIJyEW-Xrpy43R4ZBXlyQqnheGLlbOEY8NLDbyNHLRMMOGbcr~67SVE3Iw3RqQ3Dhrkq2FCaQwcDucfIUCCbOfCZgu0hlnCkS42xsUvegQeiwMxbdI~h9v7vcR3yFFOrHX6WQvIZSbFLKNGArGJcfmOJVLqw1wTC4AgYXjk3csVDPd-QWbMXOuodyBgrg27Ds2BBYTsVXWskoo6ASsMIQZ6jMfL7PkY9dPLCRParIyzb9aPmf~MntNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHNqwgkhJnBW4ymaRsdVmITAha-ff0UiALfKSlznqp5HcSewgMHbzQ0I01TQytFnW\n")
+sys.stdout.write(sess.recv(1000))
+
+sock =  socket.socket(
+    socket.AF_INET, socket.SOCK_STREAM)
+sock.connect(("127.0.0.1",7656));
+sock.send("HELLO VERSION MIN=3.0 MAX=3.0\n")
+sys.stdout.write(sock.recv(1000))
+sock.send("STREAM CONNECT"+name+" DESTINATION=http://amiga.i2p"+silent+"\n")
+
+# wait for acknowledgement before sending data, if we asked for it
+if (silent==" SILENT=false") :
+	sys.stdout.write(sock.recv(1000))
+
+while (1) :
+    buf=sock.recv(1000)
+    sys.stdout.write(buf)
+    if not buf : break
+
+print
+
+
diff --git a/apps/sam/Demos/streamTests/server.py b/apps/sam/Demos/streamTests/server.py
new file mode 100755
index 0000000000000000000000000000000000000000..829a37e321385468d12ea3939fb0e46381ed0c53
--- /dev/null
+++ b/apps/sam/Demos/streamTests/server.py
@@ -0,0 +1,41 @@
+#!/usr/bin/python
+
+
+# echo server
+# accepts a socket on specified port, writes on stdout and send back incoming data
+
+import socket
+import sys
+
+if len(sys.argv)>=2 :
+	port = eval(sys.argv[1])
+else :  port = 25000
+
+#create an INET, STREAMing socket
+serversocket = socket.socket(
+    socket.AF_INET, socket.SOCK_STREAM)
+#bind the socket to a public host, 
+# and a well-known port
+serversocket.bind(("0.0.0.0", port))
+    #become a server socket
+serversocket.listen(1)
+
+
+        #accept connections from outside
+(clientsocket, address) = serversocket.accept()
+        #now do something with the clientsocket
+        #in this case, we'll pretend this is a threaded server
+
+i = 0
+while 1 :
+    chunk = clientsocket.recv(1024)
+    i = i + 1
+    sys.stdout.write(str(i)+' '+chunk)
+    if not chunk: break
+    clientsocket.send(str(i)+' '+chunk)
+
+
+clientsocket.close()
+
+print
+
diff --git a/apps/sam/doc/sam.3.0-protocol.txt b/apps/sam/doc/sam.3.0-protocol.txt
new file mode 100644
index 0000000000000000000000000000000000000000..51c5597983a82a33f348163bb6c8c679ac28789f
--- /dev/null
+++ b/apps/sam/doc/sam.3.0-protocol.txt
@@ -0,0 +1,487 @@
+----------------------------------------------------------------------
+Simple Anonymous Messaging (SAM version 3.0)
+----------------------------------------------------------------------
+Client application talks to SAM bridge, which deals with
+all of the I2P functionality (using the ministreaming 
+lib for virtual streams, or I2CP directly for async messages).
+
+All client<-->SAM bridge communication is unencrypted and 
+unauthenticated.  Access to the SAM
+bridge should be protected through firewalls or other means
+(perhaps the bridge may have ACLs on what IPs it accepts 
+connections from).
+
+All of these SAM messages are sent on a single line in plain ASCII,
+terminated by the newline character (\n).  The formatting shown
+below is merely for readability, and while the first two words in
+each message must stay in their specific order, the ordering of
+the key=value pairs can change (e.g. "ONE TWO A=B C=D" or 
+"ONE TWO C=D A=B" are both perfectly valid constructions).  In
+addition, the protocol is case-sensitive.
+In the following, message examples are preceded by "-> " for
+messages sent by the client to the SAM bridge, and by "<- " for
+messages sent by the SAM bridge to the client.
+
+I2P communications can take three distinct forms:
+* Virtual streams
+* Repliable datagrams (messages with a FROM field)
+* Anonymous datagrams (raw anonymous messages)
+
+I2P communications are supported by I2P sessions, and each I2P
+session is bound to an address (called destination). An I2P session
+is associated with one of the three types above, and cannot carry
+communications of another type.
+
+  
+----------------------------------------------------------------------
+SAM connection handshake
+----------------------------------------------------------------------
+No SAM communication can occur until after the client and bridge have
+agreed on a protocol version, which is done by the client sending
+a HELLO and the bridge sending a HELLO REPLY: 
+
+->  HELLO VERSION MIN=$min MAX=$max
+
+and
+
+<-  HELLO REPLY RESULT=OK VERSION=3.0
+
+*** In order to force protocol version 3.0, the values of $min and $max
+*** must be "3.0".
+
+If the SAM bridge cannot find a suitable version, it replies with :
+
+<- HELLO REPLY RESULT=NOVERSION
+
+If some error occurred, such as a bad request format, it replies with :
+
+<- HELLO REPLY RESULT=I2P_ERROR MESSAGE={$message}
+
+
+----------------------------------------------------------------------
+SAM sessions
+----------------------------------------------------------------------
+A SAM session is created by a client opening a socket to the SAM 
+bridge, operating a handshake, and sending a SESSION CREATE message, 
+and the session terminates when the socket is disconnected.
+
+Each registered I2P Destination is uniquely associated with a session ID
+(or nickname).
+
+Each session is uniquely associated with :
+ * the socket from which the client creates the session
+ * its ID (or nickname)
+
+The session creation message can only use one of these forms (messages 
+received through other forms are answered with an error message) :
+
+->  SESSION CREATE 
+          STYLE={STREAM,DATAGRAM,RAW}
+          ID={$nickname}
+          DESTINATION={$private_destination_key,TRANSIENT}
+          [option=value]*
+
+DESTINATION specifies what destination should be used for 
+sending and receiving messages/streams.  It has to be a suitable
+private base64 destination key. If the destination is
+specified as TRANSIENT, the SAM bridge creates a new destination.
+
+{$nickname} is the choice of the client. No whitespace is allowed.
+
+Additional options given are passed to the I2P session 
+configuration if not interpreted by the SAM bridge (e.g. 
+outbound.length=0). These options are documented below.
+
+The SAM bridge itself should already be configured with what router 
+it should communicate over I2P through (though if need be there may
+be a way to provide an override, e.g. i2cp.tcp.host=localhost and 
+i2cp.tcp.port=7654).
+
+After receiving the session create message, the SAM bridge will reply
+with a session status message, as follows:
+
+If the creation was successful :
+<-  SESSION STATUS RESULT=OK DESTINATION={$private_destination_key}
+
+If the nickname is already associated with a session :
+<-  SESSION STATUS RESULT=DUPLICATED_ID
+
+If the destination is already in use :
+<-  SESSION STATUS RESULT=DUPLICATED_DEST
+
+If the destination is not a valid private destination key :
+<-  SESSION STATUS RESULT=INVALID_KEY
+
+If some other error has occurred :
+<-  SESSION STATUS RESULT=I2P_ERROR MESSAGE={$message}
+
+If it's not OK, the MESSAGE should contain human-readable information
+as to why the session could not be created.
+
+
+SAM sessions live and die with the socket they are associated with.
+When the socket is closed, the session dies, and all communications
+using the session die at the same time. And the other way round, when
+the session dies for any reason, the SAM bridge closes the socket.
+
+
+----------------------------------------------------------------------
+SAM virtual streams
+----------------------------------------------------------------------
+Virtual streams are guaranteed to be sent reliably and in order, with
+failure and success notification as soon as it is available.
+
+Streams are bidirectional communication sockets between two I2P
+destinations, but their opening has to be requested by one of them. 
+Hereafter, CONNECT commands are used by the SAM client for such a
+request. FORWARD / ACCEPT commands are used by the SAM client when
+he wants to listen to requests coming from other I2P destinations.
+
+
+-----------------------------
+SAM virtual streams : CONNECT
+-----------------------------
+A client asks for a connection by :
+ * opening a new socket with the SAM bridge
+ * passing the same HELLO handshake as above
+ * sending the connection command :  
+
+-> STREAM CONNECT
+         ID={$nickname}
+         DESTINATION=$peer_public_base64_key
+         [SILENCE={true,false}]
+
+This establishes a new virtual connection from the local session
+whose ID is {$nickname} to the specified peer.
+
+If SILENCE=true is passed, the SAM bridge won't issue any other message
+on the socket : if the connection fails, the socket will be closed.
+If the connection succeeds, all remaining data passing through the
+current socket is forwarded from and to the connected I2P destination
+peer.
+
+If SILENCE=false, which is the default value, the SAM bridge sends a
+last message to its client before forwarding or shutting down the
+socket :
+
+<-  STREAM STATUS 
+         RESULT=$result
+         [MESSAGE=...]
+
+The RESULT value may be one of:
+
+    OK
+    CANT_REACH_PEER
+    I2P_ERROR
+    INVALID_KEY
+    INVALID_ID
+    TIMEOUT
+
+If the RESULT is OK, all remaining data passing through the
+current socket is forwarded from and to the connected I2P destination
+peer. If the connection was not possible (timeout, etc),
+RESULT will contain the appropriate error value (accompanied by an
+optional human-readable MESSAGE), and the SAM bridge closes the
+socket.
+
+----------------------------
+SAM virtual streams : ACCEPT
+----------------------------
+
+A client waits for an incoming connection request by :
+ * opening a new socket with the SAM bridge
+ * passing the same HELLO handshake as above
+ * sending the accept command :  
+
+-> STREAM ACCEPT
+         ID={$nickname}
+         [SILENCE={true,false}]
+
+This makes the session ${nickname} listen for one incoming
+connection request from the I2P network.
+
+The SAM bridge answers with :
+
+<-  STREAM STATUS 
+         RESULT=$result
+         [MESSAGE=...]
+
+The RESULT value may be one of:
+
+    OK
+    I2P_ERROR
+    INVALID_ID
+
+If the result is not OK, the socket is closed immediately by the SAM
+bridge. If the result is OK, the SAM bridge starts waiting for an
+incoming connection request from another I2P peer. When a request
+arrives, the SAM bridge accepts it and :
+
+ * If SILENCE=true was passed, the SAM bridge won't issue any other message
+on the client socket : all remaining data passing through the
+current socket is forwarded from and to the connected I2P destination
+peer.
+ * If SILENCE=false was passed, which is the default value, the SAM bridge
+sends the client an ASCII line containing the base64 public destination key
+of the requesting peer. After this '\n' terminated line, all remaining data 
+passing through the current socket is forwarded from and to the connected
+I2P destination peer, until one of the peer closes the socket.
+
+-----------------------------
+SAM virtual streams : FORWARD
+-----------------------------
+
+A client can use a regular socket server and wait for connection requests
+coming from I2P. For that, the client has to :
+ * open a new socket with the SAM bridge
+ * pass the same HELLO handshake as above
+ * send the forward command :
+
+-> STREAM FORWARD
+         ID={$nickname}
+         PORT={$port}
+         [HOST={$host}]
+         [SILENCE={true,false}]
+
+This makes the session ${nickname} listen for incoming
+connection requests from the I2P network.
+
+The SAM bridge answers with :
+
+<-  STREAM STATUS 
+         RESULT=$result
+         [MESSAGE=...]
+
+The RESULT value may be one of:
+
+    OK
+    I2P_ERROR
+    INVALID_ID
+
+ * {$host} is the hostname or IP address of the socket server to which
+SAM will forward connection requests. If not given, SAM takes the IP
+of the socket that issued the forward command.
+
+ * {$port} is the port number of the socket server to which SAM will
+forward connection requests. It is mandatory.
+
+When a connexion request arrives from I2P, the SAM bridge requests a
+socket connexion from {$host}:{$port}. If it is accepted after no more
+than 3 seconds, SAM will accept the connexion from I2P, and then :
+
+ * If SILENCE=true was passed, all data passing through the obtained
+current socket is forwarded from and to the connected I2P destination
+peer.
+ * If SILENCE=false was passed, which is the default value, the SAM bridge
+sends on the obtained socket an ASCII line containing the base64 public
+destination key of the requesting peer. After this '\n' terminated line,
+all remaining data passing through the socket is forwarded from and to 
+the connected I2P destination peer, until one of the sides closes the
+socket.
+
+
+
+The I2P router will stop listening to incoming connection requests as
+soon as the "forwarding" socket is closed.
+
+
+
+
+----------------------------------------------------------------------
+SAM repliable datagrams : sending a datagram
+----------------------------------------------------------------------
+While I2P doesn't inherently contain a FROM address, for ease of use
+an additional layer is provided as repliable datagrams - unordered
+and unreliable messages of up to 31KB in size that include a FROM 
+address (leaving up to 1KB for header material).  This FROM address 
+is authenticated internally by SAM (making use of the destination's 
+signing key to verify the source) and includes replay prevention.
+
+** First method :
+
+After establishing a SAM session with STYLE=DATAGRAM, the client can
+send datagrams through SAM's UDP port (7655).
+
+The first line of a datagram sent through this port has to be in the
+following format :
+
+3.0 {$nickname} {$base64_public_destination_key}
+
+ * 3.0 is the version of SAM
+ * {$nickname} is the id of the DGRAM session that will be used
+ * {$base64_public_destination_key} is the destination of the
+    datagram
+ * this line is '\n' terminated.
+
+The first line will be discarded by SAM before sending the remaining
+of the message to the specified destination.
+
+** Second method :
+
+Datagrams can also be sent through the socket from which the datagram
+session was opened. See the "DATAGRAM SEND" command of SAM versions 1
+and 2.
+
+----------------------------------------------------------------------
+SAM repliable datagrams : receiving a datagram
+----------------------------------------------------------------------
+Received datagrams are written by SAM on the socket from which the
+datagram session was opened, unless specified otherwise by the CREATE
+command.
+
+When a datagram arrives, the bridge delivers it to the client via the
+message :
+
+<-  DATAGRAM RECEIVED
+           DESTINATION=$base64key
+           SIZE=$numBytes\n[$numBytes of data]
+
+The SAM bridge never exposes to the client the authentication headers
+or other fields, merely the data that the sender provided.  This 
+continues until the session is closed (by the client dropping the
+connection).
+
+----------------------------------------------------------------------
+SAM repliable datagrams : forwarding datagrams
+----------------------------------------------------------------------
+When creating a datagram session, the client can ask SAM to forward
+incoming messages to a specified ip:port. It does so by issuing the
+CREATE command with PORT and HOST options :
+
+-> SESSION CREATE 
+          STYLE=DATAGRAM
+          ID={$nickname}
+          DESTINATION={$private_destination_key,TRANSIENT}
+          PORT={$port}
+          [HOST={$host}]
+          [option=value]*
+
+ * {$host} is the hostname or IP address of the datagram server to
+     which SAM will forward datagrams. If not given, SAM takes the
+     IP of the socket that issued the forward command.
+
+ * {$port} is the port number of the datagram server to which SAM
+     will forward datagrams.
+
+When a datagram arrives, the bridge sends to the specified host:port
+a message containing the following data :
+
+${sender_base64_destination_key}\n{$datagram_payload}
+
+
+----------------------------------------------------------------------
+SAM anonymous datagrams
+----------------------------------------------------------------------
+Squeezing the most out of I2P's bandwidth, SAM allows clients to send
+and receive anonymous datagrams, leaving authentication and reply 
+information up to the client themselves.  These datagrams are 
+unreliable and unordered, and may be up to 32KB in size.
+
+After establishing a SAM session with STYLE=RAW, the client can
+send anonymous datagrams throug the SAM bridge exactly the same way
+he sends non anonymous datagrams.
+
+Both ways of receiving datagrams are also available for anonymous
+datagrams.
+
+When anonymous datagrams are to be written to the socket that created
+the session,the bridge delivers it to the client via:
+
+<- RAW RECEIVED
+      SIZE=$numBytes\n[$numBytes of data]
+
+When anonymous datagrams are to be forwarded to some host:port,
+the bridge sends to the specified host:port a message containing 
+the following data :
+
+{$datagram_payload}
+
+
+----------------------------------------------------------------------
+SAM utility functionality
+----------------------------------------------------------------------
+The following message can be used by the client to query the SAM
+bridge for name resolution:
+
+ NAMING LOOKUP 
+        NAME=$name
+
+which is answered by
+
+ NAMING REPLY 
+        RESULT=$result
+        NAME=$name 
+        [VALUE=$base64key]
+        [MESSAGE=$message]
+
+
+The RESULT value may be one of:
+
+    OK
+    INVALID_KEY
+    KEY_NOT_FOUND
+
+If NAME=ME, then the reply will contain the base64key used by the
+current session (useful if you're using a TRANSIENT one).  If $result
+is not OK, MESSAGE may convey a descriptive message, such as "bad
+format", etc.
+
+Public and private base64 keys can be generated using the following
+message:
+
+ DEST GENERATE
+
+which is answered by
+
+ DEST REPLY
+      PUB=$pubkey
+      PRIV=$privkey
+
+----------------------------------------------------------------------
+RESULT values
+----------------------------------------------------------------------
+These are the values that can be carried by the RESULT field, with
+their meaning:
+
+ OK              Operation completed succesfully
+ CANT_REACH_PEER The peer exists, but cannot be reached
+ DUPLICATED_DEST The specified Destination is already in use
+ I2P_ERROR       A generic I2P error (e.g. I2CP disconnection, etc.)
+ INVALID_KEY     The specified key is not valid (bad format, etc.)
+ KEY_NOT_FOUND   The naming system can't resolve the given name
+ PEER_NOT_FOUND  The peer cannot be found on the network
+ TIMEOUT         Timeout while waiting for an event (e.g. peer answer)
+
+----------------------------------------------------------------------
+Tunnel Pool Options
+----------------------------------------------------------------------
+
+These options can be passed in as name=value pairs at the end of a
+SAM SESSION CREATE line.
+
+ inbound.nickname            - Name shows up in I2P router console.
+ inbound.quantity            - Number of tunnels, default 2.
+ inbound.backupQuantity      - Number of backup tunnels, default 0.
+ inbound.rebuildPeriod       - Obsolete - ignored - the router controls rebuilding
+ inbound.duration            - Tunnels last X ms, default 10*60*1000.
+                               (change not recommended, will break anonymmity
+                                if it works at all)
+ inbound.length              - Depth of tunnels, default 2.
+ inbound.lengthVariance      - If negative, randomly skews from
+                               (length - variance) to
+                               (length + variance).  If positive, from
+                               length to (length + var), inclusive.
+                               Default -1.
+ inbound.allowZeroHop        - Zero hop allowed?  Default "true".
+ outbound.*                  - Same properties as inbound.
+ i2p.streaming.connectDelay  - If 0, connect ASAP.  If positive, wait
+                               until X ms have passed or output stream
+                               is flushed or buffer fills.  Default 0.
+ i2p.streaming.maxWindowSize - Max window size, default 64.
+
+----------------------------------------------------------------------
+Client library implementations:
+---------------------------------------------------------------------- 
+ C/C++:  libSAM: http://www.innographx.com/mpc/libsam/ or i2p/sam/c/
+ Python: Python/I2P: http://dev.i2p.net/contrib/apps/sam/python/index.html
+ Others: See apps/sam/ in I2P CVS.
diff --git a/apps/sam/java/build.xml b/apps/sam/java/build.xml
index 78f9cdfb0a26af4c91cb8a504b8f1b50d5b0e1f8..6a987b3411587e724d4b9049ded9d6c8e96ac88b 100644
--- a/apps/sam/java/build.xml
+++ b/apps/sam/java/build.xml
@@ -4,6 +4,7 @@
     <target name="build" depends="builddep, jar" />
     <target name="builddep">
         <ant dir="../../ministreaming/java/" target="build" />
+        <ant dir="../../streaming/java/" target="build" />
 	<!-- ministreaming will build core -->
     </target>
     <condition property="depend.available">
@@ -18,6 +19,7 @@
             <classpath>
                 <pathelement location="../../../core/java/build/obj" />
                 <pathelement location="../../ministreaming/java/build/obj" />
+                <pathelement location="../../streaming/java/build/obj" />
             </classpath>
         </depend>
     </target>
@@ -29,7 +31,7 @@
             srcdir="./src" 
             debug="true" deprecation="on" source="1.5" target="1.5" 
             destdir="./build/obj" 
-            classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" >
+            classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar:../../streaming/java/build/streaming.jar" >
             <compilerarg line="${javac.compilerargs}" />
         </javac>
     </target>
@@ -38,7 +40,7 @@
             srcdir="./test" 
             debug="true" deprecation="on" source="1.5" target="1.5" 
             destdir="./build/obj" 
-            classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" >
+            classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar:../../streaming/java/build/streaming.jar" >
             <compilerarg line="${javac.compilerargs}" />
         </javac>
     </target>
@@ -46,7 +48,7 @@
         <jar destfile="./build/sam.jar" basedir="./build/obj" includes="**/*.class">
             <manifest>
                 <attribute name="Main-Class" value="net.i2p.sam.SAMBridge" />
-                <attribute name="Class-Path" value="i2p.jar mstreaming.jar" />
+                <attribute name="Class-Path" value="i2p.jar mstreaming.jar streaming.jar" />
             </manifest>
         </jar>
     </target>
@@ -57,7 +59,7 @@
         <mkdir dir="./build" />
         <mkdir dir="./build/javadoc" />
         <javadoc 
-            sourcepath="./src:../../../core/java/src:../../ministreaming/java/src" destdir="./build/javadoc" 
+            sourcepath="./src:../../../core/java/src:../../ministreaming/java/src:../../streaming/java/src" destdir="./build/javadoc" 
             packagenames="*" 
             use="true" 
             splitindex="true" 
@@ -69,9 +71,11 @@
     <target name="cleandep" depends="clean">
 	<!-- ministreaming will clean core -->
         <ant dir="../../ministreaming/java/" target="distclean" />
+        <ant dir="../../streaming/java/" target="distclean" />
     </target>
     <target name="distclean" depends="clean">
 	<!-- ministreaming will clean core -->
         <ant dir="../../ministreaming/java/" target="distclean" />
+        <ant dir="../../streaming/java/" target="distclean" />
     </target>
 </project>
diff --git a/apps/sam/java/src/net/i2p/sam/SAMBridge.java b/apps/sam/java/src/net/i2p/sam/SAMBridge.java
index ee7e33bb4a442a014c2732fc4bd39f1665056718..d6ed45ffe6876181973121b4ffb570127817b9c7 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMBridge.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMBridge.java
@@ -14,9 +14,10 @@ import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
-import java.net.InetAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
+import java.net.InetSocketAddress;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.ByteBuffer;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
@@ -34,7 +35,7 @@ import net.i2p.util.Log;
  */
 public class SAMBridge implements Runnable {
     private final static Log _log = new Log(SAMBridge.class);
-    private ServerSocket serverSocket;
+    private ServerSocketChannel serverSocket;
     private Properties i2cpProps;
     /** 
      * filename in which the name to private key mapping should 
@@ -45,12 +46,23 @@ public class SAMBridge implements Runnable {
      * app designated destination name to the base64 of the I2P formatted 
      * destination keys (Destination+PrivateKey+SigningPrivateKey)
      */
-    private Map nameToPrivKeys;
+    private Map<String,String> nameToPrivKeys;
 
     private boolean acceptConnections = true;
 
     private static final int SAM_LISTENPORT = 7656;
+    
     public static final String DEFAULT_SAM_KEYFILE = "sam.keys";
+    public static final String PROP_TCP_HOST = "sam.tcp.host";
+    public static final String PROP_TCP_PORT = "sam.tcp.port";
+    protected static final String DEFAULT_TCP_HOST = "0.0.0.0";
+    protected static final String DEFAULT_TCP_PORT = "7656";
+    
+    public static final String PROP_DATAGRAM_HOST = "sam.udp.host";
+    public static final String PROP_DATAGRAM_PORT = "sam.udp.port";
+    protected static final String DEFAULT_DATAGRAM_HOST = "0.0.0.0";
+    protected static final String DEFAULT_DATAGRAM_PORT = "7655";
+
     
     private SAMBridge() {}
     
@@ -64,16 +76,18 @@ public class SAMBridge implements Runnable {
      */
     public SAMBridge(String listenHost, int listenPort, Properties i2cpProps, String persistFile) {
         persistFilename = persistFile;
-        nameToPrivKeys = new HashMap(8);
+        nameToPrivKeys = new HashMap<String,String>(8);
         loadKeys();
         try {
             if ( (listenHost != null) && !("0.0.0.0".equals(listenHost)) ) {
-                serverSocket = new ServerSocket(listenPort, 0, InetAddress.getByName(listenHost));
+                serverSocket = ServerSocketChannel.open();
+                serverSocket.socket().bind(new InetSocketAddress(listenHost, listenPort));
                 if (_log.shouldLog(Log.DEBUG))
                     _log.debug("SAM bridge listening on "
                                + listenHost + ":" + listenPort);
             } else {
-                serverSocket = new ServerSocket(listenPort);
+                serverSocket = ServerSocketChannel.open();
+                serverSocket.socket().bind(new InetSocketAddress(listenPort));
                 if (_log.shouldLog(Log.DEBUG))
                     _log.debug("SAM bridge listening on 0.0.0.0:" + listenPort);
             }
@@ -191,36 +205,50 @@ public class SAMBridge implements Runnable {
         }
     }
     
+    static class HelpRequested extends Exception {static final long serialVersionUID=0x1;}
+    
     /**
      * Usage:
-     *  <pre>SAMBridge [[listenHost ]listenPort[ name=val]*]</pre>
-     * 
+     *  <pre>SAMBridge [ keyfile [listenHost ] listenPort [ name=val ]* ]</pre>
+     * or:
+     *  <pre>SAMBridge [ name=val ]* </pre>
+     *  
      * name=val options are passed to the I2CP code to build a session, 
      * allowing the bridge to specify an alternate I2CP host and port, tunnel
      * depth, etc.
-     * @param args [[listenHost ]listenPort[ name=val]*]
+     * @param args [ keyfile [ listenHost ] listenPort [ name=val ]* ]
      */
     public static void main(String args[]) {
         String keyfile = DEFAULT_SAM_KEYFILE;
         int port = SAM_LISTENPORT;
-        String host = "0.0.0.0";
+        String host = DEFAULT_TCP_HOST;
         Properties opts = null;
         if (args.length > 0) {
-            keyfile = args[0];
-            int portIndex = 1;
-            try {
-                port = Integer.parseInt(args[portIndex]);
-            } catch (NumberFormatException nfe) {
-                host = args[1];
-                portIndex++;
-                try {
-                    port = Integer.parseInt(args[portIndex]);
-                } catch (NumberFormatException nfe1) {
-                    usage();
-                    return;
-                }
-            }
-            opts = parseOptions(args, portIndex+1);
+        	try {
+        		opts = parseOptions(args, 0);
+        		keyfile = args[0];
+        		int portIndex = 1;
+        		try {
+        			if (args.length>portIndex) port = Integer.parseInt(args[portIndex]);
+        		} catch (NumberFormatException nfe) {
+        			host = args[portIndex];
+        			portIndex++;
+        			try {
+        				if (args.length>portIndex) port = Integer.parseInt(args[portIndex]);
+        			} catch (NumberFormatException nfe1) {
+        				try {
+        					port = Integer.parseInt(opts.getProperty(SAMBridge.PROP_TCP_PORT, SAMBridge.DEFAULT_TCP_PORT));
+        					host = opts.getProperty(SAMBridge.PROP_TCP_HOST, SAMBridge.DEFAULT_TCP_HOST);
+        				} catch (NumberFormatException e) {
+        					usage();
+        					return;
+        				}
+        			}
+        		}
+        	} catch (HelpRequested e) {
+        		usage();
+        		return;
+        	}
         }
         SAMBridge bridge = new SAMBridge(host, port, opts, keyfile);
         I2PAppThread t = new I2PAppThread(bridge, "SAMListener");
@@ -236,10 +264,11 @@ public class SAMBridge implements Runnable {
         t.start();
     }
 
-    private static Properties parseOptions(String args[], int startArgs) {
+    private static Properties parseOptions(String args[], int startArgs) throws HelpRequested {
         Properties props = new Properties();
         // skip over first few options
         for (int i = startArgs; i < args.length; i++) {
+        	if (args[i].equals("-h")) throw new HelpRequested();
             int eq = args[i].indexOf('=');
             if (eq <= 0) continue;
             if (eq >= args[i].length()-1) continue;
@@ -255,50 +284,76 @@ public class SAMBridge implements Runnable {
     
     private static void usage() {
         System.err.println("Usage: SAMBridge [keyfile [listenHost] listenPortNum[ name=val]*]");
+        System.err.println("or:");
+        System.err.println("       SAMBridge [ name=val ]*");
         System.err.println(" keyfile: location to persist private keys (default sam.keys)");
         System.err.println(" listenHost: interface to listen on (0.0.0.0 for all interfaces)");
         System.err.println(" listenPort: port to listen for SAM connections on (default 7656)");
         System.err.println(" name=val: options to pass when connecting via I2CP, such as ");
         System.err.println("           i2cp.host=localhost and i2cp.port=7654");
+        System.err.println("");
+        System.err.println("Host and ports of the SAM bridge can be specified with the alternate");
+        System.err.println("form by specifying options "+SAMBridge.PROP_TCP_HOST+" and/or "+
+        		SAMBridge.PROP_TCP_PORT);
+        System.err.println("");
+        System.err.println("Options "+SAMBridge.PROP_DATAGRAM_HOST+" and "+SAMBridge.PROP_DATAGRAM_PORT+
+        		" specify the listening ip");
+        System.err.println("range and the port of SAM datagram server. This server is");
+        System.err.println("only launched after a client creates the first SAM datagram");
+        System.err.println("or raw session, after a handshake with SAM version >= 3.0.");
+        System.err.println("");
+        System.err.println("The option loglevel=[DEBUG|WARN|ERROR|CRIT] can be used");
+        System.err.println("for tuning the log verbosity.\n");
     }
     
     public void run() {
         if (serverSocket == null) return;
         try {
             while (acceptConnections) {
-                Socket s = serverSocket.accept();
+                SocketChannel s = serverSocket.accept();
                 if (_log.shouldLog(Log.DEBUG))
                     _log.debug("New connection from "
-                               + s.getInetAddress().toString() + ":"
-                               + s.getPort());
+                               + s.socket().getInetAddress().toString() + ":"
+                               + s.socket().getPort());
 
-                try {
-                    SAMHandler handler = SAMHandlerFactory.createSAMHandler(s, i2cpProps);
-                    if (handler == null) {
-                        if (_log.shouldLog(Log.DEBUG))
-                            _log.debug("SAM handler has not been instantiated");
+                class HelloHandler implements Runnable {
+                	SocketChannel s ;
+                	SAMBridge parent ;
+                	HelloHandler(SocketChannel s, SAMBridge parent) { 
+                		this.s = s ;
+                		this.parent = parent ;
+                	}
+                	public void run() {
                         try {
-                            s.close();
-                        } catch (IOException e) {}
-                        continue;
-                    }
-                    handler.setBridge(this);
-                    handler.startHandling();
-                } catch (SAMException e) {
-                    if (_log.shouldLog(Log.ERROR))
-                        _log.error("SAM error: " + e.getMessage(), e);
-                    try {
-                        String reply = "HELLO REPLY RESULT=I2P_ERROR MESSAGE=\"" + e.getMessage() + "\"\n";
-                        s.getOutputStream().write(reply.getBytes("ISO-8859-1"));
-                    } catch (IOException ioe) {
-                        if (_log.shouldLog(Log.ERROR))
-                            _log.error("SAM Error sending error reply", ioe);
-                    }
-                    try { s.close(); } catch (IOException ioe) {}
-                } catch (Exception ee) {
-                    try { s.close(); } catch (IOException ioe) {}
-                    _log.log(Log.CRIT, "Unexpected error handling SAM connection", ee);
-                } 
+                            SAMHandler handler = SAMHandlerFactory.createSAMHandler(s, i2cpProps);
+                            if (handler == null) {
+                                if (_log.shouldLog(Log.DEBUG))
+                                    _log.debug("SAM handler has not been instantiated");
+                                try {
+                                    s.close();
+                                } catch (IOException e) {}
+                                return;
+                            }
+                            handler.setBridge(parent);
+                            handler.startHandling();
+                        } catch (SAMException e) {
+                            if (_log.shouldLog(Log.ERROR))
+                                _log.error("SAM error: " + e.getMessage(), e);
+                            try {
+                                String reply = "HELLO REPLY RESULT=I2P_ERROR MESSAGE=\"" + e.getMessage() + "\"\n";
+                                s.write(ByteBuffer.wrap(reply.getBytes("ISO-8859-1")));
+                            } catch (IOException ioe) {
+                                if (_log.shouldLog(Log.ERROR))
+                                    _log.error("SAM Error sending error reply", ioe);
+                            }
+                            try { s.close(); } catch (IOException ioe) {}
+                        } catch (Exception ee) {
+                            try { s.close(); } catch (IOException ioe) {}
+                            _log.log(Log.CRIT, "Unexpected error handling SAM connection", ee);
+                        }                		
+                	}
+                }
+                new I2PAppThread(new HelloHandler(s,this), "HelloHandler").start();
             }
         } catch (Exception e) {
             if (_log.shouldLog(Log.ERROR))
diff --git a/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java b/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java
index a3e20f7df8caedc475beaac4e0287bdc0490a831..c8d31b489d145f0f811013ad37ce874514368a33 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java
@@ -30,7 +30,7 @@ public class SAMDatagramSession extends SAMMessageSession {
     private final static Log _log = new Log(SAMDatagramSession.class);
     public static int DGRAM_SIZE_MAX = 31*1024;
 
-    private SAMDatagramReceiver recv = null;
+    protected SAMDatagramReceiver recv = null;
 
     private I2PDatagramMaker dgramMaker;
     private I2PDatagramDissector dgramDissector = new I2PDatagramDissector();
@@ -84,9 +84,10 @@ public class SAMDatagramSession extends SAMMessageSession {
     public boolean sendBytes(String dest, byte[] data) throws DataFormatException {
         if (data.length > DGRAM_SIZE_MAX)
             throw new DataFormatException("Datagram size exceeded (" + data.length + ")");
-        
-        byte[] dgram = dgramMaker.makeI2PDatagram(data);
-
+        byte[] dgram ;
+        synchronized (dgramMaker) {
+        	dgram = dgramMaker.makeI2PDatagram(data);
+        }
         return sendBytesThroughMessageSession(dest, dgram);
     }
 
diff --git a/apps/sam/java/src/net/i2p/sam/SAMException.java b/apps/sam/java/src/net/i2p/sam/SAMException.java
index e51e35ea4fcf8aad07dd23727022e4c1a606a932..ae965a4c8c66cf98ebbeff629cf4ba61cce60f79 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMException.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMException.java
@@ -15,11 +15,13 @@ package net.i2p.sam;
  */
 public class SAMException extends Exception {
 
+	static final long serialVersionUID = 1 ;
+
     public SAMException() {
-	super();
+    	super();
     }
     
     public SAMException(String s) {
-	super(s);
+    	super(s);
     }
 }
diff --git a/apps/sam/java/src/net/i2p/sam/SAMHandler.java b/apps/sam/java/src/net/i2p/sam/SAMHandler.java
index 64d824a5782dc8eb0622e994be704997f03a8a65..d53a5a66217f6906e63ee20ac5d24a7f29953ab7 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMHandler.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMHandler.java
@@ -9,9 +9,8 @@ package net.i2p.sam;
  */
 
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.Socket;
+import java.nio.channels.SocketChannel;
+import java.nio.ByteBuffer;
 import java.util.Properties;
 
 import net.i2p.util.I2PAppThread;
@@ -32,8 +31,7 @@ public abstract class SAMHandler implements Runnable {
     protected SAMBridge bridge = null;
 
     private Object socketWLock = new Object(); // Guards writings on socket
-    private Socket socket = null;
-    private OutputStream socketOS = null; // Stream associated to socket
+    protected SocketChannel socket = null;
 
     protected int verMajor = 0;
     protected int verMinor = 0;
@@ -53,10 +51,9 @@ public abstract class SAMHandler implements Runnable {
      * @param i2cpProps properties to configure the I2CP connection (host, port, etc)
      * @throws IOException 
      */
-    protected SAMHandler(Socket s,
+    protected SAMHandler(SocketChannel s,
                          int verMajor, int verMinor, Properties i2cpProps) throws IOException {
         socket = s;
-        socketOS = socket.getOutputStream();
 
         this.verMajor = verMajor;
         this.verMinor = verMinor;
@@ -86,8 +83,8 @@ public abstract class SAMHandler implements Runnable {
      * @return input stream
      * @throws IOException 
      */
-    protected final InputStream getClientSocketInputStream() throws IOException {
-        return socket.getInputStream();
+    protected final SocketChannel getClientSocket() {
+        return socket ;
     }
 
     /**
@@ -98,13 +95,17 @@ public abstract class SAMHandler implements Runnable {
      * @param data A byte array to be written
      * @throws IOException 
      */
-    protected final void writeBytes(byte[] data) throws IOException {
+    protected final void writeBytes(ByteBuffer data) throws IOException {
         synchronized (socketWLock) {
-            socketOS.write(data);
-            socketOS.flush();
+            writeBytes(data, socket);
         }
     }
     
+    static public void writeBytes(ByteBuffer data, SocketChannel out) throws IOException {
+    	while (data.hasRemaining()) out.write(data);           
+    	out.socket().getOutputStream().flush();
+    }
+    
     /** 
      * If you're crazy enough to write to the raw socket, grab the write lock
      * with getWriteLock(), synchronize against it, and write to the getOut()
@@ -112,7 +113,6 @@ public abstract class SAMHandler implements Runnable {
      * @return socket Write lock object
      */
     protected Object getWriteLock() { return socketWLock; }
-    protected OutputStream getOut() { return socketOS; }
 
     /**
      * Write a string to the handler's socket.  This method must
@@ -121,21 +121,25 @@ public abstract class SAMHandler implements Runnable {
      *
      * @param str A byte array to be written
      *
-     * @return True is the string was successfully written, false otherwise
+     * @return True if the string was successfully written, false otherwise
      */
     protected final boolean writeString(String str) {
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Sending the client: [" + str + "]");
-        try {
-            writeBytes(str.getBytes("ISO-8859-1"));
+        return writeString(str, socket);
+    }
+
+    public static boolean writeString(String str, SocketChannel out)
+    {
+    	try {
+            writeBytes(ByteBuffer.wrap(str.getBytes("ISO-8859-1")), out);
         } catch (IOException e) {
             _log.debug("Caught IOException", e);
             return false;
         }
-
-        return true;
+        return true ;
     }
-
+    
     /**
      * Close the socket connected to the SAM client.
      *
@@ -178,8 +182,8 @@ public abstract class SAMHandler implements Runnable {
         return ("SAM handler (class: " + this.getClass().getName()
                 + "; SAM version: " + verMajor + "." + verMinor
                 + "; client: "
-                + this.socket.getInetAddress().toString() + ":"
-                + this.socket.getPort() + ")");
+                + this.socket.socket().getInetAddress().toString() + ":"
+                + this.socket.socket().getPort() + ")");
     }
 
     public final void run() {
diff --git a/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java b/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java
index 21a0e97d277b858f47d7ffcae8361a33fbc1bc4d..e9a51214b992dc427f95eb00f1ef63648e16bd46 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java
@@ -9,9 +9,9 @@ package net.i2p.sam;
  */
 
 import java.io.IOException;
-import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
-import java.net.Socket;
+import java.nio.channels.SocketChannel;
+import java.nio.ByteBuffer;
 import java.util.Properties;
 import java.util.StringTokenizer;
 
@@ -34,17 +34,17 @@ public class SAMHandlerFactory {
      * @throws SAMException if the connection handshake (HELLO message) was malformed
      * @return A SAM protocol handler, or null if the client closed before the handshake
      */
-    public static SAMHandler createSAMHandler(Socket s, Properties i2cpProps) throws SAMException {
+    public static SAMHandler createSAMHandler(SocketChannel s, Properties i2cpProps) throws SAMException {
         String line;
         StringTokenizer tok;
 
         try {
-            line = DataHelper.readLine(s.getInputStream());
+            line = DataHelper.readLine(s.socket().getInputStream());
             if (line == null) {
                 _log.debug("Connection closed by client");
                 return null;
             }
-            tok = new StringTokenizer(line, " ");
+            tok = new StringTokenizer(line.trim(), " ");
         } catch (IOException e) {
             throw new SAMException("Error reading from socket: "
                                    + e.getMessage());
@@ -84,14 +84,15 @@ public class SAMHandlerFactory {
         }
 
         String ver = chooseBestVersion(minVer, maxVer);
-        if (ver == null)
-            throw new SAMException("No version specified");
 
-        // Let's answer positively
         try {
-            OutputStream out = s.getOutputStream();
-            out.write(("HELLO REPLY RESULT=OK VERSION="
-                       + ver + "\n").getBytes("ISO-8859-1"));
+            if (ver == null) {
+            	s.write(ByteBuffer.wrap(("HELLO REPLY RESULT=NOVERSION\n").getBytes("ISO-8859-1")));
+            	return null ;
+            }
+            // Let's answer positively
+            s.write(ByteBuffer.wrap(("HELLO REPLY RESULT=OK VERSION="
+                       + ver + "\n").getBytes("ISO-8859-1")));
         } catch (UnsupportedEncodingException e) {
             _log.error("Caught UnsupportedEncodingException ("
                        + e.getMessage() + ")");
@@ -115,6 +116,9 @@ public class SAMHandlerFactory {
             case 2:
                 handler = new SAMv2Handler(s, verMajor, verMinor, i2cpProps);
                 break;
+            case 3:
+            	handler = new SAMv3Handler(s, verMajor, verMinor, i2cpProps);
+            	break;
             default:
                 _log.error("BUG! Trying to initialize the wrong SAM version!");
                 throw new SAMException("BUG! (in handler instantiation)");
@@ -128,6 +132,7 @@ public class SAMHandlerFactory {
 
     /* Return the best version we can use, or null on failure */
     private static String chooseBestVersion(String minVer, String maxVer) {
+    	
         int minMajor = getMajor(minVer), minMinor = getMinor(minVer);
         int maxMajor = getMajor(maxVer), maxMinor = getMinor(maxVer);
 
@@ -143,6 +148,8 @@ public class SAMHandlerFactory {
 	float fmaxVer = (float) maxMajor + (float) maxMinor / 10 ;
 	
 
+	if ( ( fminVer <=  3.0 ) && ( fmaxVer >= 3.0 ) ) return "3.0" ;
+
 	if ( ( fminVer <=  2.0 ) && ( fmaxVer >= 2.0 ) ) return "2.0" ;
 	
 	if ( ( fminVer <=  1.0 ) && ( fmaxVer >= 1.0 ) ) return "1.0" ;
diff --git a/apps/sam/java/src/net/i2p/sam/SAMInvalidDirectionException.java b/apps/sam/java/src/net/i2p/sam/SAMInvalidDirectionException.java
index b52ecda65d676549df4dda155bfb1bcd8a46b711..cd1c6b1a57d3d78f71cf37deb015676142264fac 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMInvalidDirectionException.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMInvalidDirectionException.java
@@ -15,7 +15,8 @@ package net.i2p.sam;
  * @author human
  */
 public class SAMInvalidDirectionException extends Exception {
-
+	static final long serialVersionUID = 1 ;
+	
     public SAMInvalidDirectionException() {
 	super();
     }
diff --git a/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java b/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java
index b29b2f84c5f214c05728441a9407fc0caee9b5af..2c8ed2756bec07c5c2f644d4bef9902a097c716e 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java
@@ -109,8 +109,7 @@ public abstract class SAMMessageSession {
      * @throws DataFormatException 
      */
     protected boolean sendBytesThroughMessageSession(String dest, byte[] data) throws DataFormatException {
-	Destination d = new Destination();
-	d.fromBase64(dest);
+	Destination d = SAMUtils.getDest(dest);
 
 	if (_log.shouldLog(Log.DEBUG)) {
 	    _log.debug("Sending " + data.length + " bytes to " + dest);
diff --git a/apps/sam/java/src/net/i2p/sam/SAMRawSession.java b/apps/sam/java/src/net/i2p/sam/SAMRawSession.java
index 7f56066b1b3e22712ce3a2e26a7e0bff1fc0b22b..92bf4960dd7bd36bfd51f0a45e016e759b5622a5 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMRawSession.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMRawSession.java
@@ -26,7 +26,7 @@ public class SAMRawSession extends SAMMessageSession {
     private final static Log _log = new Log(SAMRawSession.class);
     public static final int RAW_SIZE_MAX = 32*1024;
 
-    private SAMRawReceiver recv = null;
+    protected SAMRawReceiver recv = null;
     /**
      * Create a new SAM RAW session.
      *
diff --git a/apps/sam/java/src/net/i2p/sam/SAMStreamReceiver.java b/apps/sam/java/src/net/i2p/sam/SAMStreamReceiver.java
index 6d6d824b5fcd92d8b196fd23586b4bc904fb7d73..a57ddd6811aea436e7c3311b5774f5725433ba92 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMStreamReceiver.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMStreamReceiver.java
@@ -9,6 +9,7 @@ package net.i2p.sam;
  */
 
 import java.io.IOException;
+import java.nio.ByteBuffer;
 
 import net.i2p.data.Destination;
 
@@ -53,14 +54,13 @@ public interface SAMStreamReceiver {
     public void notifyStreamOutgoingConnection(int id, String result, String msg) throws IOException;
 
     /**
-     * Send a byte array to a SAM client.
+     * Transmit a byte array from I2P to a SAM client.
      *
      * @param id Connection id
      * @param data Byte array to be received
-     * @param len Number of bytes in data
      * @throws IOException 
      */
-    public void receiveStreamBytes(int id, byte data[], int len) throws IOException;
+    public void receiveStreamBytes(int id, ByteBuffer data) throws IOException;
 
     /**
      * Notify that a connection has been closed
diff --git a/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java b/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java
index 280562e48ed9dee401d4ca0a6e328bafa970c0ab..aef2802bd867f9d2ec4f41ec6dd98998ff2f0d26 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java
@@ -13,6 +13,8 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InterruptedIOException;
 import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
 import java.net.ConnectException;
 import java.net.NoRouteToHostException;
 import java.util.ArrayList;
@@ -51,15 +53,15 @@ public class SAMStreamSession {
 
     protected SAMStreamReceiver recv = null;
 
-    private SAMStreamSessionServer server = null;
+    protected SAMStreamSessionServer server = null;
 
     protected I2PSocketManager socketMgr = null;
 
     private Object handlersMapLock = new Object();
     /** stream id (Long) to SAMStreamSessionSocketReader */
-    private HashMap handlersMap = new HashMap();
+    private HashMap<Integer,SAMStreamSessionSocketReader> handlersMap = new HashMap<Integer,SAMStreamSessionSocketReader>();
     /** stream id (Long) to StreamSender */
-    private HashMap sendersMap = new HashMap();
+    private HashMap<Integer,StreamSender> sendersMap = new HashMap<Integer,StreamSender>();
 
     private Object idLock = new Object();
     private int lastNegativeId = 0;
@@ -76,6 +78,10 @@ public class SAMStreamSession {
     public static String PROP_FORCE_FLUSH = "sam.forceFlush";
     public static String DEFAULT_FORCE_FLUSH = "false";
     
+    public SAMStreamSession() {
+    	
+    }
+    
     /**
      * Create a new SAM STREAM session.
      *
@@ -166,7 +172,7 @@ public class SAMStreamSession {
         }
     }
     
-    private class DisconnectListener implements I2PSocketManager.DisconnectListener {
+    protected class DisconnectListener implements I2PSocketManager.DisconnectListener {
         public void sessionDisconnected() {
             close();
         }
@@ -572,19 +578,20 @@ public class SAMStreamSession {
             _log.debug("run() called for socket reader " + id);
 
             int read = -1;
-            byte[] data = new byte[SOCKET_HANDLER_BUF_SIZE];
+            ByteBuffer data = ByteBuffer.allocateDirect(SOCKET_HANDLER_BUF_SIZE);
 
             try {
                 InputStream in = i2pSocket.getInputStream();
 
                 while (stillRunning) {
-                    read = in.read(data);
+                	data.clear();
+                    read = Channels.newChannel(in).read(data);
                     if (read == -1) {
                         _log.debug("Handler " + id + ": connection closed");
                         break;
                     }
-                    
-                    recv.receiveStreamBytes(id, data, read);
+                    data.flip();
+                    recv.receiveStreamBytes(id, data);
                 }
             } catch (IOException e) {
                 _log.debug("Caught IOException", e);
@@ -650,7 +657,7 @@ public class SAMStreamSession {
 
     protected class v1StreamSender extends StreamSender
       {
-        private List _data;
+        private List<ByteArray> _data;
         private int _id;
         private ByteCache _cache;
         private OutputStream _out = null;
@@ -660,7 +667,7 @@ public class SAMStreamSession {
         
 	public v1StreamSender ( I2PSocket s, int id ) throws IOException {
 	    super ( s, id );
-            _data = new ArrayList(1);
+            _data = new ArrayList<ByteArray>(1);
             _id = id;
             _cache = ByteCache.getInstance(4, 32*1024);
             _out = s.getOutputStream();
diff --git a/apps/sam/java/src/net/i2p/sam/SAMUtils.java b/apps/sam/java/src/net/i2p/sam/SAMUtils.java
index 8bb3fac3008a84270f9ff0dfd1e9fddd75e7509e..61578e333fb82d022992be794c9785e02a1acd8f 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMUtils.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMUtils.java
@@ -8,6 +8,7 @@ package net.i2p.sam;
  *
  */
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.Enumeration;
@@ -19,8 +20,11 @@ import net.i2p.I2PException;
 import net.i2p.client.I2PClient;
 import net.i2p.client.I2PClientFactory;
 import net.i2p.client.naming.NamingService;
+import net.i2p.data.Base64;
 import net.i2p.data.DataFormatException;
 import net.i2p.data.Destination;
+import net.i2p.data.PrivateKey;
+import net.i2p.data.SigningPrivateKey;
 import net.i2p.util.Log;
 
 /**
@@ -73,6 +77,22 @@ public class SAMUtils {
             return false;
         }
     }
+    
+    public static class InvalidDestination extends Exception {
+    	static final long serialVersionUID = 0x1 ;
+    }
+    public static void checkPrivateDestination(String dest) throws InvalidDestination {
+    	ByteArrayInputStream destKeyStream = new ByteArrayInputStream(Base64.decode(dest));
+
+    	try {
+    		new Destination().readBytes(destKeyStream);
+    		new PrivateKey().readBytes(destKeyStream);
+    		new SigningPrivateKey().readBytes(destKeyStream);
+    	} catch (Exception e) {
+    		throw new InvalidDestination();
+    	}
+    }
+
 
     /**
      * Resolved the specified hostname.
@@ -101,6 +121,27 @@ public class SAMUtils {
         return dest;
     }
     
+    /**
+     * Resolve the destination from a key or a hostname
+     *
+     * @param s Hostname or key to be resolved
+     *
+     * @return the Destination for the specified hostname, or null if not found
+     */
+    public static Destination getDest(String s) throws DataFormatException
+    {
+    	Destination d = new Destination() ;
+    	try {
+    		d.fromBase64(s);
+    	} catch (DataFormatException e) {
+    		d = lookupHost(s, null);
+    		if ( d==null ) {
+    			throw e ;
+    		}
+    	}
+    	return d ;
+    }
+
     /**
      * Parse SAM parameters, and put them into a Propetries object
      *
diff --git a/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java
index 93a9a8d6696d7e82f51a9674c73d43eb13926b3a..4e6bd5dc788fb49e276c380afa6170a0c43e5a9e 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java
@@ -12,12 +12,11 @@ import java.io.ByteArrayOutputStream;
 import java.io.DataInputStream;
 import java.io.EOFException;
 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 java.nio.channels.SocketChannel;
+import java.nio.ByteBuffer;
 import java.util.Properties;
 import java.util.StringTokenizer;
 
@@ -40,14 +39,15 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
     
     private final static Log _log = new Log(SAMv1Handler.class);
 
-    private final static int IN_BUFSIZE = 2048;
+    protected SAMRawSession rawSession = null;
+    protected SAMDatagramSession datagramSession = null;
+    protected SAMStreamSession streamSession = null;
+    protected SAMRawSession getRawSession() {return rawSession ;}
+    protected SAMDatagramSession getDatagramSession() {return datagramSession ;}	
+    protected SAMStreamSession getStreamSession() {return streamSession ;}
 
-    private SAMRawSession rawSession = null;
-    private SAMDatagramSession datagramSession = null;
-  protected SAMStreamSession streamSession = null;
-
-    private long _id;
-    private static volatile long __id = 0;
+    protected long _id;
+    protected static volatile long __id = 0;
     
     /**
      * Create a new SAM version 1 handler.  This constructor expects
@@ -60,7 +60,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
      * @throws SAMException
      * @throws IOException 
      */
-    public SAMv1Handler(Socket s, int verMajor, int verMinor) throws SAMException, IOException {
+    public SAMv1Handler(SocketChannel s, int verMajor, int verMinor) throws SAMException, IOException {
         this(s, verMajor, verMinor, new Properties());
     }
     /**
@@ -75,7 +75,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
      * @throws SAMException
      * @throws IOException 
      */
-    public SAMv1Handler(Socket s, int verMajor, int verMinor, Properties i2cpProps) throws SAMException, IOException {
+    public SAMv1Handler(SocketChannel s, int verMajor, int verMinor, Properties i2cpProps) throws SAMException, IOException {
         super(s, verMajor, verMinor, i2cpProps);
         _id = ++__id;
         _log.debug("SAM version 1 handler instantiated");
@@ -101,16 +101,13 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
         _log.debug("SAM handling started");
 
         try {
-            InputStream in = getClientSocketInputStream();
-            int b = -1;
-
             while (true) {
                 if (shouldStop()) {
                     _log.debug("Stop request found");
                     break;
                 }
 
-                msg = DataHelper.readLine(in);
+                msg = DataHelper.readLine(getClientSocket().socket().getInputStream()).trim();
                 if (msg == null) {
                     _log.debug("Connection closed by client");
                     break;
@@ -175,27 +172,27 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
             } catch (IOException e) {
                 _log.error("Error closing socket: " + e.getMessage());
             }
-            if (rawSession != null) {
-                rawSession.close();
+            if (getRawSession() != null) {
+            	getRawSession().close();
             }
-            if (datagramSession != null) {
-                datagramSession.close();
+            if (getDatagramSession() != null) {
+            	getDatagramSession().close();
             }
-            if (streamSession != null) {
-                streamSession.close();
+            if (getStreamSession() != null) {
+            	getStreamSession().close();
             }
         }
     }
 
     /* Parse and execute a SESSION message */
-    private boolean execSessionMessage(String opcode, Properties props) {
+    protected boolean execSessionMessage(String opcode, Properties props) {
 
         String dest = "BUG!";
 
         try{
             if (opcode.equals("CREATE")) {
-                if ((rawSession != null) || (datagramSession != null)
-                    || (streamSession != null)) {
+                if ((getRawSession() != null) || (getDatagramSession() != null)
+                    || (getStreamSession() != null)) {
                     _log.debug("Trying to create a session, but one still exists");
                     return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Session already exists\"\n");
                 }
@@ -293,7 +290,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
   }
 		
     /* Parse and execute a DEST message*/
-    private boolean execDestMessage(String opcode, Properties props) {
+  protected boolean execDestMessage(String opcode, Properties props) {
 
         if (opcode.equals("GENERATE")) {
             if (props.size() > 0) {
@@ -318,7 +315,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
     }
 
     /* Parse and execute a NAMING message */
-    private boolean execNamingMessage(String opcode, Properties props) {
+  protected boolean execNamingMessage(String opcode, Properties props) {
         if (opcode.equals("LOOKUP")) {
             if (props == null) {
                 _log.debug("No parameters specified in NAMING LOOKUP message");
@@ -331,20 +328,23 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
                 return false;
             }
 
-            Destination dest;
+            Destination dest = null ;
             if (name.equals("ME")) {
-                if (rawSession != null) {
-                    dest = rawSession.getDestination();
-                } else if (streamSession != null) {
-                    dest = streamSession.getDestination();
-                } else if (datagramSession != null) {
-                    dest = datagramSession.getDestination();
+                if (getRawSession() != null) {
+                    dest = getRawSession().getDestination();
+                } else if (getStreamSession() != null) {
+                    dest = getStreamSession().getDestination();
+                } else if (getDatagramSession() != null) {
+                    dest = getDatagramSession().getDestination();
                 } else {
                     _log.debug("Lookup for SESSION destination, but session is null");
                     return false;
                 }
             } else {
-                dest = SAMUtils.lookupHost(name, null);
+            	try {
+            		dest = SAMUtils.getDest(name);
+            	} catch (DataFormatException e) {
+            	}
             }
             
             if (dest == null) {
@@ -364,8 +364,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
 
 
     /* Parse and execute a DATAGRAM message */
-    private boolean execDatagramMessage(String opcode, Properties props) {
-        if (datagramSession == null) {
+    protected boolean execDatagramMessage(String opcode, Properties props) {
+        if (getDatagramSession() == null) {
             _log.error("DATAGRAM message received, but no DATAGRAM session exists");
             return false;
         }
@@ -403,12 +403,12 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
             }
 
             try {
-                DataInputStream in = new DataInputStream(getClientSocketInputStream());
+                DataInputStream in = new DataInputStream(getClientSocket().socket().getInputStream());
                 byte[] data = new byte[size];
 
                 in.readFully(data);
 
-                if (!datagramSession.sendBytes(dest, data)) {
+                if (!getDatagramSession().sendBytes(dest, data)) {
                     _log.error("DATAGRAM SEND failed");
                     return true;
                 }
@@ -435,8 +435,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
     }
 
     /* Parse and execute a RAW message */
-    private boolean execRawMessage(String opcode, Properties props) {
-        if (rawSession == null) {
+    protected boolean execRawMessage(String opcode, Properties props) {
+        if (getRawSession() == null) {
             _log.error("RAW message received, but no RAW session exists");
             return false;
         }
@@ -474,12 +474,12 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
             }
 
             try {
-                DataInputStream in = new DataInputStream(getClientSocketInputStream());
+                DataInputStream in = new DataInputStream(getClientSocket().socket().getInputStream());
                 byte[] data = new byte[size];
 
                 in.readFully(data);
 
-                if (!rawSession.sendBytes(dest, data)) {
+                if (!getRawSession().sendBytes(dest, data)) {
                     _log.error("RAW SEND failed");
                     return true;
                 }
@@ -507,7 +507,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
 
     /* Parse and execute a STREAM message */
     protected boolean execStreamMessage(String opcode, Properties props) {
-        if (streamSession == null) {
+        if (getStreamSession() == null) {
             _log.error("STREAM message received, but no STREAM session exists");
             return false;
         }
@@ -567,11 +567,11 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
         }
 
         try {
-            if (!streamSession.sendBytes(id, getClientSocketInputStream(), size)) { // data)) {
+            if (!getStreamSession().sendBytes(id, getClientSocket().socket().getInputStream(), size)) { // data)) {
                 if (_log.shouldLog(Log.WARN))
                     _log.warn("STREAM SEND [" + size + "] failed");
                 boolean rv = writeString("STREAM CLOSED RESULT=CANT_REACH_PEER ID=" + id + " MESSAGE=\"Send of " + size + " bytes failed\"\n");
-                streamSession.closeConnection(id);
+                getStreamSession().closeConnection(id);
                 return rv;
             }
 
@@ -622,7 +622,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
 
         try {
             try {
-                if (!streamSession.connect(id, dest, props)) {
+                if (!getStreamSession().connect(id, dest, props)) {
                     _log.debug("STREAM connection failed");
                     return false;
                 }
@@ -673,7 +673,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
             }
         }
 
-        boolean closed = streamSession.closeConnection(id);
+        boolean closed = getStreamSession().closeConnection(id);
         if ( (!closed) && (_log.shouldLog(Log.WARN)) )
             _log.warn("Stream unable to be closed, but this is non fatal");
         return true;
@@ -691,7 +691,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
     
     // SAMRawReceiver implementation
     public void receiveRawBytes(byte data[]) throws IOException {
-        if (rawSession == null) {
+        if (getRawSession() == null) {
             _log.error("BUG! Received raw bytes, but session is null!");
             throw new NullPointerException("BUG! RAW session is null!");
         }
@@ -701,17 +701,18 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
         String msgText = "RAW RECEIVED SIZE=" + data.length + "\n";
         msg.write(msgText.getBytes("ISO-8859-1"));
         msg.write(data);
+        msg.flush();
         
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("sending to client: " + msgText);
 
-        writeBytes(msg.toByteArray());
+        writeBytes(ByteBuffer.wrap(msg.toByteArray()));
     }
 
     public void stopRawReceiving() {
         _log.debug("stopRawReceiving() invoked");
 
-        if (rawSession == null) {
+        if (getRawSession() == null) {
             _log.error("BUG! Got raw receiving stop, but session is null!");
             throw new NullPointerException("BUG! RAW session is null!");
         }
@@ -726,7 +727,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
 
     // SAMDatagramReceiver implementation
     public void receiveDatagramBytes(Destination sender, byte data[]) throws IOException {
-        if (datagramSession == null) {
+        if (getDatagramSession() == null) {
             _log.error("BUG! Received datagram bytes, but session is null!");
             throw new NullPointerException("BUG! DATAGRAM session is null!");
         }
@@ -740,14 +741,14 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("sending to client: " + msgText);
         msg.write(data);
-
-        writeBytes(msg.toByteArray());
+        msg.flush();
+        writeBytes(ByteBuffer.wrap(msg.toByteArray()));
     }
 
     public void stopDatagramReceiving() {
         _log.debug("stopDatagramReceiving() invoked");
 
-        if (datagramSession == null) {
+        if (getDatagramSession() == null) {
             _log.error("BUG! Got datagram receiving stop, but session is null!");
             throw new NullPointerException("BUG! DATAGRAM session is null!");
         }
@@ -764,7 +765,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
 
     public void streamSendAnswer( int id, String result, String bufferState ) throws IOException
     {
-        if ( streamSession == null )
+        if ( getStreamSession() == null )
         {
             _log.error ( "BUG! Want to answer to stream SEND, but session is null!" );
             throw new NullPointerException ( "BUG! STREAM session is null!" );
@@ -782,7 +783,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
 
     public void notifyStreamSendBufferFree( int id ) throws IOException
     {
-        if ( streamSession == null )
+        if ( getStreamSession() == null )
         {
             _log.error ( "BUG! Stream outgoing buffer is free, but session is null!" );
             throw new NullPointerException ( "BUG! STREAM session is null!" );
@@ -796,7 +797,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
 
 
     public void notifyStreamIncomingConnection(int id, Destination d) throws IOException {
-        if (streamSession == null) {
+        if (getStreamSession() == null) {
             _log.error("BUG! Received stream connection, but session is null!");
             throw new NullPointerException("BUG! STREAM session is null!");
         }
@@ -810,7 +811,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
 
     public void notifyStreamOutgoingConnection ( int id, String result, String msg ) throws IOException
     {
-        if ( streamSession == null )
+        if ( getStreamSession() == null )
         {
             _log.error ( "BUG! Received stream connection, but session is null!" );
             throw new NullPointerException ( "BUG! STREAM session is null!" );
@@ -830,34 +831,28 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
         }
     }
   
-    public void receiveStreamBytes(int id, byte data[], int len) throws IOException {
-        if (streamSession == null) {
+    public void receiveStreamBytes(int id, ByteBuffer data) throws IOException {
+        if (getStreamSession() == null) {
             _log.error("Received stream bytes, but session is null!");
             throw new NullPointerException("BUG! STREAM session is null!");
         }
 
-        String msgText = "STREAM RECEIVED ID=" + id +" SIZE=" + len + "\n";
+        String msgText = "STREAM RECEIVED ID=" + id +" SIZE=" + data.remaining() + "\n";
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("sending to client: " + msgText);
         
-        byte prefix[] = msgText.getBytes("ISO-8859-1");
+        ByteBuffer prefix = ByteBuffer.wrap(msgText.getBytes("ISO-8859-1"));
         
-        // dont waste so much memory
-        //ByteArrayOutputStream msg = new ByteArrayOutputStream();
-        //msg.write(msgText.getBytes("ISO-8859-1"));
-        //msg.write(data, 0, len);
-        // writeBytes(msg.toByteArray());
         Object writeLock = getWriteLock();
-        OutputStream out = getOut();
         synchronized (writeLock) {
-            out.write(prefix);
-            out.write(data, 0, len);
-            out.flush();
+        	while (prefix.hasRemaining()) socket.write(prefix);
+            while (data.hasRemaining()) socket.write(data);
+            socket.socket().getOutputStream().flush();
         }
     }
 
     public void notifyStreamDisconnection(int id, String result, String msg) throws IOException {
-        if (streamSession == null) {
+        if (getStreamSession() == null) {
             _log.error("BUG! Received stream disconnection, but session is null!");
             throw new NullPointerException("BUG! STREAM session is null!");
         }
@@ -873,7 +868,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
     public void stopStreamReceiving() {
         _log.debug("stopStreamReceiving() invoked", new Exception("stopped"));
 
-        if (streamSession == null) {
+        if (getStreamSession() == null) {
             _log.error("BUG! Got stream receiving stop, but session is null!");
             throw new NullPointerException("BUG! STREAM session is null!");
         }
diff --git a/apps/sam/java/src/net/i2p/sam/SAMv2Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv2Handler.java
index 75f1bd4b4788655240847d7cd126ec1a8d182cbe..fe1f379b7e0d3c4f1b7f8daa24972388cf759f65 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMv2Handler.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMv2Handler.java
@@ -9,7 +9,7 @@ package net.i2p.sam;
  */
 
 import java.io.IOException;
-import java.net.Socket;
+import java.nio.channels.SocketChannel;
 import java.util.Properties;
 
 import net.i2p.data.DataFormatException;
@@ -36,7 +36,7 @@ public class SAMv2Handler extends SAMv1Handler implements SAMRawReceiver, SAMDat
 		 * @param verMajor SAM major version to manage (should be 2)
 		 * @param verMinor SAM minor version to manage
 		 */
-		public SAMv2Handler ( Socket s, int verMajor, int verMinor ) throws SAMException, IOException
+		public SAMv2Handler ( SocketChannel s, int verMajor, int verMinor ) throws SAMException, IOException
 		{
 			this ( s, verMajor, verMinor, new Properties() );
 		}
@@ -52,7 +52,7 @@ public class SAMv2Handler extends SAMv1Handler implements SAMRawReceiver, SAMDat
 		 * @param i2cpProps properties to configure the I2CP connection (host, port, etc)
 		 */
 
-		public SAMv2Handler ( Socket s, int verMajor, int verMinor, Properties i2cpProps ) throws SAMException, IOException
+		public SAMv2Handler ( SocketChannel s, int verMajor, int verMinor, Properties i2cpProps ) throws SAMException, IOException
 		{
 			super ( s, verMajor, verMinor, i2cpProps );
 		}
@@ -72,7 +72,7 @@ public class SAMv2Handler extends SAMv1Handler implements SAMRawReceiver, SAMDat
 		/* Parse and execute a STREAM message */
 		protected boolean execStreamMessage ( String opcode, Properties props )
 		{
-			if ( streamSession == null )
+			if ( getStreamSession() == null )
 			{
 				_log.error ( "STREAM message received, but no STREAM session exists" );
 				return false;
@@ -173,7 +173,7 @@ public class SAMv2Handler extends SAMv1Handler implements SAMRawReceiver, SAMDat
 				}
 			}
 
-			streamSession.setReceiveLimit ( id, limit, nolimit ) ;
+			getStreamSession().setReceiveLimit ( id, limit, nolimit ) ;
 
 			return true;
 		}
diff --git a/apps/sam/java/src/net/i2p/sam/SAMv2StreamSession.java b/apps/sam/java/src/net/i2p/sam/SAMv2StreamSession.java
index de5b7851bc7de434f29dac13e3ac2c175893413a..4197597eb0d56e9f26bcd1cbab60b2669916786b 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMv2StreamSession.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMv2StreamSession.java
@@ -12,6 +12,8 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InterruptedIOException;
 import java.io.OutputStream;
+import java.nio.channels.Channels;
+import java.nio.ByteBuffer;
 import java.net.ConnectException;
 import java.net.NoRouteToHostException;
 import java.util.ArrayList;
@@ -140,9 +142,6 @@ public class SAMv2StreamSession extends SAMStreamSession
 		public class StreamConnector implements Runnable
 		{
 
-				private Object runningLock = new Object();
-				private boolean stillRunning = true;
-
 				private int id;
 				private Destination      dest ;
 				private I2PSocketOptions opts ;
@@ -245,7 +244,7 @@ public class SAMv2StreamSession extends SAMStreamSession
 		protected class v2StreamSender extends StreamSender
 
 		{
-				private List _data;
+				private List<ByteArray> _data;
 				private int _dataSize;
 				private int _id;
 				private ByteCache _cache;
@@ -257,7 +256,7 @@ public class SAMv2StreamSession extends SAMStreamSession
 				public v2StreamSender ( I2PSocket s, int id ) throws IOException
 				{
 					super ( s, id );
-					_data = new ArrayList ( 1 );
+					_data = new ArrayList<ByteArray> ( 1 );
 					_dataSize = 0;
 					_id = id;
 					_cache = ByteCache.getInstance ( 10, 32 * 1024 );
@@ -511,7 +510,7 @@ public class SAMv2StreamSession extends SAMStreamSession
 					_log.debug ( "run() called for socket reader " + id );
 
 					int read = -1;
-					byte[] data = new byte[SOCKET_HANDLER_BUF_SIZE];
+					ByteBuffer data = ByteBuffer.allocateDirect(SOCKET_HANDLER_BUF_SIZE);
 
 					try
 					{
@@ -533,7 +532,8 @@ public class SAMv2StreamSession extends SAMStreamSession
 									break ;
 							}
 							
-							read = in.read ( data );
+							data.clear();
+							read = Channels.newChannel(in).read ( data );
 
 							if ( read == -1 )
 							{
@@ -542,8 +542,8 @@ public class SAMv2StreamSession extends SAMStreamSession
 							}
 
 							totalReceived += read ;
-							
-							recv.receiveStreamBytes ( id, data, read );
+							data.flip();
+							recv.receiveStreamBytes ( id, data );
 						}
 					}
 					catch ( IOException e )
diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java
new file mode 100644
index 0000000000000000000000000000000000000000..69f14430df448c734cf5e19db6d32699f77374d7
--- /dev/null
+++ b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java
@@ -0,0 +1,90 @@
+/**
+ * @author MKVore
+ *
+ */
+
+package net.i2p.sam;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.Properties;
+
+import net.i2p.client.I2PSessionException;
+import net.i2p.data.DataFormatException;
+import net.i2p.data.Destination;
+import net.i2p.util.Log;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress ;
+import java.nio.ByteBuffer;
+
+public class SAMv3DatagramSession extends SAMDatagramSession implements SAMv3Handler.Session, SAMDatagramReceiver {
+	
+	private final static Log _log = new Log ( SAMv3DatagramSession.class );
+
+	SAMv3Handler handler = null ;
+	SAMv3Handler.DatagramServer server = null ;
+	String nick = null ;
+	SocketAddress clientAddress = null ;
+	
+	public String getNick() { return nick; }
+
+	/**
+	 *   build a DatagramSession according to informations registered
+	 *   with the given nickname
+	 * @param nick nickname of the session
+	 * @throws IOException
+	 * @throws DataFormatException
+	 * @throws I2PSessionException
+	 */
+	public SAMv3DatagramSession(String nick) 
+	throws IOException, DataFormatException, I2PSessionException {
+		
+		super(SAMv3Handler.sSessionsHash.get(nick).getDest(),
+				SAMv3Handler.sSessionsHash.get(nick).getProps(),
+				null
+				);
+		this.nick = nick ;
+		this.recv = this ;
+		this.server = SAMv3Handler.DatagramServer.getInstance() ;
+
+		SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick);
+        if ( rec==null ) throw new InterruptedIOException() ;
+
+        this.handler = rec.getHandler();
+		
+        Properties props = rec.getProps();
+    	String portStr = props.getProperty("PORT") ;
+    	if ( portStr==null ) {
+    		_log.debug("receiver port not specified. Current socket will be used.");
+    	}
+    	else {
+    		int port = Integer.parseInt(portStr);
+    	
+    		String host = props.getProperty("HOST");
+    		if ( host==null ) {    		
+    			host = rec.getHandler().getClientIP();
+    			_log.debug("no host specified. Taken from the client socket : " + host+':'+port);
+    		}
+
+    	
+    		this.clientAddress = new InetSocketAddress(host,port);
+    	}
+	}
+
+	public void receiveDatagramBytes(Destination sender, byte[] data) throws IOException {
+		if (this.clientAddress==null) {
+			this.handler.receiveDatagramBytes(sender, data);
+		} else {
+			String msg = sender.toBase64()+"\n";
+			ByteBuffer msgBuf = ByteBuffer.allocate(msg.length()+data.length);
+			msgBuf.put(msg.getBytes("ISO-8859-1"));
+			msgBuf.put(data);
+			msgBuf.flip();
+			this.server.send(this.clientAddress, msgBuf);
+		}
+	}
+
+	public void stopDatagramReceiving() {
+	}
+}
diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a64d84416c76b7a8ec629d434de392838c94579
--- /dev/null
+++ b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java
@@ -0,0 +1,783 @@
+package net.i2p.sam;
+/*
+ * free (adj.): unencumbered; not under the control of others
+ * Written by human in 2004 and released into the public domain
+ * with no warranty of any kind, either expressed or implied.
+ * It probably won't  make your computer catch on fire, or eat
+ * your children, but it might.  Use at your own risk.
+ *
+ */
+
+
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.net.ConnectException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.NoRouteToHostException;
+import java.nio.channels.DatagramChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.ByteBuffer;
+import java.util.Properties;
+import java.util.HashMap;
+import java.util.StringTokenizer;
+
+import net.i2p.I2PException;
+import net.i2p.client.I2PSessionException;
+import net.i2p.data.Base64;
+import net.i2p.data.DataFormatException;
+import net.i2p.data.DataHelper;
+import net.i2p.data.Destination;
+import net.i2p.util.Log;
+import net.i2p.util.I2PAppThread;
+
+/**
+ * Class able to handle a SAM version 3 client connection.
+ *
+ * @author mkvore
+ */
+
+public class SAMv3Handler extends SAMv1Handler
+{
+	private final static Log _log = new Log ( SAMv3Handler.class );
+
+	protected SAMv3RawSession rawSession = null ;
+	protected SAMv3DatagramSession datagramSession = null ;
+	protected SAMv3StreamSession streamSession = null ;
+	
+	protected SAMRawSession getRawSession() {
+		return rawSession ;
+	}
+	protected SAMDatagramSession getDatagramSession() {
+		return datagramSession ;
+	}
+	protected SAMStreamSession getStreamSession() {
+		return streamSession ;
+	}
+	
+	protected Session session = null ;
+	
+	interface Session {
+		String getNick();
+		void close();
+		boolean sendBytes(String dest, byte[] data) throws DataFormatException;
+	}
+	
+	/**
+	 * Create a new SAM version 3 handler.  This constructor expects
+	 * that the SAM HELLO message has been still answered (and
+	 * stripped) from the socket input stream.
+	 *
+	 * @param s Socket attached to a SAM client
+	 * @param verMajor SAM major version to manage (should be 3)
+	 * @param verMinor SAM minor version to manage
+	 */
+	public SAMv3Handler ( SocketChannel s, int verMajor, int verMinor ) throws SAMException, IOException
+	{
+		this ( s, verMajor, verMinor, new Properties() );
+	}
+
+	/**
+	 * Create a new SAM version 3 handler.  This constructor expects
+	 * that the SAM HELLO message has been still answered (and
+	 * stripped) from the socket input stream.
+	 *
+	 * @param s Socket attached to a SAM client
+	 * @param verMajor SAM major version to manage (should be 3)
+	 * @param verMinor SAM minor version to manage
+	 * @param i2cpProps properties to configure the I2CP connection (host, port, etc)
+	 */
+
+	public SAMv3Handler ( SocketChannel s, int verMajor, int verMinor, Properties i2cpProps ) throws SAMException, IOException
+	{
+		super ( s, verMajor, verMinor, i2cpProps );
+		_log.debug("SAM version 3 handler instantiated");
+	}
+
+	public boolean verifVersion()
+	{
+		return (verMajor == 3 && verMinor == 0) ;
+	}
+
+	static public class DatagramServer  {
+		
+		private static DatagramServer _instance = null ;
+		private static DatagramChannel server = null ;
+		
+		public static DatagramServer getInstance() throws IOException {
+			return getInstance(new Properties());
+		}
+		
+		public static DatagramServer getInstance(Properties props) throws IOException {
+			if (_instance==null) {
+				_instance = new DatagramServer(props);
+			}
+			return _instance ;
+		}
+		
+		public DatagramServer(Properties props) throws IOException {
+			if (server==null) {
+				server = DatagramChannel.open();
+			}
+			
+			String host = props.getProperty(SAMBridge.PROP_DATAGRAM_HOST, SAMBridge.DEFAULT_DATAGRAM_HOST);
+			String portStr = props.getProperty(SAMBridge.PROP_DATAGRAM_PORT, SAMBridge.DEFAULT_DATAGRAM_PORT);
+			int port ;
+			try {
+				port = Integer.parseInt(portStr);
+			} catch (NumberFormatException e) {
+				port = Integer.parseInt(SAMBridge.DEFAULT_DATAGRAM_PORT);
+			}
+			
+			server.socket().bind(new InetSocketAddress(host, port));
+			new I2PAppThread(new Listener(server), "DatagramListener").start();
+		}
+		
+		public void send(SocketAddress addr, ByteBuffer msg) throws IOException {
+			server.send(msg, addr);
+		}
+		
+		class Listener implements Runnable {
+			
+			DatagramChannel server = null;
+			
+			public Listener(DatagramChannel server)
+			{
+				this.server = server ;
+			}
+			public void run()
+			{
+				ByteBuffer inBuf = ByteBuffer.allocateDirect(SAMRawSession.RAW_SIZE_MAX+1024);
+				
+				while (!Thread.interrupted())
+				{
+					inBuf.clear();
+					try {
+						server.receive(inBuf);
+					} catch (IOException e) {
+						break ;
+					}
+					inBuf.flip();
+					ByteBuffer outBuf = ByteBuffer.wrap(new byte[inBuf.remaining()]);
+					outBuf.put(inBuf);
+					outBuf.flip();
+					new I2PAppThread(new MessageDispatcher(outBuf.array()), "MessageDispatcher").start();
+				}
+			}
+		}
+	}
+
+	public static class MessageDispatcher implements Runnable
+	{
+		ByteArrayInputStream is = null ;
+		
+		public MessageDispatcher(byte[] buf)
+		{
+			this.is = new java.io.ByteArrayInputStream(buf) ;
+		}
+		
+		public void run() {
+			String header = null ;
+			String nick ;
+			String dest ;
+			String version ;
+
+			try {
+				header = DataHelper.readLine(is).trim();
+				StringTokenizer tok = new StringTokenizer(header, " ");
+				if (tok.countTokens() != 3) {
+					// This is not a correct message, for sure
+					_log.debug("Error in message format");
+					return;
+				}
+				version = tok.nextToken();
+				if (!"3.0".equals(version)) return ;
+				nick = tok.nextToken();
+				dest = tok.nextToken();
+
+				byte[] data = new byte[is.available()];
+				is.read(data);
+				SessionRecord rec = sSessionsHash.get(nick);
+				if (rec!=null) {
+					rec.getHandler().session.sendBytes(dest,data);
+				}
+			} catch (Exception e) {}
+		}
+	}
+	
+	public class SessionRecord
+	{
+		protected String m_dest ;
+		protected Properties m_props ;
+		protected ThreadGroup m_threadgroup ;
+		protected SAMv3Handler m_handler ;
+
+		public SessionRecord( String dest, Properties props, SAMv3Handler handler )
+		{
+			m_dest = new String(dest) ; 
+			m_props = new Properties() ;
+			m_props.putAll(props);
+			m_threadgroup = null ;
+			m_handler = handler ;
+		}
+
+		public SessionRecord( SessionRecord in )
+		{
+			m_dest = in.getDest();
+			m_props = in.getProps();
+			m_threadgroup = in.getThreadGroup();
+			m_handler = in.getHandler();
+		}
+
+		synchronized public String getDest()
+		{
+			return new String(m_dest) ;
+		}
+		synchronized public Properties getProps()
+		{
+			Properties p = new Properties();
+			p.putAll(m_props);
+			return m_props;
+		}
+		synchronized public SAMv3Handler getHandler()
+		{
+			return m_handler ;
+		}
+		synchronized public ThreadGroup getThreadGroup()
+		{
+			return m_threadgroup ;
+		}
+		synchronized public void createThreadGroup(String name)
+		{
+			if (m_threadgroup == null)
+				m_threadgroup = new ThreadGroup(name);
+		}
+	}
+
+	public static class SessionsDB
+	{
+		static final long serialVersionUID = 0x1 ;
+
+		class ExistingId   extends Exception {
+			static final long serialVersionUID = 0x1 ;
+		}
+		class ExistingDest extends Exception {
+			static final long serialVersionUID = 0x1 ;
+		}
+		
+		HashMap<String, SessionRecord> map ;
+
+		public SessionsDB() {
+			map = new HashMap<String, SessionRecord>() ;
+		}
+
+		synchronized public boolean put( String nick, SessionRecord session ) throws ExistingId, ExistingDest
+		{
+			if ( map.containsKey(nick) ) {
+				throw new ExistingId();
+			}
+			for ( SessionRecord r : map.values() ) {
+				if (r.getDest().equals(session.getDest())) {
+					throw new ExistingDest();
+				}
+			}
+
+			if ( !map.containsKey(nick) ) {
+				session.createThreadGroup("SAM session "+nick);
+				map.put(nick, session) ;
+				return true ;
+			}
+			else
+				return false ;
+		}
+		synchronized public boolean del( String nick )
+		{
+			SessionRecord rec = map.get(nick);
+			
+			if ( rec!=null ) {
+				map.remove(nick);
+				return true ;
+			}
+			else
+				return false ;
+		}
+		synchronized public SessionRecord get(String nick)
+		{
+			return map.get(nick);
+		}
+		synchronized public boolean containsKey( String nick )
+		{
+			return map.containsKey(nick);
+		}
+	}
+
+	public static SessionsDB sSessionsHash = new SessionsDB() ;
+
+	public String getClientIP()
+	{
+		return this.socket.socket().getInetAddress().getHostAddress();
+	}
+	
+	boolean stolenSocket = false ;
+	
+	boolean streamForwardingSocket = false ;
+	
+	public void stealSocket()
+	{
+		stolenSocket = true ;
+		this.stopHandling();
+	}
+	
+	public void handle() {
+		String msg = null;
+		String domain = null;
+		String opcode = null;
+		boolean canContinue = false;
+		StringTokenizer tok;
+		Properties props;
+
+		this.thread.setName("SAMv3Handler " + _id);
+		_log.debug("SAM handling started");
+
+		try {
+			InputStream in = getClientSocket().socket().getInputStream();
+
+			while (true) {
+				if (shouldStop()) {
+					_log.debug("Stop request found");
+					break;
+				}
+				String line = DataHelper.readLine(in) ;
+				if (line==null) {
+					_log.debug("Connection closed by client (line read : null)");
+					break;
+				}
+				msg = line.trim();
+
+				if (_log.shouldLog(Log.DEBUG)) {
+					_log.debug("New message received: [" + msg + "]");
+				}
+
+				if(msg.equals("")) {
+					_log.debug("Ignoring newline");
+					continue;
+				}
+
+				tok = new StringTokenizer(msg, " ");
+				if (tok.countTokens() < 2) {
+					// This is not a correct message, for sure
+					_log.debug("Error in message format");
+					break;
+				}
+				domain = tok.nextToken();
+				opcode = tok.nextToken();
+				if (_log.shouldLog(Log.DEBUG)) {
+					_log.debug("Parsing (domain: \"" + domain
+							+ "\"; opcode: \"" + opcode + "\")");
+				}
+				props = SAMUtils.parseParams(tok);
+
+				if (domain.equals("STREAM")) {
+					canContinue = execStreamMessage(opcode, props);
+				} else if (domain.equals("SESSION")) {
+					if (i2cpProps != null)
+						props.putAll(i2cpProps); // make sure we've got the i2cp settings
+					canContinue = execSessionMessage(opcode, props);
+				} else if (domain.equals("DEST")) {
+					canContinue = execDestMessage(opcode, props);
+				} else if (domain.equals("NAMING")) {
+					canContinue = execNamingMessage(opcode, props);
+				} else if (domain.equals("DATAGRAM")) {
+					canContinue = execDatagramMessage(opcode, props);
+				} else {
+					_log.debug("Unrecognized message domain: \""
+							+ domain + "\"");
+					break;
+				}
+
+				if (!canContinue) {
+					break;
+				}
+			}
+		} catch (IOException e) {
+			_log.debug("Caught IOException ("
+					+ e.getMessage() + ") for message [" + msg + "]", e);
+		} catch (Exception e) {
+			_log.error("Unexpected exception for message [" + msg + "]", e);
+		} finally {
+			_log.debug("Stopping handler");
+			
+			if (!this.stolenSocket)
+			{
+				try {
+					closeClientSocket();
+				} catch (IOException e) {
+					_log.error("Error closing socket: " + e.getMessage());
+				}
+			}
+			if (streamForwardingSocket) 
+			{
+				if (this.getStreamSession()!=null) {
+					try {
+						this.streamSession.stopForwardingIncoming();
+					} catch (SAMException e) {
+						_log.error("Error while stopping forwarding connections: " + e.getMessage());
+					} catch (InterruptedIOException e) {
+						_log.error("Interrupted while stopping forwarding connections: " + e.getMessage());
+					}
+				}
+			}
+		
+
+
+			die();
+		}
+	}
+
+	protected void die() {
+		SessionRecord rec = null ;
+		
+		if (session!=null) {
+			session.close();
+			rec = sSessionsHash.get(session.getNick());
+		}
+		if (rec!=null) {
+			rec.getThreadGroup().interrupt() ;
+			while (rec.getThreadGroup().activeCount()>0)
+				try {
+					Thread.sleep(1000);
+				} catch ( InterruptedException e) {}
+			rec.getThreadGroup().destroy();
+			sSessionsHash.del(session.getNick());
+		}
+	}
+	
+	/* Parse and execute a SESSION message */
+	@Override
+	protected boolean execSessionMessage(String opcode, Properties props) {
+
+		String dest = "BUG!";
+		String nick =  null ;
+		boolean ok = false ;
+
+		try{
+			if (opcode.equals("CREATE")) {
+				if ((this.getRawSession()!= null) || (this.getDatagramSession() != null)
+						|| (this.getStreamSession() != null)) {
+					_log.debug("Trying to create a session, but one still exists");
+					return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Session already exists\"\n");
+				}
+				if (props == null) {
+					_log.debug("No parameters specified in SESSION CREATE message");
+					return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No parameters for SESSION CREATE\"\n");
+				}
+
+				dest = props.getProperty("DESTINATION");
+				if (dest == null) {
+					_log.debug("SESSION DESTINATION parameter not specified");
+					return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"DESTINATION not specified\"\n");
+				}
+				props.remove("DESTINATION");
+
+
+				if (dest.equals("TRANSIENT")) {
+					_log.debug("TRANSIENT destination requested");
+					ByteArrayOutputStream priv = new ByteArrayOutputStream(640);
+					SAMUtils.genRandomKey(priv, null);
+
+					dest = Base64.encode(priv.toByteArray());
+				} else {
+					_log.debug("Custom destination specified [" + dest + "]");
+				}
+
+				try {
+					SAMUtils.checkPrivateDestination(dest);
+				} catch ( SAMUtils.InvalidDestination e ) {
+                    return writeString("SESSION STATUS RESULT=INVALID_KEY\n");
+				}
+
+				nick = props.getProperty("ID");
+				if (nick == null) {
+					_log.debug("SESSION ID parameter not specified");
+					return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"ID not specified\"\n");
+				}
+				props.remove("ID");
+
+
+				String style = props.getProperty("STYLE");
+				if (style == null) {
+					_log.debug("SESSION STYLE parameter not specified");
+					return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No SESSION STYLE specified\"\n");
+				}
+				props.remove("STYLE");
+
+
+
+				// Record the session in the database sSessionsHash
+				Properties allProps = new Properties();
+				allProps.putAll(i2cpProps);
+				allProps.putAll(props);
+				
+
+				try {
+					sSessionsHash.put( nick, new SessionRecord(dest, allProps, this) ) ;
+				} catch (SessionsDB.ExistingId e) {
+					_log.debug("SESSION ID parameter already in use");
+					return writeString("SESSION STATUS RESULT=DUPLICATED_ID\n");
+				} catch (SessionsDB.ExistingDest e) {
+					return writeString("SESSION STATUS RESULT=DUPLICATED_DEST\n");
+				}
+
+				
+				// Create the session
+
+				if (style.equals("RAW")) {
+					DatagramServer.getInstance(i2cpProps);
+					rawSession = newSAMRawSession(nick);
+					this.session = rawSession ;
+				} else if (style.equals("DATAGRAM")) {
+					DatagramServer.getInstance(i2cpProps);
+					datagramSession = newSAMDatagramSession(nick);
+					this.session = datagramSession ;
+				} else if (style.equals("STREAM")) {
+					streamSession = newSAMStreamSession(nick);
+					this.session = streamSession ;
+				} else {
+					_log.debug("Unrecognized SESSION STYLE: \"" + style +"\"");
+					return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized SESSION STYLE\"\n");
+				}
+				ok = true ;
+				return writeString("SESSION STATUS RESULT=OK DESTINATION="
+						+ dest + "\n");
+			} else {
+				_log.debug("Unrecognized SESSION message opcode: \""
+						+ opcode + "\"");
+				return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized opcode\"\n");
+			}
+		} catch (DataFormatException e) {
+			_log.debug("Invalid destination specified");
+			return writeString("SESSION STATUS RESULT=INVALID_KEY DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
+		} catch (I2PSessionException e) {
+			_log.debug("I2P error when instantiating session", e);
+			return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
+		} catch (SAMException e) {
+			_log.error("Unexpected SAM error", e);
+			return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
+		} catch (IOException e) {
+			_log.error("Unexpected IOException", e);
+			return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
+		} finally {
+			// unregister the session if it has not been created
+			if ( !ok && nick!=null ) {
+				sSessionsHash.del(nick) ;
+				session = null ;
+			}
+		}
+	}
+
+	SAMv3StreamSession newSAMStreamSession(String login )
+	throws IOException, DataFormatException, SAMException
+	{
+		return new SAMv3StreamSession( login ) ;
+	}
+
+	SAMv3RawSession newSAMRawSession(String login )
+	throws IOException, DataFormatException, SAMException, I2PSessionException
+	{
+		return new SAMv3RawSession( login ) ;
+	}
+
+	SAMv3DatagramSession newSAMDatagramSession(String login )
+	throws IOException, DataFormatException, SAMException, I2PSessionException
+	{
+		return new SAMv3DatagramSession( login ) ;
+	}
+
+	/* Parse and execute a STREAM message */
+	protected boolean execStreamMessage ( String opcode, Properties props )
+	{
+		String nick = null ;
+		SessionRecord rec = null ;
+
+		if ( session != null )
+		{
+			_log.error ( "STREAM message received, but this session is a master session" );
+			
+			try {
+				notifyStreamResult(true, "I2P_ERROR", "master session cannot be used for streams");
+			} catch (IOException e) {}
+			return false;
+		}
+
+		nick = props.getProperty("ID");
+		if (nick == null) {
+			_log.debug("SESSION ID parameter not specified");
+			try {
+				notifyStreamResult(true, "I2P_ERROR", "ID not specified");
+			} catch (IOException e) {}
+			return false ;
+		}
+		props.remove("ID");
+
+		rec = sSessionsHash.get(nick);
+
+		if ( rec==null ) {
+			_log.debug("STREAM SESSION ID does not exist");
+			try {
+				notifyStreamResult(true, "INVALID_ID", "STREAM SESSION ID does not exist");
+			} catch (IOException e) {}
+			return false ;
+		}
+		
+		streamSession = rec.getHandler().streamSession ;
+		
+		if (streamSession==null) {
+			_log.debug("specified ID is not a stream session");
+			try {
+				notifyStreamResult(true, "I2P_ERROR",  "specified ID is not a STREAM session");
+			} catch (IOException e) {}
+			return false ;
+		}
+
+		if ( opcode.equals ( "CONNECT" ) )
+		{
+			return execStreamConnect ( props );
+		} 
+		else if ( opcode.equals ( "ACCEPT" ) )
+		{
+			return execStreamAccept ( props );
+		}
+		else if ( opcode.equals ( "FORWARD") )
+		{
+			return execStreamForwardIncoming( props );
+		}
+		else
+		{
+			_log.debug ( "Unrecognized RAW message opcode: \""
+					+ opcode + "\"" );
+			try {
+				notifyStreamResult(true, "I2P_ERROR",  "Unrecognized RAW message opcode: "+opcode );
+			} catch (IOException e) {}
+			return false;
+		}
+	}
+
+	
+	protected boolean execStreamConnect( Properties props) {
+		try {
+			if (props == null) {
+				notifyStreamResult(true,"I2P_ERROR","No parameters specified in STREAM CONNECT message");
+				_log.debug("No parameters specified in STREAM CONNECT message");
+				return false;
+			}
+			boolean verbose = props.getProperty("SILENT","false").equals("false");
+		
+			String dest = props.getProperty("DESTINATION");
+			if (dest == null) {
+				notifyStreamResult(verbose, "I2P_ERROR", "Destination not specified in RAW SEND message");
+				_log.debug("Destination not specified in RAW SEND message");
+				return false;
+			}
+			props.remove("DESTINATION");
+
+			try {
+				streamSession.connect( this, dest, props );
+				return true ;
+			} catch (DataFormatException e) {
+				_log.debug("Invalid destination in STREAM CONNECT message");
+				notifyStreamResult ( verbose, "INVALID_KEY", null );
+			} catch (ConnectException e) {
+				_log.debug("STREAM CONNECT failed: " + e.getMessage());
+				notifyStreamResult ( verbose, "CONNECTION_REFUSED", null );
+			} catch (NoRouteToHostException e) {
+				_log.debug("STREAM CONNECT failed: " + e.getMessage());
+				notifyStreamResult ( verbose, "CANT_REACH_PEER", null );
+			} catch (InterruptedIOException e) {
+				_log.debug("STREAM CONNECT failed: " + e.getMessage());
+				notifyStreamResult ( verbose, "TIMEOUT", null );
+			} catch (I2PException e) {
+				_log.debug("STREAM CONNECT failed: " + e.getMessage());
+				notifyStreamResult ( verbose, "I2P_ERROR", e.getMessage() );
+			}
+		} catch (IOException e) {
+		}
+		return false ;
+	}
+
+	protected boolean execStreamForwardIncoming( Properties props ) {
+		try {
+			try {
+				streamForwardingSocket = true ;
+				streamSession.startForwardingIncoming(props);
+				notifyStreamResult( true, "OK", null );
+				return true ;
+			} catch (SAMException e) {
+				_log.debug("Forwarding STREAM connections failed: " + e.getMessage());
+				notifyStreamResult ( true, "I2P_ERROR", "Forwarding failed : " + e.getMessage() );
+			}
+		} catch (IOException e) {
+		}
+		return false ;		
+	}
+
+	protected boolean execStreamAccept( Properties props )
+	{
+		boolean verbose = props.getProperty( "SILENT", "false").equals("false");
+		try {
+			try {
+				notifyStreamResult(verbose, "OK", null);
+				streamSession.accept(this, verbose);
+				return true ;
+			} catch (InterruptedIOException e) {
+				_log.debug("STREAM ACCEPT failed: " + e.getMessage());
+				notifyStreamResult( verbose, "TIMEOUT", e.getMessage() );
+			} catch (I2PException e) {
+				_log.debug("STREAM ACCEPT failed: " + e.getMessage());
+				notifyStreamResult ( verbose, "I2P_ERROR", e.getMessage() );
+			} catch (SAMException e) {
+				_log.debug("STREAM ACCEPT failed: " + e.getMessage());
+				notifyStreamResult ( verbose, "ALREADY_ACCEPTING", null );
+			}
+		} catch (IOException e) {
+		}
+		return false ;
+	}
+	
+
+	public void notifyStreamResult(boolean verbose, String result, String message) throws IOException
+    {
+		if (!verbose) return ;
+		
+		String out = "STREAM STATUS RESULT="+result;
+		if (message!=null)
+			out = out + " MESSAGE=\"" + message + "\"";
+		out = out + '\n';
+        
+        if ( !writeString ( out ) )
+        {
+            throw new IOException ( "Error notifying connection to SAM client" );
+        }
+    }
+
+	public void notifyStreamIncomingConnection(Destination d) throws IOException {
+	    if (getStreamSession() == null) {
+	        _log.error("BUG! Received stream connection, but session is null!");
+	        throw new NullPointerException("BUG! STREAM session is null!");
+	    }
+
+	    if (!writeString(d.toBase64() + "\n")) {
+	        throw new IOException("Error notifying connection to SAM client");
+	    }
+	}
+	
+	public static void notifyStreamIncomingConnection(SocketChannel client, Destination d) throws IOException {
+	    if (!writeString(d.toBase64() + "\n", client)) {
+	        throw new IOException("Error notifying connection to SAM client");
+	    }
+	}
+
+}
+
diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java
new file mode 100644
index 0000000000000000000000000000000000000000..3695bf3ddaf8294f85841f4ff4c3bef2bab650e0
--- /dev/null
+++ b/apps/sam/java/src/net/i2p/sam/SAMv3RawSession.java
@@ -0,0 +1,90 @@
+/**
+ * 
+ */
+package net.i2p.sam;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.util.Properties;
+
+import net.i2p.client.I2PSessionException;
+import net.i2p.data.DataFormatException;
+import net.i2p.util.Log;
+
+/**
+ * @author MKVore
+ *
+ */
+public class SAMv3RawSession extends SAMRawSession  implements SAMv3Handler.Session, SAMRawReceiver {
+	
+	String nick = null ;
+	SAMv3Handler handler = null ;
+	SAMv3Handler.DatagramServer server ;
+	private final static Log _log = new Log ( SAMv3DatagramSession.class );
+	SocketAddress clientAddress = null ;
+
+	public String getNick() { return nick; }
+
+	/**
+	 *   Build a Raw Datagram Session according to information
+	 *   registered with the given nickname
+	 *   
+	 * @param nick nickname of the session
+	 * @throws IOException
+	 * @throws DataFormatException
+	 * @throws I2PSessionException
+	 */
+	public SAMv3RawSession(String nick) 
+	throws IOException, DataFormatException, I2PSessionException {
+		
+		super(SAMv3Handler.sSessionsHash.get(nick).getDest(),
+				SAMv3Handler.sSessionsHash.get(nick).getProps(),
+				SAMv3Handler.sSessionsHash.get(nick).getHandler()
+				);
+		this.nick = nick ;
+		this.recv = this ;
+		this.server = SAMv3Handler.DatagramServer.getInstance() ;
+
+		SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick);
+        if ( rec==null ) throw new InterruptedIOException() ;
+
+        this.handler = rec.getHandler();
+		
+        Properties props = rec.getProps();
+        
+        
+    	String portStr = props.getProperty("PORT") ;
+    	if ( portStr==null ) {
+    		_log.debug("receiver port not specified. Current socket will be used.");
+    	}
+    	else {
+    		int port = Integer.parseInt(portStr);
+    	
+    		String host = props.getProperty("HOST");
+    		if ( host==null ) {
+    			host = rec.getHandler().getClientIP();
+
+    			_log.debug("no host specified. Taken from the client socket : " + host +':'+port);
+    		}
+
+    	
+    		this.clientAddress = new InetSocketAddress(host,port);
+    	}
+	}
+	
+	public void receiveRawBytes(byte[] data) throws IOException {
+		if (this.clientAddress==null) {
+			this.handler.receiveRawBytes(data);
+		} else {
+			ByteBuffer msgBuf = ByteBuffer.allocate(data.length);
+			msgBuf.put(data);
+			msgBuf.flip();
+			this.server.send(this.clientAddress, msgBuf);
+		}
+	}
+
+	public void stopRawReceiving() {}
+}
diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java
new file mode 100644
index 0000000000000000000000000000000000000000..26d99fa014eb04dcd25a341770e01e9fd5327cb2
--- /dev/null
+++ b/apps/sam/java/src/net/i2p/sam/SAMv3StreamSession.java
@@ -0,0 +1,408 @@
+package net.i2p.sam;
+/*
+ * free (adj.): unencumbered; not under the control of others
+ * Written by human in 2004 and released into the public domain
+ * with no warranty of any kind, either expressed or implied.
+ * It probably won't  make your computer catch on fire, or eat
+ * your children, but it might.  Use at your own risk.
+ *
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.ConnectException;
+import java.net.NoRouteToHostException;
+import java.util.Properties;
+
+import net.i2p.I2PException;
+import net.i2p.client.I2PClient;
+import net.i2p.client.streaming.I2PServerSocket;
+import net.i2p.client.streaming.I2PSocket;
+import net.i2p.client.streaming.I2PSocketManagerFactory;
+import net.i2p.client.streaming.I2PSocketOptions;
+import net.i2p.data.Base64;
+import net.i2p.data.DataFormatException;
+import net.i2p.data.Destination;
+import net.i2p.util.I2PAppThread;
+import net.i2p.util.Log;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.nio.ByteBuffer ;
+import java.nio.channels.SocketChannel;
+
+/**
+ * SAMv3 STREAM session class.
+ *
+ * @author mkvore
+ */
+
+public class SAMv3StreamSession  extends SAMStreamSession implements SAMv3Handler.Session
+{
+
+		private final static Log _log = new Log ( SAMv3StreamSession.class );
+		
+		protected final int BUFFER_SIZE = 1024 ;
+		
+		protected Object socketServerLock = new Object();
+		protected I2PServerSocket socketServer = null;
+	
+		protected String nick ;
+		
+		public String getNick() {
+			return nick ;
+		}
+		
+		   /**
+	     * Create a new SAM STREAM session, according to information
+	     * registered with the given nickname
+	     *
+	     * @param login The nickname
+	     * @throws IOException
+	     * @throws DataFormatException
+	     * @throws SAMException 
+	     */
+	    public SAMv3StreamSession(String login)
+	    		throws IOException, DataFormatException, SAMException
+	    {
+	    	initSAMStreamSession(login);
+	    }
+
+	    public static SAMv3Handler.SessionsDB getDB()
+	    {
+	    	return SAMv3Handler.sSessionsHash ;
+	    }
+
+	    private void initSAMStreamSession(String login)
+	    	throws IOException, DataFormatException, SAMException {
+
+	        SAMv3Handler.SessionRecord rec = getDB().get(login);
+	        String dest = rec.getDest() ;
+	        ByteArrayInputStream ba_dest = new ByteArrayInputStream(Base64.decode(dest));
+
+	        this.recv = rec.getHandler();
+
+	    	_log.debug("SAM STREAM session instantiated");
+
+	    	Properties allprops = new Properties();
+	    	allprops.putAll(System.getProperties());
+	    	allprops.putAll(rec.getProps());
+	    	
+	    	String i2cpHost = allprops.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
+	    	int i2cpPort ;
+	    	String port = allprops.getProperty(I2PClient.PROP_TCP_PORT, "7654");
+	    	try {
+	    		i2cpPort = Integer.parseInt(port);
+	    	} catch (NumberFormatException nfe) {
+	    		throw new SAMException("Invalid I2CP port specified [" + port + "]");
+	    	}
+
+	    	_log.debug("Creating I2PSocketManager...");
+	    	socketMgr = I2PSocketManagerFactory.createManager(ba_dest,
+	    			i2cpHost,
+	    			i2cpPort, 
+	    			allprops);
+	    	if (socketMgr == null) {
+	    		throw new SAMException("Error creating I2PSocketManager towards "+i2cpHost+":"+i2cpPort);
+	    	}
+
+	    	socketMgr.addDisconnectListener(new DisconnectListener());
+	    	this.nick = login ;
+	    }
+
+	    /**
+	     * Connect the SAM STREAM session to the specified Destination
+	     *
+	     * @param handler The handler that communicates with the requesting client
+	     * @param dest Base64-encoded Destination to connect to
+	     * @param props Options to be used for connection
+	     *
+	     * @throws DataFormatException if the destination is not valid
+	     * @throws ConnectException if the destination refuses connections
+	     * @throws NoRouteToHostException if the destination can't be reached
+	     * @throws InterruptedIOException if the connection timeouts
+	     * @throws I2PException if there's another I2P-related error
+	     * @throws IOException 
+	     */
+	    public void connect ( SAMv3Handler handler, String dest, Properties props ) 
+	    throws I2PException, ConnectException, NoRouteToHostException, 
+	    		DataFormatException, InterruptedIOException, IOException {
+
+	    	boolean verbose = (props.getProperty("SILENT", "false").equals("false"));
+	        Destination d = SAMUtils.getDest(dest);
+
+	        I2PSocketOptions opts = socketMgr.buildOptions(props);
+	        if (props.getProperty(I2PSocketOptions.PROP_CONNECT_TIMEOUT) == null)
+	            opts.setConnectTimeout(60 * 1000);
+
+	        _log.debug("Connecting new I2PSocket...");
+
+	        // blocking connection (SAMv3)
+
+	        I2PSocket i2ps = socketMgr.connect(d, opts);
+
+	        SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick);
+	        
+	        if ( rec==null ) throw new InterruptedIOException() ;
+	        
+	        handler.notifyStreamResult(verbose, "OK", null) ;
+
+	        handler.stealSocket() ;
+	        
+	        ReadableByteChannel fromClient = handler.getClientSocket();
+	        ReadableByteChannel fromI2P    = Channels.newChannel(i2ps.getInputStream());
+	        WritableByteChannel toClient   = handler.getClientSocket();
+	        WritableByteChannel toI2P      = Channels.newChannel(i2ps.getOutputStream());
+	        
+	        (new Thread(rec.getThreadGroup(), new I2PAppThread(new Pipe(fromClient,toI2P, "SAMPipeClientToI2P"), "SAMPipeClientToI2P"), "SAMPipeClientToI2P")).start();
+	        (new Thread(rec.getThreadGroup(), new I2PAppThread(new Pipe(fromI2P,toClient, "SAMPipeI2PToClient"), "SAMPipeI2PToClient"), "SAMPipeI2PToClient")).start();
+	        
+	    }
+
+	    /**
+	     * Accept an incoming STREAM
+	     *
+	     * @param handler The handler that communicates with the requesting client
+	     * @param verbose If true, SAM will send the Base64-encoded peer Destination of an
+	     *                incoming socket as the first line of data sent to its client
+	     *                on the handler socket
+	     *
+	     * @throws DataFormatException if the destination is not valid
+	     * @throws ConnectException if the destination refuses connections
+	     * @throws NoRouteToHostException if the destination can't be reached
+	     * @throws InterruptedIOException if the connection timeouts
+	     * @throws I2PException if there's another I2P-related error
+	     * @throws IOException 
+	     */
+	    public void accept(SAMv3Handler handler, boolean verbose) 
+	    	throws I2PException, InterruptedIOException, IOException, SAMException {
+
+	    	synchronized( this.socketServerLock )
+	    	{
+	    		if (this.socketServer!=null) {
+	    			_log.debug("a socket server is already defined for this destination");
+	    			throw new SAMException("a socket server is already defined for this destination");
+	    		}
+	    		this.socketServer = this.socketMgr.getServerSocket();
+	    	}
+	    	
+			I2PSocket i2ps;
+			i2ps = this.socketServer.accept();
+
+	    	synchronized( this.socketServerLock )
+	    	{
+	    		this.socketServer = null ;
+	    	}
+	    	
+	    	SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick);
+	        
+	        if ( rec==null ) throw new InterruptedIOException() ;
+	        
+			if (verbose)
+				handler.notifyStreamIncomingConnection(i2ps.getPeerDestination()) ;
+
+	        handler.stealSocket() ;
+	        ReadableByteChannel fromClient = handler.getClientSocket();
+	        ReadableByteChannel fromI2P    = Channels.newChannel(i2ps.getInputStream());
+	        WritableByteChannel toClient   = handler.getClientSocket();
+	        WritableByteChannel toI2P      = Channels.newChannel(i2ps.getOutputStream());
+	        
+	        (new Thread(rec.getThreadGroup(), new I2PAppThread(new Pipe(fromClient,toI2P, "SAMPipeClientToI2P"), "SAMPipeClientToI2P"), "SAMPipeClientToI2P")).start();
+	        (new Thread(rec.getThreadGroup(), new I2PAppThread(new Pipe(fromI2P,toClient, "SAMPipeI2PToClient"), "SAMPipeI2PToClient"), "SAMPipeI2PToClient")).start();	        
+	    }
+
+	    
+	    public void startForwardingIncoming( Properties props ) throws SAMException, InterruptedIOException
+	    {
+	    	SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick);
+	    	boolean verbose = props.getProperty("SILENT", "false").equals("false");
+	        
+	        if ( rec==null ) throw new InterruptedIOException() ;
+	        
+	    	String portStr = props.getProperty("PORT") ;
+	    	if ( portStr==null ) {
+	    		_log.debug("receiver port not specified");
+	    		throw new SAMException("receiver port not specified");
+	    	}
+	    	int port = Integer.parseInt(portStr);
+	    	
+	    	String host = props.getProperty("HOST");
+	    	if ( host==null ) {
+	    		host = rec.getHandler().getClientIP();
+	    		_log.debug("no host specified. Taken from the client socket : " + host +':'+port);
+	    	}
+
+	    	
+	    	synchronized( this.socketServerLock )
+	    	{
+	    		if (this.socketServer!=null) {
+	    			_log.debug("a socket server is already defined for this destination");
+	    			throw new SAMException("a socket server is already defined for this destination");
+    			}
+	    		this.socketServer = this.socketMgr.getServerSocket();
+	    	}
+	    	
+	    	SocketForwarder forwarder = new SocketForwarder(host, port, this, verbose);
+	    	(new Thread(rec.getThreadGroup(), new I2PAppThread(forwarder, "SAMStreamForwarder"), "SAMStreamForwarder")).start();
+	    }
+	    
+	    public class SocketForwarder extends Thread
+	    {
+	    	String host = null ;
+	    	int port = 0 ;
+	    	SAMv3StreamSession session;
+	    	boolean verbose;
+	    	
+	    	SocketForwarder(String host, int port, SAMv3StreamSession session, boolean verbose) {
+	    		this.host = host ;
+	    		this.port = port ;
+	    		this.session = session ;
+	    		this.verbose = verbose ;
+	    	}
+	    	
+	    	public void run()
+	    	{
+	    		while (session.getSocketServer()!=null) {
+	    			
+	    			// wait and accept a connection from I2P side
+	    			I2PSocket i2ps = null ;
+	    			try {
+	    				i2ps = session.getSocketServer().accept();
+	    			} catch (Exception e) {}
+	    			
+	    			if (i2ps==null) {
+	    				continue ;
+	    			}
+
+	    			// open a socket towards client
+	    			java.net.InetSocketAddress addr = new java.net.InetSocketAddress(host,port);
+	    			
+	    			SocketChannel clientServerSock = null ;
+	    			try {
+	    				clientServerSock = SocketChannel.open(addr) ;
+	    			}
+	    			catch ( IOException e ) {
+	    				continue ;
+	    			}
+	    			if (clientServerSock==null) {
+	    				try {
+	    					i2ps.close();
+	    				} catch (IOException ee) {}
+	    				continue ;
+	    			}
+
+	    			// build pipes between both sockets
+	    			try {
+	    				if (this.verbose)
+	    					SAMv3Handler.notifyStreamIncomingConnection(
+	    							clientServerSock, i2ps.getPeerDestination());
+	    				ReadableByteChannel fromClient = clientServerSock ;
+	    				ReadableByteChannel fromI2P    = Channels.newChannel(i2ps.getInputStream());
+	    				WritableByteChannel toClient   = clientServerSock ;
+	    				WritableByteChannel toI2P      = Channels.newChannel(i2ps.getOutputStream());
+	    				(new I2PAppThread(new Pipe(fromClient,toI2P, "SAMPipeClientToI2P"), "SAMPipeClientToI2P")).start();
+	    				(new I2PAppThread(new Pipe(fromI2P,toClient, "SAMPipeI2PToClient"), "SAMPipeI2PToClient")).start();
+
+	    			} catch (IOException e) {
+	    				try {
+	    					clientServerSock.close();
+	    				} catch (IOException ee) {}
+	    				try {
+	    					i2ps.close();
+	    				} catch (IOException ee) {}
+	    				continue ;
+	    			}
+	    		}
+	    	}
+	    }
+	    public class Pipe extends Thread
+	    {
+	    	ReadableByteChannel in  ;
+	    	WritableByteChannel out ;
+	    	ByteBuffer buf ;
+	    	
+	    	public Pipe(ReadableByteChannel in, WritableByteChannel out, String name)
+	    	{
+	    		super(name);
+	    		this.in  = in ;
+	    		this.out = out ;
+	    		this.buf = ByteBuffer.allocate(BUFFER_SIZE) ;
+	    	}
+	    	
+	    	public void run()
+	    	{
+    			try {
+    				while (!Thread.interrupted() && (in.read(buf)>=0 || buf.position() != 0)) {
+	    				 buf.flip();
+	    				 out.write(buf);
+	    				 buf.compact();
+	    			}
+	    		}
+				catch (IOException e)
+				{
+					this.interrupt();
+				}
+    			try {
+    				in.close();
+    			}
+    			catch (IOException e) {}
+    			try {
+    				buf.flip();
+    				while (buf.hasRemaining())
+    					out.write(buf);
+    			}
+    			catch (IOException e) {}
+    			try {
+    				out.close();
+    			}
+    			catch (IOException e) {}
+	    	}
+	    }
+	    
+	    public I2PServerSocket getSocketServer()
+	    {
+	    	synchronized ( this.socketServerLock ) {
+	    		return this.socketServer ;
+	    	}
+	    }
+	    /**
+	     *  stop Forwarding Incoming connection coming from I2P
+	     * @throws SAMException
+	     * @throws InterruptedIOException
+	     */
+	    public void stopForwardingIncoming() throws SAMException, InterruptedIOException
+	    {
+	    	SAMv3Handler.SessionRecord rec = SAMv3Handler.sSessionsHash.get(nick);
+	        
+	        if ( rec==null ) throw new InterruptedIOException() ;
+	        
+	    	I2PServerSocket server = null ;
+	    	synchronized( this.socketServerLock )
+	    	{
+	    		if (this.socketServer==null) {
+	    			_log.debug("no socket server is defined for this destination");
+	    			throw new SAMException("no socket server is defined for this destination");
+    			}
+	    		server = this.socketServer ;
+	    		this.socketServer = null ;
+	    		_log.debug("nulling socketServer in stopForwardingIncoming. Object " + this );
+	    	}
+	    	try {
+	    		server.close();
+	    	} catch ( I2PException e) {}
+	    }
+
+	    /**
+	     * Close the stream session
+	     */
+	    @Override
+	    public void close() {
+	        socketMgr.destroySocketManager();
+	    }
+
+	    public boolean sendBytes(String s, byte[] b) throws DataFormatException
+	    {
+	    	throw new DataFormatException(null);
+	    }
+}
diff --git a/apps/sam/java/src/net/i2p/sam/client/SAMEventHandler.java b/apps/sam/java/src/net/i2p/sam/client/SAMEventHandler.java
index 7df1a23242eb95c411cad13033c4d580bcc5777f..9df867aa5542f915d190c2fc5fd4404d38dbab00 100644
--- a/apps/sam/java/src/net/i2p/sam/client/SAMEventHandler.java
+++ b/apps/sam/java/src/net/i2p/sam/client/SAMEventHandler.java
@@ -12,17 +12,17 @@ import net.i2p.util.Log;
  *
  */
 public class SAMEventHandler extends SAMClientEventListenerImpl {
-    private I2PAppContext _context;
+    //private I2PAppContext _context;
     private Log _log;
     private Boolean _helloOk;
     private Object _helloLock = new Object();
     private Boolean _sessionCreateOk;
     private Object _sessionCreateLock = new Object();
     private Object _namingReplyLock = new Object();
-    private Map _namingReplies = new HashMap();
+    private Map<String,String> _namingReplies = new HashMap<String,String>();
 
     public SAMEventHandler(I2PAppContext ctx) {
-        _context = ctx;
+        //_context = ctx;
         _log = ctx.logManager().getLog(getClass());
     }
     
diff --git a/apps/sam/java/src/net/i2p/sam/client/SAMStreamSend.java b/apps/sam/java/src/net/i2p/sam/client/SAMStreamSend.java
index 4e9d1133b063ed52e47c27b1d9143476718add2b..80db744a3167468fbf0e6e516685092ad963a5ab 100644
--- a/apps/sam/java/src/net/i2p/sam/client/SAMStreamSend.java
+++ b/apps/sam/java/src/net/i2p/sam/client/SAMStreamSend.java
@@ -31,10 +31,10 @@ public class SAMStreamSend {
     private OutputStream _samOut;
     private InputStream _samIn;
     private SAMReader _reader;
-    private boolean _dead;
+    //private boolean _dead;
     private SAMEventHandler _eventHandler;
     /** Connection id (Integer) to peer (Flooder) */
-    private Map _remotePeers;
+    private Map<Integer, Sender> _remotePeers;
     
     public static void main(String args[]) {
         if (args.length < 4) {
@@ -42,7 +42,7 @@ public class SAMStreamSend {
             return;
         }
         I2PAppContext ctx = new I2PAppContext();
-        String files[] = new String[args.length - 3];
+        //String files[] = new String[args.length - 3];
         SAMStreamSend sender = new SAMStreamSend(ctx, args[0], args[1], args[2], args[3]);
         sender.startup();
     }
@@ -50,14 +50,14 @@ public class SAMStreamSend {
     public SAMStreamSend(I2PAppContext ctx, String samHost, String samPort, String destFile, String dataFile) {
         _context = ctx;
         _log = ctx.logManager().getLog(SAMStreamSend.class);
-        _dead = false;
+        //_dead = false;
         _samHost = samHost;
         _samPort = samPort;
         _destFile = destFile;
         _dataFile = dataFile;
         _conOptions = "";
         _eventHandler = new SendEventHandler(_context);
-        _remotePeers = new HashMap();
+        _remotePeers = new HashMap<Integer,Sender>();
     }
     
     public void startup() {
@@ -207,7 +207,6 @@ public class SAMStreamSend {
             _started = _context.clock().now();
             _context.statManager().addRateData("send." + _connectionId + ".started", 1, 0);
             byte data[] = new byte[1024];
-            long value = 0;
             long lastSend = _context.clock().now();
             while (!_closed) {
                 try {
diff --git a/apps/sam/java/src/net/i2p/sam/client/SAMStreamSink.java b/apps/sam/java/src/net/i2p/sam/client/SAMStreamSink.java
index 8d29e37994136d5698e07ffb11fa0fd688ec489a..406150b367596fc451836b509220dc078812818d 100644
--- a/apps/sam/java/src/net/i2p/sam/client/SAMStreamSink.java
+++ b/apps/sam/java/src/net/i2p/sam/client/SAMStreamSink.java
@@ -31,10 +31,10 @@ public class SAMStreamSink {
     private OutputStream _samOut;
     private InputStream _samIn;
     private SAMReader _reader;
-    private boolean _dead;
+    //private boolean _dead;
     private SAMEventHandler _eventHandler;
     /** Connection id (Integer) to peer (Flooder) */
-    private Map _remotePeers;
+    private Map<Integer, Sink> _remotePeers;
     
     public static void main(String args[]) {
         if (args.length < 4) {
@@ -49,14 +49,14 @@ public class SAMStreamSink {
     public SAMStreamSink(I2PAppContext ctx, String samHost, String samPort, String destFile, String sinkDir) {
         _context = ctx;
         _log = ctx.logManager().getLog(SAMStreamSink.class);
-        _dead = false;
+        //_dead = false;
         _samHost = samHost;
         _samPort = samPort;
         _destFile = destFile;
         _sinkDir = sinkDir;
         _conOptions = "";
         _eventHandler = new SinkEventHandler(_context);
-        _remotePeers = new HashMap();
+        _remotePeers = new HashMap<Integer,Sink>();
     }
     
     public void startup() {
@@ -70,7 +70,8 @@ public class SAMStreamSink {
             String ourDest = handshake();
             _log.debug("Handshake complete.  we are " + ourDest);
             if (ourDest != null) {
-                boolean written = writeDest(ourDest);
+                //boolean written = 
+                	writeDest(ourDest);
                 _log.debug("Dest written");
             }
         }
diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java
index 2d198ad66e75bc4af62fa1aa24fe7b2ab293dd60..30d849ba4411de595c149ebaaf12e56b2bf4820a 100644
--- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java
+++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionHandler.java
@@ -2,8 +2,6 @@ package net.i2p.client.streaming;
 
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
-import java.util.ArrayList;
-import java.util.List;
 
 import net.i2p.I2PAppContext;
 import net.i2p.util.Log;
@@ -41,7 +39,7 @@ class ConnectionHandler {
         _context = context;
         _log = context.logManager().getLog(ConnectionHandler.class);
         _manager = mgr;
-        _synQueue = new LinkedBlockingQueue(MAX_QUEUE_SIZE);
+        _synQueue = new LinkedBlockingQueue<Packet>(MAX_QUEUE_SIZE);
         _active = false;
         _acceptTimeout = DEFAULT_ACCEPT_TIMEOUT;
     }
@@ -126,7 +124,7 @@ class ConnectionHandler {
                 if (timeoutMs <= 0) {
                     try {
                        syn = _synQueue.take(); // waits forever
-                    } catch (InterruptedException ie) { break;}
+                    } catch (InterruptedException ie) { } // { break;}
                 } else {
                     long remaining = expiration - _context.clock().now();
                     // (dont think this applies anymore for LinkedBlockingQueue)
diff --git a/apps/streaming/java/src/net/i2p/client/streaming/MessageHandler.java b/apps/streaming/java/src/net/i2p/client/streaming/MessageHandler.java
index 75adf6e593bc5d4dbbb77983c1202146fbc6dc89..1ff65248ddd27c623ae3770435eea4cbc5968d99 100644
--- a/apps/streaming/java/src/net/i2p/client/streaming/MessageHandler.java
+++ b/apps/streaming/java/src/net/i2p/client/streaming/MessageHandler.java
@@ -73,8 +73,8 @@ public class MessageHandler implements I2PSessionListener {
      * @param session that has been terminated
      */
     public void disconnected(I2PSession session) {
-        if (_log.shouldLog(Log.ERROR))
-            _log.error("I2PSession disconnected");
+        if (_log.shouldLog(Log.WARN))
+            _log.warn("I2PSession disconnected");
         _manager.disconnectAllHard();
         
         List listeners = null;
diff --git a/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java b/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java
index 197b9275412b1f8fa538c78e1a518b3b421d0de5..827be5b04fb6e035629c582a533716eae2eea625 100644
--- a/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java
+++ b/apps/streaming/java/src/net/i2p/client/streaming/PacketLocal.java
@@ -213,7 +213,7 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
                         timeRemaining = 10*1000;
                     wait(timeRemaining);
                 }
-            } catch (InterruptedException ie) { break; }
+            } catch (InterruptedException ie) { }//{ break; }
         }
         if (!writeSuccessful())
             releasePayload();
diff --git a/build.xml b/build.xml
index 6ed84ca7342ec9807598e7c8c1773c6f3e102044..fc251a40a99bb0d477ab5bd4f74ca99487b23cd9 100644
--- a/build.xml
+++ b/build.xml
@@ -25,6 +25,10 @@
         <echo message="  javadoc:   generate javadoc for the entire project into ./build/javadoc" />
         <echo message="  slackpkg:  generate Slackware packages in ./Slackware/i2p and ./Slackware/i2p-base" />
         <echo message="  debianhowto: instructions on building Debian packages" />
+        <echo message="  updaterWithDesktopgui: tar the built files and desktopgui in an i2pupdate.zip" />
+        <echo message="  pkgWithDesktopgui: distclean then package everything up with the desktopgui" />
+        <echo message="  distWithDesktopgui: pkgWithDesktopgui and javadoc" />
+        <echo message="  distcleanWithDesktopgui: clean up all derived files (including desktopgui files)" />
     </target>
     <target name="debianhowto">
         <echo message="To build debian packages, you must run dpkg-buildpackage as root in the source directory. It will then run ant for you. dpkg-buildpackage is found in the 'dpkg-dev' package. Also it should work fine to use the 'fakeroot' package with dpkg-buildpackage, if you don't want to run as root. Please read 'man dpkg-buildpackage' before building any packages yourself." />
diff --git a/core/java/src/net/i2p/client/I2PSessionImpl.java b/core/java/src/net/i2p/client/I2PSessionImpl.java
index 9e42eef5fa3e983f083c76c0b850c9da444e9b2f..a5d8ed94de3003a0e21717c41eeb0aaac2a5160b 100644
--- a/core/java/src/net/i2p/client/I2PSessionImpl.java
+++ b/core/java/src/net/i2p/client/I2PSessionImpl.java
@@ -16,7 +16,6 @@ import java.net.Socket;
 import java.net.UnknownHostException;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -90,7 +89,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
     protected I2PAppContext _context;
 
     /** monitor for waiting until a lease set has been granted */
-    private Object _leaseSetWait = new Object();
+    private final Object _leaseSetWait = new Object();
 
     /** whether the session connection has already been closed (or not yet opened) */
     protected boolean _closed;
@@ -101,7 +100,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
     /** have we received the current date from the router yet? */
     private boolean _dateReceived;
     /** lock that we wait upon, that the SetDateMessageHandler notifies */
-    private Object _dateReceivedLock = new Object();
+    private final Object _dateReceivedLock = new Object();
     
     /** 
      * thread that we tell when new messages are available who then tells us 
@@ -253,6 +252,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
         try {
             if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "connect begin to " + _hostname + ":" + _portNum);
             _socket = new Socket(_hostname, _portNum);
+            // _socket.setSoTimeout(1000000); // Uhmmm we could really-really use a real timeout, and handle it.
             _out = _socket.getOutputStream();
             synchronized (_out) {
                 _out.write(I2PClient.PROTOCOL_BYTE);
diff --git a/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java b/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java
index a3e5e57a7f870087cff8648ca15e39bce4d7d3d9..58b5cae9f29005e774df26d8bb407ecd0ca86a0a 100644
--- a/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java
+++ b/core/java/src/net/i2p/client/I2PSessionMuxedImpl.java
@@ -4,18 +4,16 @@ package net.i2p.client;
  * public domain
  */
 
-import java.io.IOException;
 import java.io.InputStream;
 import java.util.concurrent.LinkedBlockingQueue;
-import java.util.HashSet;
 import java.util.Properties;
 import java.util.Set;
 
+import java.util.concurrent.atomic.AtomicBoolean;
 import net.i2p.I2PAppContext;
 import net.i2p.data.DataHelper;
 import net.i2p.data.Destination;
 import net.i2p.data.SessionKey;
-import net.i2p.data.SessionTag;
 import net.i2p.data.i2cp.MessagePayloadMessage;
 import net.i2p.util.Log;
 import net.i2p.util.SimpleScheduler;
@@ -97,6 +95,7 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 implements I2PSession {
      *         255 disallowed
      *  @param port 1-65535 or PORT_ANY for all
      */
+    @Override
     public void addSessionListener(I2PSessionListener lsnr, int proto, int port) {
         _demultiplexer.addListener(lsnr, proto, port);
     }
@@ -107,11 +106,13 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 implements I2PSession {
      *  @param proto 1-254 or 0 for all; 255 disallowed
      *  @param port 1-65535 or 0 for all
      */
+    @Override
     public void addMuxedSessionListener(I2PSessionMuxedListener l, int proto, int port) {
         _demultiplexer.addMuxedListener(l, proto, port);
     }
 
     /** removes the specified listener (only) */
+    @Override
     public void removeListener(int proto, int port) {
         _demultiplexer.removeListener(proto, port);
     }
@@ -149,6 +150,7 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 implements I2PSession {
      *  @param fromPort 1-65535 or 0 for unset
      *  @param toPort 1-65535 or 0 for unset
      */
+    @Override
     public boolean sendMessage(Destination dest, byte[] payload, int offset, int size,
                                SessionKey keyUsed, Set tagsSent, long expires,
                                int proto, int fromPort, int toPort)
@@ -198,24 +200,40 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 implements I2PSession {
 
     protected class MuxedAvailabilityNotifier extends AvailabilityNotifier {
         private LinkedBlockingQueue<MsgData> _msgs;
-        private boolean _alive;
+        private volatile boolean _alive = false;
         private static final int POISON_SIZE = -99999;
- 
+        private final AtomicBoolean stopping = new AtomicBoolean(false);
+
         public MuxedAvailabilityNotifier() {
             _msgs = new LinkedBlockingQueue();
         }
-        
-        public void stopNotifying() { 
-            _msgs.clear(); 
-            if (_alive) {
-                _alive = false; 
-                try {
-                    _msgs.put(new MsgData(0, POISON_SIZE, 0, 0, 0));
-                } catch (InterruptedException ie) {}
+
+        @Override
+        public void stopNotifying() {
+            boolean again = true;
+            synchronized (stopping) {
+                if( !stopping.getAndSet(true)) {
+                    if (_alive == true) {
+                        // System.out.println("I2PSessionMuxedImpl.stopNotifying()");
+                        _msgs.clear();
+                        while(again) {
+                            try {
+                                _msgs.put(new MsgData(0, POISON_SIZE, 0, 0, 0));
+                                again = false;
+                                // System.out.println("I2PSessionMuxedImpl.stopNotifying() success.");
+                            } catch (InterruptedException ie) {
+                                continue;
+                            }
+                        }
+                    }
+                    _alive = false;
+                    stopping.set(false);
+                }
+                // stopping.notifyAll();
             }
         }
-        
         /** unused */
+        @Override
         public void available(long msgId, int size) { throw new IllegalArgumentException("no"); }
 
         public void available(long msgId, int size, int proto, int fromPort, int toPort) {
@@ -224,20 +242,24 @@ class I2PSessionMuxedImpl extends I2PSessionImpl2 implements I2PSession {
             } catch (InterruptedException ie) {}
         }
 
+        @Override
         public void run() {
-            _alive = true;
-            while (true) {
-                MsgData msg;
+            MsgData msg;
+            _alive=true;
+            while (_alive) {
                 try {
                     msg = _msgs.take();
                 } catch (InterruptedException ie) {
+                    _log.debug("I2PSessionMuxedImpl.run() InterruptedException " + String.valueOf(_msgs.size()) + " Messages, Alive " + _alive);
                     continue;
                 }
-                if (msg.size == POISON_SIZE)
+                if (msg.size == POISON_SIZE) {
+                    // System.out.println("I2PSessionMuxedImpl.run() POISONED");
                     break;
+                }
                 try {
-                    _demultiplexer.messageAvailable(I2PSessionMuxedImpl.this, msg.id,
-                                                    msg.size, msg.proto, msg.fromPort, msg.toPort);
+                    _demultiplexer.messageAvailable(I2PSessionMuxedImpl.this,
+                        msg.id, msg.size, msg.proto, msg.fromPort, msg.toPort);
                 } catch (Exception e) {
                     _log.error("Error notifying app of message availability");
                 }
diff --git a/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java b/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java
index 5c79d94f3c70ca356c0e2a8918451299953fe761..461c4b08a4f9dbf25de23be53c896e406e818540 100644
--- a/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java
+++ b/core/java/src/net/i2p/data/i2cp/I2CPMessageReader.java
@@ -115,8 +115,8 @@ public class I2CPMessageReader {
     }
 
     private class I2CPMessageReaderRunner implements Runnable {
-        private boolean _doRun;
-        private boolean _stayAlive;
+        private volatile boolean _doRun;
+        private volatile boolean _stayAlive;
 
         public I2CPMessageReaderRunner() {
             _doRun = true;
diff --git a/history.txt b/history.txt
index 50315edcd199ca59741c0f48c30d5bc4525d01c7..e802d6da420f72b0376247745ecd01b812954eda 100644
--- a/history.txt
+++ b/history.txt
@@ -1,5 +1,135 @@
+2009-05-12 sponge
+    * BOB clean up, change println's to _log.warn, bump BOB version
+    * I2PSessionMuxedImpl.java changes as per zzz, and they test OK for me.
+
+2009-05-12 mkvore
+    * SAM: fix: warnings when generating javadoc
+
+2009-05-11 zzz
+    * Connect client: Fix NPE when used with advanced i2ptunnel features
+    * Context: Don't instantiate unused AdminManager
+    * logs.jsp: Put critical log at the top
+    * NetDb: Don't accept stores of our own LeaseSets or RouterInfo
+
+2009-05-11 mkvore
+    * SAM: fix: removed ERROR level logging when a client disconnects
+
+2009-05-09 sponge
+    * merge
+
+2009-05-09 sponge
+    * fixed OOM on lock (woops! my bad!)
+
+2009-05-08 Mathiasdm
+    * desktopgui: moved files to stop polluting the namespace
+          (everything now in net.i2p.desktopgui)
+    * desktopgui: some variable renaming in general configuration
+
+2009-05-07 mkvore
+    * SAM: version 3 added
+    * SAM: blocking case corrected on simultaneous client connection (v.1-3)
+
+2009-05-07 zzz
+    * Add nibble.i2p to proxy list and hosts.txt
+
+2009-05-07 zzz
+    * Addressbook: Name the thread
+    * Console:
+      - More IE button fixes, try harder to not refresh the iframe after shutdown
+      - Disable idle options for streamr client, it will never be
+        idle because it pings the server
+    * Floodfill Monitor: Slow down the volunteers
+    * Throttle: Throttle at 90% so we throttle before we WRED
+
+2009-05-06 Mathiasdm
+    * Improvements to popup menu rightclick action
+    * Added general configuration options (still not available by default)
+    * General fixes
+    * Added ant build options (irc says eche|on would like that ;))
+
+2009-05-06 sponge
+    * Hopefully the last fixes for BOB.
+    * Fixes to prevent race in client-side I2CP and Notifier.
+
+2009-05-03 sponge
+    * More hopeful fixes for BOB.
+    * Added new Robert ID to snark
+
+2009-05-01 zzz
+    * Build files:
+      - Fix up susidns build file so it will work with gcj
+      - Add consoleDocs target
+    * Client: Fix race NPE (thanks sponge)
+    * Console: fix ERR-UDP Disabled and Inbound TCP host/port not set
+    * I2CP: Fix race NPE
+    * I2PTunnel:
+      - Try to fix locking to prevent duplicate destinations when using
+        the new option new-dest-on-resume. Still not right for shared clients
+        but should be better for non-shared.
+    * Router console:
+      - Add jbigi and cpu info to logs.jsp
+    * Session key manager:
+      - Log before a hang maybe
+    * URL Launcher:
+      - Launcher on linux was stopping after trying opera, whether it succeeded or failed.
+        Now it keeps going to try firefox, etc. as designed.
+      - Extend default delay from 5s to 15s so it will reliably start
+
+2009-04-27 sponge
+    * more BOB fixes, complete with warnings when things go wrong, and
+      success messages when things turn around and go right. Terminates
+      early so that applications wait no more than 10 seconds or so.
+    * Reversed a few earlier patches that caused some odd behavior.
+    * Changed some core println()'s to debugging messages.
+
+2009-04-27 zzz
+    * Build files:
+      - New updaterWithJettyFixes target, build it for pkg
+      - Pass compiler args down from top build.xml
+    * GarlicMessageBuilder: Reduce bundled tags to 40 (was 100)
+    * i2psnark: Add Postman2 tracker
+    * I2PTunnel: Allow spaces in dest and proxy lists
+    * NetDb:
+      - Adjust RouterInfo expiration down to control memory usage
+      - Display LeaseSets and RouterInfos on separate console pages
+    * NTCP:
+      - Correct the meanings of the i2np.ntcp.autoip and i2np.ntcp.autoport
+        advanced config. If you have one of these set but not the other, you
+        will have to adjust your configuration on config.jsp.
+    * RouterConsole: iframe tweaks
+    * StatisticsManager: Cleanup
+    * Streaming: Don't let jrandom yell so loud
+    * Tunnel Pool: Don't self-destruct if more than 6 IB tunnels configured
+
+2009-04-25 sponge
+    * I2PSessionMuxedImpl atomic fixes
+    * BOB fixes. This should be the final bug wack. Good Luck to everybody!
+
+2009-04-23 zzz
+    * Blocklist: cleanup
+    * eepget: handle -h, --help, bad options, etc.
+      (http://forum.i2p/viewtopic.php?p=16261#16261)
+    * Fragmenter: don't re-throw the corrupt fragment IllegalStateException,
+      to limit the damage - root cause still not found
+    * i2psnark: (http://forum.i2p/viewtopic.php?t=3317)
+      - Change file limit to 512 (was 256)
+      - Change size limit to 10GB (was 5GB)
+      - Change request size to 16KB (was 32KB)
+      - Change pipeline to 5 (was 3)
+    * logs.jsp: Move version info to the top
+    * Jetty: Fix temp dir name handling on windows, which was
+      causing susidns not to start
+      (http://forum.i2p/viewtopic.php?t=3364)
+    * NTCP: Prevent IllegalStateException
+    * PeerProfile:
+      - Replace a hot lock with concurrent RW lock
+      - Rewrite ugly IP Restriction code
+      - Also use transport IP in restriction code
+    * RouterConsole: Make summary bar a refreshing iframe
+    * Transport: Start the previously unused CleanupUnreachable
+
 2009-04-21 sponge
-    * Code janator work, basic corrections involving @Override, and
+    * Code janitor work, basic corrections involving @Override, and
       appling final where it is important. Also fixed some equals methods
       and commented places that need fixing.
 
diff --git a/hosts.txt b/hosts.txt
index 95ca28af62be0cdbcca1c6feb0756aa7d5b8fb0c..44cdacd754d4b9955406c02e4760761ed9d57e5c 100644
--- a/hosts.txt
+++ b/hosts.txt
@@ -313,3 +313,5 @@ tracker.mastertracker.i2p=VzXD~stRKbL3MOmeTn1iaCQ0CFyTmuFHiKYyo0Rd~dFPZFCYH-22rT
 codevoid.i2p=tV-4GJjgYIoCDTTJ91nfDbhSnT8B2o3v-TUfHtiAAjJJdroCAEDbmJWFPUQJEEispvrjNe~fP7VAYkk9fAhSrmdBLtEGB3NUESdiZEPsDtKJBdxijPGb1erZF2Z6eYHoK-t5g7MWWTsgLz~4xn211Jpfa-T4pqL2tcjsa7ixsaMpHF8NXFrITdyxSJRPz8OnHYgDR~ULFyzroi255MpiSUBzGcUZEiQSFLHLhjT5D5tP~gfJirFnfgOHvzWBK9L7y91qY~gYvM2eDcxMxq4Ac1gw0JeahkzAk3j6Spco3LHW3bJvELopf1QmLFu3nfPaegH1Hejt9AhXEH~FV-~M9F1BePipcIYlm7nKyre3aVPLYDZSCvkUx~8nnD3HEpMijD8fdfqSFPU7aZQe19a7rZJUbX~a4M3rBDO-C4uAid6Uznb1tLu2XR1GVVITGHaLwmumImXjlU~1nEnluBQB6iBQPZ9xJccArlYgWSooR9gpyN93PwTPsPe5cPkxCFuxAAAA
 echelon.i2p=w6zK9m4fqSfvJck9EGIR1wRIbWsEQ2DkjZ-VI57ESFqLqbTIA1cD5nOfSSbpELqPyhjifdrNiBNAsSdyil3C0a2B7CGtwUcTS2dCG0tKf2nAbvpsbcCK17nI4Xbu5KqZU0y3hJ~l7rcJqQBR0nfV5cU30ZDrpQV6VL875cihGlnmwLFq6qSzNcEb88Nw6wFG~FIgB2PJ6A3jJyuTnLrdiMvwqgD6nSyeOylOgBCsNxXh8-drrhASjladfNrwjlGRCZTiQ~H92HIyOwiabDiG3TUugMaFWs87yuXnZ~ni9jgjoAMFo8xV8Od2BiRgCxkZoMU07FhgUjew9qtXNa04wkexf3gx77nVPhqE0GHqCuwHwmBVf92RdYEys76u~akaOMq5UhayDpCBCaHiYLkKDNqmh47tfMCwxf6z8VIcR4zv25QfJDIWPs~RA~9U7m4raytiAs5PvYZBn4B3SqOL8XdkL9sDT54sQXbsYCJr3olu6ieMtNWlmos0uohYXNUyAAAA
 crstrack.i2p=b4G9sCdtfvccMAXh~SaZrPqVQNyGQbhbYMbw6supq2XGzbjU4NcOmjFI0vxQ8w1L05twmkOvg5QERcX6Mi8NQrWnR0stLExu2LucUXg1aYjnggxIR8TIOGygZVIMV3STKH4UQXD--wz0BUrqaLxPhrm2Eh9Hwc8TdB6Na4ShQUq5Xm8D4elzNUVdpM~RtChEyJWuQvoGAHY3ppX-EJJLkiSr1t77neS4Lc-KofMVmgI9a2tSSpNAagBiNI6Ak9L1T0F9uxeDfEG9bBSQPNMOSUbAoEcNxtt7xOW~cNOAyMyGydwPMnrQ5kIYPY8Pd3XudEko970vE0D6gO19yoBMJpKx6Dh50DGgybLQ9CpRaynh2zPULTHxm8rneOGRcQo8D3mE7FQ92m54~SvfjXjD2TwAVGI~ae~n9HDxt8uxOecAAvjjJ3TD4XM63Q9TmB38RmGNzNLDBQMEmJFpqQU8YeuhnS54IVdUoVQFqui5SfDeLXlSkh4vYoMU66pvBfWbAAAA
+tracker2.postman.i2p=lnQ6yoBTxQuQU8EQ1FlF395ITIQF-HGJxUeFvzETLFnoczNjQvKDbtSB7aHhn853zjVXrJBgwlB9sO57KakBDaJ50lUZgVPhjlI19TgJ-CxyHhHSCeKx5JzURdEW-ucdONMynr-b2zwhsx8VQCJwCEkARvt21YkOyQDaB9IdV8aTAmP~PUJQxRwceaTMn96FcVenwdXqleE16fI8CVFOV18jbJKrhTOYpTtcZKV4l1wNYBDwKgwPx5c0kcrRzFyw5~bjuAKO~GJ5dR7BQsL7AwBoQUS4k1lwoYrG1kOIBeDD3XF8BWb6K3GOOoyjc1umYKpur3G~FxBuqtHAsDRICkEbKUqJ9mPYQlTSujhNxiRIW-oLwMtvayCFci99oX8MvazPS7~97x0Gsm-onEK1Td9nBdmq30OqDxpRtXBimbzkLbR1IKObbg9HvrKs3L-kSyGwTUmHG9rSQSoZEvFMA-S0EXO~o4g21q1oikmxPMhkeVwQ22VHB0-LZJfmLr4SAAAA
+nibble.i2p=V2XQ31BQWcwLcBNz2ywb4xy0Q1GMjdziQyjKql-lGdYPOX7w9g3j8IkA1jfW6YYwNi5QZc0JurjrSNH1yx6Y1goI8SB1l-yWdzst73fGWo6B1UtL45XrfXPg5k34RpktCNa4KoeIsUnGnxHQESSj5hw389hvexKXlkAHXQg9eUfbBYyzZc~~Kt4YdYX4cfMpXXjg443kyEiwKisOaRuiEN-YjqZ8pJTyAQsOKNg8hL3e15XFNPfAAkCSsALPAqj0~HZDwCZDeV0Cp4iaCGjw8tsNQ7xBeSjnhOeMoZKtrPAbbK4vNh7OIcakcVu16ykfEf-FcqbPQQe9rjilMy8V-BcjhggjUcZmtWj9qE7RMfUFpbAIfNHgWXTl5yR5V~brqxxuBxHQWn4oyB5NpY02dBkvvxXwdk~XFzXlSz~uEZKVswvI8rUHR4a2N3YDss5iQ~uscvKwNvsTZiDUaN66~CacZLYU9BtDBNnAxClz9LSu5b9CiunKeacbH6l5qrPpAAAA
diff --git a/installer/resources/clients.config b/installer/resources/clients.config
index 170f4c8fe5f0a6c5098f61a1247521295ec44406..88bba5f9a8a957ac44b1ee5edcc833bdf509ffea 100644
--- a/installer/resources/clients.config
+++ b/installer/resources/clients.config
@@ -1,32 +1,32 @@
 # fire up the web console
 clientApp.0.args=7657 ::1,127.0.0.1 ./webapps/
 clientApp.0.main=net.i2p.router.web.RouterConsoleRunner
-clientApp.0.name=webConsole
+clientApp.0.name=Web console
 clientApp.0.onBoot=true
 clientApp.0.startOnLoad=true
 
 # SAM bridge
 clientApp.1.main=net.i2p.sam.SAMBridge
-clientApp.1.name=SAMBridge
+clientApp.1.name=SAM application bridge
 clientApp.1.args=sam.keys 127.0.0.1 7656 i2cp.tcp.host=127.0.0.1 i2cp.tcp.port=7654
 clientApp.1.startOnLoad=false
 
 # poke the i2ptunnels defined in i2ptunnel.config
 clientApp.2.main=net.i2p.i2ptunnel.TunnelControllerGroup
-clientApp.2.name=Tunnels
+clientApp.2.name=Application tunnels
 clientApp.2.args=i2ptunnel.config
 clientApp.2.startOnLoad=true
 
 # run our own eepsite with a seperate jetty instance
 clientApp.3.main=org.mortbay.jetty.Server
-clientApp.3.name=eepsite
+clientApp.3.name=My eepsite web server
 clientApp.3.args=eepsite/jetty.xml
 clientApp.3.delay=30
 clientApp.3.startOnLoad=true
 
 # load a browser pointing at the web console whenever we start up
 clientApp.4.main=net.i2p.apps.systray.UrlLauncher
-clientApp.4.name=consoleBrowser
+clientApp.4.name=Browser launch at startup
 clientApp.4.args=http://127.0.0.1:7657/index.jsp
 clientApp.4.delay=15
 clientApp.4.startOnLoad=true
@@ -35,5 +35,5 @@ clientApp.4.startOnLoad=true
 clientApp.5.args=
 clientApp.5.delay=10
 clientApp.5.main=net.i2p.BOB.BOB
-clientApp.5.name=BOB
+clientApp.5.name=BOB application bridge
 clientApp.5.startOnLoad=false
diff --git a/installer/resources/i2ptunnel.config b/installer/resources/i2ptunnel.config
index 48d18b95ec10335d88de0240203fadfd12de4241..c0dd002dbddf6460999aad737b6eb97fdfb7e007 100644
--- a/installer/resources/i2ptunnel.config
+++ b/installer/resources/i2ptunnel.config
@@ -5,7 +5,7 @@ tunnel.0.type=httpclient
 tunnel.0.sharedClient=true
 tunnel.0.interface=127.0.0.1
 tunnel.0.listenPort=4444
-tunnel.0.proxyList=false.i2p
+tunnel.0.proxyList=false.i2p,nibble.i2p
 tunnel.0.i2cpHost=127.0.0.1
 tunnel.0.i2cpPort=7654
 tunnel.0.option.inbound.nickname=shared clients
diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java
index c9a95dd552208c43145fa8b634cb375b7656635f..2c9a1319ea24cd2ab5ece01a1cdd265522185046 100644
--- a/router/java/src/net/i2p/router/Router.java
+++ b/router/java/src/net/i2p/router/Router.java
@@ -999,8 +999,7 @@ public class Router {
     }
     
     public static void main(String args[]) {
-        System.out.println("Starting I2P " + RouterVersion.VERSION + "-" + RouterVersion.BUILD);
-        System.out.println(RouterVersion.ID);
+        System.out.println("Starting I2P " + RouterVersion.FULL_VERSION);
         installUpdates();
         verifyWrapperConfig();
         Router r = new Router();
diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java
index 1d4797d6fc46971d6b640b96122df2e67994299a..e4b934bb21ad025f6d07e78f0ce4187bfdf9af95 100644
--- a/router/java/src/net/i2p/router/RouterVersion.java
+++ b/router/java/src/net/i2p/router/RouterVersion.java
@@ -15,11 +15,15 @@ import net.i2p.CoreVersion;
  *
  */
 public class RouterVersion {
-    public final static String ID = "$Revision: 1.548 $ $Date: 2008-06-07 23:00:00 $";
+    /** deprecated */
+    public final static String ID = "Monotone";
     public final static String VERSION = CoreVersion.VERSION;
-    public final static long BUILD = 1;
+    public final static long BUILD = 14;
+    /** for example "-test" */
+    public final static String EXTRA = "";
+    public final static String FULL_VERSION = VERSION + "-" + BUILD + EXTRA;
     public static void main(String args[]) {
-        System.out.println("I2P Router version: " + VERSION + "-" + BUILD);
+        System.out.println("I2P Router version: " + FULL_VERSION);
         System.out.println("Router ID: " + RouterVersion.ID);
         System.out.println("I2P Core version: " + CoreVersion.VERSION);
         System.out.println("Core ID: " + CoreVersion.ID);