0.5 merging

This commit is contained in:
jrandom
2005-02-16 22:37:24 +00:00
committed by zzz
parent 9646ac2911
commit 7ef9ce8cc6
16 changed files with 1139 additions and 1213 deletions

View File

@ -1,45 +0,0 @@
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<title>I2P Router Console - config clients</title>
<link rel="stylesheet" href="default.css" type="text/css" />
</head><body>
<%@include file="nav.jsp" %>
<%@include file="summary.jsp" %>
<jsp:useBean class="net.i2p.router.web.ConfigClientsHelper" id="clientshelper" scope="request" />
<jsp:setProperty name="clientshelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
<div class="main" id="main">
<%@include file="confignav.jsp" %>
<jsp:useBean class="net.i2p.router.web.ConfigClientsHandler" id="formhandler" scope="request" />
<jsp:setProperty name="formhandler" property="*" />
<jsp:setProperty name="formhandler" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
<font color="red"><jsp:getProperty name="formhandler" property="errors" /></font>
<i><jsp:getProperty name="formhandler" property="notices" /></i>
<form action="configclients.jsp" method="POST">
<% 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()+""); %>
<input type="hidden" name="nonce" value="<%=System.getProperty("net.i2p.router.web.ConfigClientsHandler.nonce")%>" />
<input type="hidden" name="action" value="blah" />
<b>Estimated number of clients/destinations:</b>
<jsp:getProperty name="clientshelper" property="clientCountSelectBox" /><br />
<b>Default number of inbound tunnels per client:</b>
<jsp:getProperty name="clientshelper" property="tunnelCountSelectBox" /><br />
<b>Default number of hops per tunnel:</b>
<jsp:getProperty name="clientshelper" property="tunnelDepthSelectBox" /><br />
<b>Hops per outbound tunnel:</b>
<jsp:getProperty name="clientshelper" property="tunnelDepthOutboundSelectBox" /><br />
<hr />
<input type="submit" name="shouldsave" value="Save changes" /> <input type="reset" value="Cancel" />
</form>
</div>
</body>
</html>

View File

@ -0,0 +1,41 @@
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<title>I2P Router Console - config tunnels</title>
<link rel="stylesheet" href="default.css" type="text/css" />
</head><body>
<%@include file="nav.jsp" %>
<%@include file="summary.jsp" %>
<jsp:useBean class="net.i2p.router.web.ConfigTunnelsHelper" id="tunnelshelper" scope="request" />
<jsp:setProperty name="tunnelshelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
<div class="main" id="main">
<%@include file="confignav.jsp" %>
<jsp:useBean class="net.i2p.router.web.ConfigTunnelsHandler" id="formhandler" scope="request" />
<jsp:setProperty name="formhandler" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
<jsp:setProperty name="formhandler" property="shouldsave" value="<%=request.getParameter("shouldsave")%>" />
<jsp:setProperty name="formhandler" property="action" value="<%=request.getParameter("action")%>" />
<jsp:setProperty name="formhandler" property="nonce" value="<%=request.getParameter("nonce")%>" />
<jsp:setProperty name="formhandler" property="settings" value="<%=request.getParameterMap()%>" />
<font color="red"><jsp:getProperty name="formhandler" property="errors" /></font>
<i><jsp:getProperty name="formhandler" property="notices" /></i>
<form action="configtunnels.jsp" method="POST">
<% 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()+""); %>
<input type="hidden" name="nonce" value="<%=System.getProperty("net.i2p.router.web.ConfigTunnelsHandler.nonce")%>" />
<input type="hidden" name="action" value="blah" />
<jsp:getProperty name="tunnelshelper" property="form" />
<hr />
<input type="submit" name="shouldsave" value="Save changes" /> <input type="reset" value="Cancel" />
</form>
</div>
</body>
</html>

View File

@ -0,0 +1,21 @@
<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<title>I2P Router Console - tunnel summary</title>
<link rel="stylesheet" href="default.css" type="text/css" />
</head><body>
<%@include file="nav.jsp" %>
<%@include file="summary.jsp" %>
<div class="main" id="main">
<jsp:useBean class="net.i2p.router.web.TunnelHelper" id="tunnelHelper" scope="request" />
<jsp:setProperty name="tunnelHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
<jsp:setProperty name="tunnelHelper" property="writer" value="<%=out%>" />
<jsp:getProperty name="tunnelHelper" property="tunnelSummary" />
</div>
</body>
</html>

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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));
}
}

View File

@ -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); }
}

View File

@ -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)");
}
}

View File

@ -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</A>
*
* 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);
}
}

View File

@ -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 <A HREF="mailto:jddixon@users.sourceforge.net">Jim Dixon</A>
*
* 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);
}
}

View File

@ -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();
}
}

View File

@ -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() + "]";
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}