0.5 merging
This commit is contained in:
@ -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>
|
|
41
apps/routerconsole/jsp/configtunnels.jsp
Normal file
41
apps/routerconsole/jsp/configtunnels.jsp
Normal 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>
|
21
apps/routerconsole/jsp/tunnels.jsp
Normal file
21
apps/routerconsole/jsp/tunnels.jsp
Normal 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>
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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));
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
27
core/java/src/net/i2p/util/CachingByteArrayOutputStream.java
Normal file
27
core/java/src/net/i2p/util/CachingByteArrayOutputStream.java
Normal 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); }
|
||||||
|
}
|
225
core/java/src/net/i2p/util/DecayingBloomFilter.java
Normal file
225
core/java/src/net/i2p/util/DecayingBloomFilter.java
Normal 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)");
|
||||||
|
}
|
||||||
|
}
|
224
core/java/src/org/xlattice/crypto/filters/BloomSHA1.java
Normal file
224
core/java/src/org/xlattice/crypto/filters/BloomSHA1.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
245
core/java/src/org/xlattice/crypto/filters/KeySelector.java
Normal file
245
core/java/src/org/xlattice/crypto/filters/KeySelector.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
80
router/java/src/net/i2p/data/i2np/DateMessage.java
Normal file
80
router/java/src/net/i2p/data/i2np/DateMessage.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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() + "]";
|
|
||||||
}
|
|
||||||
}
|
|
139
router/java/src/net/i2p/data/i2np/TunnelDataMessage.java
Normal file
139
router/java/src/net/i2p/data/i2np/TunnelDataMessage.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
137
router/java/src/net/i2p/data/i2np/TunnelGatewayMessage.java
Normal file
137
router/java/src/net/i2p/data/i2np/TunnelGatewayMessage.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user