diff --git a/apps/routerconsole/jsp/configclients.jsp b/apps/routerconsole/jsp/configclients.jsp
deleted file mode 100644
index 369df5ead..000000000
--- a/apps/routerconsole/jsp/configclients.jsp
+++ /dev/null
@@ -1,45 +0,0 @@
-<%@page contentType="text/html"%>
-<%@page pageEncoding="UTF-8"%>
-
-
-
-I2P Router Console - config clients
-
-
-
-<%@include file="nav.jsp" %>
-<%@include file="summary.jsp" %>
-
-
-" />
-
-
- <%@include file="confignav.jsp" %>
-
-
-
- " />
-
-
-
-
-
-
-
-
diff --git a/apps/routerconsole/jsp/configtunnels.jsp b/apps/routerconsole/jsp/configtunnels.jsp
new file mode 100644
index 000000000..84ecf4341
--- /dev/null
+++ b/apps/routerconsole/jsp/configtunnels.jsp
@@ -0,0 +1,41 @@
+<%@page contentType="text/html"%>
+<%@page pageEncoding="UTF-8"%>
+
+
+
+I2P Router Console - config tunnels
+
+
+
+<%@include file="nav.jsp" %>
+<%@include file="summary.jsp" %>
+
+
+" />
+
+
+ <%@include file="confignav.jsp" %>
+
+
+ " />
+ " />
+ " />
+ " />
+
+
+
+
+
+
+
+
+
diff --git a/apps/routerconsole/jsp/tunnels.jsp b/apps/routerconsole/jsp/tunnels.jsp
new file mode 100644
index 000000000..3bdcfab08
--- /dev/null
+++ b/apps/routerconsole/jsp/tunnels.jsp
@@ -0,0 +1,21 @@
+<%@page contentType="text/html"%>
+<%@page pageEncoding="UTF-8"%>
+
+
+
+I2P Router Console - tunnel summary
+
+
+
+<%@include file="nav.jsp" %>
+<%@include file="summary.jsp" %>
+
+
+
+ " />
+
+
+
+
+
+
diff --git a/core/java/src/net/i2p/client/ConnectionRunner.java b/core/java/src/net/i2p/client/ConnectionRunner.java
deleted file mode 100644
index a3548f0c8..000000000
--- a/core/java/src/net/i2p/client/ConnectionRunner.java
+++ /dev/null
@@ -1,395 +0,0 @@
-package net.i2p.client;
-
-/*
- * free (adj.): unencumbered; not under the control of others
- * Written by jrandom in 2003 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.IOException;
-import java.io.OutputStream;
-import java.net.Socket;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-
-import net.i2p.crypto.KeyGenerator;
-import net.i2p.data.Certificate;
-import net.i2p.data.Destination;
-import net.i2p.data.Payload;
-import net.i2p.data.PublicKey;
-import net.i2p.data.RouterIdentity;
-import net.i2p.data.SigningPublicKey;
-import net.i2p.data.TunnelId;
-import net.i2p.data.i2cp.CreateSessionMessage;
-import net.i2p.data.i2cp.DisconnectMessage;
-import net.i2p.data.i2cp.I2CPMessage;
-import net.i2p.data.i2cp.I2CPMessageException;
-import net.i2p.data.i2cp.I2CPMessageReader;
-import net.i2p.data.i2cp.MessageId;
-import net.i2p.data.i2cp.MessagePayloadMessage;
-import net.i2p.data.i2cp.MessageStatusMessage;
-import net.i2p.data.i2cp.ReceiveMessageBeginMessage;
-import net.i2p.data.i2cp.ReceiveMessageEndMessage;
-import net.i2p.data.i2cp.RequestLeaseSetMessage;
-import net.i2p.data.i2cp.SendMessageMessage;
-import net.i2p.data.i2cp.SessionConfig;
-import net.i2p.data.i2cp.SessionId;
-import net.i2p.data.i2cp.SessionStatusMessage;
-import net.i2p.util.Clock;
-import net.i2p.util.Log;
-
-/**
- * Run the server side of a connection as part of the TestServer. This class
- * actually manages the state of that system too, but this is a very, very, very
- * rudimentary implementation. And not a very clean one at that.
- *
- * @author jrandom
- */
-class ConnectionRunner implements I2CPMessageReader.I2CPMessageEventListener {
- private final static Log _log = new Log(ConnectionRunner.class);
- /**
- * static mapping of Destination to ConnectionRunner, allowing connections to pass
- * messages to each other
- */
- private static Map _connections = Collections.synchronizedMap(new HashMap());
- /**
- * static mapping of MessageId to Payload, storing messages for retrieval
- *
- */
- private static Map _messages = Collections.synchronizedMap(new HashMap());
- /** socket for this particular peer connection */
- private Socket _socket;
- /**
- * output stream of the socket that I2CP messages bound to the client
- * should be written to
- */
- private OutputStream _out;
- /** session ID of the current client */
- private SessionId _sessionId;
- /** next available session id */
- private static int _id = 0;
- /** next available message id */
- private static int _messageId = 0;
- private SessionConfig _config;
-
- private Object _sessionIdLock = new Object();
- private Object _messageIdLock = new Object();
-
- // this *should* be mod 65536, but UnsignedInteger is still b0rked. FIXME
- protected int getNextSessionId() {
- synchronized (_sessionIdLock) {
- int id = (++_id) % 32767;
- _id = id;
- return id;
- }
- }
-
- // this *should* be mod 65536, but UnsignedInteger is still b0rked. FIXME
- protected int getNextMessageId() {
- synchronized (_messageIdLock) {
- int id = (++_messageId) % 32767;
- _messageId = id;
- return id;
- }
- }
-
- protected SessionId getSessionId() {
- return _sessionId;
- }
-
- protected ConnectionRunner getRunner(Destination dest) {
- return (ConnectionRunner) _connections.get(dest);
- }
-
- protected Set getRunnerDestinations() {
- return new HashSet(_connections.keySet());
- }
-
- /**
- * Create a new runner against the given socket
- *
- */
- public ConnectionRunner(Socket socket) {
- _socket = socket;
- _config = null;
- }
-
- /**
- * Actually run the connection - listen for I2CP messages and respond. This
- * is the main driver for this class, though it gets all its meat from the
- * {@link net.i2p.data.i2cp.I2CPMessageReader I2CPMessageReader}
- *
- */
- public void doYourThing() throws IOException {
- I2CPMessageReader reader = new I2CPMessageReader(_socket.getInputStream(), this);
- _out = _socket.getOutputStream();
- reader.startReading();
- }
-
- /**
- * Receive notifiation that the peer disconnected
- */
- public void disconnected(I2CPMessageReader reader) {
- _log.info("Disconnected");
- }
-
- /**
- * Handle an incoming message and dispatch it to the appropriate handler
- *
- */
- public void messageReceived(I2CPMessageReader reader, I2CPMessage message) {
- _log.info("Message received: \n" + message);
- switch (message.getType()) {
- case CreateSessionMessage.MESSAGE_TYPE:
- handleCreateSession(reader, (CreateSessionMessage) message);
- break;
- case SendMessageMessage.MESSAGE_TYPE:
- handleSendMessage(reader, (SendMessageMessage) message);
- break;
- case ReceiveMessageBeginMessage.MESSAGE_TYPE:
- handleReceiveBegin(reader, (ReceiveMessageBeginMessage) message);
- break;
- case ReceiveMessageEndMessage.MESSAGE_TYPE:
- handleReceiveEnd(reader, (ReceiveMessageEndMessage) message);
- break;
- }
- }
-
- /**
- * Handle a CreateSessionMessage
- *
- */
- protected void handleCreateSession(I2CPMessageReader reader, CreateSessionMessage message) {
- if (message.getSessionConfig().verifySignature()) {
- _log.debug("Signature verified correctly on create session message");
- } else {
- _log.error("Signature verification *FAILED* on a create session message. Hijack attempt?");
- DisconnectMessage msg = new DisconnectMessage();
- msg.setReason("Invalid signature on CreateSessionMessage");
- try {
- doSend(msg);
- } catch (I2CPMessageException ime) {
- _log.error("Error writing out the disconnect message", ime);
- } catch (IOException ioe) {
- _log.error("Error writing out the disconnect message", ioe);
- }
- return;
- }
- SessionStatusMessage msg = new SessionStatusMessage();
- SessionId id = new SessionId();
- id.setSessionId(getNextSessionId()); // should be mod 65535, but UnsignedInteger isn't fixed yet. FIXME.
- _sessionId = id;
- msg.setSessionId(id);
- msg.setStatus(SessionStatusMessage.STATUS_CREATED);
- try {
- doSend(msg);
- _connections.put(message.getSessionConfig().getDestination(), this);
- _config = message.getSessionConfig();
- sessionCreated();
- } catch (I2CPMessageException ime) {
- _log.error("Error writing out the session status message", ime);
- } catch (IOException ioe) {
- _log.error("Error writing out the session status message", ioe);
- }
-
- // lets also request a new fake lease
- RequestLeaseSetMessage rlsm = new RequestLeaseSetMessage();
- rlsm.setEndDate(new Date(Clock.getInstance().now() + 60 * 60 * 1000));
- rlsm.setSessionId(id);
- RouterIdentity ri = new RouterIdentity();
- Object rikeys[] = KeyGenerator.getInstance().generatePKIKeypair();
- Object riSigningkeys[] = KeyGenerator.getInstance().generateSigningKeypair();
- ri.setCertificate(new Certificate(Certificate.CERTIFICATE_TYPE_NULL, null));
- ri.setPublicKey((PublicKey) rikeys[0]);
- ri.setSigningPublicKey((SigningPublicKey) riSigningkeys[0]);
- TunnelId tunnel = new TunnelId();
- tunnel.setTunnelId(42);
- rlsm.addEndpoint(ri, tunnel);
- try {
- doSend(rlsm);
- } catch (I2CPMessageException ime) {
- _log.error("Error writing out the request for a lease set", ime);
- } catch (IOException ioe) {
- _log.error("Error writing out the request for a lease set", ioe);
- }
-
- }
-
- protected void sessionCreated() { // nop
- }
-
- protected SessionConfig getConfig() {
- return _config;
- }
-
- /**
- * Handle a SendMessageMessage
- *
- */
- protected void handleSendMessage(I2CPMessageReader reader, SendMessageMessage message) {
- _log.debug("handleSendMessage called");
- Payload payload = message.getPayload();
- Destination dest = message.getDestination();
- MessageId id = new MessageId();
- id.setMessageId(getNextMessageId());
- _log.debug("** Receiving message [" + id.getMessageId() + "] with payload: " + "[" + payload + "]");
- _messages.put(id, payload);
- MessageStatusMessage status = new MessageStatusMessage();
- status.setMessageId(id);
- status.setSessionId(message.getSessionId());
- status.setSize(0L);
- status.setNonce(message.getNonce());
- status.setStatus(MessageStatusMessage.STATUS_SEND_ACCEPTED);
- try {
- doSend(status);
- } catch (I2CPMessageException ime) {
- _log.error("Error writing out the message status message", ime);
- } catch (IOException ioe) {
- _log.error("Error writing out the message status message", ioe);
- }
- distributeMessageToPeer(status, dest, id);
- }
-
- /**
- * distribute the message to the destination, passing on the appropriate status
- * messages to the sender of the SendMessageMessage
- *
- */
- private void distributeMessageToPeer(MessageStatusMessage status, Destination dest, MessageId id) {
- ConnectionRunner runner = (ConnectionRunner) _connections.get(dest);
- if (runner == null) {
- distributeNonLocal(status, dest, id);
- } else {
- distributeLocal(runner, status, dest, id);
- }
- _log.debug("Done handling send message");
- }
-
- protected void distributeLocal(ConnectionRunner runner, MessageStatusMessage status, Destination dest, MessageId id) {
- if (runner.messageAvailable(id, 0L)) {
- status.setStatus(MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS);
- status.setNonce(2);
- try {
- doSend(status);
- } catch (I2CPMessageException ime) {
- _log.error("Error writing out the success status message", ime);
- } catch (IOException ioe) {
- _log.error("Error writing out the success status message", ioe);
- }
- _log.debug("Guaranteed success with the status message sent");
- } else {
- status.setStatus(MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE);
- try {
- doSend(status);
- } catch (I2CPMessageException ime) {
- _log.error("Error writing out the failure status message", ime);
- } catch (IOException ioe) {
- _log.error("Error writing out the failure status message", ioe);
- }
- _log.debug("Guaranteed failure since messageAvailable failed");
- }
- }
-
- protected void distributeNonLocal(MessageStatusMessage status, Destination dest, MessageId id) {
- status.setStatus(MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE);
- try {
- doSend(status);
- } catch (I2CPMessageException ime) {
- _log.error("Error writing out the failure status message", ime);
- } catch (IOException ioe) {
- _log.error("Error writing out the failure status message", ioe);
- }
- _log.debug("Guaranteed failure!");
- }
-
- /**
- * The client asked for a message, so we send it to them. This currently
- * does not do any security checking (like making sure they're the one to
- * whom the message ID is destined, but its encrypted, so why not...
- * (bad attitude, I know. consider this a bug to be fixed)
- *
- */
- public void handleReceiveBegin(I2CPMessageReader reader, ReceiveMessageBeginMessage message) {
- _log.debug("Handling receive begin: id = " + message.getMessageId());
- MessagePayloadMessage msg = new MessagePayloadMessage();
- msg.setMessageId(message.getMessageId());
- msg.setSessionId(_sessionId);
- Payload payload = (Payload) _messages.get(message.getMessageId());
- if (payload == null) {
- _log.error("Payload for message id [" + message.getMessageId() + "] is null! Unknown message id?",
- new Exception("Error, null payload"));
- StringBuffer buf = new StringBuffer();
- for (Iterator iter = _messages.keySet().iterator(); iter.hasNext();) {
- buf.append("messageId: ").append(iter.next()).append(", ");
- }
- _log.error("Known message IDs: " + buf.toString());
- return;
- }
- msg.setPayload(payload);
- try {
- doSend(msg);
- } catch (IOException ioe) {
- _log.error("Error delivering the payload", ioe);
- } catch (I2CPMessageException ime) {
- _log.error("Error delivering the payload", ime);
- }
- }
-
- /**
- * The client told us that the message has been received completely. This currently
- * does not do any security checking prior to removing the message from the
- * pending queue, though it should.
- *
- */
- public void handleReceiveEnd(I2CPMessageReader reader, ReceiveMessageEndMessage message) {
- _messages.remove(message.getMessageId());
- }
-
- /**
- * Deliver notification to the client that the given message is available.
- * This is called from the ConnectionRunner the message was sent from.
- *
- */
- public boolean messageAvailable(MessageId id, long size) {
- MessageStatusMessage msg = new MessageStatusMessage();
- msg.setMessageId(id);
- msg.setSessionId(_sessionId);
- msg.setSize(size);
- msg.setNonce(1);
- msg.setStatus(MessageStatusMessage.STATUS_AVAILABLE);
- try {
- doSend(msg);
- return true;
- } catch (I2CPMessageException ime) {
- _log.error("Error writing out the message status message", ime);
- } catch (IOException ioe) {
- _log.error("Error writing out the message status message", ioe);
- }
- return false;
- }
-
- /**
- * Handle notifiation that there was an error
- *
- */
- public void readError(I2CPMessageReader reader, Exception error) {
- _log.info("Error occurred", error);
- }
-
- private Object _sendLock = new Object();
-
- protected void doSend(I2CPMessage msg) throws I2CPMessageException, IOException {
- synchronized (_sendLock) {
- msg.writeMessage(_out);
- _out.flush();
- }
- }
-}
diff --git a/core/java/src/net/i2p/client/TestClient.java b/core/java/src/net/i2p/client/TestClient.java
deleted file mode 100644
index 47a00fc3c..000000000
--- a/core/java/src/net/i2p/client/TestClient.java
+++ /dev/null
@@ -1,145 +0,0 @@
-package net.i2p.client;
-
-/*
- * free (adj.): unencumbered; not under the control of others
- * Written by jrandom in 2003 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.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.util.Properties;
-
-import net.i2p.data.Destination;
-import net.i2p.util.I2PThread;
-import net.i2p.util.Log;
-
-/**
- * Quick and dirty test harness for sending messages from one destination to another.
- * This will print out some debugging information and containg the statement
- * "Hello other side. I am dest1" if the router and the client libraries work.
- * This class bootstraps itself each time - creating new keys and destinations
- *
- * @author jrandom
- */
-public class TestClient implements I2PSessionListener {
- private final static Log _log = new Log(TestClient.class);
- private static Destination _dest1;
- private static Destination _dest2;
- private boolean _stillRunning;
-
- public void runTest(String keyfile, boolean isDest1) {
- _stillRunning = true;
- try {
- I2PClient client = I2PClientFactory.createClient();
- File file = new File(keyfile);
- Destination d = client.createDestination(new FileOutputStream(file));
- if (isDest1)
- _dest1 = d;
- else
- _dest2 = d;
- _log.debug("Destination written to " + file.getAbsolutePath());
- Properties options = new Properties();
-
- if (System.getProperty(I2PClient.PROP_TCP_HOST) == null)
- options.setProperty(I2PClient.PROP_TCP_HOST, "localhost");
- else
- options.setProperty(I2PClient.PROP_TCP_HOST, System.getProperty(I2PClient.PROP_TCP_HOST));
- if (System.getProperty(I2PClient.PROP_TCP_PORT) == null)
- options.setProperty(I2PClient.PROP_TCP_PORT, "7654");
- else
- options.setProperty(I2PClient.PROP_TCP_PORT, System.getProperty(I2PClient.PROP_TCP_PORT));
-
- I2PSession session = client.createSession(new FileInputStream(file), options);
- session.setSessionListener(this);
- _log.debug("Before connect...");
- session.connect();
- _log.debug("Connected");
-
- // wait until the other one is connected
- while ((_dest1 == null) || (_dest2 == null))
- try {
- Thread.sleep(500);
- } catch (InterruptedException ie) { // nop
- }
-
- if (isDest1) {
- Destination otherD = (isDest1 ? _dest2 : _dest1);
- boolean accepted = session
- .sendMessage(
- otherD,
- ("Hello other side. I am" + (isDest1 ? "" : " NOT") + " dest1")
- .getBytes());
- } else {
- while (_stillRunning) {
- try {
- _log.debug("waiting for a message...");
- Thread.sleep(1000);
- } catch (InterruptedException ie) { // nop
- }
- }
- try {
- Thread.sleep(5000);
- } catch (InterruptedException ie) { // nop
- }
- System.exit(0);
- }
- //session.destroySession();
- } catch (Exception e) {
- _log.error("Error running the test for isDest1? " + isDest1, e);
- }
- }
-
- public static void main(String args[]) {
- doTest();
- try {
- Thread.sleep(30 * 1000);
- } catch (InterruptedException ie) { // nop
- }
- }
-
- static void doTest() {
- Thread test1 = new I2PThread(new Runnable() {
- public void run() {
- (new TestClient()).runTest("test1.keyfile", true);
- }
- });
- Thread test2 = new I2PThread(new Runnable() {
- public void run() {
- (new TestClient()).runTest("test2.keyfile", false);
- }
- });
- test1.start();
- test2.start();
- _log.debug("Test threads started");
- }
-
- public void disconnected(I2PSession session) {
- _log.debug("Disconnected");
- _stillRunning = false;
- }
-
- public void errorOccurred(I2PSession session, String message, Throwable error) {
- _log.debug("Error occurred: " + message, error);
- }
-
- public void messageAvailable(I2PSession session, int msgId, long size) {
- _log.debug("Message available for us! id = " + msgId + " of size " + size);
- try {
- byte msg[] = session.receiveMessage(msgId);
- _log.debug("Content of message " + msgId + ":\n" + new String(msg));
- _stillRunning = false;
- } catch (I2PSessionException ise) {
- _log.error("Error fetching available message", ise);
- }
- }
-
- public void reportAbuse(I2PSession session, int severity) {
- _log.debug("Abuse reported of severity " + severity);
- }
-
-}
\ No newline at end of file
diff --git a/core/java/src/net/i2p/client/TestServer.java b/core/java/src/net/i2p/client/TestServer.java
deleted file mode 100644
index ee7b78bf3..000000000
--- a/core/java/src/net/i2p/client/TestServer.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package net.i2p.client;
-
-/*
- * free (adj.): unencumbered; not under the control of others
- * Written by jrandom in 2003 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.IOException;
-import java.net.ServerSocket;
-import java.net.Socket;
-
-import net.i2p.util.I2PThread;
-import net.i2p.util.Log;
-
-/**
- * Implement a local only router for testing purposes. This router is minimal
- * in that it doesn't verify signatures, communicate with other routers, or handle
- * failures very gracefully. It is simply a test harness to allow I2CP based
- * applications to run.
- *
- * @author jrandom
- */
-public class TestServer implements Runnable {
- private final static Log _log = new Log(TestServer.class);
- private ServerSocket _socket;
- public static int LISTEN_PORT = 7654;
-
- protected void setPort(int port) {
- LISTEN_PORT = port;
- }
-
- /**
- * Start up the socket listener, listens for connections, and
- * fires those connections off via {@link #runConnection runConnection}.
- * This only returns if the socket cannot be opened or there is a catastrophic
- * failure.
- *
- */
- public void runServer() {
- try {
- _socket = new ServerSocket(LISTEN_PORT);
- } catch (IOException ioe) {
- _log.error("Error listening", ioe);
- return;
- }
- while (true) {
- try {
- Socket socket = _socket.accept();
- runConnection(socket);
- } catch (IOException ioe) {
- _log.error("Server error accepting", ioe);
- }
- }
- }
-
- /**
- * Handle the connection by passing it off to a ConnectionRunner
- *
- */
- protected void runConnection(Socket socket) throws IOException {
- ConnectionRunner runner = new ConnectionRunner(socket);
- runner.doYourThing();
- }
-
- public void run() {
- runServer();
- }
-
- /**
- * Fire up the router
- */
- public static void main(String args[]) {
- if (args.length == 1) { // nop
- } else if (args.length == 2) {
- try {
- LISTEN_PORT = Integer.parseInt(args[1]);
- } catch (NumberFormatException nfe) {
- _log.error("Invalid port number specified (" + args[1] + "), using " + LISTEN_PORT, nfe);
- }
- }
- TestServer server = new TestServer();
- Thread t = new I2PThread(server);
- t.start();
- }
-}
\ No newline at end of file
diff --git a/core/java/src/net/i2p/data/UnsignedInteger.java b/core/java/src/net/i2p/data/UnsignedInteger.java
deleted file mode 100644
index cc07ac260..000000000
--- a/core/java/src/net/i2p/data/UnsignedInteger.java
+++ /dev/null
@@ -1,290 +0,0 @@
-package net.i2p.data;
-
-/*
- * free (adj.): unencumbered; not under the control of others
- * Written by jrandom in 2003 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.IOException;
-import java.io.OutputStream;
-import java.math.BigInteger;
-
-import net.i2p.util.Log;
-
-/**
- * Manage an arbitrarily large unsigned integer, using the first bit and first byte
- * as the most significant one. Also allows the exporting to byte arrays with whatever
- * padding is requested.
- *
- * WARNING: Range is currently limited to 0 through 2^63-1, due to Java's two's complement
- * format. Fix when we need it.
- *
- * @author jrandom
- */
-public class UnsignedInteger {
- private final static Log _log = new Log(UnsignedInteger.class);
- private byte[] _data;
- private long _value;
-
- /**
- * Construct the integer from the bytes given, making the value accessible
- * immediately.
- *
- * @param data unsigned number in network byte order (first bit, first byte
- * is the most significant)
- */
- public UnsignedInteger(byte[] data) {
- // strip off excess bytes
- int start = 0;
- for (int i = 0; i < data.length; i++) {
- if (data[i] == 0) {
- start++;
- } else {
- break;
- }
- }
- _data = new byte[data.length - start];
- for (int i = 0; i < _data.length; i++)
- _data[i] = data[i + start];
- // done stripping excess bytes, now calc
- _value = calculateValue(_data);
- }
-
- /**
- * Construct the integer with the java number given, making the bytes
- * available immediately.
- *
- * @param value number to represent
- * @throws IllegalArgumentException if the value is negative
- */
- public UnsignedInteger(long value) throws IllegalArgumentException {
- _value = value;
- _data = calculateBytes(value);
- }
-
- /**
- * Calculate the value of the array of bytes, treating it as an unsigned integer
- * with the most significant bit and byte first
- *
- */
- private static long calculateValue(byte[] data) {
- if (data == null) {
- _log.error("Null data to be calculating for", new Exception("Argh"));
- return 0;
- } else if (data.length == 0) { return 0; }
- long rv = 0;
- for (int i = 0; i < data.length; i++) {
- long cur = data[i] & 0xFF;
- if (cur < 0) cur = cur+256;
- cur = (cur << (8*(data.length-i-1)));
- rv += cur;
- }
- // only fire up this expensive assert if we're worried about it
- if (_log.shouldLog(Log.DEBUG)) {
- BigInteger bi = new BigInteger(1, data);
- long biVal = bi.longValue();
- if (biVal != rv) {
- _log.log(Log.CRIT, "ERR: " + bi.toString(2) + " /\t" + bi.toString(16) + " /\t" + bi.toString()
- + " != \n " + Long.toBinaryString(rv) + " /\t" + Long.toHexString(rv)
- + " /\t" + rv);
- for (int i = 0; i < data.length; i++) {
- long cur = data[i] & 0xFF;
- if (cur < 0) cur = cur+256;
- long shiftBy = (8*(data.length-i-1));
- long old = cur;
- cur = (cur << shiftBy);
- _log.log(Log.CRIT, "cur["+ i+"]=" + Long.toHexString(cur) + " data = "
- + Long.toHexString((data[i]&0xFF)) + " shiftBy: " + shiftBy
- + " old: " + Long.toHexString(old));
- }
- throw new RuntimeException("b0rked on " + bi.toString() + " / " + rv);
- }
- }
- return rv;
-
- }
-
- /**
- * hexify the byte array
- *
- */
- private final static String toString(byte[] val) {
- return "0x" + DataHelper.toString(val, val.length);
- }
-
- /**
- * Calculate the bytes as an unsigned integer with the most significant
- * bit and byte in the first position. The return value always has at least
- * one byte in it.
- *
- * @throws IllegalArgumentException if the value is negative
- */
- private static byte[] calculateBytes(long value) throws IllegalArgumentException {
- if (value < 0)
- throw new IllegalArgumentException("unsigned integer, and you want a negative? " + value);
- byte val[] = new byte[8];
- val[0] = (byte)(value >>> 56);
- val[1] = (byte)(value >>> 48);
- val[2] = (byte)(value >>> 40);
- val[3] = (byte)(value >>> 32);
- val[4] = (byte)(value >>> 24);
- val[5] = (byte)(value >>> 16);
- val[6] = (byte)(value >>> 8);
- val[7] = (byte)value;
-
- int firstNonZero = -1;
- for (int i = 0; i < val.length; i++) {
- if (val[i] != 0x00) {
- firstNonZero = i;
- break;
- }
- }
-
- if (firstNonZero == 0)
- return val;
- if (firstNonZero == -1)
- return new byte[1]; // initialized as 0
-
- byte rv[] = new byte[8-firstNonZero];
- System.arraycopy(val, firstNonZero, rv, 0, rv.length);
- return rv;
- /*
- BigInteger bi = new BigInteger("" + value);
- byte buf[] = bi.toByteArray();
- if ((buf == null) || (buf.length <= 0))
- throw new IllegalArgumentException("Value [" + value + "] cannot be transformed");
- int trim = 0;
- while ((trim < buf.length) && (buf[trim] == 0x00))
- trim++;
- byte rv[] = new byte[buf.length - trim];
- System.arraycopy(buf, trim, rv, 0, rv.length);
- return rv;
- */
- }
-
- /**
- * Get the unsigned bytes, most significant bit and bytes first, without any padding
- *
- */
- public byte[] getBytes() {
- return _data;
- }
-
- /**
- * Get the unsigned bytes, most significant bit and bytes first, zero padded to the
- * specified number of bytes
- *
- * @throws IllegalArgumentException if numBytes < necessary number of bytes
- */
- public byte[] getBytes(int numBytes) throws IllegalArgumentException {
- if ((_data == null) || (numBytes < _data.length))
- throw new IllegalArgumentException("Value (" + _value + ") is greater than the requested number of bytes ("
- + numBytes + ")");
-
- if (numBytes == _data.length) return _data;
-
- byte[] data = new byte[numBytes];
- System.arraycopy(_data, 0, data, numBytes - _data.length, _data.length);
- return data;
- }
-
-
- public static void writeBytes(OutputStream rawStream, int numBytes, long value)
- throws DataFormatException, IOException {
- if (value < 0) throw new DataFormatException("Invalid value (" + value + ")");
- for (int i = numBytes - 1; i >= 0; i--) {
- byte cur = (byte)( (value >>> (i*8) ) & 0xFF);
- rawStream.write(cur);
- }
- }
-
- public BigInteger getBigInteger() {
- return new BigInteger(1, _data);
- }
-
- public long getLong() {
- return _value;
- }
-
- public int getInt() {
- return (int) _value;
- }
-
- public short getShort() {
- return (short) _value;
- }
-
- public boolean equals(Object obj) {
- if ((obj != null) && (obj instanceof UnsignedInteger)) {
- return DataHelper.eq(_data, ((UnsignedInteger) obj)._data)
- && DataHelper.eq(_value, ((UnsignedInteger) obj)._value);
- }
- return false;
- }
-
- public int hashCode() {
- return DataHelper.hashCode(_data) + (int) _value;
- }
-
- public String toString() {
- return "UnsignedInteger: " + getLong() + "/" + toString(getBytes());
- }
-
- public static void main(String args[]) {
- try {
- _log.debug("Testing 1024");
- testNum(1024L);
- _log.debug("Testing 1025");
- testNum(1025L);
- _log.debug("Testing 2Gb-1");
- testNum(1024 * 1024 * 1024 * 2L - 1L);
- _log.debug("Testing 4Gb-1");
- testNum(1024 * 1024 * 1024 * 4L - 1L);
- _log.debug("Testing 4Gb");
- testNum(1024 * 1024 * 1024 * 4L);
- _log.debug("Testing 4Gb+1");
- testNum(1024 * 1024 * 1024 * 4L + 1L);
- _log.debug("Testing MaxLong");
- testNum(Long.MAX_VALUE);
- testWrite();
- } catch (Throwable t) { t.printStackTrace(); }
- try {
- Thread.sleep(1000);
- } catch (Throwable t) { // nop
- }
- }
-
- private static void testNum(long num) {
- UnsignedInteger i = new UnsignedInteger(num);
- _log.debug(num + " turned into an unsigned integer: " + i + " (" + i.getLong() + "/" + toString(i.getBytes())
- + ")");
- _log.debug(num + " turned into an BigInteger: " + i.getBigInteger());
- byte[] val = i.getBytes();
- UnsignedInteger val2 = new UnsignedInteger(val);
- _log.debug(num + " turned into a byte array and back again: " + val2 + " (" + val2.getLong() + "/"
- + toString(val2.getBytes()) + ")");
- _log.debug(num + " As an 8 byte array: " + toString(val2.getBytes(8)));
- BigInteger bi = new BigInteger(num+"");
- _log.debug(num + " As a bigInteger: 0x" + bi.toString(16));
- BigInteger tbi = new BigInteger(1, calculateBytes(num));
- _log.debug(num + " As a shifted : 0x" + tbi.toString(16));
- }
-
- private static void testWrite() throws Exception {
- java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(8);
- UnsignedInteger i = new UnsignedInteger(12345);
- baos.write(i.getBytes(8));
- byte v1[] = baos.toByteArray();
- baos.reset();
- UnsignedInteger.writeBytes(baos, 8, 12345);
- byte v2[] = baos.toByteArray();
- System.out.println("v1 len: " + v1.length + " v2 len: " + v2.length);
- System.out.println("v1: " + DataHelper.toHexString(v1));
- System.out.println("v2: " + DataHelper.toHexString(v2));
-
- }
-}
\ No newline at end of file
diff --git a/core/java/src/net/i2p/util/CachingByteArrayOutputStream.java b/core/java/src/net/i2p/util/CachingByteArrayOutputStream.java
new file mode 100644
index 000000000..e1d3354d2
--- /dev/null
+++ b/core/java/src/net/i2p/util/CachingByteArrayOutputStream.java
@@ -0,0 +1,27 @@
+package net.i2p.util;
+
+import java.io.ByteArrayOutputStream;
+
+import net.i2p.data.ByteArray;
+
+/**
+ * simple extension to the baos to try to use a ByteCache for its
+ * internal buffer. This caching only works when the array size
+ * provided is sufficient for the entire buffer. After doing what
+ * needs to be done (e.g. write(foo); toByteArray();), call releaseBuffer
+ * to put the buffer back into the cache.
+ *
+ */
+public class CachingByteArrayOutputStream extends ByteArrayOutputStream {
+ private ByteCache _cache;
+ private ByteArray _buf;
+
+ public CachingByteArrayOutputStream(int cacheQuantity, int arraySize) {
+ super(0);
+ _cache = ByteCache.getInstance(cacheQuantity, arraySize);
+ _buf = _cache.acquire();
+ super.buf = _buf.getData();
+ }
+
+ public void releaseBuffer() { _cache.release(_buf); }
+}
diff --git a/core/java/src/net/i2p/util/DecayingBloomFilter.java b/core/java/src/net/i2p/util/DecayingBloomFilter.java
new file mode 100644
index 000000000..4727de926
--- /dev/null
+++ b/core/java/src/net/i2p/util/DecayingBloomFilter.java
@@ -0,0 +1,225 @@
+package net.i2p.util;
+
+import org.xlattice.crypto.filters.BloomSHA1;
+import net.i2p.I2PAppContext;
+import net.i2p.data.DataHelper;
+
+import java.util.Random;
+
+/**
+ * Series of bloom filters which decay over time, allowing their continual use
+ * for time sensitive data. This has a fixed size (currently 1MB per decay
+ * period, using two periods overall), allowing this to pump through hundreds of
+ * entries per second with virtually no false positive rate. Down the line,
+ * this may be refactored to allow tighter control of the size necessary for the
+ * contained bloom filters, but a fixed 2MB overhead isn't that bad.
+ */
+public class DecayingBloomFilter {
+ private I2PAppContext _context;
+ private Log _log;
+ private BloomSHA1 _current;
+ private BloomSHA1 _previous;
+ private int _durationMs;
+ private int _entryBytes;
+ private byte _extenders[][];
+ private byte _extended[];
+ private byte _longToEntry[];
+ private long _longToEntryMask;
+ private long _currentDuplicates;
+ private boolean _keepDecaying;
+ private DecayEvent _decayEvent;
+
+ /**
+ * Create a bloom filter that will decay its entries over time.
+ *
+ * @param durationMs entries last for at least this long, but no more than twice this long
+ * @param entryBytes how large are the entries to be added? if this is less than 32 bytes,
+ * the entries added will be expanded by concatenating their XORing
+ * against with sufficient random values.
+ */
+ public DecayingBloomFilter(I2PAppContext context, int durationMs, int entryBytes) {
+ _context = context;
+ _log = context.logManager().getLog(DecayingBloomFilter.class);
+ _entryBytes = entryBytes;
+ _current = new BloomSHA1(23, 11);
+ _previous = new BloomSHA1(23, 11);
+ _durationMs = durationMs;
+ int numExtenders = (32+ (entryBytes-1))/entryBytes - 1;
+ if (numExtenders < 0)
+ numExtenders = 0;
+ _extenders = new byte[numExtenders][entryBytes];
+ for (int i = 0; i < numExtenders; i++)
+ _context.random().nextBytes(_extenders[i]);
+ if (numExtenders > 0) {
+ _extended = new byte[32];
+ _longToEntry = new byte[_entryBytes];
+ _longToEntryMask = (1l << (_entryBytes * 8l)) -1;
+ }
+ _currentDuplicates = 0;
+ _decayEvent = new DecayEvent();
+ _keepDecaying = true;
+ SimpleTimer.getInstance().addEvent(_decayEvent, _durationMs);
+ }
+
+ /**
+ * return true if the entry added is a duplicate
+ *
+ */
+ public boolean add(byte entry[]) {
+ if (entry == null)
+ throw new IllegalArgumentException("Null entry");
+ if (entry.length != _entryBytes)
+ throw new IllegalArgumentException("Bad entry [" + entry.length + ", expected "
+ + _entryBytes + "]");
+ synchronized (this) {
+ return locked_add(entry);
+ }
+ }
+
+ /**
+ * return true if the entry added is a duplicate. the number of low order
+ * bits used is determined by the entryBytes parameter used on creation of the
+ * filter.
+ *
+ */
+ public boolean add(long entry) {
+ synchronized (this) {
+ if (_entryBytes <= 7)
+ entry &= _longToEntryMask;
+ if (entry < 0) {
+ DataHelper.toLong(_longToEntry, 0, _entryBytes, 0-entry);
+ _longToEntry[0] |= (1 << 7);
+ } else {
+ DataHelper.toLong(_longToEntry, 0, _entryBytes, entry);
+ }
+ return locked_add(_longToEntry);
+ }
+ }
+
+ private boolean locked_add(byte entry[]) {
+ if (_extended != null) {
+ // extend the entry to 32 bytes
+ System.arraycopy(entry, 0, _extended, 0, entry.length);
+ for (int i = 0; i < _extenders.length; i++)
+ DataHelper.xor(entry, 0, _extenders[i], 0, _extended, _entryBytes * (i+1), _entryBytes);
+
+ boolean seen = _current.member(_extended);
+ seen = seen || _previous.member(_extended);
+ if (seen) {
+ _currentDuplicates++;
+ return true;
+ } else {
+ _current.insert(_extended);
+ _previous.insert(_extended);
+ return false;
+ }
+ } else {
+ boolean seen = _current.locked_member(entry);
+ seen = seen || _previous.locked_member(entry);
+ if (seen) {
+ _currentDuplicates++;
+ return true;
+ } else {
+ _current.locked_insert(entry);
+ _previous.locked_insert(entry);
+ return false;
+ }
+ }
+ }
+
+ public void clear() {
+ synchronized (this) {
+ _current.clear();
+ _previous.clear();
+ _currentDuplicates = 0;
+ }
+ }
+
+ public void stopDecaying() {
+ _keepDecaying = false;
+ SimpleTimer.getInstance().removeEvent(_decayEvent);
+ }
+
+ private void decay() {
+ int currentCount = 0;
+ long dups = 0;
+ synchronized (this) {
+ BloomSHA1 tmp = _previous;
+ currentCount = _current.size();
+ _previous = _current;
+ _current = tmp;
+ _current.clear();
+ dups = _currentDuplicates;
+ _currentDuplicates = 0;
+ }
+ if (_log.shouldLog(Log.DEBUG))
+ _log.debug("Decaying the filter after inserting " + currentCount
+ + " elements and " + dups + " false positives");
+ }
+
+ private class DecayEvent implements SimpleTimer.TimedEvent {
+ public void timeReached() {
+ if (_keepDecaying) {
+ decay();
+ SimpleTimer.getInstance().addEvent(DecayEvent.this, _durationMs);
+ }
+ }
+ }
+
+ public static void main(String args[]) {
+ int kbps = 256;
+ int iterations = 100;
+ testByLong(kbps, iterations);
+ testByBytes(kbps, iterations);
+ }
+ public static void testByLong(int kbps, int numRuns) {
+ int messages = 60 * 10 * kbps;
+ Random r = new Random();
+ DecayingBloomFilter filter = new DecayingBloomFilter(I2PAppContext.getGlobalContext(), 600*1000, 8);
+ int falsePositives = 0;
+ long totalTime = 0;
+ for (int j = 0; j < numRuns; j++) {
+ long start = System.currentTimeMillis();
+ for (int i = 0; i < messages; i++) {
+ if (filter.add(r.nextLong())) {
+ falsePositives++;
+ System.out.println("False positive " + falsePositives + " (testByLong j=" + j + " i=" + i + ")");
+ }
+ }
+ totalTime += System.currentTimeMillis() - start;
+ filter.clear();
+ }
+ filter.stopDecaying();
+ System.out.println("After " + numRuns + " runs pushing " + messages + " entries in "
+ + DataHelper.formatDuration(totalTime/numRuns) + " per run, there were "
+ + falsePositives + " false positives");
+
+ }
+ public static void testByBytes(int kbps, int numRuns) {
+ byte iv[][] = new byte[60*10*kbps][16];
+ Random r = new Random();
+ for (int i = 0; i < iv.length; i++)
+ r.nextBytes(iv[i]);
+
+ DecayingBloomFilter filter = new DecayingBloomFilter(I2PAppContext.getGlobalContext(), 600*1000, 16);
+ int falsePositives = 0;
+ long totalTime = 0;
+ for (int j = 0; j < numRuns; j++) {
+ long start = System.currentTimeMillis();
+ for (int i = 0; i < iv.length; i++) {
+ if (filter.add(iv[i])) {
+ falsePositives++;
+ System.out.println("False positive " + falsePositives + " (testByLong j=" + j + " i=" + i + ")");
+ }
+ }
+ totalTime += System.currentTimeMillis() - start;
+ filter.clear();
+ }
+ filter.stopDecaying();
+ System.out.println("After " + numRuns + " runs pushing " + iv.length + " entries in "
+ + DataHelper.formatDuration(totalTime/numRuns) + " per run, there were "
+ + falsePositives + " false positives");
+ //System.out.println("inserted: " + bloom.size() + " with " + bloom.capacity()
+ // + " (" + bloom.falsePositives()*100.0d + "% false positive)");
+ }
+}
diff --git a/core/java/src/org/xlattice/crypto/filters/BloomSHA1.java b/core/java/src/org/xlattice/crypto/filters/BloomSHA1.java
new file mode 100644
index 000000000..2b8dee4d2
--- /dev/null
+++ b/core/java/src/org/xlattice/crypto/filters/BloomSHA1.java
@@ -0,0 +1,224 @@
+/* BloomSHA1.java */
+package org.xlattice.crypto.filters;
+
+/**
+ * A Bloom filter for sets of SHA1 digests. A Bloom filter uses a set
+ * of k hash functions to determine set membership. Each hash function
+ * produces a value in the range 0..M-1. The filter is of size M. To
+ * add a member to the set, apply each function to the new member and
+ * set the corresponding bit in the filter. For M very large relative
+ * to k, this will normally set k bits in the filter. To check whether
+ * x is a member of the set, apply each of the k hash functions to x
+ * and check whether the corresponding bits are set in the filter. If
+ * any are not set, x is definitely not a member. If all are set, x
+ * may be a member. The probability of error (the false positive rate)
+ * is f = (1 - e^(-kN/M))^k, where N is the number of set members.
+ *
+ * This class takes advantage of the fact that SHA1 digests are good-
+ * quality pseudo-random numbers. The k hash functions are the values
+ * of distinct sets of bits taken from the 20-byte SHA1 hash. The
+ * number of bits in the filter, M, is constrained to be a power of
+ * 2; M == 2^m. The number of bits in each hash function may not
+ * exceed floor(m/k).
+ *
+ * This class is designed to be thread-safe, but this has not been
+ * exhaustively tested.
+ *
+ * @author < A HREF="mailto:jddixon@users.sourceforge.net">Jim Dixon
+ *
+ * BloomSHA1.java and KeySelector.java are BSD licensed from the xlattice
+ * app - http://xlattice.sourceforge.net/
+ *
+ * minor tweaks by jrandom, exposing unsynchronized access and
+ * allowing larger M and K. changes released into the public domain.
+ */
+
+public class BloomSHA1 {
+ protected final int m;
+ protected final int k;
+ protected int count;
+
+ protected final int[] filter;
+ protected KeySelector ks;
+ protected final int[] wordOffset;
+ protected final int[] bitOffset;
+
+ // convenience variables
+ protected final int filterBits;
+ protected final int filterWords;
+
+ /**
+ * Creates a filter with 2^m bits and k 'hash functions', where
+ * each hash function is portion of the 160-bit SHA1 hash.
+
+ * @param m determines number of bits in filter, defaults to 20
+ * @param k number of hash functions, defaults to 8
+ */
+ public BloomSHA1( int m, int k) {
+ // XXX need to devise more reasonable set of checks
+ //if ( m < 2 || m > 20) {
+ // throw new IllegalArgumentException("m out of range");
+ //}
+ //if ( k < 1 || ( k * m > 160 )) {
+ // throw new IllegalArgumentException(
+ // "too many hash functions for filter size");
+ //}
+ this.m = m;
+ this.k = k;
+ count = 0;
+ filterBits = 1 << m;
+ filterWords = (filterBits + 31)/32; // round up
+ filter = new int[filterWords];
+ doClear();
+ // offsets into the filter
+ wordOffset = new int[k];
+ bitOffset = new int[k];
+ ks = new KeySelector(m, k, bitOffset, wordOffset);
+
+ // DEBUG
+ //System.out.println("Bloom constructor: m = " + m + ", k = " + k
+ // + "\n filterBits = " + filterBits
+ // + ", filterWords = " + filterWords);
+ // END
+ }
+
+ /**
+ * Creates a filter of 2^m bits, with the number of 'hash functions"
+ * k defaulting to 8.
+ * @param m determines size of filter
+ */
+ public BloomSHA1 (int m) {
+ this(m, 8);
+ }
+
+ /**
+ * Creates a filter of 2^20 bits with k defaulting to 8.
+ */
+ public BloomSHA1 () {
+ this (20, 8);
+ }
+ /** Clear the filter, unsynchronized */
+ protected void doClear() {
+ for (int i = 0; i < filterWords; i++) {
+ filter[i] = 0;
+ }
+ }
+ /** Synchronized version */
+ public void clear() {
+ synchronized (this) {
+ doClear();
+ }
+ }
+ /**
+ * Returns the number of keys which have been inserted. This
+ * class (BloomSHA1) does not guarantee uniqueness in any sense; if the
+ * same key is added N times, the number of set members reported
+ * will increase by N.
+ *
+ * @return number of set members
+ */
+ public final int size() {
+ synchronized (this) {
+ return count;
+ }
+ }
+ /**
+ * @return number of bits in filter
+ */
+ public final int capacity () {
+ return filterBits;
+ }
+
+ /**
+ * Add a key to the set represented by the filter.
+ *
+ * XXX This version does not maintain 4-bit counters, it is not
+ * a counting Bloom filter.
+ *
+ * @param b byte array representing a key (SHA1 digest)
+ */
+ public void insert (byte[]b) {
+ synchronized(this) {
+ locked_insert(b);
+ }
+ }
+
+ public final void locked_insert(byte[]b) {
+ ks.getOffsets(b);
+ for (int i = 0; i < k; i++) {
+ filter[wordOffset[i]] |= 1 << bitOffset[i];
+ }
+ count++;
+ }
+
+ /**
+ * Is a key in the filter. Sets up the bit and word offset arrays.
+ *
+ * @param b byte array representing a key (SHA1 digest)
+ * @return true if b is in the filter
+ */
+ protected final boolean isMember(byte[] b) {
+ ks.getOffsets(b);
+ for (int i = 0; i < k; i++) {
+ if (! ((filter[wordOffset[i]] & (1 << bitOffset[i])) != 0) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public final boolean locked_member(byte[]b) { return isMember(b); }
+
+ /**
+ * Is a key in the filter. External interface, internally synchronized.
+ *
+ * @param b byte array representing a key (SHA1 digest)
+ * @return true if b is in the filter
+ */
+ public final boolean member(byte[]b) {
+ synchronized (this) {
+ return isMember(b);
+ }
+ }
+
+ /**
+ * @param n number of set members
+ * @return approximate false positive rate
+ */
+ public final double falsePositives(int n) {
+ // (1 - e(-kN/M))^k
+ return java.lang.Math.pow (
+ (1l - java.lang.Math.exp( ((double)k) * (long)n / (long)filterBits)), (long)k);
+ }
+
+ public final double falsePositives() {
+ return falsePositives(count);
+ }
+ // DEBUG METHODS
+ public static String keyToString(byte[] key) {
+ StringBuffer sb = new StringBuffer().append(key[0]);
+ for (int i = 1; i < key.length; i++) {
+ sb.append(".").append(Integer.toString(key[i], 16));
+ }
+ return sb.toString();
+ }
+ /** convert 64-bit integer to hex String */
+ public static String ltoh (long i) {
+ StringBuffer sb = new StringBuffer().append("#")
+ .append(Long.toString(i, 16));
+ return sb.toString();
+ }
+
+ /** convert 32-bit integer to String */
+ public static String itoh (int i) {
+ StringBuffer sb = new StringBuffer().append("#")
+ .append(Integer.toString(i, 16));
+ return sb.toString();
+ }
+ /** convert single byte to String */
+ public static String btoh (byte b) {
+ int i = 0xff & b;
+ return itoh(i);
+ }
+}
+
diff --git a/core/java/src/org/xlattice/crypto/filters/KeySelector.java b/core/java/src/org/xlattice/crypto/filters/KeySelector.java
new file mode 100644
index 000000000..6f8dd417f
--- /dev/null
+++ b/core/java/src/org/xlattice/crypto/filters/KeySelector.java
@@ -0,0 +1,245 @@
+/* KeySelector.java */
+package org.xlattice.crypto.filters;
+
+/**
+ * Given a key, populates arrays determining word and bit offsets into
+ * a Bloom filter.
+ *
+ * @author Jim Dixon
+ *
+ * BloomSHA1.java and KeySelector.java are BSD licensed from the xlattice
+ * app - http://xlattice.sourceforge.net/
+ *
+ * minor tweaks by jrandom, exposing unsynchronized access and
+ * allowing larger M and K. changes released into the public domain.
+ */
+public class KeySelector {
+
+ private int m;
+ private int k;
+ private byte[] b;
+ private int[] bitOffset;
+ private int[] wordOffset;
+ private BitSelector bitSel;
+ private WordSelector wordSel;
+
+ public interface BitSelector {
+ public void getBitSelectors();
+ }
+ public interface WordSelector {
+ public void getWordSelectors();
+ }
+ /** AND with byte to expose index-many bits */
+ public final static int[] UNMASK = {
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ 0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767};
+ /** AND with byte to zero out index-many bits */
+ public final static int[] MASK = {
+ ~0,~1,~3,~7,~15,~31,~63,~127,~255,~511,~1023,~2047,~4095,~8191,~16383,~32767};
+
+ public final static int TWO_UP_15 = 32 * 1024;
+
+ /**
+ * Creates a key selector for a Bloom filter. When a key is presented
+ * to the getOffsets() method, the k 'hash function' values are
+ * extracted and used to populate bitOffset and wordOffset arrays which
+ * specify the k flags to be set or examined in the filter.
+ *
+ * @param m size of the filter as a power of 2
+ * @param k number of 'hash functions'
+ * @param bitOffset array of k bit offsets (offset of flag bit in word)
+ * @param wordOffset array of k word offsets (offset of word flag is in)
+ */
+ public KeySelector (int m, int k, int[] bitOffset, int [] wordOffset) {
+ //if ( (m < 2) || (m > 20)|| (k < 1)
+ // || (bitOffset == null) || (wordOffset == null)) {
+ // throw new IllegalArgumentException();
+ //}
+ this.m = m;
+ this.k = k;
+ this.bitOffset = bitOffset;
+ this.wordOffset = wordOffset;
+ bitSel = new GenericBitSelector();
+ wordSel = new GenericWordSelector();
+ }
+
+ /**
+ * Extracts the k bit offsets from a key, suitable for general values
+ * of m and k.
+ */
+ public class GenericBitSelector implements BitSelector {
+ /** Do the extraction */
+ public void getBitSelectors() {
+ int curBit = 0;
+ int curByte;
+ for (int j = 0; j < k; j++) {
+ curByte = curBit / 8;
+ int bitsUnused = ((curByte + 1) * 8) - curBit; // left in byte
+
+// // DEBUG
+// System.out.println (
+// "this byte = " + btoh(b[curByte])
+// + ", next byte = " + btoh(b[curByte + 1])
+// + "; curBit=" + curBit + ", curByte= " + curByte
+// + ", bitsUnused=" + bitsUnused);
+// // END
+ if (bitsUnused > 5) {
+ bitOffset[j] = ((0xff & b[curByte])
+ >> (bitsUnused - 5)) & UNMASK[5];
+// // DEBUG
+// System.out.println(
+// " before shifting: " + btoh(b[curByte])
+// + "\n after shifting: "
+// + itoh( (0xff & b[curByte]) >> (bitsUnused - 5))
+// + "\n mask: " + itoh(UNMASK[5]) );
+// // END
+ } else if (bitsUnused == 5) {
+ bitOffset[j] = b[curByte] & UNMASK[5];
+ } else {
+ bitOffset[j] = (b[curByte] & UNMASK[bitsUnused])
+ | (((0xff & b[curByte + 1]) >> 3)
+ & MASK[bitsUnused]);
+// // DEBUG
+// System.out.println(
+// " contribution from first byte: "
+// + itoh(b[curByte] & UNMASK[bitsUnused])
+// + "\n second byte: " + btoh(b[curByte + 1])
+// + "\n shifted: " + itoh((0xff & b[curByte + 1]) >> 3)
+// + "\n mask: " + itoh(MASK[bitsUnused])
+// + "\n contribution from second byte: "
+// + itoh((0xff & b[curByte + 1] >> 3) & MASK[bitsUnused]));
+// // END
+ }
+// // DEBUG
+// System.out.println (" bitOffset[j] = " + bitOffset[j]);
+// // END
+ curBit += 5;
+ }
+ }
+ }
+ /**
+ * Extracts the k word offsets from a key. Suitable for general
+ * values of m and k.
+ */
+ public class GenericWordSelector implements WordSelector {
+ /** Extract the k offsets into the word offset array */
+ public void getWordSelectors() {
+ int stride = m - 5;
+ //assert true: stride<16;
+ int curBit = k * 5;
+ int curByte;
+ for (int j = 0; j < k; j++) {
+ curByte = curBit / 8;
+ int bitsUnused = ((curByte + 1) * 8) - curBit; // left in byte
+
+// // DEBUG
+// System.out.println (
+// "curr 3 bytes: " + btoh(b[curByte])
+// + (curByte < 19 ?
+// " " + btoh(b[curByte + 1]) : "")
+// + (curByte < 18 ?
+// " " + btoh(b[curByte + 2]) : "")
+// + "; curBit=" + curBit + ", curByte= " + curByte
+// + ", bitsUnused=" + bitsUnused);
+// // END
+
+ if (bitsUnused > stride) {
+ // the value is entirely within the current byte
+ wordOffset[j] = ((0xff & b[curByte])
+ >> (bitsUnused - stride))
+ & UNMASK[stride];
+ } else if (bitsUnused == stride) {
+ // the value fills the current byte
+ wordOffset[j] = b[curByte] & UNMASK[stride];
+ } else { // bitsUnused < stride
+ // value occupies more than one byte
+ // bits from first byte, right-aligned in result
+ wordOffset[j] = b[curByte] & UNMASK[bitsUnused];
+// // DEBUG
+// System.out.println(" first byte contributes "
+// + itoh(wordOffset[j]));
+// // END
+ // bits from second byte
+ int bitsToGet = stride - bitsUnused;
+ if (bitsToGet >= 8) {
+ // 8 bits from second byte
+ wordOffset[j] |= (0xff & b[curByte + 1]) << bitsUnused;
+// // DEBUG
+// System.out.println(" second byte contributes "
+// + itoh(
+// (0xff & b[curByte + 1]) << bitsUnused
+// ));
+// // END
+
+ // bits from third byte
+ bitsToGet -= 8;
+ if (bitsToGet > 0) {
+ wordOffset[j] |=
+ ((0xff & b[curByte + 2]) >> (8 - bitsToGet))
+ << (stride - bitsToGet) ;
+// // DEBUG
+// System.out.println(" third byte contributes "
+// + itoh(
+// (((0xff & b[curByte + 2]) >> (8 - bitsToGet))
+// << (stride - bitsToGet))
+// ));
+// // END
+ }
+ } else {
+ // all remaining bits are within second byte
+ wordOffset[j] |= ((b[curByte + 1] >> (8 - bitsToGet))
+ & UNMASK[bitsToGet])
+ << bitsUnused;
+// // DEBUG
+// System.out.println(" second byte contributes "
+// + itoh(
+// ((b[curByte + 1] >> (8 - bitsToGet))
+// & UNMASK[bitsToGet])
+// << bitsUnused
+// ));
+// // END
+ }
+ }
+// // DEBUG
+// System.out.println (
+// " wordOffset[" + j + "] = " + wordOffset[j]
+// + ", " + itoh(wordOffset[j])
+// );
+// // END
+ curBit += stride;
+ }
+ }
+ }
+ /**
+ * Given a key, populate the word and bit offset arrays, each
+ * of which has k elements.
+ *
+ * @param key cryptographic key used in populating the arrays
+ */
+ public void getOffsets (byte[] key) {
+ if (key == null) {
+ throw new IllegalArgumentException("null key");
+ }
+ if (key.length < 20) {
+ throw new IllegalArgumentException(
+ "key must be at least 20 bytes long");
+ }
+ b = key;
+// // DEBUG
+// System.out.println("KeySelector.getOffsets for "
+// + BloomSHA1.keyToString(b));
+// // END
+ bitSel.getBitSelectors();
+ wordSel.getWordSelectors();
+ }
+
+ // DEBUG METHODS ////////////////////////////////////////////////
+ String itoh(int i) {
+ return BloomSHA1.itoh(i);
+ }
+ String btoh(byte b) {
+ return BloomSHA1.btoh(b);
+ }
+}
+
+
diff --git a/router/java/src/net/i2p/data/i2np/DateMessage.java b/router/java/src/net/i2p/data/i2np/DateMessage.java
new file mode 100644
index 000000000..3b27f886a
--- /dev/null
+++ b/router/java/src/net/i2p/data/i2np/DateMessage.java
@@ -0,0 +1,80 @@
+package net.i2p.data.i2np;
+/*
+ * free (adj.): unencumbered; not under the control of others
+ * Written by jrandom in 2003 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.IOException;
+import java.io.InputStream;
+import java.util.Date;
+
+import net.i2p.I2PAppContext;
+import net.i2p.data.DataFormatException;
+import net.i2p.data.DataHelper;
+import net.i2p.util.Log;
+
+/**
+ * Contains the sending router's current time, to sync (and verify sync)
+ *
+ */
+public class DateMessage extends I2NPMessageImpl {
+ private final static Log _log = new Log(DateMessage.class);
+ public final static int MESSAGE_TYPE = 16;
+ private long _now;
+
+ public DateMessage(I2PAppContext context) {
+ super(context);
+ _now = context.clock().now();
+ }
+
+ public long getNow() { return _now; }
+ public void setNow(long now) { _now = now; }
+
+ public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException, IOException {
+ if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
+ int curIndex = offset;
+
+ _now = DataHelper.fromLong(data, curIndex, DataHelper.DATE_LENGTH);
+ }
+
+ /** calculate the message body's length (not including the header and footer */
+ protected int calculateWrittenLength() {
+ return DataHelper.DATE_LENGTH; // now
+ }
+ /** write the message body to the output array, starting at the given index */
+ protected int writeMessageBody(byte out[], int curIndex) throws I2NPMessageException {
+ if (_now <= 0) throw new I2NPMessageException("Not enough data to write out");
+
+ DataHelper.toLong(out, curIndex, DataHelper.DATE_LENGTH, _now);
+ curIndex += DataHelper.DATE_LENGTH;
+ return curIndex;
+ }
+
+ public int getType() { return MESSAGE_TYPE; }
+
+ public int hashCode() {
+ return (int)getNow();
+ }
+
+ public boolean equals(Object object) {
+ if ( (object != null) && (object instanceof DateMessage) ) {
+ DateMessage msg = (DateMessage)object;
+ return msg.getNow() == getNow();
+ } else {
+ return false;
+ }
+ }
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+ buf.append("[DateMessage: ");
+ buf.append("Now: ").append(_now);
+ buf.append("]");
+ return buf.toString();
+ }
+}
diff --git a/router/java/src/net/i2p/data/i2np/TunnelConfigurationSessionKey.java b/router/java/src/net/i2p/data/i2np/TunnelConfigurationSessionKey.java
deleted file mode 100644
index 86aa79328..000000000
--- a/router/java/src/net/i2p/data/i2np/TunnelConfigurationSessionKey.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package net.i2p.data.i2np;
-/*
- * free (adj.): unencumbered; not under the control of others
- * Written by jrandom in 2003 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.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import net.i2p.data.DataFormatException;
-import net.i2p.data.DataHelper;
-import net.i2p.data.DataStructureImpl;
-import net.i2p.data.SessionKey;
-import net.i2p.util.Log;
-
-/**
- * Contains the session key used by the owner/creator of the tunnel to modify
- * its operational settings.
- *
- * @author jrandom
- */
-public class TunnelConfigurationSessionKey extends DataStructureImpl {
- private final static Log _log = new Log(TunnelConfigurationSessionKey.class);
- private SessionKey _key;
-
- public TunnelConfigurationSessionKey() { this(null); }
- public TunnelConfigurationSessionKey(SessionKey key) { setKey(key); }
-
- public SessionKey getKey() { return _key; }
- public void setKey(SessionKey key) { _key= key; }
-
- public void readBytes(InputStream in) throws DataFormatException, IOException {
- _key = new SessionKey();
- _key.readBytes(in);
- }
-
- public void writeBytes(OutputStream out) throws DataFormatException, IOException {
- if (_key == null) throw new DataFormatException("Invalid key");
- _key.writeBytes(out);
- }
-
- public boolean equals(Object obj) {
- if ( (obj == null) || !(obj instanceof TunnelConfigurationSessionKey))
- return false;
- return DataHelper.eq(getKey(), ((TunnelConfigurationSessionKey)obj).getKey());
- }
-
- public int hashCode() {
- if (_key == null) return 0;
- return getKey().hashCode();
- }
-
- public String toString() {
- return "[TunnelConfigurationSessionKey: " + getKey() + "]";
- }
-}
diff --git a/router/java/src/net/i2p/data/i2np/TunnelDataMessage.java b/router/java/src/net/i2p/data/i2np/TunnelDataMessage.java
new file mode 100644
index 000000000..0614086c4
--- /dev/null
+++ b/router/java/src/net/i2p/data/i2np/TunnelDataMessage.java
@@ -0,0 +1,139 @@
+package net.i2p.data.i2np;
+/*
+ * free (adj.): unencumbered; not under the control of others
+ * Written by jrandom in 2003 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.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import net.i2p.I2PAppContext;
+import net.i2p.data.Base64;
+import net.i2p.data.ByteArray;
+import net.i2p.data.DataFormatException;
+import net.i2p.data.DataHelper;
+import net.i2p.data.Hash;
+import net.i2p.data.Signature;
+import net.i2p.data.TunnelId;
+import net.i2p.util.ByteCache;
+import net.i2p.util.Log;
+
+/**
+ * Defines the message sent between routers as part of the tunnel delivery
+ *
+ */
+public class TunnelDataMessage extends I2NPMessageImpl {
+ private Log _log;
+ private TunnelId _tunnelId;
+ private byte[] _data;
+
+ public final static int MESSAGE_TYPE = 18;
+ private static final int DATA_SIZE = 1024;
+ /** if we can't deliver a tunnel message in 10s, fuck it */
+ private static final int EXPIRATION_PERIOD = 10*1000;
+
+ private static final ByteCache _cache = ByteCache.getInstance(512, DATA_SIZE);
+ /**
+ * When true, it means this tunnelDataMessage is being used as part of a tunnel
+ * processing pipeline, where the byte array is acquired during the TunnelDataMessage's
+ * creation (per readMessage), held onto through several transitions (updating and
+ * moving that array between different TunnelDataMessage instances or the fragment
+ * handler's cache, etc), until it is finally released back into the cache when written
+ * to the next peer (or explicitly by the fragment handler's completion).
+ * Setting this to false just increases memory churn
+ */
+ private static final boolean PIPELINED_CACHE = true;
+
+ public TunnelDataMessage(I2PAppContext context) {
+ super(context);
+ _log = context.logManager().getLog(TunnelDataMessage.class);
+ setMessageExpiration(context.clock().now() + EXPIRATION_PERIOD);
+ }
+
+ public TunnelId getTunnelId() { return _tunnelId; }
+ public void setTunnelId(TunnelId id) { _tunnelId = id; }
+
+ public byte[] getData() { return _data; }
+ public void setData(byte data[]) {
+ if ( (data == null) || (data.length <= 0) )
+ throw new IllegalArgumentException("Empty tunnel payload?");
+ _data = data;
+ }
+
+ public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException, IOException {
+ if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
+ int curIndex = offset;
+
+ _tunnelId = new TunnelId(DataHelper.fromLong(data, curIndex, 4));
+ curIndex += 4;
+
+ if (_tunnelId.getTunnelId() <= 0)
+ throw new I2NPMessageException("Invalid tunnel Id " + _tunnelId);
+
+ // we cant cache it in trivial form, as other components (e.g. HopProcessor)
+ // call getData() and use it as the buffer to write with. it is then used
+ // again to pass to the 'receiver', which may even cache it in a FragmentMessage.
+ if (PIPELINED_CACHE)
+ _data = _cache.acquire().getData();
+ else
+ _data = new byte[DATA_SIZE];
+ System.arraycopy(data, curIndex, _data, 0, DATA_SIZE);
+ }
+
+ /** calculate the message body's length (not including the header and footer */
+ protected int calculateWrittenLength() { return 4 + DATA_SIZE; }
+ /** write the message body to the output array, starting at the given index */
+ protected int writeMessageBody(byte out[], int curIndex) throws I2NPMessageException {
+ if ( (_tunnelId == null) || (_data == null) )
+ throw new I2NPMessageException("Not enough data to write out (id=" + _tunnelId + " data=" + _data + ")");
+ if (_data.length <= 0)
+ throw new I2NPMessageException("Not enough data to write out (data.length=" + _data.length + ")");
+
+ DataHelper.toLong(out, curIndex, 4, _tunnelId.getTunnelId());
+ curIndex += 4;
+ System.arraycopy(_data, 0, out, curIndex, DATA_SIZE);
+ curIndex += _data.length;
+ if (PIPELINED_CACHE)
+ _cache.release(new ByteArray(_data));
+ return curIndex;
+ }
+
+ public int getType() { return MESSAGE_TYPE; }
+
+ public int hashCode() {
+ return DataHelper.hashCode(getTunnelId()) +
+ DataHelper.hashCode(_data);
+ }
+
+ public boolean equals(Object object) {
+ if ( (object != null) && (object instanceof TunnelDataMessage) ) {
+ TunnelDataMessage msg = (TunnelDataMessage)object;
+ return DataHelper.eq(getTunnelId(),msg.getTunnelId()) &&
+ DataHelper.eq(getData(),msg.getData());
+ } else {
+ return false;
+ }
+ }
+
+ public byte[] toByteArray() {
+ byte rv[] = super.toByteArray();
+ if (rv == null)
+ throw new RuntimeException("unable to toByteArray(): " + toString());
+ return rv;
+ }
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+ buf.append("[TunnelDataMessage:");
+ buf.append(" MessageId: ").append(getUniqueId());
+ buf.append(" Tunnel ID: ").append(getTunnelId());
+ buf.append("]");
+ return buf.toString();
+ }
+}
diff --git a/router/java/src/net/i2p/data/i2np/TunnelGatewayMessage.java b/router/java/src/net/i2p/data/i2np/TunnelGatewayMessage.java
new file mode 100644
index 000000000..a2554f633
--- /dev/null
+++ b/router/java/src/net/i2p/data/i2np/TunnelGatewayMessage.java
@@ -0,0 +1,137 @@
+package net.i2p.data.i2np;
+/*
+ * free (adj.): unencumbered; not under the control of others
+ * Written by jrandom in 2003 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.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Date;
+
+import net.i2p.I2PAppContext;
+import net.i2p.data.Base64;
+import net.i2p.data.DataFormatException;
+import net.i2p.data.DataHelper;
+import net.i2p.data.Hash;
+import net.i2p.data.Signature;
+import net.i2p.data.TunnelId;
+import net.i2p.util.Log;
+
+/**
+ * Defines the message sent between one tunnel's endpoint and another's gateway.
+ * format: { tunnelId, sizeof(i2npMessage.toByteArray()), i2npMessage.toByteArray() }
+ *
+ */
+public class TunnelGatewayMessage extends I2NPMessageImpl {
+ private Log _log;
+ private TunnelId _tunnelId;
+ private I2NPMessage _msg;
+ private byte _msgData[];
+ private Exception _creator;
+
+ public final static int MESSAGE_TYPE = 19;
+ /** if we can't deliver a tunnel message in 10s, fuck it */
+ private static final int EXPIRATION_PERIOD = 10*1000;
+
+ public TunnelGatewayMessage(I2PAppContext context) {
+ super(context);
+ _log = context.logManager().getLog(TunnelGatewayMessage.class);
+ setMessageExpiration(context.clock().now() + EXPIRATION_PERIOD);
+ //_creator = new Exception("i made this");
+ }
+
+ public TunnelId getTunnelId() { return _tunnelId; }
+ public void setTunnelId(TunnelId id) { _tunnelId = id; }
+
+ public I2NPMessage getMessage() { return _msg; }
+ public void setMessage(I2NPMessage msg) {
+ if (msg == null)
+ throw new IllegalArgumentException("wtf, dont set me to null");
+ _msg = msg;
+ }
+
+ protected int calculateWrittenLength() {
+ synchronized (this) {
+ if (_msgData == null) {
+ _msgData = _msg.toByteArray();
+ _msg = null;
+ }
+ }
+ return _msgData.length + 4 + 2;
+ }
+
+ /** write the message body to the output array, starting at the given index */
+ protected int writeMessageBody(byte out[], int curIndex) throws I2NPMessageException {
+ if ( (_tunnelId == null) || ( (_msg == null) && (_msgData == null) ) ) {
+ _log.log(Log.CRIT, "failing to write out gateway message, created by: ", _creator);
+ throw new I2NPMessageException("Not enough data to write out (id=" + _tunnelId + " data=" + _msg + ")");
+ }
+
+ DataHelper.toLong(out, curIndex, 4, _tunnelId.getTunnelId());
+ curIndex += 4;
+ synchronized (this) {
+ if (_msgData == null) {
+ _msgData = _msg.toByteArray();
+ _msg = null;
+ }
+ }
+ DataHelper.toLong(out, curIndex, 2, _msgData.length);
+ curIndex += 2;
+ System.arraycopy(_msgData, 0, out, curIndex, _msgData.length);
+ curIndex += _msgData.length;
+ return curIndex;
+ }
+
+
+ public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException, IOException {
+ if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
+ int curIndex = offset;
+
+ _tunnelId = new TunnelId(DataHelper.fromLong(data, curIndex, 4));
+ curIndex += 4;
+
+ if (_tunnelId.getTunnelId() <= 0)
+ throw new I2NPMessageException("Invalid tunnel Id " + _tunnelId);
+
+ int size = (int)DataHelper.fromLong(data, curIndex, 2);
+ curIndex += 2;
+ I2NPMessageHandler h = new I2NPMessageHandler(_context);
+ curIndex = h.readMessage(data, curIndex);
+ _msg = h.lastRead();
+ if (_msg == null)
+ throw new I2NPMessageException("wtf, message read has no payload?");
+ }
+
+ public int getType() { return MESSAGE_TYPE; }
+
+ public int hashCode() {
+ return DataHelper.hashCode(getTunnelId()) +
+ DataHelper.hashCode(_msg);
+ }
+
+ public boolean equals(Object object) {
+ if ( (object != null) && (object instanceof TunnelGatewayMessage) ) {
+ TunnelGatewayMessage msg = (TunnelGatewayMessage)object;
+ return DataHelper.eq(getTunnelId(),msg.getTunnelId()) &&
+ DataHelper.eq(_msgData, msg._msgData) &&
+ DataHelper.eq(getMessage(), msg.getMessage());
+ } else {
+ return false;
+ }
+ }
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+ buf.append("[TunnelGatewayMessage:");
+ buf.append(" Tunnel ID: ").append(getTunnelId());
+ buf.append(" Message: ").append(_msg);
+ buf.append("]");
+ return buf.toString();
+ }
+}
diff --git a/router/java/src/net/i2p/data/i2np/TunnelMessage.java b/router/java/src/net/i2p/data/i2np/TunnelMessage.java
deleted file mode 100644
index af33d877d..000000000
--- a/router/java/src/net/i2p/data/i2np/TunnelMessage.java
+++ /dev/null
@@ -1,188 +0,0 @@
-package net.i2p.data.i2np;
-/*
- * free (adj.): unencumbered; not under the control of others
- * Written by jrandom in 2003 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.IOException;
-import java.io.InputStream;
-
-import net.i2p.I2PAppContext;
-import net.i2p.data.DataFormatException;
-import net.i2p.data.DataHelper;
-import net.i2p.data.Hash;
-import net.i2p.data.Signature;
-import net.i2p.data.TunnelId;
-import net.i2p.util.Log;
-
-/**
- * Defines the message sent between routers for tunnel delivery
- *
- * @author jrandom
- */
-public class TunnelMessage extends I2NPMessageImpl {
- private final static Log _log = new Log(TunnelMessage.class);
- public final static int MESSAGE_TYPE = 8;
- private TunnelId _tunnelId;
- private long _size;
- private byte[] _data;
- private TunnelVerificationStructure _verification;
- private byte[] _encryptedInstructions;
-
- private final static int FLAG_INCLUDESTRUCTURE = 0;
- private final static int FLAG_DONT_INCLUDESTRUCTURE = 1;
-
- public TunnelMessage(I2PAppContext context) {
- super(context);
- setTunnelId(null);
- setData(null);
- setVerificationStructure(null);
- setEncryptedDeliveryInstructions(null);
- }
-
- public TunnelId getTunnelId() { return _tunnelId; }
- public void setTunnelId(TunnelId id) {
- _tunnelId = id;
- }
-
- public byte[] getData() { return _data; }
- public void setData(byte data[]) {
- _data = data;
- if ( (data != null) && (_data.length <= 0) )
- throw new IllegalArgumentException("Empty tunnel payload?");
- }
-
- public TunnelVerificationStructure getVerificationStructure() { return _verification; }
- public void setVerificationStructure(TunnelVerificationStructure verification) { _verification = verification; }
-
- public byte[] getEncryptedDeliveryInstructions() { return _encryptedInstructions; }
- public void setEncryptedDeliveryInstructions(byte instructions[]) { _encryptedInstructions = instructions; }
-
- public void readMessage(byte data[], int offset, int dataSize, int type) throws I2NPMessageException, IOException {
- if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message");
- int curIndex = offset;
-
- _tunnelId = new TunnelId(DataHelper.fromLong(data, curIndex, 4));
- curIndex += 4;
-
- if (_tunnelId.getTunnelId() <= 0)
- throw new I2NPMessageException("Invalid tunnel Id " + _tunnelId);
-
- _size = DataHelper.fromLong(data, curIndex, 4);
- curIndex += 4;
-
- if (_size < 0) throw new I2NPMessageException("Invalid size in the structure: " + _size);
- if (_size > 64*1024) throw new I2NPMessageException("Invalid size in the structure: " + _size);
- _data = new byte[(int)_size];
- System.arraycopy(data, curIndex, _data, 0, (int)_size);
- curIndex += _size;
-
- int includeVerification = (int)DataHelper.fromLong(data, curIndex, 1);
- curIndex++;
- if (includeVerification == FLAG_INCLUDESTRUCTURE) {
- byte vHash[] = new byte[Hash.HASH_LENGTH];
- System.arraycopy(data, curIndex, vHash, 0, Hash.HASH_LENGTH);
- curIndex += Hash.HASH_LENGTH;
- byte vSig[] = new byte[Signature.SIGNATURE_BYTES];
- System.arraycopy(data, curIndex, vSig, 0, Signature.SIGNATURE_BYTES);
- curIndex += Signature.SIGNATURE_BYTES;
- _verification = new TunnelVerificationStructure(new Hash(vHash), new Signature(vSig));
-
- int len = (int)DataHelper.fromLong(data, curIndex, 2);
- curIndex += 2;
- if ( (len <= 0) || (len > 4*1024) ) throw new I2NPMessageException("wtf, size of instructions: " + len);
- _encryptedInstructions = new byte[len];
- System.arraycopy(data, curIndex, _encryptedInstructions, 0, len);
- curIndex += len;
- }
- }
-
- /** calculate the message body's length (not including the header and footer */
- protected int calculateWrittenLength() {
- int length = 0;
- length += 4; // tunnelId
- length += 4; // data length
- length += _data.length;
- if ( (_verification == null) || (_encryptedInstructions == null) ) {
- length += 1; // include verification?
- } else {
- length += 1; // include verification?
- length += Hash.HASH_LENGTH + Signature.SIGNATURE_BYTES;
- length += 2; // instructions length
- length += _encryptedInstructions.length;
- }
- return length;
- }
- /** write the message body to the output array, starting at the given index */
- protected int writeMessageBody(byte out[], int curIndex) throws I2NPMessageException {
- if ( (_tunnelId == null) || (_data == null) )
- throw new I2NPMessageException("Not enough data to write out (id=" + _tunnelId + " data=" + _data + ")");
- if (_data.length <= 0)
- throw new I2NPMessageException("Not enough data to write out (data.length=" + _data.length + ")");
-
- byte id[] = DataHelper.toLong(4, _tunnelId.getTunnelId());
- System.arraycopy(id, 0, out, curIndex, 4);
- curIndex += 4;
- byte len[] = DataHelper.toLong(4, _data.length);
- System.arraycopy(len, 0, out, curIndex, 4);
- curIndex += 4;
- System.arraycopy(_data, 0, out, curIndex, _data.length);
- curIndex += _data.length;
- if ( (_verification == null) || (_encryptedInstructions == null) ) {
- byte flag[] = DataHelper.toLong(1, FLAG_DONT_INCLUDESTRUCTURE);
- out[curIndex++] = flag[0];
- } else {
- byte flag[] = DataHelper.toLong(1, FLAG_INCLUDESTRUCTURE);
- out[curIndex++] = flag[0];
- System.arraycopy(_verification.getMessageHash().getData(), 0, out, curIndex, Hash.HASH_LENGTH);
- curIndex += Hash.HASH_LENGTH;
- System.arraycopy(_verification.getAuthorizationSignature().getData(), 0, out, curIndex, Signature.SIGNATURE_BYTES);
- curIndex += Signature.SIGNATURE_BYTES;
- len = DataHelper.toLong(2, _encryptedInstructions.length);
- System.arraycopy(len, 0, out, curIndex, 2);
- curIndex += 2;
- System.arraycopy(_encryptedInstructions, 0, out, curIndex, _encryptedInstructions.length);
- curIndex += _encryptedInstructions.length;
- }
- return curIndex;
- }
-
- public int getType() { return MESSAGE_TYPE; }
-
- public int hashCode() {
- return DataHelper.hashCode(getTunnelId()) +
- DataHelper.hashCode(_data) +
- DataHelper.hashCode(getVerificationStructure()) +
- DataHelper.hashCode(getEncryptedDeliveryInstructions());
- }
-
- public boolean equals(Object object) {
- if ( (object != null) && (object instanceof TunnelMessage) ) {
- TunnelMessage msg = (TunnelMessage)object;
- return DataHelper.eq(getTunnelId(),msg.getTunnelId()) &&
- DataHelper.eq(getVerificationStructure(),msg.getVerificationStructure()) &&
- DataHelper.eq(getData(),msg.getData()) &&
- DataHelper.eq(getEncryptedDeliveryInstructions(), msg.getEncryptedDeliveryInstructions());
- } else {
- return false;
- }
- }
-
- public String toString() {
- StringBuffer buf = new StringBuffer();
- buf.append("[TunnelMessage: ");
- buf.append("\n\tMessageId: ").append(getUniqueId());
- buf.append("\n\tExpiration: ").append(getMessageExpiration());
- buf.append("\n\tTunnel ID: ").append(getTunnelId());
- buf.append("\n\tVerification Structure: ").append(getVerificationStructure());
- buf.append("\n\tEncrypted Instructions: ").append(getEncryptedDeliveryInstructions());
- buf.append("\n\tData size: ").append(getData().length);
- buf.append("]");
- return buf.toString();
- }
-}