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" %> - - - - " /> - - - -
- <% String prev = System.getProperty("net.i2p.router.web.ConfigClientsHandler.nonce"); - if (prev != null) System.setProperty("net.i2p.router.web.ConfigClientsHandler.noncePrev", prev); - System.setProperty("net.i2p.router.web.ConfigClientsHandler.nonce", new java.util.Random().nextLong()+""); %> - " /> - - Estimated number of clients/destinations: -
- Default number of inbound tunnels per client: -
- Default number of hops per tunnel: -
- Hops per outbound tunnel: -
-
- - -
- - - 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" %> + + + " /> + " /> + " /> + " /> + + + + +
+ <% String prev = System.getProperty("net.i2p.router.web.ConfigTunnelsHandler.nonce"); + if (prev != null) System.setProperty("net.i2p.router.web.ConfigTunnelsHandler.noncePrev", prev); + System.setProperty("net.i2p.router.web.ConfigTunnelsHandler.nonce", new java.util.Random().nextLong()+""); %> + " /> + + +
+ + +
+ + + 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(); - } -}