forked from I2P_Developers/i2p.i2p
Compare commits
140 Commits
i2p_0_4_0_
...
i2p_0_4_1_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98b5252a2d | ||
|
|
8abf42023a | ||
|
|
b792238f3f | ||
|
|
2486e5e75f | ||
|
|
5f113f1610 | ||
|
|
592e9dc3ff | ||
|
|
314316cee0 | ||
|
|
9cf663063e | ||
|
|
9ea9210a4b | ||
|
|
2bf1a94608 | ||
|
|
7a0236ad29 | ||
|
|
4341a0c198 | ||
|
|
8071612c12 | ||
|
|
ea9cc3da04 | ||
|
|
0df588fffb | ||
|
|
a622311dbc | ||
|
|
1c95ac2470 | ||
|
|
6ef22166f9 | ||
|
|
1107e50108 | ||
|
|
c19355a7b2 | ||
|
|
65d415fade | ||
|
|
b37313d3f9 | ||
|
|
58fcbad20a | ||
|
|
b571f331ec | ||
|
|
2547d4b3e7 | ||
|
|
892786bf0c | ||
|
|
0c51f2b583 | ||
|
|
d5607ca195 | ||
|
|
48cdf17a4f | ||
|
|
669a8fae15 | ||
|
|
d592936873 | ||
|
|
87898dd2f1 | ||
|
|
15c227f568 | ||
|
|
8de41acfe1 | ||
|
|
9680effb9f | ||
|
|
40df846e3f | ||
|
|
eee94fbf84 | ||
|
|
813679ba25 | ||
|
|
2b9e16c9c9 | ||
|
|
f9bb7f7cff | ||
|
|
41e9569094 | ||
|
|
336ee07191 | ||
|
|
81e0a145f1 | ||
|
|
a95a968fa8 | ||
|
|
6c08941d8b | ||
|
|
e13a5b3865 | ||
|
|
9011d5604a | ||
|
|
78aa4ca137 | ||
|
|
93111842df | ||
|
|
88693f8adc | ||
|
|
f904b012e9 | ||
|
|
cebe0a151f | ||
|
|
8fffad0891 | ||
|
|
fb1263dad7 | ||
|
|
28c5d6c10d | ||
|
|
e7a6f6836e | ||
|
|
f8ffe016d1 | ||
|
|
ec322f0966 | ||
|
|
0674709fc6 | ||
|
|
d91ac7ef21 | ||
|
|
2f0c3c7baf | ||
|
|
be68407707 | ||
|
|
f799a25aeb | ||
|
|
8329d045f1 | ||
|
|
503b289240 | ||
|
|
35e3bbb862 | ||
|
|
8dc261da79 | ||
|
|
65676f8988 | ||
|
|
730da3aa27 | ||
|
|
ff8674bca9 | ||
|
|
c7cfef3b61 | ||
|
|
32188b1cc0 | ||
|
|
37479d8c0d | ||
|
|
f5c7d6576d | ||
|
|
38c422bbc0 | ||
|
|
39d4e5ea81 | ||
|
|
4191ad1cbf | ||
|
|
29287da37c | ||
|
|
98c780415b | ||
|
|
756af9c699 | ||
|
|
7f9076bb1d | ||
|
|
2404f1ab9a | ||
|
|
64bcfd09ec | ||
|
|
6251d22c6e | ||
|
|
de1b4937a1 | ||
|
|
d092dd79ba | ||
|
|
a3ba968386 | ||
|
|
5ca2b97128 | ||
|
|
c9daad1cfd | ||
|
|
0526d5b53a | ||
|
|
34163fb8e4 | ||
|
|
98d2d661a8 | ||
|
|
d9f0a0fd74 | ||
|
|
d20d043e0f | ||
|
|
ce186e1872 | ||
|
|
a14da92e1d | ||
|
|
2b54d850ea | ||
|
|
a63c1b19fc | ||
|
|
34f74cd6ef | ||
|
|
c0b8e62135 | ||
|
|
ea24166b8e | ||
|
|
178b229d66 | ||
|
|
276493da65 | ||
|
|
e85dadfef2 | ||
|
|
1c70efb350 | ||
|
|
6804a0c564 | ||
|
|
6eb7ecc2d4 | ||
|
|
f4956b06b6 | ||
|
|
9a2f7c2660 | ||
|
|
b6017c558a | ||
|
|
62ed6c6a58 | ||
|
|
24966c812f | ||
|
|
ea8dc2e0af | ||
|
|
010b285e67 | ||
|
|
774231f347 | ||
|
|
ff1dfd8f25 | ||
|
|
2741ac195d | ||
|
|
cf780e296e | ||
|
|
0361246db0 | ||
|
|
63355ecd5b | ||
|
|
0f54ba59fb | ||
|
|
b67b243ebd | ||
|
|
4c29c20613 | ||
|
|
4c2619d948 | ||
|
|
ea5662a4a2 | ||
|
|
7c1ce777a1 | ||
|
|
3bb85f2d61 | ||
|
|
93e36b3113 | ||
|
|
932fb670e3 | ||
|
|
54dce61a95 | ||
|
|
e686c0e0a2 | ||
|
|
05acf32f39 | ||
|
|
67064012c9 | ||
|
|
10e93c3b1b | ||
|
|
5b2ec1cbb5 | ||
|
|
972f701c5c | ||
|
|
51285efbc3 | ||
|
|
e2635705f9 | ||
|
|
7762107543 | ||
|
|
9123ad89c8 |
@@ -274,9 +274,9 @@ public class PeerData {
|
||||
|
||||
_lostRate.addData(numTimedOut, 0);
|
||||
|
||||
_receiveRate.coallesceStats();
|
||||
_sendRate.coallesceStats();
|
||||
_lostRate.coallesceStats();
|
||||
_receiveRate.coalesceStats();
|
||||
_sendRate.coalesceStats();
|
||||
_lostRate.coalesceStats();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Peer data cleaned up " + numTimedOut + " timed out pings and removed " + numDropped
|
||||
@@ -409,4 +409,4 @@ public class PeerData {
|
||||
_wasPonged = pong;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,10 +199,14 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
host = getHostName(destination);
|
||||
if ( (host != null) && ("i2p".equals(host)) ) {
|
||||
int pos2;
|
||||
if ((pos2 = line.indexOf("?")) != -1) {
|
||||
if ((pos2 = request.indexOf("?")) != -1) {
|
||||
// Try to find an address helper in the fragments
|
||||
String fragments = line.substring(pos2 + 1);
|
||||
// and split the request into it's component parts for rebuilding later
|
||||
String fragments = request.substring(pos2 + 1);
|
||||
String uriPath = request.substring(0, pos2);
|
||||
pos2 = fragments.indexOf(" ");
|
||||
String protocolVersion = fragments.substring(pos2 + 1);
|
||||
String urlEncoding = "";
|
||||
fragments = fragments.substring(0, pos2);
|
||||
fragments = fragments + "&";
|
||||
String fragment;
|
||||
@@ -215,8 +219,17 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
if (pos2 >= 0) {
|
||||
addressHelpers.put(destination,fragment.substring(pos2 + 1));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// append each fragment unless it's the address helper
|
||||
if ("".equals(urlEncoding)) {
|
||||
urlEncoding = "?" + fragment;
|
||||
} else {
|
||||
urlEncoding = urlEncoding + "&" + fragment;
|
||||
}
|
||||
}
|
||||
}
|
||||
// reconstruct the request minus the i2paddresshelper GET var
|
||||
request = uriPath + urlEncoding + " " + protocolVersion;
|
||||
}
|
||||
|
||||
String addressHelper = (String) addressHelpers.get(destination);
|
||||
@@ -285,11 +298,17 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
line = "User-Agent: MYOB/6.66 (AN/ON)";
|
||||
} else if (line.startsWith("Referer: ")) {
|
||||
// Shouldn't we be more specific, like accepting in-site referers ?
|
||||
line = "Referer: i2p";
|
||||
//line = "Referer: i2p";
|
||||
line = null;
|
||||
continue; // completely strip the line
|
||||
} else if (line.startsWith("Via: ")) {
|
||||
line = "Via: i2p";
|
||||
//line = "Via: i2p";
|
||||
line = null;
|
||||
continue; // completely strip the line
|
||||
} else if (line.startsWith("From: ")) {
|
||||
line = "From: i2p";
|
||||
//line = "From: i2p";
|
||||
line = null;
|
||||
continue; // completely strip the line
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -148,6 +148,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
|
||||
I2PServerSocket i2pss = sockMgr.getServerSocket();
|
||||
while (true) {
|
||||
I2PSocket i2ps = i2pss.accept();
|
||||
if (i2ps == null) throw new I2PException("I2PServerSocket closed");
|
||||
I2PThread t = new I2PThread(new Handler(i2ps));
|
||||
t.start();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
@@ -375,10 +376,16 @@ public class TunnelController implements Logging {
|
||||
buf.append("Full destination: ");
|
||||
buf.append("<input type=\"text\" size=\"10\" onclick=\"this.select();\" ");
|
||||
buf.append("value=\"").append(dest.toBase64()).append("\" />\n");
|
||||
long val = new Random().nextLong();
|
||||
if (val < 0) val = 0 - val;
|
||||
buf.append("<br />You can <a href=\"http://temp").append(val);
|
||||
buf.append(".i2p/?i2paddresshelper=").append(dest.toBase64()).append("\">view</a>");
|
||||
buf.append(" it in a browser (only when you're using the eepProxy)\n");
|
||||
buf.append("<br />If you are going to share this on IRC, you need to split it up:<br />\n");
|
||||
String str = dest.toBase64();
|
||||
buf.append(str.substring(0, str.length()/2)).append("<br />\n");
|
||||
buf.append(str.substring(str.length()/2)).append("<br />\n");
|
||||
buf.append("You can also post it to <a href=\"http://forum.i2p/viewforum.php?f=16\">Eepsite announcement forum</a><br />");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import java.util.Random;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
* Uuuugly... generate the edit/add forms for the various
|
||||
* Uuuugly code to generate the edit/add forms for the various
|
||||
* I2PTunnel types (httpclient/client/server)
|
||||
*
|
||||
*/
|
||||
@@ -63,7 +63,7 @@ class WebEditPageFormGenerator {
|
||||
addOptions(buf, controller);
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Save\">\n");
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
|
||||
buf.append(" <i>confirm</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
|
||||
buf.append(" <i>confirm removal:</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
|
||||
buf.append("</form>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
@@ -83,7 +83,7 @@ class WebEditPageFormGenerator {
|
||||
addOptions(buf, controller);
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Save\"><br />\n");
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
|
||||
buf.append(" <i>confirm</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
|
||||
buf.append(" <i>confirm removal:</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
|
||||
buf.append("</form>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
@@ -118,7 +118,7 @@ class WebEditPageFormGenerator {
|
||||
addOptions(buf, controller);
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Save\">\n");
|
||||
buf.append("<input type=\"submit\" name=\"action\" value=\"Remove\">\n");
|
||||
buf.append(" <i>confirm</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
|
||||
buf.append(" <i>confirm removal:</i> <input type=\"checkbox\" name=\"removeConfirm\" value=\"true\" />\n");
|
||||
buf.append("</form>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
<jsp:useBean class="net.i2p.i2ptunnel.WebStatusPageHelper" id="helper" scope="request" />
|
||||
<jsp:setProperty name="helper" property="*" />
|
||||
<h2>Messages since last page load:</h2>
|
||||
<b><jsp:getProperty name="helper" property="actionResults" /></b>
|
||||
|
||||
<jsp:getProperty name="helper" property="summaryList" />
|
||||
|
||||
@@ -24,7 +24,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
public static final int MAX_PACKET_SIZE = 1024 * 32;
|
||||
public static final int PACKET_DELAY = 100;
|
||||
|
||||
private I2PSocketManager manager;
|
||||
private I2PSocketManagerImpl manager;
|
||||
private Destination local;
|
||||
private Destination remote;
|
||||
private String localID;
|
||||
@@ -69,7 +69,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
* @param outgoing did we initiate the connection (true) or did we receive it (false)?
|
||||
* @param localID what is our half of the socket ID?
|
||||
*/
|
||||
public I2PSocketImpl(Destination peer, I2PSocketManager mgr, boolean outgoing, String localID) {
|
||||
public I2PSocketImpl(Destination peer, I2PSocketManagerImpl mgr, boolean outgoing, String localID) {
|
||||
this.outgoing = outgoing;
|
||||
manager = mgr;
|
||||
remote = peer;
|
||||
@@ -157,7 +157,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("TIMING: RemoteID set to "
|
||||
+ I2PSocketManager.getReadableForm(remoteID) + " for "
|
||||
+ I2PSocketManagerImpl.getReadableForm(remoteID) + " for "
|
||||
+ this.hashCode());
|
||||
}
|
||||
return remoteID;
|
||||
@@ -234,7 +234,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the socket from the I2P side, e. g. by a close packet.
|
||||
* Close the socket from the I2P side (by a close packet)
|
||||
*/
|
||||
protected void internalClose() {
|
||||
synchronized (flagLock) {
|
||||
@@ -249,9 +249,9 @@ class I2PSocketImpl implements I2PSocket {
|
||||
|
||||
private byte getMask(int add) {
|
||||
if (outgoing)
|
||||
return (byte)(I2PSocketManager.DATA_IN + (byte)add);
|
||||
return (byte)(I2PSocketManagerImpl.DATA_IN + (byte)add);
|
||||
else
|
||||
return (byte)(I2PSocketManager.DATA_OUT + (byte)add);
|
||||
return (byte)(I2PSocketManagerImpl.DATA_OUT + (byte)add);
|
||||
}
|
||||
|
||||
public void setOptions(I2PSocketOptions options) {
|
||||
@@ -614,7 +614,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
_log.info(getPrefix() + ":" + Thread.currentThread().getName()
|
||||
+ "Sending close packet: (we started? " + outgoing
|
||||
+ ") after reading " + _bytesRead + " and writing " + _bytesWritten);
|
||||
byte[] packet = I2PSocketManager.makePacket(getMask(0x02), remoteID, new byte[0]);
|
||||
byte[] packet = I2PSocketManagerImpl.makePacket(getMask(0x02), remoteID, new byte[0]);
|
||||
boolean sent = manager.getSession().sendMessage(remote, packet);
|
||||
if (!sent) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -645,7 +645,7 @@ class I2PSocketImpl implements I2PSocket {
|
||||
_log.error(getPrefix() + "NULL REMOTEID");
|
||||
return false;
|
||||
}
|
||||
byte[] packet = I2PSocketManager.makePacket(getMask(0x00), remoteID, data);
|
||||
byte[] packet = I2PSocketManagerImpl.makePacket(getMask(0x00), remoteID, data);
|
||||
boolean sent;
|
||||
synchronized (flagLock) {
|
||||
if (closed2) return false;
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.net.NoRouteToHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
@@ -34,62 +35,8 @@ import net.i2p.util.Log;
|
||||
* or receive any messages with its .receiveMessage
|
||||
*
|
||||
*/
|
||||
public class I2PSocketManager implements I2PSessionListener {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private I2PSession _session;
|
||||
private I2PServerSocketImpl _serverSocket = null;
|
||||
private Object lock = new Object(); // for locking socket lists
|
||||
private HashMap _outSockets;
|
||||
private HashMap _inSockets;
|
||||
private I2PSocketOptions _defaultOptions;
|
||||
private long _acceptTimeout;
|
||||
private String _name;
|
||||
private static int __managerId = 0;
|
||||
|
||||
public static final short ACK = 0x51;
|
||||
public static final short CLOSE_OUT = 0x52;
|
||||
public static final short DATA_OUT = 0x50;
|
||||
public static final short SYN = 0xA1;
|
||||
public static final short CLOSE_IN = 0xA2;
|
||||
public static final short DATA_IN = 0xA0;
|
||||
public static final short CHAFF = 0xFF;
|
||||
|
||||
/**
|
||||
* How long to wait for the client app to accept() before sending back CLOSE?
|
||||
* This includes the time waiting in the queue. Currently set to 5 seconds.
|
||||
*/
|
||||
private static final long ACCEPT_TIMEOUT_DEFAULT = 5*1000;
|
||||
|
||||
public I2PSocketManager() {
|
||||
this("SocketManager " + (++__managerId));
|
||||
}
|
||||
public I2PSocketManager(String name) {
|
||||
_name = name;
|
||||
_session = null;
|
||||
_inSockets = new HashMap(16);
|
||||
_outSockets = new HashMap(16);
|
||||
_acceptTimeout = ACCEPT_TIMEOUT_DEFAULT;
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(I2PSocketManager.class);
|
||||
_context.statManager().createRateStat("streaming.lifetime", "How long before the socket is closed?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.sent", "How many bytes are sent in the stream?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.received", "How many bytes are received in the stream?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.transferBalance", "How many streams send more than they receive (positive means more sent, negative means more received)?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.synNoAck", "How many times have we sent a SYN but not received an ACK?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.ackSendFailed", "How many times have we tried to send an ACK to a SYN and failed?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.nackSent", "How many times have we refused a SYN with a NACK?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.nackReceived", "How many times have we received a NACK to our SYN?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
}
|
||||
|
||||
public I2PSession getSession() {
|
||||
return _session;
|
||||
}
|
||||
|
||||
public void setSession(I2PSession session) {
|
||||
_session = session;
|
||||
if (session != null) session.setSessionListener(this);
|
||||
}
|
||||
public interface I2PSocketManager {
|
||||
public I2PSession getSession();
|
||||
|
||||
/**
|
||||
* How long should we wait for the client to .accept() a socket before
|
||||
@@ -97,340 +44,11 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
*
|
||||
* @param ms milliseconds to wait, maximum
|
||||
*/
|
||||
public void setAcceptTimeout(long ms) { _acceptTimeout = ms; }
|
||||
public long getAcceptTimeout() { return _acceptTimeout; }
|
||||
|
||||
public void disconnected(I2PSession session) {
|
||||
_log.info(getName() + ": Disconnected from the session");
|
||||
destroySocketManager();
|
||||
}
|
||||
|
||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||
_log.error(getName() + ": Error occurred: [" + message + "]", error);
|
||||
}
|
||||
|
||||
public void messageAvailable(I2PSession session, int msgId, long size) {
|
||||
try {
|
||||
I2PSocketImpl s;
|
||||
byte msg[] = session.receiveMessage(msgId);
|
||||
if (msg.length == 1 && msg[0] == -1) {
|
||||
_log.debug(getName() + ": Ping received");
|
||||
return;
|
||||
}
|
||||
if (msg.length < 4) {
|
||||
_log.error(getName() + ": ==== packet too short ====");
|
||||
return;
|
||||
}
|
||||
int type = msg[0] & 0xff;
|
||||
String id = toString(new byte[] { msg[1], msg[2], msg[3]});
|
||||
byte[] payload = new byte[msg.length - 4];
|
||||
System.arraycopy(msg, 4, payload, 0, payload.length);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Message read: type = [" + Integer.toHexString(type)
|
||||
+ "] id = [" + getReadableForm(id)
|
||||
+ "] payload length: [" + payload.length + "]");
|
||||
switch (type) {
|
||||
case ACK:
|
||||
ackAvailable(id, payload);
|
||||
return;
|
||||
case CLOSE_OUT:
|
||||
disconnectAvailable(id, payload);
|
||||
return;
|
||||
case DATA_OUT:
|
||||
sendOutgoingAvailable(id, payload);
|
||||
return;
|
||||
case SYN:
|
||||
synIncomingAvailable(id, payload, session);
|
||||
return;
|
||||
case CLOSE_IN:
|
||||
disconnectIncoming(id, payload);
|
||||
return;
|
||||
case DATA_IN:
|
||||
sendIncoming(id, payload);
|
||||
case CHAFF:
|
||||
// ignore
|
||||
return;
|
||||
default:
|
||||
handleUnknown(type, id, payload);
|
||||
return;
|
||||
}
|
||||
} catch (I2PException ise) {
|
||||
_log.error(getName() + ": Error processing", ise);
|
||||
} catch (IllegalStateException ise) {
|
||||
_log.debug(getName() + ": Error processing", ise);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received an ACK packet (hopefully, in response to a SYN that we
|
||||
* recently sent out). Notify the associated I2PSocket that we now have
|
||||
* the remote stream ID (which should get things going, since the handshake
|
||||
* is complete).
|
||||
*
|
||||
*/
|
||||
private void ackAvailable(String id, byte payload[]) {
|
||||
long begin = _context.clock().now();
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
if (s == null) {
|
||||
_log.warn(getName() + ": No socket responsible for ACK packet");
|
||||
return;
|
||||
}
|
||||
|
||||
long socketRetrieved = _context.clock().now();
|
||||
|
||||
String remoteId = null;
|
||||
remoteId = s.getRemoteID(false);
|
||||
|
||||
if ( (payload.length == 3) && (remoteId == null) ) {
|
||||
String newID = toString(payload);
|
||||
long beforeSetRemId = _context.clock().now();
|
||||
s.setRemoteID(newID);
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getName() + ": ackAvailable - socket retrieval took "
|
||||
+ (socketRetrieved-begin) + "ms, getRemoteId took "
|
||||
+ (beforeSetRemId-socketRetrieved) + "ms, setRemoteId took "
|
||||
+ (_context.clock().now()-beforeSetRemId) + "ms");
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
// (payload.length != 3 || getRemoteId != null)
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
if (payload.length != 3)
|
||||
_log.warn(getName() + ": Ack packet had " + payload.length + " bytes");
|
||||
else
|
||||
_log.warn(getName() + ": Remote ID already exists? " + remoteId);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getName() + ": invalid ack - socket retrieval took "
|
||||
+ (socketRetrieved-begin) + "ms, overall took "
|
||||
+ (_context.clock().now()-begin) + "ms");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We received a disconnect packet, telling us to tear down the specified
|
||||
* stream.
|
||||
*/
|
||||
private void disconnectAvailable(String id, byte payload[]) {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
_log.debug(getName() + ": *Disconnect outgoing for socket " + s);
|
||||
try {
|
||||
if (s != null) {
|
||||
if (payload.length > 0) {
|
||||
_log.debug(getName() + ": Disconnect packet had "
|
||||
+ payload.length + " bytes");
|
||||
}
|
||||
if (s.getRemoteID(false) == null) {
|
||||
s.setRemoteID(null); // Just to wake up socket
|
||||
return;
|
||||
}
|
||||
s.internalClose();
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} catch (Exception t) {
|
||||
_log.error(getName() + ": Ignoring error on disconnect for socket " + s, t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received data on a stream we created - toss the data onto
|
||||
* the socket for handling.
|
||||
*
|
||||
* @throws IllegalStateException if the socket isn't open or isn't known
|
||||
*/
|
||||
private void sendOutgoingAvailable(String id, byte payload[]) throws IllegalStateException {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
// packet send outgoing
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": *Packet send outgoing [" + payload.length + "] for socket " + s);
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getName() + ": Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received a SYN packet (a request for a new stream). If the client has
|
||||
* said they want incoming sockets (by retrieving the serverSocket), the stream
|
||||
* will be ACKed, but if they have not, they'll be NACKed)
|
||||
*
|
||||
* @throws DataFormatException if the destination in the SYN was invalid
|
||||
* @throws I2PSessionException if there was an I2P error sending the ACK or NACK
|
||||
*/
|
||||
private void synIncomingAvailable(String id, byte payload[], I2PSession session)
|
||||
throws DataFormatException, I2PSessionException {
|
||||
Destination d = new Destination();
|
||||
d.fromByteArray(payload);
|
||||
|
||||
I2PSocketImpl s = null;
|
||||
boolean acceptConnections = (_serverSocket != null);
|
||||
String newLocalID = null;
|
||||
synchronized (lock) {
|
||||
newLocalID = makeID(_inSockets);
|
||||
if (acceptConnections) {
|
||||
s = new I2PSocketImpl(d, this, false, newLocalID);
|
||||
s.setRemoteID(id);
|
||||
}
|
||||
}
|
||||
_log.debug(getName() + ": *Syn! for socket " + s);
|
||||
|
||||
if (!acceptConnections) {
|
||||
// The app did not instantiate an I2PServerSocket
|
||||
byte[] packet = makePacket((byte) CLOSE_OUT, id, toBytes(newLocalID));
|
||||
boolean replySentOk = false;
|
||||
synchronized (_session) {
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
}
|
||||
if (!replySentOk) {
|
||||
_log.error(getName() + ": Error sending close to " + d.calculateHash().toBase64()
|
||||
+ " in response to a new con message",
|
||||
new Exception("Failed creation"));
|
||||
}
|
||||
_context.statManager().addRateData("streaming.nackSent", 1, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_serverSocket.addWaitForAccept(s, _acceptTimeout)) {
|
||||
_inSockets.put(newLocalID, s);
|
||||
byte[] packet = makePacket((byte) ACK, id, toBytes(newLocalID));
|
||||
boolean replySentOk = false;
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
if (!replySentOk) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getName() + ": Error sending reply to " + d.calculateHash().toBase64()
|
||||
+ " in response to a new con message for socket " + s,
|
||||
new Exception("Failed creation"));
|
||||
s.internalClose();
|
||||
_context.statManager().addRateData("streaming.ackSendFailed", 1, 1);
|
||||
}
|
||||
} else {
|
||||
// timed out or serverSocket closed
|
||||
byte[] packet = toBytes(" " + id);
|
||||
packet[0] = CLOSE_OUT;
|
||||
boolean nackSent = session.sendMessage(d, packet);
|
||||
if (!nackSent) {
|
||||
_log.warn(getName() + ": Error sending NACK for session creation for socket " + s);
|
||||
}
|
||||
s.internalClose();
|
||||
_context.statManager().addRateData("streaming,nackSent", 1, 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received a disconnect for a socket we didn't initiate, so kill
|
||||
* the socket.
|
||||
*
|
||||
*/
|
||||
private void disconnectIncoming(String id, byte payload[]) {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
if (payload.length == 0 && s != null) {
|
||||
_inSockets.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
_log.debug(getName() + ": *Disconnect incoming for socket " + s);
|
||||
|
||||
try {
|
||||
if (payload.length == 0 && s != null) {
|
||||
s.internalClose();
|
||||
return;
|
||||
} else {
|
||||
if ( (payload.length > 0) && (_log.shouldLog(Log.ERROR)) )
|
||||
_log.error(getName() + ": Disconnect packet had " + payload.length + " bytes");
|
||||
if (s != null)
|
||||
s.internalClose();
|
||||
return;
|
||||
}
|
||||
} catch (Exception t) {
|
||||
_log.error(getName() + ": Ignoring error on disconnect", t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received data on a stream we received - toss the data onto
|
||||
* the socket for handling.
|
||||
*
|
||||
* @throws IllegalStateException if the socket isn't open or isn't known
|
||||
*/
|
||||
private void sendIncoming(String id, byte payload[]) throws IllegalStateException {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": *Packet send incoming [" + payload.length + "] for socket " + s);
|
||||
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
_log.info(getName() + ": Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unknown packet. moo.
|
||||
*
|
||||
*/
|
||||
private void handleUnknown(int type, String id, byte payload[]) {
|
||||
_log.error(getName() + ": \n\n=============== Unknown packet! " + "============"
|
||||
+ "\nType: " + (int) type
|
||||
+ "\nID: " + getReadableForm(id)
|
||||
+ "\nBase64'ed Data: " + Base64.encode(payload)
|
||||
+ "\n\n\n");
|
||||
if (id != null) {
|
||||
synchronized (lock) {
|
||||
_inSockets.remove(id);
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void reportAbuse(I2PSession session, int severity) {
|
||||
_log.error(getName() + ": Abuse reported [" + severity + "]");
|
||||
}
|
||||
|
||||
public void setDefaultOptions(I2PSocketOptions options) {
|
||||
_defaultOptions = options;
|
||||
}
|
||||
|
||||
public I2PSocketOptions getDefaultOptions() {
|
||||
return _defaultOptions;
|
||||
}
|
||||
|
||||
public I2PServerSocket getServerSocket() {
|
||||
if (_serverSocket == null) {
|
||||
_serverSocket = new I2PServerSocketImpl(this);
|
||||
}
|
||||
return _serverSocket;
|
||||
}
|
||||
public void setAcceptTimeout(long ms);
|
||||
public long getAcceptTimeout();
|
||||
public void setDefaultOptions(I2PSocketOptions options);
|
||||
public I2PSocketOptions getDefaultOptions();
|
||||
public I2PServerSocket getServerSocket();
|
||||
|
||||
/**
|
||||
* Create a new connected socket (block until the socket is created)
|
||||
@@ -445,86 +63,7 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
*/
|
||||
public I2PSocket connect(Destination peer, I2PSocketOptions options)
|
||||
throws I2PException, ConnectException,
|
||||
NoRouteToHostException, InterruptedIOException {
|
||||
String localID, lcID;
|
||||
I2PSocketImpl s;
|
||||
synchronized (lock) {
|
||||
localID = makeID(_outSockets);
|
||||
lcID = getReadableForm(localID);
|
||||
s = new I2PSocketImpl(peer, this, true, localID);
|
||||
_outSockets.put(localID, s);
|
||||
}
|
||||
try {
|
||||
ByteArrayOutputStream pubkey = new ByteArrayOutputStream();
|
||||
_session.getMyDestination().writeBytes(pubkey);
|
||||
String remoteID;
|
||||
byte[] packet = makePacket((byte) SYN, localID, pubkey.toByteArray());
|
||||
boolean sent = false;
|
||||
sent = _session.sendMessage(peer, packet);
|
||||
if (!sent) {
|
||||
_log.info(getName() + ": Unable to send & receive ack for SYN packet for socket " + s);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
|
||||
throw new I2PException("Error sending through I2P network");
|
||||
}
|
||||
if (options != null)
|
||||
remoteID = s.getRemoteID(true, options.getConnectTimeout());
|
||||
else
|
||||
remoteID = s.getRemoteID(true, getDefaultOptions().getConnectTimeout());
|
||||
|
||||
if (remoteID == null) {
|
||||
_context.statManager().addRateData("streaming.nackReceived", 1, 1);
|
||||
throw new ConnectException("Connection refused by peer for socket " + s);
|
||||
}
|
||||
if ("".equals(remoteID)) {
|
||||
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
|
||||
throw new NoRouteToHostException("Unable to reach peer for socket " + s);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": TIMING: s given out for remoteID "
|
||||
+ getReadableForm(remoteID) + " for socket " + s);
|
||||
|
||||
return s;
|
||||
} catch (InterruptedIOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error(getName() + ": Timeout waiting for ack from syn for id "
|
||||
+ getReadableForm(lcID) + " for socket " + s, ioe);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
|
||||
throw new InterruptedIOException("Timeout waiting for ack");
|
||||
} catch (ConnectException ex) {
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (NoRouteToHostException ex) {
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (IOException ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error(getName() + ": Error sending syn on id " + getReadableForm(lcID) + " for socket " + s, ex);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
throw new I2PException("Unhandled IOException occurred");
|
||||
} catch (I2PException ex) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getName() + ": Error sending syn on id " + getReadableForm(lcID) + " for socket " + s, ex);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (Exception e) {
|
||||
s.internalClose();
|
||||
_log.error(getName() + ": Unhandled error connecting", e);
|
||||
throw new ConnectException("Unhandled error connecting: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
NoRouteToHostException, InterruptedIOException;
|
||||
|
||||
/**
|
||||
* Create a new connected socket (block until the socket is created)
|
||||
@@ -537,179 +76,30 @@ public class I2PSocketManager implements I2PSessionListener {
|
||||
* @throws I2PException if there is some other I2P-related problem
|
||||
*/
|
||||
public I2PSocket connect(Destination peer) throws I2PException, ConnectException,
|
||||
NoRouteToHostException, InterruptedIOException {
|
||||
return connect(peer, null);
|
||||
}
|
||||
|
||||
NoRouteToHostException, InterruptedIOException;
|
||||
|
||||
/**
|
||||
* Destroy the socket manager, freeing all the associated resources. This
|
||||
* method will block untill all the managed sockets are closed.
|
||||
*
|
||||
*/
|
||||
public void destroySocketManager() {
|
||||
if (_serverSocket != null) {
|
||||
_serverSocket.close();
|
||||
_serverSocket = null;
|
||||
}
|
||||
|
||||
synchronized (lock) {
|
||||
Iterator iter;
|
||||
String id = null;
|
||||
I2PSocketImpl sock;
|
||||
|
||||
iter = _inSockets.keySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
id = (String)iter.next();
|
||||
sock = (I2PSocketImpl)_inSockets.get(id);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Closing inSocket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
sock.internalClose();
|
||||
}
|
||||
|
||||
iter = _outSockets.keySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
id = (String)iter.next();
|
||||
sock = (I2PSocketImpl)_outSockets.get(id);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Closing outSocket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
sock.internalClose();
|
||||
}
|
||||
}
|
||||
|
||||
_log.debug(getName() + ": Waiting for all open sockets to really close...");
|
||||
synchronized (lock) {
|
||||
while ((_inSockets.size() != 0) || (_outSockets.size() != 0)) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
_log.debug(getName() + ": Destroying I2P session...");
|
||||
_session.destroySession();
|
||||
_log.debug(getName() + ": I2P session destroyed");
|
||||
} catch (I2PSessionException e) {
|
||||
_log.error(getName() + ": Error destroying I2P session", e);
|
||||
}
|
||||
}
|
||||
public void destroySocketManager();
|
||||
|
||||
/**
|
||||
* Retrieve a set of currently connected I2PSockets, either initiated locally or remotely.
|
||||
*
|
||||
*/
|
||||
public Set listSockets() {
|
||||
Set sockets = new HashSet(8);
|
||||
synchronized (lock) {
|
||||
sockets.addAll(_inSockets.values());
|
||||
sockets.addAll(_outSockets.values());
|
||||
}
|
||||
return sockets;
|
||||
}
|
||||
public Set listSockets();
|
||||
|
||||
/**
|
||||
* Ping the specified peer, returning true if they replied to the ping within
|
||||
* the timeout specified, false otherwise. This call blocks.
|
||||
*
|
||||
*/
|
||||
public boolean ping(Destination peer, long timeoutMs) {
|
||||
try {
|
||||
return _session.sendMessage(peer, new byte[] { (byte) CHAFF});
|
||||
} catch (I2PException ex) {
|
||||
_log.error(getName() + ": I2PException:", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public boolean ping(Destination peer, long timeoutMs);
|
||||
|
||||
public void removeSocket(I2PSocketImpl sock) {
|
||||
synchronized (lock) {
|
||||
_inSockets.remove(sock.getLocalID());
|
||||
_outSockets.remove(sock.getLocalID());
|
||||
lock.notify();
|
||||
}
|
||||
public String getName();
|
||||
public void setName(String name);
|
||||
|
||||
long now = _context.clock().now();
|
||||
long lifetime = now - sock.getCreatedOn();
|
||||
long timeSinceClose = now - sock.getClosedOn();
|
||||
long sent = sock.getBytesSent();
|
||||
long recv = sock.getBytesReceived();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getName() + ": Removing socket \"" + getReadableForm(sock.getLocalID()) + "\" [" + sock
|
||||
+ ", send: " + sent + ", recv: " + recv
|
||||
+ ", lifetime: " + lifetime + "ms, time since close: " + timeSinceClose + ")]",
|
||||
new Exception("removeSocket called"));
|
||||
}
|
||||
|
||||
_context.statManager().addRateData("streaming.lifetime", lifetime, lifetime);
|
||||
_context.statManager().addRateData("streaming.sent", sent, lifetime);
|
||||
_context.statManager().addRateData("streaming.received", recv, lifetime);
|
||||
|
||||
if (sent > recv) {
|
||||
_context.statManager().addRateData("streaming.transferBalance", 1, lifetime);
|
||||
} else if (recv > sent) {
|
||||
_context.statManager().addRateData("streaming.transferBalance", -1, lifetime);
|
||||
} else {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() { return _name; }
|
||||
public void setName(String name) { _name = name; }
|
||||
|
||||
public static String getReadableForm(String id) {
|
||||
if (id == null) return "(null)";
|
||||
if (id.length() != 3) return "Bogus";
|
||||
return Base64.encode(toBytes(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new part the connection ID that is locally unique
|
||||
*
|
||||
* @param uniqueIn map of already known local IDs so we don't collide. WARNING - NOT THREADSAFE!
|
||||
*/
|
||||
private static String makeID(HashMap uniqueIn) {
|
||||
String newID;
|
||||
do {
|
||||
int id = (int) (Math.random() * 16777215 + 1);
|
||||
byte[] nid = new byte[3];
|
||||
nid[0] = (byte) (id / 65536);
|
||||
nid[1] = (byte) ((id / 256) % 256);
|
||||
nid[2] = (byte) (id % 256);
|
||||
newID = toString(nid);
|
||||
} while (uniqueIn.get(newID) != null);
|
||||
return newID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new packet of the given type for the specified connection containing
|
||||
* the given payload
|
||||
*/
|
||||
public static byte[] makePacket(byte type, String id, byte[] payload) {
|
||||
byte[] packet = new byte[payload.length + 4];
|
||||
packet[0] = type;
|
||||
byte[] temp = toBytes(id);
|
||||
if (temp.length != 3) throw new RuntimeException("Incorrect ID length: " + temp.length);
|
||||
System.arraycopy(temp, 0, packet, 1, 3);
|
||||
System.arraycopy(payload, 0, packet, 4, payload.length);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static final String toString(byte data[]) {
|
||||
try {
|
||||
return new String(data, "ISO-8859-1");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
|
||||
}
|
||||
}
|
||||
|
||||
private static final byte[] toBytes(String str) {
|
||||
try {
|
||||
return str.getBytes("ISO-8859-1");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
|
||||
}
|
||||
}
|
||||
public void init(I2PAppContext context, I2PSession session, Properties opts, String name);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
@@ -23,6 +25,10 @@ import net.i2p.util.Log;
|
||||
public class I2PSocketManagerFactory {
|
||||
private final static Log _log = new Log(I2PSocketManagerFactory.class);
|
||||
|
||||
public static final String PROP_MANAGER = "i2p.streaming.manager";
|
||||
public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.I2PSocketManagerImpl";
|
||||
//public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.I2PSocketManagerFull";
|
||||
|
||||
/**
|
||||
* Create a socket manager using a brand new destination connected to the
|
||||
* I2CP router on the local machine on the default port (7654).
|
||||
@@ -76,23 +82,71 @@ public class I2PSocketManagerFactory {
|
||||
public static I2PSocketManager createManager(InputStream myPrivateKeyStream, String i2cpHost, int i2cpPort,
|
||||
Properties opts) {
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
|
||||
if (opts == null)
|
||||
opts = new Properties();
|
||||
for (Iterator iter = System.getProperties().keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
if (!opts.containsKey(name))
|
||||
opts.setProperty(name, System.getProperty(name));
|
||||
}
|
||||
boolean oldLib = DEFAULT_MANAGER.equals(opts.getProperty(PROP_MANAGER, DEFAULT_MANAGER));
|
||||
if (oldLib) {
|
||||
// for the old streaming lib
|
||||
opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
|
||||
//opts.setProperty("tunnels.depthInbound", "0");
|
||||
} else {
|
||||
// for new streaming lib:
|
||||
opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_BEST_EFFORT);
|
||||
//p.setProperty("tunnels.depthInbound", "0");
|
||||
}
|
||||
|
||||
opts.setProperty(I2PClient.PROP_TCP_HOST, i2cpHost);
|
||||
opts.setProperty(I2PClient.PROP_TCP_PORT, "" + i2cpPort);
|
||||
|
||||
try {
|
||||
I2PSession session = client.createSession(myPrivateKeyStream, opts);
|
||||
session.connect();
|
||||
return createManager(session);
|
||||
return createManager(session, opts, "manager");
|
||||
} catch (I2PSessionException ise) {
|
||||
_log.error("Error creating session for socket manager", ise);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static I2PSocketManager createManager(I2PSession session) {
|
||||
I2PSocketManager mgr = new I2PSocketManager();
|
||||
mgr.setSession(session);
|
||||
mgr.setDefaultOptions(new I2PSocketOptions());
|
||||
return mgr;
|
||||
private static I2PSocketManager createManager(I2PSession session, Properties opts, String name) {
|
||||
if (false) {
|
||||
I2PSocketManagerImpl mgr = new I2PSocketManagerImpl();
|
||||
mgr.setSession(session);
|
||||
mgr.setDefaultOptions(new I2PSocketOptions());
|
||||
return mgr;
|
||||
} else {
|
||||
String classname = opts.getProperty(PROP_MANAGER, DEFAULT_MANAGER);
|
||||
if (classname != null) {
|
||||
try {
|
||||
Class cls = Class.forName(classname);
|
||||
Object obj = cls.newInstance();
|
||||
if (obj instanceof I2PSocketManager) {
|
||||
I2PSocketManager mgr = (I2PSocketManager)obj;
|
||||
I2PAppContext context = I2PAppContext.getGlobalContext();
|
||||
mgr.init(context, session, opts, name);
|
||||
return mgr;
|
||||
} else {
|
||||
throw new IllegalStateException("Invalid manager class [" + classname + "]");
|
||||
}
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
_log.error("Error loading " + classname, cnfe);
|
||||
throw new IllegalStateException("Invalid manager class [" + classname + "] - not found");
|
||||
} catch (InstantiationException ie) {
|
||||
_log.error("Error loading " + classname, ie);
|
||||
throw new IllegalStateException("Invalid manager class [" + classname + "] - unable to instantiate");
|
||||
} catch (IllegalAccessException iae) {
|
||||
_log.error("Error loading " + classname, iae);
|
||||
throw new IllegalStateException("Invalid manager class [" + classname + "] - illegal access");
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("No manager class specified");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,758 @@
|
||||
/*
|
||||
* licensed under BSD license...
|
||||
* (if you know the proper clause for that, add it ...)
|
||||
*/
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
|
||||
/**
|
||||
* Centralize the coordination and multiplexing of the local client's streaming.
|
||||
* There should be one I2PSocketManager for each I2PSession, and if an application
|
||||
* is sending and receiving data through the streaming library using an
|
||||
* I2PSocketManager, it should not attempt to call I2PSession's setSessionListener
|
||||
* or receive any messages with its .receiveMessage
|
||||
*
|
||||
*/
|
||||
public class I2PSocketManagerImpl implements I2PSocketManager, I2PSessionListener {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private I2PSession _session;
|
||||
private I2PServerSocketImpl _serverSocket = null;
|
||||
private Object lock = new Object(); // for locking socket lists
|
||||
private HashMap _outSockets;
|
||||
private HashMap _inSockets;
|
||||
private I2PSocketOptions _defaultOptions;
|
||||
private long _acceptTimeout;
|
||||
private String _name;
|
||||
private static int __managerId = 0;
|
||||
|
||||
public static final short ACK = 0x51;
|
||||
public static final short CLOSE_OUT = 0x52;
|
||||
public static final short DATA_OUT = 0x50;
|
||||
public static final short SYN = 0xA1;
|
||||
public static final short CLOSE_IN = 0xA2;
|
||||
public static final short DATA_IN = 0xA0;
|
||||
public static final short CHAFF = 0xFF;
|
||||
|
||||
/**
|
||||
* How long to wait for the client app to accept() before sending back CLOSE?
|
||||
* This includes the time waiting in the queue. Currently set to 5 seconds.
|
||||
*/
|
||||
private static final long ACCEPT_TIMEOUT_DEFAULT = 5*1000;
|
||||
|
||||
public I2PSocketManagerImpl() {
|
||||
this("SocketManager " + (++__managerId));
|
||||
}
|
||||
public I2PSocketManagerImpl(String name) {
|
||||
init(I2PAppContext.getGlobalContext(), null, null, name);
|
||||
}
|
||||
|
||||
public void init(I2PAppContext context, I2PSession session, Properties opts, String name) {
|
||||
_name = name;
|
||||
_context = context;
|
||||
_log = _context.logManager().getLog(I2PSocketManager.class);
|
||||
_inSockets = new HashMap(16);
|
||||
_outSockets = new HashMap(16);
|
||||
_acceptTimeout = ACCEPT_TIMEOUT_DEFAULT;
|
||||
setSession(session);
|
||||
setDefaultOptions(new I2PSocketOptions());
|
||||
_context.statManager().createRateStat("streaming.lifetime", "How long before the socket is closed?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.sent", "How many bytes are sent in the stream?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.received", "How many bytes are received in the stream?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.transferBalance", "How many streams send more than they receive (positive means more sent, negative means more received)?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.synNoAck", "How many times have we sent a SYN but not received an ACK?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.ackSendFailed", "How many times have we tried to send an ACK to a SYN and failed?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.nackSent", "How many times have we refused a SYN with a NACK?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
_context.statManager().createRateStat("streaming.nackReceived", "How many times have we received a NACK to our SYN?", "streaming", new long[] { 10*60*1000, 60*60*1000, 24*60*60*1000 });
|
||||
}
|
||||
|
||||
public I2PSession getSession() {
|
||||
return _session;
|
||||
}
|
||||
|
||||
public void setSession(I2PSession session) {
|
||||
_session = session;
|
||||
if (session != null) session.setSessionListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* How long should we wait for the client to .accept() a socket before
|
||||
* sending back a NACK/Close?
|
||||
*
|
||||
* @param ms milliseconds to wait, maximum
|
||||
*/
|
||||
public void setAcceptTimeout(long ms) { _acceptTimeout = ms; }
|
||||
public long getAcceptTimeout() { return _acceptTimeout; }
|
||||
|
||||
public void disconnected(I2PSession session) {
|
||||
_log.info(getName() + ": Disconnected from the session");
|
||||
destroySocketManager();
|
||||
}
|
||||
|
||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||
_log.error(getName() + ": Error occurred: [" + message + "]", error);
|
||||
}
|
||||
|
||||
public void messageAvailable(I2PSession session, int msgId, long size) {
|
||||
try {
|
||||
I2PSocketImpl s;
|
||||
byte msg[] = session.receiveMessage(msgId);
|
||||
if (msg.length == 1 && msg[0] == -1) {
|
||||
_log.debug(getName() + ": Ping received");
|
||||
return;
|
||||
}
|
||||
if (msg.length < 4) {
|
||||
_log.warn(getName() + ": ==== packet too short ====");
|
||||
return;
|
||||
}
|
||||
int type = msg[0] & 0xff;
|
||||
String id = toString(new byte[] { msg[1], msg[2], msg[3]});
|
||||
byte[] payload = new byte[msg.length - 4];
|
||||
System.arraycopy(msg, 4, payload, 0, payload.length);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Message read: type = [" + Integer.toHexString(type)
|
||||
+ "] id = [" + getReadableForm(id)
|
||||
+ "] payload length: [" + payload.length + "]");
|
||||
switch (type) {
|
||||
case ACK:
|
||||
ackAvailable(id, payload);
|
||||
return;
|
||||
case CLOSE_OUT:
|
||||
disconnectAvailable(id, payload);
|
||||
return;
|
||||
case DATA_OUT:
|
||||
sendOutgoingAvailable(id, payload);
|
||||
return;
|
||||
case SYN:
|
||||
synIncomingAvailable(id, payload, session);
|
||||
return;
|
||||
case CLOSE_IN:
|
||||
disconnectIncoming(id, payload);
|
||||
return;
|
||||
case DATA_IN:
|
||||
sendIncoming(id, payload);
|
||||
case CHAFF:
|
||||
// ignore
|
||||
return;
|
||||
default:
|
||||
handleUnknown(type, id, payload);
|
||||
return;
|
||||
}
|
||||
} catch (I2PException ise) {
|
||||
_log.warn(getName() + ": Error processing", ise);
|
||||
} catch (IllegalStateException ise) {
|
||||
_log.debug(getName() + ": Error processing", ise);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received an ACK packet (hopefully, in response to a SYN that we
|
||||
* recently sent out). Notify the associated I2PSocket that we now have
|
||||
* the remote stream ID (which should get things going, since the handshake
|
||||
* is complete).
|
||||
*
|
||||
*/
|
||||
private void ackAvailable(String id, byte payload[]) {
|
||||
long begin = _context.clock().now();
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
if (s == null) {
|
||||
_log.warn(getName() + ": No socket responsible for ACK packet for id " + getReadableForm(id));
|
||||
return;
|
||||
}
|
||||
|
||||
long socketRetrieved = _context.clock().now();
|
||||
|
||||
String remoteId = null;
|
||||
remoteId = s.getRemoteID(false);
|
||||
|
||||
if ( (payload.length == 3) && (remoteId == null) ) {
|
||||
String newID = toString(payload);
|
||||
long beforeSetRemId = _context.clock().now();
|
||||
s.setRemoteID(newID);
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getName() + ": ackAvailable - socket retrieval took "
|
||||
+ (socketRetrieved-begin) + "ms, getRemoteId took "
|
||||
+ (beforeSetRemId-socketRetrieved) + "ms, setRemoteId took "
|
||||
+ (_context.clock().now()-beforeSetRemId) + "ms");
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
// (payload.length != 3 || getRemoteId != null)
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
if (payload.length != 3)
|
||||
_log.warn(getName() + ": Ack packet had " + payload.length + " bytes");
|
||||
else
|
||||
_log.warn(getName() + ": Remote ID already exists? " + remoteId);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getName() + ": invalid ack - socket retrieval took "
|
||||
+ (socketRetrieved-begin) + "ms, overall took "
|
||||
+ (_context.clock().now()-begin) + "ms");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We received a disconnect packet, telling us to tear down the specified
|
||||
* stream.
|
||||
*/
|
||||
private void disconnectAvailable(String id, byte payload[]) {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
_log.debug(getName() + ": *Disconnect outgoing for socket " + s + " on id "
|
||||
+ getReadableForm(id));
|
||||
try {
|
||||
if (s != null) {
|
||||
if (payload.length > 0) {
|
||||
_log.debug(getName() + ": Disconnect packet had "
|
||||
+ payload.length + " bytes");
|
||||
}
|
||||
if (s.getRemoteID(false) == null) {
|
||||
s.setRemoteID(null); // Just to wake up socket
|
||||
return;
|
||||
}
|
||||
s.internalClose();
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} catch (Exception t) {
|
||||
_log.warn(getName() + ": Ignoring error on disconnect for socket " + s, t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received data on a stream we created - toss the data onto
|
||||
* the socket for handling.
|
||||
*
|
||||
* @throws IllegalStateException if the socket isn't open or isn't known
|
||||
*/
|
||||
private void sendOutgoingAvailable(String id, byte payload[]) throws IllegalStateException {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _outSockets.get(id);
|
||||
}
|
||||
|
||||
// packet send outgoing
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": *Packet send outgoing [" + payload.length + "] for socket "
|
||||
+ s + " on id " + getReadableForm(id));
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getName() + ": Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received a SYN packet (a request for a new stream). If the client has
|
||||
* said they want incoming sockets (by retrieving the serverSocket), the stream
|
||||
* will be ACKed, but if they have not, they'll be NACKed)
|
||||
*
|
||||
* @throws DataFormatException if the destination in the SYN was invalid
|
||||
* @throws I2PSessionException if there was an I2P error sending the ACK or NACK
|
||||
*/
|
||||
private void synIncomingAvailable(String id, byte payload[], I2PSession session)
|
||||
throws DataFormatException, I2PSessionException {
|
||||
Destination d = new Destination();
|
||||
d.fromByteArray(payload);
|
||||
|
||||
I2PSocketImpl s = null;
|
||||
boolean acceptConnections = (_serverSocket != null);
|
||||
String newLocalID = null;
|
||||
synchronized (lock) {
|
||||
newLocalID = makeID(_inSockets);
|
||||
if (acceptConnections) {
|
||||
s = new I2PSocketImpl(d, this, false, newLocalID);
|
||||
s.setRemoteID(id);
|
||||
}
|
||||
}
|
||||
_log.debug(getName() + ": *Syn! for socket " + s + " on id " + getReadableForm(newLocalID)
|
||||
+ " from " + d.calculateHash().toBase64().substring(0,6));
|
||||
|
||||
if (!acceptConnections) {
|
||||
// The app did not instantiate an I2PServerSocket
|
||||
byte[] packet = makePacket((byte) CLOSE_OUT, id, toBytes(newLocalID));
|
||||
boolean replySentOk = false;
|
||||
synchronized (_session) {
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
}
|
||||
if (!replySentOk) {
|
||||
_log.warn(getName() + ": Error sending close to " + d.calculateHash().toBase64()
|
||||
+ " in response to a new con message",
|
||||
new Exception("Failed creation"));
|
||||
}
|
||||
_context.statManager().addRateData("streaming.nackSent", 1, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_serverSocket.addWaitForAccept(s, _acceptTimeout)) {
|
||||
_inSockets.put(newLocalID, s);
|
||||
byte[] packet = makePacket((byte) ACK, id, toBytes(newLocalID));
|
||||
boolean replySentOk = false;
|
||||
replySentOk = _session.sendMessage(d, packet);
|
||||
if (!replySentOk) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getName() + ": Error sending reply to " + d.calculateHash().toBase64()
|
||||
+ " in response to a new con message for socket " + s,
|
||||
new Exception("Failed creation"));
|
||||
s.internalClose();
|
||||
_context.statManager().addRateData("streaming.ackSendFailed", 1, 1);
|
||||
}
|
||||
} else {
|
||||
// timed out or serverSocket closed
|
||||
byte[] packet = toBytes(" " + id);
|
||||
packet[0] = CLOSE_OUT;
|
||||
boolean nackSent = session.sendMessage(d, packet);
|
||||
if (!nackSent) {
|
||||
_log.warn(getName() + ": Error sending NACK for session creation for socket " + s);
|
||||
}
|
||||
s.internalClose();
|
||||
_context.statManager().addRateData("streaming,nackSent", 1, 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received a disconnect for a socket we didn't initiate, so kill
|
||||
* the socket.
|
||||
*
|
||||
*/
|
||||
private void disconnectIncoming(String id, byte payload[]) {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
if (payload.length == 0 && s != null) {
|
||||
_inSockets.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
_log.debug(getName() + ": *Disconnect incoming for socket " + s);
|
||||
|
||||
try {
|
||||
if (payload.length == 0 && s != null) {
|
||||
s.internalClose();
|
||||
return;
|
||||
} else {
|
||||
if ( (payload.length > 0) && (_log.shouldLog(Log.ERROR)) )
|
||||
_log.warn(getName() + ": Disconnect packet had " + payload.length + " bytes");
|
||||
if (s != null)
|
||||
s.internalClose();
|
||||
return;
|
||||
}
|
||||
} catch (Exception t) {
|
||||
_log.warn(getName() + ": Ignoring error on disconnect", t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received data on a stream we received - toss the data onto
|
||||
* the socket for handling.
|
||||
*
|
||||
* @throws IllegalStateException if the socket isn't open or isn't known
|
||||
*/
|
||||
private void sendIncoming(String id, byte payload[]) throws IllegalStateException {
|
||||
I2PSocketImpl s = null;
|
||||
synchronized (lock) {
|
||||
s = (I2PSocketImpl) _inSockets.get(id);
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": *Packet send incoming [" + payload.length + "] for socket " + s);
|
||||
|
||||
if (s != null) {
|
||||
s.queueData(payload);
|
||||
return;
|
||||
} else {
|
||||
_log.info(getName() + ": Null socket with data available");
|
||||
throw new IllegalStateException("Null socket with data available");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unknown packet. moo.
|
||||
*
|
||||
*/
|
||||
private void handleUnknown(int type, String id, byte payload[]) {
|
||||
_log.error(getName() + ": \n\n=============== Unknown packet! " + "============"
|
||||
+ "\nType: " + (int) type
|
||||
+ "\nID: " + getReadableForm(id)
|
||||
+ "\nBase64'ed Data: " + Base64.encode(payload)
|
||||
+ "\n\n\n");
|
||||
if (id != null) {
|
||||
synchronized (lock) {
|
||||
_inSockets.remove(id);
|
||||
_outSockets.remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void reportAbuse(I2PSession session, int severity) {
|
||||
_log.error(getName() + ": Abuse reported [" + severity + "]");
|
||||
}
|
||||
|
||||
public void setDefaultOptions(I2PSocketOptions options) {
|
||||
_defaultOptions = options;
|
||||
}
|
||||
|
||||
public I2PSocketOptions getDefaultOptions() {
|
||||
return _defaultOptions;
|
||||
}
|
||||
|
||||
public I2PServerSocket getServerSocket() {
|
||||
if (_serverSocket == null) {
|
||||
_serverSocket = new I2PServerSocketImpl(this);
|
||||
}
|
||||
return _serverSocket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new connected socket (block until the socket is created)
|
||||
*
|
||||
* @param peer Destination to connect to
|
||||
* @param options I2P socket options to be used for connecting
|
||||
*
|
||||
* @throws ConnectException if the peer refuses the connection
|
||||
* @throws NoRouteToHostException if the peer is not found or not reachable
|
||||
* @throws InterruptedIOException if the connection timeouts
|
||||
* @throws I2PException if there is some other I2P-related problem
|
||||
*/
|
||||
public I2PSocket connect(Destination peer, I2PSocketOptions options)
|
||||
throws I2PException, ConnectException,
|
||||
NoRouteToHostException, InterruptedIOException {
|
||||
String localID, lcID;
|
||||
I2PSocketImpl s;
|
||||
synchronized (lock) {
|
||||
localID = makeID(_outSockets);
|
||||
lcID = getReadableForm(localID);
|
||||
s = new I2PSocketImpl(peer, this, true, localID);
|
||||
_outSockets.put(localID, s);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": connect(" + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ ", ...): localID = " + lcID);
|
||||
|
||||
try {
|
||||
ByteArrayOutputStream pubkey = new ByteArrayOutputStream();
|
||||
_session.getMyDestination().writeBytes(pubkey);
|
||||
String remoteID;
|
||||
byte[] packet = makePacket((byte) SYN, localID, pubkey.toByteArray());
|
||||
boolean sent = false;
|
||||
sent = _session.sendMessage(peer, packet);
|
||||
if (!sent) {
|
||||
_log.info(getName() + ": Unable to send & receive ack for SYN packet for socket "
|
||||
+ s + " with localID = " + lcID);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
|
||||
throw new I2PException("Error sending through I2P network");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": syn sent ok to "
|
||||
+ peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " with localID = " + lcID);
|
||||
}
|
||||
if (options != null)
|
||||
remoteID = s.getRemoteID(true, options.getConnectTimeout());
|
||||
else
|
||||
remoteID = s.getRemoteID(true, getDefaultOptions().getConnectTimeout());
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": remoteID received from "
|
||||
+ peer.calculateHash().toBase64().substring(0,6)
|
||||
+ ": " + getReadableForm(remoteID)
|
||||
+ " with localID = " + lcID);
|
||||
|
||||
if (remoteID == null) {
|
||||
_context.statManager().addRateData("streaming.nackReceived", 1, 1);
|
||||
throw new ConnectException("Connection refused by peer for socket " + s);
|
||||
}
|
||||
if ("".equals(remoteID)) {
|
||||
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
|
||||
throw new NoRouteToHostException("Unable to reach peer for socket " + s);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": TIMING: s given out for remoteID "
|
||||
+ getReadableForm(remoteID) + " for socket " + s);
|
||||
|
||||
return s;
|
||||
} catch (InterruptedIOException ioe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getName() + ": Timeout waiting for ack from syn for id "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, ioe);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
_context.statManager().addRateData("streaming.synNoAck", 1, 1);
|
||||
throw new InterruptedIOException("Timeout waiting for ack");
|
||||
} catch (ConnectException ex) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Connection error waiting for ack from syn for id "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, ex);
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (NoRouteToHostException ex) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": No route to host waiting for ack from syn for id "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, ex);
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (IOException ex) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(getName() + ": Error sending syn on id "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, ex);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
throw new I2PException("Unhandled IOException occurred");
|
||||
} catch (I2PException ex) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getName() + ": Error sending syn on id "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, ex);
|
||||
synchronized (lock) {
|
||||
_outSockets.remove(s.getLocalID());
|
||||
}
|
||||
s.internalClose();
|
||||
throw ex;
|
||||
} catch (Exception e) {
|
||||
s.internalClose();
|
||||
_log.warn(getName() + ": Unhandled error connecting on "
|
||||
+ lcID + " to " + peer.calculateHash().toBase64().substring(0,6)
|
||||
+ " for socket " + s, e);
|
||||
throw new ConnectException("Unhandled error connecting: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new connected socket (block until the socket is created)
|
||||
*
|
||||
* @param peer Destination to connect to
|
||||
*
|
||||
* @throws ConnectException if the peer refuses the connection
|
||||
* @throws NoRouteToHostException if the peer is not found or not reachable
|
||||
* @throws InterruptedIOException if the connection timeouts
|
||||
* @throws I2PException if there is some other I2P-related problem
|
||||
*/
|
||||
public I2PSocket connect(Destination peer) throws I2PException, ConnectException,
|
||||
NoRouteToHostException, InterruptedIOException {
|
||||
return connect(peer, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the socket manager, freeing all the associated resources. This
|
||||
* method will block untill all the managed sockets are closed.
|
||||
*
|
||||
*/
|
||||
public void destroySocketManager() {
|
||||
if (_serverSocket != null) {
|
||||
_serverSocket.close();
|
||||
_serverSocket = null;
|
||||
}
|
||||
|
||||
synchronized (lock) {
|
||||
Iterator iter;
|
||||
String id = null;
|
||||
I2PSocketImpl sock;
|
||||
|
||||
iter = _inSockets.keySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
id = (String)iter.next();
|
||||
sock = (I2PSocketImpl)_inSockets.get(id);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Closing inSocket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
sock.internalClose();
|
||||
}
|
||||
|
||||
iter = _outSockets.keySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
id = (String)iter.next();
|
||||
sock = (I2PSocketImpl)_outSockets.get(id);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getName() + ": Closing outSocket \""
|
||||
+ getReadableForm(sock.getLocalID()) + "\"");
|
||||
sock.internalClose();
|
||||
}
|
||||
}
|
||||
|
||||
_log.debug(getName() + ": Waiting for all open sockets to really close...");
|
||||
synchronized (lock) {
|
||||
while ((_inSockets.size() != 0) || (_outSockets.size() != 0)) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
_log.debug(getName() + ": Destroying I2P session...");
|
||||
_session.destroySession();
|
||||
_log.debug(getName() + ": I2P session destroyed");
|
||||
} catch (I2PSessionException e) {
|
||||
_log.warn(getName() + ": Error destroying I2P session", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of currently connected I2PSockets, either initiated locally or remotely.
|
||||
*
|
||||
*/
|
||||
public Set listSockets() {
|
||||
Set sockets = new HashSet(8);
|
||||
synchronized (lock) {
|
||||
sockets.addAll(_inSockets.values());
|
||||
sockets.addAll(_outSockets.values());
|
||||
}
|
||||
return sockets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ping the specified peer, returning true if they replied to the ping within
|
||||
* the timeout specified, false otherwise. This call blocks.
|
||||
*
|
||||
*/
|
||||
public boolean ping(Destination peer, long timeoutMs) {
|
||||
try {
|
||||
return _session.sendMessage(peer, new byte[] { (byte) CHAFF});
|
||||
} catch (I2PException ex) {
|
||||
_log.warn(getName() + ": I2PException:", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void removeSocket(I2PSocketImpl sock) {
|
||||
String localId = sock.getLocalID();
|
||||
boolean removed = false;
|
||||
synchronized (lock) {
|
||||
removed = (null != _inSockets.remove(localId));
|
||||
removed = removed || (null != _outSockets.remove(localId));
|
||||
lock.notify();
|
||||
}
|
||||
|
||||
long now = _context.clock().now();
|
||||
long lifetime = now - sock.getCreatedOn();
|
||||
long timeSinceClose = now - sock.getClosedOn();
|
||||
long sent = sock.getBytesSent();
|
||||
long recv = sock.getBytesReceived();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(getName() + ": Removing socket \"" + getReadableForm(localId) + "\" [" + sock
|
||||
+ ", send: " + sent + ", recv: " + recv
|
||||
+ ", lifetime: " + lifetime + "ms, time since close: " + timeSinceClose
|
||||
+ " removed? " + removed + ")]",
|
||||
new Exception("removeSocket called"));
|
||||
}
|
||||
|
||||
_context.statManager().addRateData("streaming.lifetime", lifetime, lifetime);
|
||||
_context.statManager().addRateData("streaming.sent", sent, lifetime);
|
||||
_context.statManager().addRateData("streaming.received", recv, lifetime);
|
||||
|
||||
if (sent > recv) {
|
||||
_context.statManager().addRateData("streaming.transferBalance", 1, lifetime);
|
||||
} else if (recv > sent) {
|
||||
_context.statManager().addRateData("streaming.transferBalance", -1, lifetime);
|
||||
} else {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() { return _name; }
|
||||
public void setName(String name) { _name = name; }
|
||||
|
||||
public static String getReadableForm(String id) {
|
||||
if (id == null) return "(null)";
|
||||
if (id.length() != 3) return "Bogus";
|
||||
return Base64.encode(toBytes(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new part the connection ID that is locally unique
|
||||
*
|
||||
* @param uniqueIn map of already known local IDs so we don't collide. WARNING - NOT THREADSAFE!
|
||||
*/
|
||||
private static String makeID(HashMap uniqueIn) {
|
||||
String newID;
|
||||
do {
|
||||
int id = (int) (Math.random() * 16777215 + 1);
|
||||
byte[] nid = new byte[3];
|
||||
nid[0] = (byte) (id / 65536);
|
||||
nid[1] = (byte) ((id / 256) % 256);
|
||||
nid[2] = (byte) (id % 256);
|
||||
newID = toString(nid);
|
||||
} while (uniqueIn.get(newID) != null);
|
||||
return newID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new packet of the given type for the specified connection containing
|
||||
* the given payload
|
||||
*/
|
||||
public static byte[] makePacket(byte type, String id, byte[] payload) {
|
||||
byte[] packet = new byte[payload.length + 4];
|
||||
packet[0] = type;
|
||||
byte[] temp = toBytes(id);
|
||||
if (temp.length != 3) throw new RuntimeException("Incorrect ID length: " + temp.length);
|
||||
System.arraycopy(temp, 0, packet, 1, 3);
|
||||
System.arraycopy(payload, 0, packet, 4, payload.length);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static final String toString(byte data[]) {
|
||||
try {
|
||||
return new String(data, "ISO-8859-1");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
|
||||
}
|
||||
}
|
||||
|
||||
private static final byte[] toBytes(String str) {
|
||||
try {
|
||||
return str.getBytes("ISO-8859-1");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException("WTF! iso-8859-1 isn't supported?");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Define the configuration for streaming and verifying data on the socket.
|
||||
*
|
||||
@@ -19,7 +21,18 @@ public class I2PSocketOptions {
|
||||
_writeTimeout = DEFAULT_WRITE_TIMEOUT;
|
||||
_maxBufferSize = DEFAULT_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
public I2PSocketOptions(I2PSocketOptions opts) {
|
||||
_connectTimeout = opts.getConnectTimeout();
|
||||
_readTimeout = opts.getReadTimeout();
|
||||
_writeTimeout = opts.getWriteTimeout();
|
||||
_maxBufferSize = opts.getMaxBufferSize();
|
||||
}
|
||||
|
||||
public I2PSocketOptions(Properties opts) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* How long we will wait for the ACK from a SYN, in milliseconds.
|
||||
*
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.io.OutputStream;
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
@@ -26,6 +27,8 @@ public class StreamSinkClient {
|
||||
private int _sendSize;
|
||||
private int _writeDelay;
|
||||
private String _peerDestFile;
|
||||
private String _i2cpHost;
|
||||
private int _i2cpPort;
|
||||
|
||||
|
||||
/**
|
||||
@@ -35,6 +38,11 @@ public class StreamSinkClient {
|
||||
* @param serverDestFile file containing the StreamSinkServer's binary Destination
|
||||
*/
|
||||
public StreamSinkClient(int sendSize, int writeDelayMs, String serverDestFile) {
|
||||
this(null, -1, sendSize, writeDelayMs, serverDestFile);
|
||||
}
|
||||
public StreamSinkClient(String i2cpHost, int i2cpPort, int sendSize, int writeDelayMs, String serverDestFile) {
|
||||
_i2cpHost = i2cpHost;
|
||||
_i2cpPort = i2cpPort;
|
||||
_sendSize = sendSize;
|
||||
_writeDelay = writeDelayMs;
|
||||
_peerDestFile = serverDestFile;
|
||||
@@ -46,7 +54,11 @@ public class StreamSinkClient {
|
||||
*
|
||||
*/
|
||||
public void runClient() {
|
||||
I2PSocketManager mgr = I2PSocketManagerFactory.createManager();
|
||||
I2PSocketManager mgr = null;
|
||||
if (_i2cpHost != null)
|
||||
mgr = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, new Properties());
|
||||
else
|
||||
mgr = I2PSocketManagerFactory.createManager();
|
||||
Destination peer = null;
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
@@ -81,9 +93,9 @@ public class StreamSinkClient {
|
||||
try { Thread.sleep(_writeDelay); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
sock.close();
|
||||
long afterSending = System.currentTimeMillis();
|
||||
System.out.println("Sent " + _sendSize + "KB in " + (afterSending-beforeSending) + "ms");
|
||||
sock.close();
|
||||
} catch (InterruptedIOException iie) {
|
||||
_log.error("Timeout connecting to the peer", iie);
|
||||
return;
|
||||
@@ -103,7 +115,7 @@ public class StreamSinkClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire up the client. <code>Usage: StreamSinkClient sendSizeKB writeDelayMs serverDestFile</code> <br />
|
||||
* Fire up the client. <code>Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile</code> <br />
|
||||
* <ul>
|
||||
* <li><b>sendSizeKB</b>: how many KB to send</li>
|
||||
* <li><b>writeDelayMs</b>: how long to wait between each .write (0 for no delay)</li>
|
||||
@@ -111,25 +123,40 @@ public class StreamSinkClient {
|
||||
* </ul>
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
if (args.length != 3) {
|
||||
System.out.println("Usage: StreamSinkClient sendSizeKB writeDelayMs serverDestFile");
|
||||
} else {
|
||||
int sendSizeKB = -1;
|
||||
int writeDelayMs = -1;
|
||||
try {
|
||||
sendSizeKB = Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("Send size invalid [" + args[0] + "]");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
writeDelayMs = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("Write delay ms invalid [" + args[1] + "]");
|
||||
return;
|
||||
}
|
||||
StreamSinkClient client = new StreamSinkClient(sendSizeKB, writeDelayMs, args[2]);
|
||||
client.runClient();
|
||||
StreamSinkClient client = null;
|
||||
int sendSizeKB = -1;
|
||||
int writeDelayMs = -1;
|
||||
|
||||
switch (args.length) {
|
||||
case 3:
|
||||
try {
|
||||
sendSizeKB = Integer.parseInt(args[0]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("Send size invalid [" + args[0] + "]");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
writeDelayMs = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("Write delay ms invalid [" + args[1] + "]");
|
||||
return;
|
||||
}
|
||||
client = new StreamSinkClient(sendSizeKB, writeDelayMs, args[2]);
|
||||
break;
|
||||
case 5:
|
||||
try {
|
||||
int port = Integer.parseInt(args[1]);
|
||||
sendSizeKB = Integer.parseInt(args[2]);
|
||||
writeDelayMs = Integer.parseInt(args[3]);
|
||||
client = new StreamSinkClient(args[0], port, sendSizeKB, writeDelayMs, args[4]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("arg error");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
System.out.println("Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile");
|
||||
}
|
||||
if (client != null)
|
||||
client.runClient();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Simple streaming lib test app that connects to a given destination and sends
|
||||
* the contents of a file, then disconnects. See the {@link #main}
|
||||
*
|
||||
*/
|
||||
public class StreamSinkSend {
|
||||
private Log _log;
|
||||
private String _sendFile;
|
||||
private int _writeDelay;
|
||||
private String _peerDestFile;
|
||||
|
||||
/**
|
||||
* Build the client but don't fire it up.
|
||||
* @param filename file to send
|
||||
* @param writeDelayMs how long to wait between each .write (0 for no delay)
|
||||
* @param serverDestFile file containing the StreamSinkServer's binary Destination
|
||||
*/
|
||||
public StreamSinkSend(String filename, int writeDelayMs, String serverDestFile) {
|
||||
_sendFile = filename;
|
||||
_writeDelay = writeDelayMs;
|
||||
_peerDestFile = serverDestFile;
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(StreamSinkClient.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually connect and run the client - this call blocks until completion.
|
||||
*
|
||||
*/
|
||||
public void runClient() {
|
||||
I2PSocketManager mgr = I2PSocketManagerFactory.createManager();
|
||||
Destination peer = null;
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(_peerDestFile);
|
||||
peer = new Destination();
|
||||
peer.readBytes(fis);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error finding the peer destination to contact in " + _peerDestFile, ioe);
|
||||
return;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Peer destination is not valid in " + _peerDestFile, dfe);
|
||||
return;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
|
||||
System.out.println("Send " + _sendFile + " to " + peer.calculateHash().toBase64());
|
||||
|
||||
try {
|
||||
I2PSocket sock = mgr.connect(peer);
|
||||
byte buf[] = new byte[32*1024];
|
||||
OutputStream out = sock.getOutputStream();
|
||||
long beforeSending = System.currentTimeMillis();
|
||||
fis = new FileInputStream(_sendFile);
|
||||
long size = 0;
|
||||
while (true) {
|
||||
int read = fis.read(buf);
|
||||
if (read < 0)
|
||||
break;
|
||||
out.write(buf, 0, read);
|
||||
size += read;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Wrote " + read);
|
||||
if (_writeDelay > 0) {
|
||||
try { Thread.sleep(_writeDelay); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
fis.close();
|
||||
sock.close();
|
||||
long afterSending = System.currentTimeMillis();
|
||||
System.out.println("Sent " + (size / 1024) + "KB in " + (afterSending-beforeSending) + "ms");
|
||||
} catch (InterruptedIOException iie) {
|
||||
_log.error("Timeout connecting to the peer", iie);
|
||||
return;
|
||||
} catch (NoRouteToHostException nrthe) {
|
||||
_log.error("Unable to connect to the peer", nrthe);
|
||||
return;
|
||||
} catch (ConnectException ce) {
|
||||
_log.error("Connection already dropped", ce);
|
||||
return;
|
||||
} catch (I2PException ie) {
|
||||
_log.error("Error connecting to the peer", ie);
|
||||
return;
|
||||
} catch (IOException ioe) {
|
||||
_log.error("IO error sending", ioe);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire up the client. <code>Usage: StreamSinkClient sendFile writeDelayMs serverDestFile</code> <br />
|
||||
* <ul>
|
||||
* <li><b>sendFile</b>: filename to send</li>
|
||||
* <li><b>writeDelayMs</b>: how long to wait between each .write (0 for no delay)</li>
|
||||
* <li><b>serverDestFile</b>: file containing the StreamSinkServer's binary Destination</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
if (args.length != 3) {
|
||||
System.out.println("Usage: StreamSinkClient sendFile writeDelayMs serverDestFile");
|
||||
} else {
|
||||
int writeDelayMs = -1;
|
||||
try {
|
||||
writeDelayMs = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.err.println("Write delay ms invalid [" + args[1] + "]");
|
||||
return;
|
||||
}
|
||||
StreamSinkSend client = new StreamSinkSend(args[0], writeDelayMs, args[2]);
|
||||
client.runClient();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
@@ -23,6 +24,8 @@ public class StreamSinkServer {
|
||||
private Log _log;
|
||||
private String _sinkDir;
|
||||
private String _destFile;
|
||||
private String _i2cpHost;
|
||||
private int _i2cpPort;
|
||||
|
||||
/**
|
||||
* Create but do not start the streaming server.
|
||||
@@ -31,8 +34,13 @@ public class StreamSinkServer {
|
||||
* @param ourDestFile filename to write our binary destination to
|
||||
*/
|
||||
public StreamSinkServer(String sinkDir, String ourDestFile) {
|
||||
this(sinkDir, ourDestFile, null, -1);
|
||||
}
|
||||
public StreamSinkServer(String sinkDir, String ourDestFile, String i2cpHost, int i2cpPort) {
|
||||
_sinkDir = sinkDir;
|
||||
_destFile = ourDestFile;
|
||||
_i2cpHost = i2cpHost;
|
||||
_i2cpPort = i2cpPort;
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(StreamSinkServer.class);
|
||||
}
|
||||
|
||||
@@ -42,7 +50,11 @@ public class StreamSinkServer {
|
||||
*
|
||||
*/
|
||||
public void runServer() {
|
||||
I2PSocketManager mgr = I2PSocketManagerFactory.createManager();
|
||||
I2PSocketManager mgr = null;
|
||||
if (_i2cpHost != null)
|
||||
mgr = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, new Properties());
|
||||
else
|
||||
mgr = I2PSocketManagerFactory.createManager();
|
||||
Destination dest = mgr.getSession().getMyDestination();
|
||||
System.out.println("Listening for connections on: " + dest.calculateHash().toBase64());
|
||||
FileOutputStream fos = null;
|
||||
@@ -95,6 +107,7 @@ public class StreamSinkServer {
|
||||
sink.mkdirs();
|
||||
File cur = File.createTempFile("clientSink", ".dat", sink);
|
||||
_fos = new FileOutputStream(cur);
|
||||
System.out.println("Writing to " + cur.getAbsolutePath());
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error creating sink", ioe);
|
||||
_fos = null;
|
||||
@@ -109,27 +122,42 @@ public class StreamSinkServer {
|
||||
while ( (read = in.read(buf)) != -1) {
|
||||
_fos.write(buf, 0, read);
|
||||
}
|
||||
_log.error("Got EOF from client socket");
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing the sink", ioe);
|
||||
} finally {
|
||||
if (_fos != null) try { _fos.close(); } catch (IOException ioe) {}
|
||||
if (_sock != null) try { _sock.close(); } catch (IOException ioe) {}
|
||||
_log.error("Client socket closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire up the streaming server. <code>Usage: StreamSinkServer sinkDir ourDestFile</code><br />
|
||||
* Fire up the streaming server. <code>Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile</code><br />
|
||||
* <ul>
|
||||
* <li><b>sinkDir</b>: Directory to store received files in</li>
|
||||
* <li><b>ourDestFile</b>: filename to write our binary destination to</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
if (args.length != 2) {
|
||||
System.out.println("Usage: StreamSinkServer sinkDir ourDestFile");
|
||||
} else {
|
||||
StreamSinkServer server = new StreamSinkServer(args[0], args[1]);
|
||||
server.runServer();
|
||||
StreamSinkServer server = null;
|
||||
switch (args.length) {
|
||||
case 2:
|
||||
server = new StreamSinkServer(args[0], args[1]);
|
||||
break;
|
||||
case 4:
|
||||
try {
|
||||
int port = Integer.parseInt(args[1]);
|
||||
server = new StreamSinkServer(args[2], args[3], args[0], port);
|
||||
} catch (NumberFormatException nfe) {
|
||||
System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile");
|
||||
}
|
||||
if (server != null)
|
||||
server.runServer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,11 +206,11 @@ public class NetMonitor {
|
||||
}
|
||||
|
||||
/** drop all the old summary data */
|
||||
public void coallesceData() {
|
||||
public void coalesceData() {
|
||||
synchronized (_peerSummaries) {
|
||||
for (Iterator iter = _peerSummaries.values().iterator(); iter.hasNext(); ) {
|
||||
PeerSummary summary = (PeerSummary)iter.next();
|
||||
summary.coallesceData(_summaryDurationHours * 60*60*1000);
|
||||
summary.coalesceData(_summaryDurationHours * 60*60*1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class NetMonitorRunner implements Runnable {
|
||||
long nextExport = now + _monitor.getExportDelay() * 1000;
|
||||
while (_monitor.isRunning()) {
|
||||
now = Clock.getInstance().now();
|
||||
_monitor.coallesceData();
|
||||
_monitor.coalesceData();
|
||||
if (now >= nextHarvest) {
|
||||
runHarvest();
|
||||
nextHarvest = now + _monitor.getHarvestDelay() * 1000;
|
||||
|
||||
@@ -22,7 +22,7 @@ public class PeerSummary {
|
||||
/** statName to a List of PeerStat elements (sorted by sample date, earliest first) */
|
||||
private Map _stats;
|
||||
/** lock on this when accessing stat data */
|
||||
private Object _coallesceLock = new Object();
|
||||
private Object _coalesceLock = new Object();
|
||||
|
||||
public PeerSummary(String peer) {
|
||||
_peer = peer;
|
||||
@@ -38,7 +38,7 @@ public class PeerSummary {
|
||||
* @param val actual data harvested
|
||||
*/
|
||||
public void addData(String stat, String description, String valueDescriptions[], long when, double val[]) {
|
||||
synchronized (_coallesceLock) {
|
||||
synchronized (_coalesceLock) {
|
||||
TreeMap stats = locked_getData(stat);
|
||||
stats.put(new Long(when), new PeerStat(stat, description, valueDescriptions, when, val));
|
||||
}
|
||||
@@ -53,7 +53,7 @@ public class PeerSummary {
|
||||
* @param val actual data harvested
|
||||
*/
|
||||
public void addData(String stat, String description, String valueDescriptions[], long when, long val[]) {
|
||||
synchronized (_coallesceLock) {
|
||||
synchronized (_coalesceLock) {
|
||||
TreeMap stats = locked_getData(stat);
|
||||
stats.put(new Long(when), new PeerStat(stat, description, valueDescriptions, when, val));
|
||||
}
|
||||
@@ -68,7 +68,7 @@ public class PeerSummary {
|
||||
*
|
||||
*/
|
||||
public List getData(String statName) {
|
||||
synchronized (_coallesceLock) {
|
||||
synchronized (_coalesceLock) {
|
||||
return new ArrayList(((TreeMap)_stats.get(statName)).values());
|
||||
}
|
||||
}
|
||||
@@ -78,21 +78,21 @@ public class PeerSummary {
|
||||
*
|
||||
*/
|
||||
public Set getStatNames() {
|
||||
synchronized (_coallesceLock) {
|
||||
synchronized (_coalesceLock) {
|
||||
return new HashSet(_stats.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
/** drop old data points */
|
||||
public void coallesceData(long summaryDurationMs) {
|
||||
public void coalesceData(long summaryDurationMs) {
|
||||
long earliest = Clock.getInstance().now() - summaryDurationMs;
|
||||
synchronized (_coallesceLock) {
|
||||
locked_coallesce(earliest);
|
||||
synchronized (_coalesceLock) {
|
||||
locked_coalesce(earliest);
|
||||
}
|
||||
}
|
||||
|
||||
/** go through all the stats and remove ones from before the given date */
|
||||
private void locked_coallesce(long earliestSampleDate) {
|
||||
private void locked_coalesce(long earliestSampleDate) {
|
||||
if (true) return;
|
||||
for (Iterator iter = _stats.keySet().iterator(); iter.hasNext(); ) {
|
||||
String statName = (String)iter.next();
|
||||
@@ -116,4 +116,4 @@ public class PeerSummary {
|
||||
_stats.put(statName, new TreeMap());
|
||||
return (TreeMap)_stats.get(statName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ class PeerSummaryReader {
|
||||
}
|
||||
if (summary == null)
|
||||
return;
|
||||
summary.coallesceData(monitor.getSummaryDurationHours() * 60*60*1000);
|
||||
summary.coalesceData(monitor.getSummaryDurationHours() * 60*60*1000);
|
||||
monitor.addSummary(summary);
|
||||
}
|
||||
|
||||
@@ -103,4 +103,4 @@ class PeerSummaryReader {
|
||||
return _fmt.parse(when).getTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<pathelement location="../../../core/java/build/i2p.jar" />
|
||||
<pathelement location="../../../router/java/build/router.jar" />
|
||||
<pathelement location="../../jetty/jettylib/org.mortbay.jetty-jdk1.2.jar" />
|
||||
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
|
||||
<pathelement location="../../systray/java/build/systray.jar" />
|
||||
<pathelement location="../../systray/java/lib/systray4j.jar" />
|
||||
<pathelement location="../../../installer/lib/wrapper/win32/wrapper.jar" /> <!-- we dont care if we're not on win32 -->
|
||||
@@ -54,6 +55,7 @@
|
||||
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
|
||||
<pathelement location="../../jetty/jettylib/ant.jar" />
|
||||
<pathelement location="../../systray/java/build/obj" />
|
||||
<pathelement location="../../systray/java/lib/systray4j.jar" /> <!-- some javacs resolve recursively... -->
|
||||
<pathelement location="../../../installer/lib/wrapper/win32/wrapper.jar" /> <!-- we dont care if we're not on win32 -->
|
||||
<pathelement location="build/routerconsole.jar" />
|
||||
</classpath>
|
||||
|
||||
@@ -11,6 +11,7 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
private String _numClients;
|
||||
private String _numTunnels;
|
||||
private String _numHops;
|
||||
private String _numHopsOutbound;
|
||||
private boolean _shouldSave;
|
||||
|
||||
public void ConfigNetHandler() {
|
||||
@@ -36,6 +37,9 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
public void setTunneldepth(String num) {
|
||||
_numHops = (num != null ? num.trim() : null);
|
||||
}
|
||||
public void setTunneldepthoutbound(String num) {
|
||||
_numHopsOutbound = (num != null ? num.trim() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* The user made changes to the network config and wants to save them, so
|
||||
@@ -63,6 +67,12 @@ public class ConfigClientsHandler extends FormHandler {
|
||||
saveRequired = true;
|
||||
}
|
||||
|
||||
if ( (_numHopsOutbound != null) && (_numHopsOutbound.length() > 0) ) {
|
||||
_context.router().setConfigSetting(ClientTunnelSettings.PROP_DEPTH_OUTBOUND, _numHopsOutbound);
|
||||
addFormNotice("Updating default outbound tunnel length to " + _numHopsOutbound);
|
||||
saveRequired = true;
|
||||
}
|
||||
|
||||
if (saveRequired) {
|
||||
boolean saved = _context.router().saveConfig();
|
||||
if (saved)
|
||||
|
||||
@@ -114,4 +114,31 @@ public class ConfigClientsHelper {
|
||||
buf.append("</select>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public String getTunnelDepthOutboundSelectBox() {
|
||||
int count = ClientTunnelSettings.DEFAULT_DEPTH_OUTBOUND;
|
||||
String val = _context.router().getConfigSetting(ClientTunnelSettings.PROP_DEPTH_OUTBOUND);
|
||||
if (val != null) {
|
||||
try {
|
||||
count = Integer.parseInt(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
// ignore, use default from above
|
||||
}
|
||||
}
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
buf.append("<select name=\"tunneldepthoutbound\">\n");
|
||||
for (int i = 0; i < 4; i++) {
|
||||
buf.append("<option value=\"").append(i).append("\" ");
|
||||
if (count == i)
|
||||
buf.append("selected=\"true\" ");
|
||||
buf.append(">").append(i).append("</option>\n");
|
||||
}
|
||||
if (count >= 4) {
|
||||
buf.append("<option value=\"").append(count);
|
||||
buf.append("\" selected>").append(count);
|
||||
buf.append("</option>\n");
|
||||
}
|
||||
buf.append("</select>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,11 +145,16 @@ public class ConfigNetHandler extends FormHandler {
|
||||
}
|
||||
|
||||
int fetched = 0;
|
||||
int errors = 0;
|
||||
for (Iterator iter = urls.iterator(); iter.hasNext(); ) {
|
||||
fetchSeed(seedURL, (String)iter.next());
|
||||
fetched++;
|
||||
try {
|
||||
fetchSeed(seedURL, (String)iter.next());
|
||||
fetched++;
|
||||
} catch (Exception e) {
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
addFormNotice("Reseeded with " + fetched + " peers");
|
||||
addFormNotice("Reseeded with " + fetched + " peers (and " + errors + " failures)");
|
||||
} catch (Throwable t) {
|
||||
_context.logManager().getLog(ConfigNetHandler.class).error("Error reseeding", t);
|
||||
addFormError("Error reseeding (RESEED_EXCEPTION)");
|
||||
|
||||
@@ -4,8 +4,8 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.FileUtil;
|
||||
|
||||
public class ContentHelper {
|
||||
private String _page;
|
||||
@@ -40,14 +40,14 @@ public class ContentHelper {
|
||||
}
|
||||
}
|
||||
public String getContent() {
|
||||
String str = DataHelper.readTextFile(_page, _maxLines);
|
||||
String str = FileUtil.readTextFile(_page, _maxLines);
|
||||
if (str == null)
|
||||
return "";
|
||||
else
|
||||
return str;
|
||||
}
|
||||
public String getTextContent() {
|
||||
String str = DataHelper.readTextFile(_page, _maxLines);
|
||||
String str = FileUtil.readTextFile(_page, _maxLines);
|
||||
if (str == null)
|
||||
return "";
|
||||
else
|
||||
|
||||
@@ -4,8 +4,8 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.FileUtil;
|
||||
|
||||
public class LogsHelper {
|
||||
private RouterContext _context;
|
||||
@@ -28,7 +28,7 @@ public class LogsHelper {
|
||||
public String getLogs() {
|
||||
List msgs = _context.logManager().getBuffer().getMostRecentMessages();
|
||||
StringBuffer buf = new StringBuffer(16*1024);
|
||||
buf.append("<h2>Most recent console messages:</h2><ul>");
|
||||
buf.append("<ul>");
|
||||
buf.append("<code>\n");
|
||||
for (int i = msgs.size(); i > 0; i--) {
|
||||
String msg = (String)msgs.get(i - 1);
|
||||
@@ -42,10 +42,26 @@ public class LogsHelper {
|
||||
}
|
||||
|
||||
public String getServiceLogs() {
|
||||
String str = DataHelper.readTextFile("wrapper.log", 500);
|
||||
String str = FileUtil.readTextFile("wrapper.log", 500);
|
||||
if (str == null)
|
||||
return "";
|
||||
else
|
||||
return "<pre>" + str + "</pre>";
|
||||
}
|
||||
|
||||
public String getConnectionLogs() {
|
||||
List msgs = _context.commSystem().getMostRecentErrorMessages();
|
||||
StringBuffer buf = new StringBuffer(16*1024);
|
||||
buf.append("<ul>");
|
||||
buf.append("<code>\n");
|
||||
for (int i = msgs.size(); i > 0; i--) {
|
||||
String msg = (String)msgs.get(i - 1);
|
||||
buf.append("<li>");
|
||||
buf.append(msg);
|
||||
buf.append("</li>\n");
|
||||
}
|
||||
buf.append("</code></ul>\n");
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,14 @@ package net.i2p.router.web;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
public class NetDbHelper {
|
||||
private RouterContext _context;
|
||||
private Writer _out;
|
||||
/**
|
||||
* Configure this bean to query a particular router context
|
||||
*
|
||||
@@ -23,13 +26,21 @@ public class NetDbHelper {
|
||||
|
||||
public NetDbHelper() {}
|
||||
|
||||
public void setWriter(Writer writer) { _out = writer; }
|
||||
|
||||
public String getNetDbSummary() {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(32*1024);
|
||||
try {
|
||||
_context.netDb().renderStatusHTML(baos);
|
||||
if (_out != null) {
|
||||
_context.netDb().renderStatusHTML(_out);
|
||||
return "";
|
||||
} else {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(32*1024);
|
||||
_context.netDb().renderStatusHTML(new OutputStreamWriter(baos));
|
||||
return new String(baos.toByteArray());
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
return new String(baos.toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
/**
|
||||
* Simple helper to query the appropriate router for data necessary to render
|
||||
* any emergency notices
|
||||
*/
|
||||
public class NoticeHelper {
|
||||
private RouterContext _context;
|
||||
/**
|
||||
* Configure this bean to query a particular router context
|
||||
*
|
||||
* @param contextId begging few characters of the routerHash, or null to pick
|
||||
* the first one we come across.
|
||||
*/
|
||||
public void setContextId(String contextId) {
|
||||
try {
|
||||
_context = ContextHelper.getContext(contextId);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public String getSystemNotice() {
|
||||
if (_context.router().gracefulShutdownInProgress()) {
|
||||
return "Graceful shutdown in "
|
||||
+ DataHelper.formatDuration(_context.router().getShutdownTimeRemaining());
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package net.i2p.router.web;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.router.RouterContext;
|
||||
@@ -9,6 +11,7 @@ import net.i2p.router.admin.StatsGenerator;
|
||||
|
||||
public class OldConsoleHelper {
|
||||
private RouterContext _context;
|
||||
private Writer _out;
|
||||
/**
|
||||
* Configure this bean to query a particular router context
|
||||
*
|
||||
@@ -25,11 +28,20 @@ public class OldConsoleHelper {
|
||||
|
||||
public OldConsoleHelper() {}
|
||||
|
||||
public void setWriter(Writer writer) {
|
||||
_out = writer;
|
||||
}
|
||||
|
||||
public String getConsole() {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(128*1024);
|
||||
_context.router().renderStatusHTML(baos);
|
||||
return baos.toString();
|
||||
if (_out != null) {
|
||||
_context.router().renderStatusHTML(_out);
|
||||
return "";
|
||||
} else {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(128*1024);
|
||||
_context.router().renderStatusHTML(new OutputStreamWriter(baos));
|
||||
return baos.toString();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
return "<b>Error rending the console</b>";
|
||||
}
|
||||
@@ -38,9 +50,14 @@ public class OldConsoleHelper {
|
||||
public String getStats() {
|
||||
StatsGenerator gen = new StatsGenerator(_context);
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(32*1024);
|
||||
gen.generateStatsPage(baos);
|
||||
return baos.toString();
|
||||
if (_out != null) {
|
||||
gen.generateStatsPage(_out);
|
||||
return "";
|
||||
} else {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(32*1024);
|
||||
gen.generateStatsPage(new OutputStreamWriter(baos));
|
||||
return baos.toString();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
return "<b>Error rending the console</b>";
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package net.i2p.router.web;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
@@ -26,7 +27,7 @@ public class ProfilesHelper {
|
||||
public String getProfileSummary() {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(16*1024);
|
||||
try {
|
||||
_context.profileOrganizer().renderStatusHTML(baos);
|
||||
_context.profileOrganizer().renderStatusHTML(new OutputStreamWriter(baos));
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
@@ -36,7 +37,7 @@ public class ProfilesHelper {
|
||||
public String getShitlistSummary() {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(4*1024);
|
||||
try {
|
||||
_context.shitlist().renderStatusHTML(baos);
|
||||
_context.shitlist().renderStatusHTML(new OutputStreamWriter(baos));
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.apps.systray.SysTray;
|
||||
import net.i2p.util.FileUtil;
|
||||
|
||||
import org.mortbay.jetty.Server;
|
||||
import org.mortbay.jetty.servlet.WebApplicationContext;
|
||||
@@ -21,6 +23,10 @@ public class RouterConsoleRunner {
|
||||
private String _listenHost = "127.0.0.1";
|
||||
private String _webAppsDir = "./webapps/";
|
||||
|
||||
static {
|
||||
System.setProperty("org.mortbay.http.Version.paranoid", "true");
|
||||
}
|
||||
|
||||
public RouterConsoleRunner(String args[]) {
|
||||
if (args.length == 3) {
|
||||
_listenPort = args[0].trim();
|
||||
@@ -35,6 +41,14 @@ public class RouterConsoleRunner {
|
||||
}
|
||||
|
||||
public void startConsole() {
|
||||
File workDir = new File("work");
|
||||
boolean workDirRemoved = FileUtil.rmdir(workDir, false);
|
||||
if (!workDirRemoved)
|
||||
System.err.println("ERROR: Unable to remove Jetty temporary work directory");
|
||||
boolean workDirCreated = workDir.mkdirs();
|
||||
if (!workDirCreated)
|
||||
System.err.println("ERROR: Unable to create Jetty temporary work directory");
|
||||
|
||||
_server = new Server();
|
||||
WebApplicationContext contexts[] = null;
|
||||
try {
|
||||
|
||||
@@ -2,6 +2,7 @@ package net.i2p.router.web;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
@@ -297,26 +298,27 @@ public class SummaryHelper {
|
||||
}
|
||||
|
||||
private static String getTransferred(long bytes) {
|
||||
double val = bytes;
|
||||
int scale = 0;
|
||||
if (bytes > 1024*1024*1024) {
|
||||
// gigs transferred
|
||||
scale = 3;
|
||||
bytes /= (1024*1024*1024);
|
||||
val /= (double)(1024*1024*1024);
|
||||
} else if (bytes > 1024*1024) {
|
||||
// megs transferred
|
||||
scale = 2;
|
||||
bytes /= (1024*1024);
|
||||
val /= (double)(1024*1024);
|
||||
} else if (bytes > 1024) {
|
||||
// kbytes transferred
|
||||
scale = 1;
|
||||
bytes /= 1024;
|
||||
val /= (double)1024;
|
||||
} else {
|
||||
scale = 0;
|
||||
}
|
||||
|
||||
DecimalFormat fmt = new DecimalFormat("##0.00");
|
||||
|
||||
String str = fmt.format(bytes);
|
||||
String str = fmt.format(val);
|
||||
switch (scale) {
|
||||
case 1: return str + "KB";
|
||||
case 2: return str + "MB";
|
||||
@@ -333,7 +335,9 @@ public class SummaryHelper {
|
||||
public String getDestinations() {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
|
||||
try {
|
||||
_context.clientManager().renderStatusHTML(baos);
|
||||
OutputStreamWriter osw = new OutputStreamWriter(baos);
|
||||
_context.clientManager().renderStatusHTML(osw);
|
||||
osw.flush();
|
||||
return new String(baos.toByteArray());
|
||||
} catch (IOException ioe) {
|
||||
_context.logManager().getLog(SummaryHelper.class).error("Error rendering client info", ioe);
|
||||
@@ -396,8 +400,7 @@ public class SummaryHelper {
|
||||
if (_context == null)
|
||||
return "0ms";
|
||||
|
||||
Rate delayRate = _context.statManager().getRate("transport.sendProcessingTime").getRate(60*1000);
|
||||
return ((int)delayRate.getAverageValue()) + "ms";
|
||||
return _context.throttle().getMessageDelay() + "ms";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -409,7 +412,6 @@ public class SummaryHelper {
|
||||
if (_context == null)
|
||||
return "0ms";
|
||||
|
||||
Rate lagRate = _context.statManager().getRate("tunnel.testSuccessTime").getRate(10*60*1000);
|
||||
return ((int)lagRate.getAverageValue()) + "ms";
|
||||
return _context.throttle().getTunnelLag() + "ms";
|
||||
}
|
||||
}
|
||||
@@ -35,8 +35,11 @@
|
||||
<input name="port" type="text" size="4" value="<jsp:getProperty name="nethelper" property="port" />" /> <br />
|
||||
<i>The hostname/IP address and TCP port must be reachable from the outside world. If
|
||||
you are behind a firewall or NAT, this means you must poke a hole for this port. If
|
||||
you are using DHCP and do not have a static IP address, you must use a service like
|
||||
<a href="http://dyndns.org/">dyndns</a>. The "guess" functionality makes an HTTP request
|
||||
you are using DHCP and do not have a static IP address, you should either use a service like
|
||||
<a href="http://dyndns.org/">dyndns</a> or leave the hostname blank. If you leave it blank,
|
||||
your router will autodetect the 'correct' IP address by asking a peer (and unconditionally
|
||||
believing them if the address is routable and you don't have any established connections yet).
|
||||
The "guess" functionality makes an HTTP request
|
||||
to <a href="http://www.whatismyip.com/">www.whatismyip.com</a>.</i>
|
||||
<hr />
|
||||
<b>Enable internal time synchronization?</b> <input type="checkbox" <jsp:getProperty name="nethelper" property="enableTimeSyncChecked" /> name="enabletimesync" /><br />
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
<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>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<input type="hidden" name="action" value="blah" />
|
||||
<b>Logging filename:</b>
|
||||
<input type="text" name="logfilename" size="40" value="<jsp:getProperty name="logginghelper" property="logFilePattern" />" /><br />
|
||||
<i>(the symbol '#' will be replaced during log rotation)</i><br />
|
||||
<i>(the symbol '@' will be replaced during log rotation)</i><br />
|
||||
<b>Log record format:</b>
|
||||
<input type="text" name="logformat" size="20" value="<jsp:getProperty name="logginghelper" property="recordPattern" />" /><br />
|
||||
<i>(use 'd' = date, 'c' = class, 't' = thread, 'p' = priority, 'm' = message)</i><br />
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
<h4>Router logs:</h4>
|
||||
<jsp:getProperty name="logsHelper" property="logs" />
|
||||
<hr />
|
||||
<h4>Connection logs:</h4><a name="connectionlogs"> </a>
|
||||
<jsp:getProperty name="logsHelper" property="connectionLogs" />
|
||||
<hr />
|
||||
<h4>Service logs:</h4><a name="servicelogs"> </a>
|
||||
<jsp:getProperty name="logsHelper" property="serviceLogs" />
|
||||
</div>
|
||||
|
||||
@@ -25,3 +25,7 @@
|
||||
<jsp:setProperty name="navhelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
|
||||
<jsp:getProperty name="navhelper" property="clientAppLinks" />
|
||||
</h4>
|
||||
|
||||
<jsp:useBean class="net.i2p.router.web.NoticeHelper" id="noticehelper" scope="request" />
|
||||
<jsp:setProperty name="noticehelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
|
||||
<b><jsp:getProperty name="noticehelper" property="systemNotice" /></b>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<div class="main" id="main">
|
||||
<jsp:useBean class="net.i2p.router.web.NetDbHelper" id="netdbHelper" scope="request" />
|
||||
<jsp:setProperty name="netdbHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
|
||||
<jsp:setProperty name="netdbHelper" property="writer" value="<%=out%>" />
|
||||
<jsp:getProperty name="netdbHelper" property="netDbSummary" />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
<jsp:useBean class="net.i2p.router.web.OldConsoleHelper" id="conhelper" scope="request" />
|
||||
<jsp:setProperty name="conhelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
|
||||
<jsp:setProperty name="conhelper" property="writer" value="<%=out%>" />
|
||||
|
||||
<div class="main" id="main">
|
||||
<jsp:getProperty name="conhelper" property="console" />
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
<jsp:useBean class="net.i2p.router.web.OldConsoleHelper" id="oldhelper" scope="request" />
|
||||
<jsp:setProperty name="oldhelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" />
|
||||
<jsp:setProperty name="oldhelper" property="writer" value="<%=out%>" />
|
||||
|
||||
<div class="main" id="main">
|
||||
<jsp:getProperty name="oldhelper" property="stats" />
|
||||
|
||||
@@ -2,6 +2,8 @@ I need to do these things:
|
||||
|
||||
* SAM raw support
|
||||
* Write an instruction manual
|
||||
* Make dest a dynamic string
|
||||
* Change SAM parser to use a hashmap
|
||||
|
||||
Anyone can help with these things:
|
||||
|
||||
|
||||
@@ -28,6 +28,20 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* I2P-Ping won't compile on Windows because Windows lacks getopt()
|
||||
*/
|
||||
|
||||
/*
|
||||
* Exit values:
|
||||
* 0: Received at least one response from one dest, or help
|
||||
* message was successfully displayed
|
||||
* 1: Received no responses from any dest
|
||||
* 2: Naming lookup failed, or dest unspecified
|
||||
* 3: SAM error
|
||||
*/
|
||||
|
||||
#include <getopt.h> /* needed on Gentoo Linux */
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -58,10 +72,11 @@ sam_sid_t laststream = 0;
|
||||
bool mihi = false;
|
||||
bool bell = false;
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int ch;
|
||||
int count = INT_MAX; /* number of times to ping */
|
||||
int pongcount = -1;
|
||||
char *samhost = "localhost";
|
||||
uint16_t samport = 7656;
|
||||
|
||||
@@ -88,7 +103,7 @@ int main(int argc, char* argv[])
|
||||
quiet = true;
|
||||
break;
|
||||
case 'v': /* version */
|
||||
puts("$Id: i2p-ping.c,v 1.2 2004/07/31 22:20:22 mpc Exp $");
|
||||
puts("$Id: i2p-ping.c,v 1.4 2004/09/22 20:05:40 jrandom Exp $");
|
||||
puts("Copyright (c) 2004, Matthew P. Cashdollar <mpc@innographx.com>");
|
||||
break;
|
||||
case '?':
|
||||
@@ -101,7 +116,7 @@ int main(int argc, char* argv[])
|
||||
argv += optind;
|
||||
if (argc == 0) { /* they forgot to specify a ping target */
|
||||
fprintf(stderr, "Ping who?\n");
|
||||
return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Hook up the callback functions - required by LibSAM */
|
||||
@@ -120,9 +135,10 @@ int main(int argc, char* argv[])
|
||||
if (rc != SAM_OK) {
|
||||
fprintf(stderr, "SAM connection failed: %s\n", sam_strerror(rc));
|
||||
sam_session_free(&session);
|
||||
return 1;
|
||||
return 3;
|
||||
}
|
||||
|
||||
pongcount = 0;
|
||||
for (int j = 0; j < argc; j++) {
|
||||
if (strlen(argv[j]) == 516) {
|
||||
memcpy(dest, argv[j], SAM_PUBKEY_LEN);
|
||||
@@ -144,6 +160,8 @@ int main(int argc, char* argv[])
|
||||
time_t finish = time(0);
|
||||
laststream = 0;
|
||||
if (laststatus == SAM_OK) {
|
||||
pongcount++;
|
||||
|
||||
if (bell)
|
||||
printf("\a"); /* putchar() doesn't work for some reason */
|
||||
if (!mihi)
|
||||
@@ -164,7 +182,7 @@ int main(int argc, char* argv[])
|
||||
|
||||
sam_close(session);
|
||||
sam_session_free(&session);
|
||||
return 0;
|
||||
return pongcount == 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
void usage()
|
||||
@@ -178,7 +196,7 @@ void usage()
|
||||
*/
|
||||
static void closeback(sam_sess_t *session, sam_sid_t stream_id, samerr_t reason)
|
||||
{
|
||||
fprintf(stderr, "Connection closed to stream %d: %s\n", stream_id,
|
||||
fprintf(stderr, "Connection closed to stream %d: %s\n", (int)stream_id,
|
||||
sam_strerror(reason));
|
||||
}
|
||||
|
||||
@@ -207,7 +225,7 @@ static void databack(sam_sess_t *session, sam_sid_t stream_id, void *data,
|
||||
static void diedback(sam_sess_t *session)
|
||||
{
|
||||
fprintf(stderr, "Lost SAM connection!\n");
|
||||
exit(1);
|
||||
exit(3);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -228,7 +246,7 @@ static void namingback(char *name, sam_pubkey_t pubkey, samerr_t result)
|
||||
{
|
||||
if (result != SAM_OK) {
|
||||
fprintf(stderr, "Naming lookup failed: %s\n", sam_strerror(result));
|
||||
exit(1);
|
||||
exit(2);
|
||||
}
|
||||
memcpy(dest, pubkey, SAM_PUBKEY_LEN);
|
||||
gotdest = true;
|
||||
@@ -238,7 +256,7 @@ static void namingback(char *name, sam_pubkey_t pubkey, samerr_t result)
|
||||
* Our connection attempt returned a result
|
||||
*/
|
||||
static void statusback(sam_sess_t *session, sam_sid_t stream_id,
|
||||
samerr_t result)
|
||||
samerr_t result)
|
||||
{
|
||||
laststatus = result;
|
||||
laststream = stream_id;
|
||||
|
||||
60
apps/sam/c/examples/i2p-ping/pinger.sh
Normal file
60
apps/sam/c/examples/i2p-ping/pinger.sh
Normal file
@@ -0,0 +1,60 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright (c) 2004, Matthew P. Cashdollar <mpc@innographx.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name of the author nor the names of any contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
# This script I use to ping all the hosts in the I2P hosts.txt file. You should
|
||||
# edit your hosts.txt and remove all the comments and blanks lines before
|
||||
# running this.
|
||||
#
|
||||
# You can set this up to run daily via cron. It takes a very long time to run
|
||||
# so I recommend starting it in the middle of the night.
|
||||
|
||||
OUTPUT=/tmp/index.html
|
||||
FINALOUT=$HOME/www/index.html
|
||||
|
||||
echo "<html>" > $OUTPUT
|
||||
echo "<head>" >> $OUTPUT
|
||||
echo "<title>I2P Weather Report</title>" >> $OUTPUT
|
||||
echo "</head>" >> $OUTPUT
|
||||
echo "<body>" >> $OUTPUT
|
||||
echo "<h1>I2P Weather Report</h1>" >> $OUTPUT
|
||||
echo "Date: " >> $OUTPUT
|
||||
date -u >> $OUTPUT
|
||||
echo "(refreshed daily)" >> $OUTPUT
|
||||
echo "<p>" >> $OUTPUT
|
||||
echo "If your site isn't listed, that means the ping failed (I2P error)" >> $OUTPUT
|
||||
echo "<p>" >> $OUTPUT
|
||||
echo "<pre>" >> $OUTPUT
|
||||
cut -d"=" -f1 $HOME/i2p/hosts.txt | tr "\n" " " | xargs $HOME/bin/i2p-ping -q -c 1 | grep -v "I2P error" >> $OUTPUT
|
||||
echo "</pre>" >> $OUTPUT
|
||||
echo "<p>" >> $OUTPUT
|
||||
echo "<i>Disclaimer: This only indicates accessibility from my router, not the network in general.</i>" >> $OUTPUT
|
||||
echo "</body>" >> $OUTPUT
|
||||
echo "</html>" >> $OUTPUT
|
||||
cp $OUTPUT $FINALOUT
|
||||
@@ -55,7 +55,7 @@ static void namingback(char *name, sam_pubkey_t pubkey, samerr_t result);
|
||||
bool gotdest = false;
|
||||
sam_pubkey_t dest;
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
/*
|
||||
* The target of our attack is specified on the command line
|
||||
@@ -142,7 +142,7 @@ int main(int argc, char* argv[])
|
||||
* service programs don't need input ;)
|
||||
*/
|
||||
static void dgramback(sam_sess_t *session, sam_pubkey_t dest, void *data,
|
||||
size_t size)
|
||||
size_t size)
|
||||
{
|
||||
puts("Received a datagram (ignored)");
|
||||
free(data);
|
||||
|
||||
@@ -35,6 +35,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
|
||||
@@ -85,8 +85,12 @@ void (*sam_statusback)(sam_sess_t *session, sam_sid_t stream_id,
|
||||
bool sam_close(sam_sess_t *session)
|
||||
{
|
||||
assert(session != NULL);
|
||||
if (!session->connected)
|
||||
if (!session->connected) {
|
||||
#if SAM_WIRETAP
|
||||
SAMLOGS("Connection already closed - sam_close() skipped");
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef WINSOCK
|
||||
if (closesocket(session->sock) == SOCKET_ERROR) {
|
||||
@@ -95,13 +99,19 @@ bool sam_close(sam_sess_t *session)
|
||||
return false;
|
||||
}
|
||||
session->connected = false;
|
||||
if (sam_winsock_cleanup() == SAM_OK)
|
||||
if (sam_winsock_cleanup() == SAM_OK) {
|
||||
#if SAM_WIRETAP
|
||||
SAMLOGS("Connection closed safely");
|
||||
#endif
|
||||
return true;
|
||||
else
|
||||
} else
|
||||
return false;
|
||||
#else
|
||||
if (close(session->sock) == 0) {
|
||||
session->connected = false;
|
||||
#if SAM_WIRETAP
|
||||
SAMLOGS("Connection closed safely");
|
||||
#endif
|
||||
return true;
|
||||
} else {
|
||||
SAMLOG("Failed closing the SAM connection (%s)", strerror(errno));
|
||||
@@ -122,8 +132,7 @@ bool sam_close(sam_sess_t *session)
|
||||
* tunneldepth - length of the I2P tunnels created by this program (longer is
|
||||
* more anonymous, but slower)
|
||||
*
|
||||
* Returns: True on success, false on failure. If true, `session' will be ready
|
||||
* for use.
|
||||
* Returns: SAM error code. If SAM_OK, `session' will be ready for use.
|
||||
*/
|
||||
samerr_t sam_connect(sam_sess_t *session, const char *samhost, uint16_t samport,
|
||||
const char *destname, sam_conn_t style, uint_t tunneldepth)
|
||||
@@ -791,39 +800,6 @@ void sam_sendq_flush(sam_sess_t *session, sam_sid_t stream_id,
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocates memory for the session and sets its default values
|
||||
*
|
||||
* session - pointer to a previously allocated sam_sess_t to initalise, or NULL
|
||||
* if you want memory to be allocated by this function
|
||||
*/
|
||||
sam_sess_t *sam_session_init(sam_sess_t *session)
|
||||
{
|
||||
if (session == NULL) {
|
||||
session = malloc(sizeof(sam_sess_t));
|
||||
if (session == NULL) {
|
||||
SAMLOGS("Out of memory");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
session->connected = false;
|
||||
session->prev_id = 0;
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/*
|
||||
* Frees memory used by the session and sets the pointer to NULL
|
||||
*
|
||||
* session - pointer to a pointer to a sam_sess_t
|
||||
*/
|
||||
void sam_session_free(sam_sess_t **session)
|
||||
{
|
||||
assert(*session != NULL);
|
||||
free(*session);
|
||||
*session = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sends the second SAM handshake command and checks the reply
|
||||
*
|
||||
@@ -880,6 +856,39 @@ static samerr_t sam_session_create(sam_sess_t *session, const char *destname,
|
||||
return SAM_UNKNOWN;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocates memory for the session and sets its default values
|
||||
*
|
||||
* session - pointer to a previously allocated sam_sess_t to initalise, or NULL
|
||||
* if you want memory to be allocated by this function
|
||||
*/
|
||||
sam_sess_t *sam_session_init(sam_sess_t *session)
|
||||
{
|
||||
if (session == NULL) {
|
||||
session = malloc(sizeof(sam_sess_t));
|
||||
if (session == NULL) {
|
||||
SAMLOGS("Out of memory");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
session->connected = false;
|
||||
session->prev_id = 0;
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
/*
|
||||
* Frees memory used by the session and sets the pointer to NULL
|
||||
*
|
||||
* session - pointer to a pointer to a sam_sess_t
|
||||
*/
|
||||
void sam_session_free(sam_sess_t **session)
|
||||
{
|
||||
assert(*session != NULL);
|
||||
free(*session);
|
||||
*session = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Connects to a remote host and returns a connected descriptor
|
||||
*
|
||||
@@ -1120,7 +1129,7 @@ const char *sam_strerror(samerr_t code)
|
||||
case SAM_BAD_STYLE: /* Style must be stream, datagram, or raw */
|
||||
return "Bad connection style";
|
||||
case SAM_BAD_VERSION: /* sam_hello() had an unexpected reply */
|
||||
return "Bad SAM version";
|
||||
return "Not a SAM port, or bad SAM version";
|
||||
case SAM_CALLBACKS_UNSET: /* Some callbacks are still set to NULL */
|
||||
return "Callbacks unset";
|
||||
case SAM_SOCKET_ERROR: /* TCP/IP connection to the SAM host:port
|
||||
|
||||
@@ -46,7 +46,7 @@ public class SAMBridge implements Runnable {
|
||||
* app designated destination name to the base64 of the I2P formatted
|
||||
* destination keys (Destination+PrivateKey+SigningPrivateKey)
|
||||
*/
|
||||
private Map nameToPrivKeys = Collections.synchronizedMap(new HashMap(8));
|
||||
private Map nameToPrivKeys;
|
||||
|
||||
private boolean acceptConnections = true;
|
||||
|
||||
@@ -65,6 +65,7 @@ public class SAMBridge implements Runnable {
|
||||
*/
|
||||
public SAMBridge(String listenHost, int listenPort, Properties i2cpProps, String persistFile) {
|
||||
persistFilename = persistFile;
|
||||
nameToPrivKeys = new HashMap(8);
|
||||
loadKeys();
|
||||
try {
|
||||
if ( (listenHost != null) && !("0.0.0.0".equals(listenHost)) ) {
|
||||
@@ -93,16 +94,18 @@ public class SAMBridge implements Runnable {
|
||||
* @return null if the name does not exist, or if it is improperly formatted
|
||||
*/
|
||||
public Destination getDestination(String name) {
|
||||
String val = (String)nameToPrivKeys.get(name);
|
||||
if (val == null) return null;
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(val);
|
||||
return d;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error retrieving the destination from " + name, dfe);
|
||||
nameToPrivKeys.remove(name);
|
||||
return null;
|
||||
synchronized (nameToPrivKeys) {
|
||||
String val = (String)nameToPrivKeys.get(name);
|
||||
if (val == null) return null;
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(val);
|
||||
return d;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error retrieving the destination from " + name, dfe);
|
||||
nameToPrivKeys.remove(name);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,9 +117,11 @@ public class SAMBridge implements Runnable {
|
||||
* @return null if the name does not exist, else the stream
|
||||
*/
|
||||
public String getKeystream(String name) {
|
||||
String val = (String)nameToPrivKeys.get(name);
|
||||
if (val == null) return null;
|
||||
return val;
|
||||
synchronized (nameToPrivKeys) {
|
||||
String val = (String)nameToPrivKeys.get(name);
|
||||
if (val == null) return null;
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,7 +129,9 @@ public class SAMBridge implements Runnable {
|
||||
*
|
||||
*/
|
||||
public void addKeystream(String name, String stream) {
|
||||
nameToPrivKeys.put(name, stream);
|
||||
synchronized (nameToPrivKeys) {
|
||||
nameToPrivKeys.put(name, stream);
|
||||
}
|
||||
storeKeys();
|
||||
}
|
||||
|
||||
@@ -132,49 +139,52 @@ public class SAMBridge implements Runnable {
|
||||
* Load up the keys from the persistFilename
|
||||
*
|
||||
*/
|
||||
private synchronized void loadKeys() {
|
||||
Map keys = new HashMap(16);
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(persistFilename);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
String line = null;
|
||||
while ( (line = reader.readLine()) != null) {
|
||||
int eq = line.indexOf('=');
|
||||
String name = line.substring(0, eq);
|
||||
String privKeys = line.substring(eq+1);
|
||||
keys.put(name, privKeys);
|
||||
private void loadKeys() {
|
||||
synchronized (nameToPrivKeys) {
|
||||
nameToPrivKeys.clear();
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(persistFilename);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
String line = null;
|
||||
while ( (line = reader.readLine()) != null) {
|
||||
int eq = line.indexOf('=');
|
||||
String name = line.substring(0, eq);
|
||||
String privKeys = line.substring(eq+1);
|
||||
nameToPrivKeys.put(name, privKeys);
|
||||
}
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
_log.warn("Key file does not exist at " + persistFilename);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Unable to read the keys from " + persistFilename, ioe);
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
_log.warn("Key file does not exist at " + persistFilename);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Unable to read the keys from " + persistFilename, ioe);
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
nameToPrivKeys = Collections.synchronizedMap(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the current keys to disk in the location specified on creation
|
||||
*
|
||||
*/
|
||||
private synchronized void storeKeys() {
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(persistFilename);
|
||||
for (Iterator iter = nameToPrivKeys.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
String privKeys = (String)nameToPrivKeys.get(name);
|
||||
out.write(name.getBytes());
|
||||
out.write('=');
|
||||
out.write(privKeys.getBytes());
|
||||
out.write('\n');
|
||||
private void storeKeys() {
|
||||
synchronized (nameToPrivKeys) {
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(persistFilename);
|
||||
for (Iterator iter = nameToPrivKeys.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
String privKeys = (String)nameToPrivKeys.get(name);
|
||||
out.write(name.getBytes());
|
||||
out.write('=');
|
||||
out.write(privKeys.getBytes());
|
||||
out.write('\n');
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing out the SAM keys to " + persistFilename, ioe);
|
||||
} finally {
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing out the SAM keys to " + persistFilename, ioe);
|
||||
} finally {
|
||||
if (out != null) try { out.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -111,9 +111,10 @@ public class SAMUtils {
|
||||
*/
|
||||
public static Properties parseParams(StringTokenizer tok) throws SAMException {
|
||||
int pos, nprops = 0, ntoks = tok.countTokens();
|
||||
String token, param, value;
|
||||
String token, param;
|
||||
Properties props = new Properties();
|
||||
|
||||
StringBuffer value = new StringBuffer();
|
||||
for (int i = 0; i < ntoks; ++i) {
|
||||
token = tok.nextToken();
|
||||
|
||||
@@ -123,9 +124,16 @@ public class SAMUtils {
|
||||
throw new SAMException("Bad formatting for param [" + token + "]");
|
||||
}
|
||||
param = token.substring(0, pos);
|
||||
value = token.substring(pos + 1);
|
||||
value.append(token.substring(pos+1));
|
||||
if (value.charAt(0) == '"') {
|
||||
while ( (i < ntoks) && (value.lastIndexOf("\"") <= 0) ) {
|
||||
value.append(' ').append(tok.nextToken());
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
props.setProperty(param, value);
|
||||
props.setProperty(param, value.toString());
|
||||
value.setLength(0);
|
||||
nprops += 1;
|
||||
}
|
||||
|
||||
@@ -157,4 +165,19 @@ public class SAMUtils {
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
try {
|
||||
test("a=b c=d e=\"f g h\"");
|
||||
test("a=\"b c d\" e=\"f g h\" i=\"j\"");
|
||||
test("a=\"b c d\" e=f i=\"j\"");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
private static void test(String props) throws Exception {
|
||||
StringTokenizer tok = new StringTokenizer(props);
|
||||
Properties p = parseParams(tok);
|
||||
System.out.println(p);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
|
||||
msg = buf.toString("ISO-8859-1").trim();
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("New message received: " + msg);
|
||||
_log.debug("New message received: [" + msg + "]");
|
||||
}
|
||||
buf.reset();
|
||||
|
||||
|
||||
@@ -33,7 +33,11 @@ public class TestStreamTransfer {
|
||||
|
||||
private static void runTest(String samHost, int samPort, String conOptions) {
|
||||
startAlice(samHost, samPort, conOptions);
|
||||
testBob(samHost, samPort, conOptions);
|
||||
for (int i = 0; i < 20; i++) {
|
||||
testBob("bob" + i, samHost, samPort, conOptions);
|
||||
if (i % 2 == 1)
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
|
||||
private static void startAlice(String host, int port, String conOptions) {
|
||||
@@ -95,11 +99,13 @@ public class TestStreamTransfer {
|
||||
try { _out.close(); } catch (IOException ioe) {}
|
||||
try { _s.close(); } catch (IOException ioe) {}
|
||||
_streams.clear();
|
||||
_dead = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
private void doRun() throws IOException, SAMException {
|
||||
String line = _reader.readLine();
|
||||
_log.debug("Read: " + line);
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String maj = tok.nextToken();
|
||||
String min = tok.nextToken();
|
||||
@@ -146,12 +152,15 @@ public class TestStreamTransfer {
|
||||
}
|
||||
_log.info("Received from the stream " + id + ": [" + new String(payload) + "]");
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
/*
|
||||
// now echo it back
|
||||
String reply = "STREAM SEND ID=" + id +
|
||||
" SIZE=" + payloadSize +
|
||||
"\n" + payload;
|
||||
"\n" + new String(payload);
|
||||
_out.write(reply.getBytes());
|
||||
_out.flush();
|
||||
_log.info("Reply sent back [" + new String(reply.getBytes()) + "]");
|
||||
*/
|
||||
} else {
|
||||
_log.error("Received unsupported type [" + maj + "/"+ min + "]");
|
||||
return;
|
||||
@@ -159,8 +168,27 @@ public class TestStreamTransfer {
|
||||
}
|
||||
}
|
||||
|
||||
private static void testBob(String host, int port, String conOptions) {
|
||||
_log.info("\n\nTesting Bob\n\n\n");
|
||||
private static void testBob(String sessionName, String host, int port, String conOptions) {
|
||||
I2PThread t = new I2PThread(new TestBob(sessionName, host, port, conOptions), sessionName);
|
||||
t.start();
|
||||
}
|
||||
private static class TestBob implements Runnable {
|
||||
private String _sessionName;
|
||||
private String _host;
|
||||
private int _port;
|
||||
private String _opts;
|
||||
public TestBob(String name, String host, int port, String opts) {
|
||||
_sessionName = name;
|
||||
_host = host;
|
||||
_port = port;
|
||||
_opts = opts;
|
||||
}
|
||||
public void run() {
|
||||
doTestBob(_sessionName, _host, _port, _opts);
|
||||
}
|
||||
}
|
||||
private static void doTestBob(String sessionName, String host, int port, String conOptions) {
|
||||
_log.info("\n\nTesting " + sessionName + "\n\n\n");
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
@@ -168,32 +196,37 @@ public class TestStreamTransfer {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
_log.debug("line read for valid version: " + line);
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=Bob " + conOptions + "\n";
|
||||
String req = "SESSION CREATE STYLE=STREAM DESTINATION=" + sessionName + " " + conOptions + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response to creating the session with destination Bob: " + line);
|
||||
_log.info("Response to creating the session with destination "+ sessionName+": " + line);
|
||||
req = "STREAM CONNECT ID=42 DESTINATION=" + _alice + "\n";
|
||||
out.write(req.getBytes());
|
||||
line = reader.readLine();
|
||||
_log.info("Response to the stream connect from Bob to Alice: " + line);
|
||||
_log.info("Response to the stream connect from "+sessionName+" to Alice: " + line);
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String maj = tok.nextToken();
|
||||
String min = tok.nextToken();
|
||||
Properties props = SAMUtils.parseParams(tok);
|
||||
_log.info("props = " + props);
|
||||
String result = props.getProperty("RESULT");
|
||||
if (!("OK".equals(result))) {
|
||||
_log.error("Unable to connect!");
|
||||
_dead = true;
|
||||
//_dead = true;
|
||||
return;
|
||||
}
|
||||
try { Thread.sleep(5*1000) ; } catch (InterruptedException ie) {}
|
||||
req = "STREAM SEND ID=42 SIZE=10\nBlahBlah!!";
|
||||
_log.info("Sending data");
|
||||
out.write(req.getBytes());
|
||||
out.flush();
|
||||
try { Thread.sleep(20*1000); } catch (InterruptedException ie) {}
|
||||
_log.info("Sending close");
|
||||
req = "STREAM CLOSE ID=42\n";
|
||||
out.write(req.getBytes());
|
||||
try { Thread.sleep(3*1000); } catch (InterruptedException ie) {}
|
||||
_dead = true;
|
||||
out.flush();
|
||||
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
|
||||
//_dead = true;
|
||||
s.close();
|
||||
} catch (Exception e) {
|
||||
_log.error("Error testing for valid version", e);
|
||||
@@ -203,7 +236,7 @@ public class TestStreamTransfer {
|
||||
public static void main(String args[]) {
|
||||
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765 tunnels.inboundDepth=0";
|
||||
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
|
||||
String conOptions = "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765 tunnels.inboundDepth=0";
|
||||
String conOptions = "i2cp.tcp.host=localhost i2cp.tcp.port=10001 tunnels.inboundDepth=0";
|
||||
if (args.length > 0) {
|
||||
conOptions = "";
|
||||
for (int i = 0; i < args.length; i++)
|
||||
@@ -215,8 +248,8 @@ public class TestStreamTransfer {
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error running test", t);
|
||||
}
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
System.exit(0);
|
||||
//try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
//System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,9 +33,8 @@ def urlopen(url, eepaddr=eepaddr):
|
||||
eepaddr = eepaddr.rstrip('/')
|
||||
|
||||
proxy = urllib2.ProxyHandler( \
|
||||
{'http': 'http://anonymous:passwd@' + eepaddr})
|
||||
opener = urllib2.build_opener(proxy, \
|
||||
urllib2.HTTPBasicAuthHandler(), urllib2.HTTPHandler)
|
||||
{'http': 'http://' + eepaddr})
|
||||
opener = urllib2.build_opener(proxy, urllib2.HTTPHandler)
|
||||
return opener.open(url)
|
||||
|
||||
def urlget(url, eepaddr=eepaddr):
|
||||
|
||||
@@ -40,7 +40,7 @@ class Poll:
|
||||
del self.fds[self._hash(fd)]
|
||||
def poll(self, timeout=None):
|
||||
readlist, writelist, errlist = [], [], []
|
||||
for F, mask in self.fds:
|
||||
for F, mask in self.fds.values():
|
||||
if mask & POLLIN: readlist += [F]
|
||||
if mask & POLLOUT: writelist += [F]
|
||||
if mask & POLLERR: errlist += [F]
|
||||
|
||||
42
apps/streaming/java/build.xml
Normal file
42
apps/streaming/java/build.xml
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="streaming">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../ministreaming/java/" target="build" />
|
||||
<!-- ministreaming will build core -->
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac
|
||||
srcdir="./src:./test"
|
||||
debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="./build/obj"
|
||||
classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="builddep, compile">
|
||||
<jar destfile="./build/streaming.jar" basedir="./build/obj" includes="**/*.class" />
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="./src:../../../core/java/src:../../ministreaming/java/src" destdir="./build/javadoc"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
splitindex="true"
|
||||
windowtitle="Streaming" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<!-- ministreaming will clean core -->
|
||||
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<!-- ministreaming will clean core -->
|
||||
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
||||
507
apps/streaming/java/src/net/i2p/client/streaming/Connection.java
Normal file
507
apps/streaming/java/src/net/i2p/client/streaming/Connection.java
Normal file
@@ -0,0 +1,507 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.SessionTag;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
/**
|
||||
* Maintain the state controlling a streaming connection between two
|
||||
* destinations.
|
||||
*
|
||||
*/
|
||||
public class Connection {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private ConnectionManager _connectionManager;
|
||||
private Destination _remotePeer;
|
||||
private byte _sendStreamId[];
|
||||
private byte _receiveStreamId[];
|
||||
private long _lastSendTime;
|
||||
private long _lastSendId;
|
||||
private boolean _resetReceived;
|
||||
private boolean _connected;
|
||||
private MessageInputStream _inputStream;
|
||||
private MessageOutputStream _outputStream;
|
||||
private SchedulerChooser _chooser;
|
||||
private long _nextSendTime;
|
||||
private long _ackedPackets;
|
||||
private long _createdOn;
|
||||
private long _closeSentOn;
|
||||
private long _closeReceivedOn;
|
||||
private int _unackedPacketsReceived;
|
||||
private long _congestionWindowEnd;
|
||||
private long _highestAckedThrough;
|
||||
/** Packet ID (Long) to PacketLocal for sent but unacked packets */
|
||||
private Map _outboundPackets;
|
||||
private PacketQueue _outboundQueue;
|
||||
private ConnectionPacketHandler _handler;
|
||||
private ConnectionOptions _options;
|
||||
private ConnectionDataReceiver _receiver;
|
||||
private I2PSocketFull _socket;
|
||||
/** set to an error cause if the connection could not be established */
|
||||
private String _connectionError;
|
||||
|
||||
public static final long MAX_RESEND_DELAY = 60*1000;
|
||||
|
||||
public Connection(I2PAppContext ctx, ConnectionManager manager, SchedulerChooser chooser, PacketQueue queue, ConnectionPacketHandler handler) {
|
||||
this(ctx, manager, chooser, queue, handler, null);
|
||||
}
|
||||
public Connection(I2PAppContext ctx, ConnectionManager manager, SchedulerChooser chooser, PacketQueue queue, ConnectionPacketHandler handler, ConnectionOptions opts) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(Connection.class);
|
||||
_receiver = new ConnectionDataReceiver(ctx, this);
|
||||
_inputStream = new MessageInputStream(ctx);
|
||||
_outputStream = new MessageOutputStream(ctx, _receiver);
|
||||
_chooser = chooser;
|
||||
_outboundPackets = new TreeMap();
|
||||
_outboundQueue = queue;
|
||||
_handler = handler;
|
||||
_options = (opts != null ? opts : new ConnectionOptions());
|
||||
_lastSendId = -1;
|
||||
_nextSendTime = -1;
|
||||
_ackedPackets = 0;
|
||||
_createdOn = ctx.clock().now();
|
||||
_closeSentOn = -1;
|
||||
_closeReceivedOn = -1;
|
||||
_unackedPacketsReceived = 0;
|
||||
_congestionWindowEnd = 0;
|
||||
_highestAckedThrough = -1;
|
||||
_connectionManager = manager;
|
||||
_resetReceived = false;
|
||||
_connected = true;
|
||||
}
|
||||
|
||||
public long getNextOutboundPacketNum() {
|
||||
synchronized (this) {
|
||||
return ++_lastSendId;
|
||||
}
|
||||
}
|
||||
|
||||
void closeReceived() {
|
||||
setCloseReceivedOn(_context.clock().now());
|
||||
_inputStream.closeReceived();
|
||||
}
|
||||
|
||||
/**
|
||||
* Block until there is an open outbound packet slot or the write timeout
|
||||
* expires.
|
||||
*
|
||||
* @return true if the packet should be sent
|
||||
*/
|
||||
boolean packetSendChoke(long timeoutMs) {
|
||||
if (false) return true;
|
||||
long writeExpire = timeoutMs;
|
||||
while (true) {
|
||||
long timeLeft = writeExpire - _context.clock().now();
|
||||
synchronized (_outboundPackets) {
|
||||
if (_outboundPackets.size() >= _options.getWindowSize()) {
|
||||
if (writeExpire > 0) {
|
||||
if (timeLeft <= 0) {
|
||||
_log.error("Outbound window is full of " + _outboundPackets.size()
|
||||
+ " and we've waited too long (" + writeExpire + "ms)");
|
||||
return false;
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Outbound window is full (" + _outboundPackets.size() + "/" + _options.getWindowSize() + "), waiting " + timeLeft);
|
||||
try { _outboundPackets.wait(timeLeft); } catch (InterruptedException ie) {}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Outbound window is full (" + _outboundPackets.size() + "), waiting indefinitely");
|
||||
try { _outboundPackets.wait(); } catch (InterruptedException ie) {}
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ackImmediately() {
|
||||
_receiver.send(null, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush any data that we can
|
||||
*/
|
||||
void sendAvailable() {
|
||||
// this grabs the data, builds a packet, and queues it up via sendPacket
|
||||
try {
|
||||
_outputStream.flushAvailable(_receiver, false);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error flushing available", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
void sendPacket(PacketLocal packet) {
|
||||
setNextSendTime(-1);
|
||||
_unackedPacketsReceived = 0;
|
||||
if (_options.getRequireFullySigned()) {
|
||||
packet.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
|
||||
packet.setFlag(Packet.FLAG_SIGNATURE_REQUESTED);
|
||||
}
|
||||
|
||||
boolean ackOnly = false;
|
||||
|
||||
if ( (packet.getSequenceNum() == 0) && (!packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) ) {
|
||||
ackOnly = true;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("No resend for " + packet);
|
||||
} else {
|
||||
int remaining = 0;
|
||||
synchronized (_outboundPackets) {
|
||||
_outboundPackets.put(new Long(packet.getSequenceNum()), packet);
|
||||
remaining = _options.getWindowSize() - _outboundPackets.size() ;
|
||||
_outboundPackets.notifyAll();
|
||||
}
|
||||
if (remaining < 0)
|
||||
remaining = 0;
|
||||
if (packet.isFlagSet(Packet.FLAG_CLOSE) || (remaining < 2)) {
|
||||
packet.setOptionalDelay(0);
|
||||
} else {
|
||||
int delay = _options.getRTT() / 2;
|
||||
packet.setOptionalDelay(delay);
|
||||
_log.debug("Requesting ack delay of " + delay + "ms for packet " + packet);
|
||||
}
|
||||
packet.setFlag(Packet.FLAG_DELAY_REQUESTED);
|
||||
|
||||
long timeout = (_options.getRTT() < 10000 ? 10000 : _options.getRTT());
|
||||
if (timeout > MAX_RESEND_DELAY)
|
||||
timeout = MAX_RESEND_DELAY;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Resend in " + timeout + " for " + packet);
|
||||
|
||||
SimpleTimer.getInstance().addEvent(new ResendPacketEvent(packet), timeout);
|
||||
}
|
||||
|
||||
_lastSendTime = _context.clock().now();
|
||||
_outboundQueue.enqueue(packet);
|
||||
|
||||
if (ackOnly) {
|
||||
// ACK only, don't schedule this packet for retries
|
||||
// however, if we are running low on sessionTags we want to send
|
||||
// something that will get a reply so that we can deliver some new tags -
|
||||
// ACKs don't get ACKed, but pings do.
|
||||
if (packet.getTagsSent().size() > 0) {
|
||||
_log.warn("Sending a ping since the ACK we just sent has " + packet.getTagsSent().size() + " tags");
|
||||
_connectionManager.ping(_remotePeer, _options.getRTT()*2, false, packet.getKeyUsed(), packet.getTagsSent(), new PingNotifier());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PingNotifier implements ConnectionManager.PingNotifier {
|
||||
private long _startedPingOn;
|
||||
public PingNotifier() {
|
||||
_startedPingOn = _context.clock().now();
|
||||
}
|
||||
public void pingComplete(boolean ok) {
|
||||
long time = _context.clock().now()-_startedPingOn;
|
||||
if (ok)
|
||||
_options.updateRTT((int)time);
|
||||
else
|
||||
_options.updateRTT((int)time*2);
|
||||
}
|
||||
}
|
||||
|
||||
List ackPackets(long ackThrough, long nacks[]) {
|
||||
if (nacks == null) {
|
||||
_highestAckedThrough = ackThrough;
|
||||
} else {
|
||||
long lowest = -1;
|
||||
for (int i = 0; i < nacks.length; i++) {
|
||||
if ( (lowest < 0) || (nacks[i] < lowest) )
|
||||
lowest = nacks[i];
|
||||
}
|
||||
if (lowest - 1 > _highestAckedThrough)
|
||||
_highestAckedThrough = lowest - 1;
|
||||
}
|
||||
|
||||
List acked = null;
|
||||
synchronized (_outboundPackets) {
|
||||
for (Iterator iter = _outboundPackets.keySet().iterator(); iter.hasNext(); ) {
|
||||
Long id = (Long)iter.next();
|
||||
if (id.longValue() <= ackThrough) {
|
||||
boolean nacked = false;
|
||||
if (nacks != null) {
|
||||
// linear search since its probably really tiny
|
||||
for (int i = 0; i < nacks.length; i++) {
|
||||
if (nacks[i] == id.longValue()) {
|
||||
nacked = true;
|
||||
break; // NACKed
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!nacked) { // aka ACKed
|
||||
if (acked == null)
|
||||
acked = new ArrayList(1);
|
||||
PacketLocal ackedPacket = (PacketLocal)_outboundPackets.get(id);
|
||||
ackedPacket.ackReceived();
|
||||
acked.add(ackedPacket);
|
||||
}
|
||||
} else {
|
||||
break; // _outboundPackets is ordered
|
||||
}
|
||||
}
|
||||
if (acked != null) {
|
||||
for (int i = 0; i < acked.size(); i++) {
|
||||
PacketLocal p = (PacketLocal)acked.get(i);
|
||||
_outboundPackets.remove(new Long(p.getSequenceNum()));
|
||||
_ackedPackets++;
|
||||
}
|
||||
}
|
||||
_outboundPackets.notifyAll();
|
||||
}
|
||||
return acked;
|
||||
}
|
||||
|
||||
void eventOccurred() {
|
||||
_chooser.getScheduler(this).eventOccurred(this);
|
||||
}
|
||||
|
||||
void resetReceived() {
|
||||
_resetReceived = true;
|
||||
_outputStream.streamErrorOccurred(new IOException("Reset received"));
|
||||
_inputStream.streamErrorOccurred(new IOException("Reset received"));
|
||||
}
|
||||
public boolean getResetReceived() { return _resetReceived; }
|
||||
|
||||
public boolean getIsConnected() { return _connected; }
|
||||
|
||||
void disconnect(boolean cleanDisconnect) {
|
||||
disconnect(cleanDisconnect, true);
|
||||
}
|
||||
void disconnect(boolean cleanDisconnect, boolean removeFromConMgr) {
|
||||
if (!_connected) return;
|
||||
_connected = false;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Disconnecting " + toString(), new Exception("discon"));
|
||||
|
||||
if (cleanDisconnect) {
|
||||
// send close packets and schedule stuff...
|
||||
_outputStream.closeInternal();
|
||||
_inputStream.close();
|
||||
} else {
|
||||
doClose();
|
||||
synchronized (_outboundPackets) {
|
||||
for (Iterator iter = _outboundPackets.values().iterator(); iter.hasNext(); ) {
|
||||
PacketLocal pl = (PacketLocal)iter.next();
|
||||
pl.cancelled();
|
||||
}
|
||||
_outboundPackets.clear();
|
||||
_outboundPackets.notifyAll();
|
||||
}
|
||||
if (removeFromConMgr)
|
||||
_connectionManager.removeConnection(this);
|
||||
}
|
||||
}
|
||||
|
||||
void disconnectComplete() {
|
||||
_connectionManager.removeConnection(this);
|
||||
}
|
||||
|
||||
private void doClose() {
|
||||
_outputStream.streamErrorOccurred(new IOException("Hard disconnect"));
|
||||
_inputStream.closeReceived();
|
||||
}
|
||||
|
||||
/** who are we talking with */
|
||||
public Destination getRemotePeer() { return _remotePeer; }
|
||||
public void setRemotePeer(Destination peer) { _remotePeer = peer; }
|
||||
|
||||
/** what stream do we send data to the peer on? */
|
||||
public byte[] getSendStreamId() { return _sendStreamId; }
|
||||
public void setSendStreamId(byte[] id) { _sendStreamId = id; }
|
||||
|
||||
/** stream the peer sends data to us on. (may be null) */
|
||||
public byte[] getReceiveStreamId() { return _receiveStreamId; }
|
||||
public void setReceiveStreamId(byte[] id) { _receiveStreamId = id; }
|
||||
|
||||
/** when did we last send anything to the peer? */
|
||||
public long getLastSendTime() { return _lastSendTime; }
|
||||
public void setLastSendTime(long when) { _lastSendTime = when; }
|
||||
|
||||
/** what was the last packet Id sent to the peer? */
|
||||
public long getLastSendId() { return _lastSendId; }
|
||||
public void setLastSendId(long id) { _lastSendId = id; }
|
||||
|
||||
public ConnectionOptions getOptions() { return _options; }
|
||||
public void setOptions(ConnectionOptions opts) { _options = opts; }
|
||||
|
||||
public I2PSession getSession() { return _connectionManager.getSession(); }
|
||||
public I2PSocketFull getSocket() { return _socket; }
|
||||
public void setSocket(I2PSocketFull socket) { _socket = socket; }
|
||||
|
||||
public String getConnectionError() { return _connectionError; }
|
||||
public void setConnectionError(String err) { _connectionError = err; }
|
||||
|
||||
public ConnectionPacketHandler getPacketHandler() { return _handler; }
|
||||
|
||||
/**
|
||||
* Time when the scheduler next want to send a packet, or -1 if
|
||||
* never. This should be set when we want to send on timeout, for
|
||||
* instance, or want to delay an ACK.
|
||||
*/
|
||||
public long getNextSendTime() { return _nextSendTime; }
|
||||
public void setNextSendTime(long when) {
|
||||
if (_nextSendTime >= 0) {
|
||||
if (when < _nextSendTime)
|
||||
_nextSendTime = when;
|
||||
} else {
|
||||
_nextSendTime = when;
|
||||
}
|
||||
|
||||
if (_nextSendTime >= 0) {
|
||||
long max = _context.clock().now() + _options.getSendAckDelay();
|
||||
if (max < _nextSendTime)
|
||||
_nextSendTime = max;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG) && false) {
|
||||
if (_nextSendTime <= 0)
|
||||
_log.debug("set next send time to an unknown time", new Exception(toString()));
|
||||
else
|
||||
_log.debug("set next send time to " + (_nextSendTime-_context.clock().now()) + "ms from now", new Exception(toString()));
|
||||
}
|
||||
}
|
||||
|
||||
public long getAckedPackets() { return _ackedPackets; }
|
||||
public long getCreatedOn() { return _createdOn; }
|
||||
public long getCloseSentOn() { return _closeSentOn; }
|
||||
public void setCloseSentOn(long when) { _closeSentOn = when; }
|
||||
public long getCloseReceivedOn() { return _closeReceivedOn; }
|
||||
public void setCloseReceivedOn(long when) { _closeReceivedOn = when; }
|
||||
|
||||
public void incrementUnackedPacketsReceived() { _unackedPacketsReceived++; }
|
||||
public int getUnackedPacketsReceived() { return _unackedPacketsReceived; }
|
||||
public int getUnackedPacketsSent() {
|
||||
synchronized (_outboundPackets) {
|
||||
return _outboundPackets.size();
|
||||
}
|
||||
}
|
||||
|
||||
public long getCongestionWindowEnd() { return _congestionWindowEnd; }
|
||||
public void setCongestionWindowEnd(long endMsg) { _congestionWindowEnd = endMsg; }
|
||||
public long getHighestAckedThrough() { return _highestAckedThrough; }
|
||||
public void setHighestAckedThrough(long msgNum) { _highestAckedThrough = msgNum; }
|
||||
|
||||
/** stream that the local peer receives data on */
|
||||
public MessageInputStream getInputStream() { return _inputStream; }
|
||||
/** stream that the local peer sends data to the remote peer on */
|
||||
public MessageOutputStream getOutputStream() { return _outputStream; }
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("[Connection ");
|
||||
if (_receiveStreamId != null)
|
||||
buf.append(Base64.encode(_receiveStreamId));
|
||||
else
|
||||
buf.append("unknown");
|
||||
buf.append("<-->");
|
||||
if (_sendStreamId != null)
|
||||
buf.append(Base64.encode(_sendStreamId));
|
||||
else
|
||||
buf.append("unknown");
|
||||
buf.append(" wsize: ").append(_options.getWindowSize());
|
||||
buf.append(" cwin: ").append(_congestionWindowEnd - _highestAckedThrough);
|
||||
buf.append(" rtt: ").append(_options.getRTT());
|
||||
buf.append(" unacked outbound: ");
|
||||
synchronized (_outboundPackets) {
|
||||
buf.append(_outboundPackets.size()).append(" [");
|
||||
for (Iterator iter = _outboundPackets.keySet().iterator(); iter.hasNext(); ) {
|
||||
buf.append(((Long)iter.next()).longValue()).append(" ");
|
||||
}
|
||||
buf.append("] ");
|
||||
}
|
||||
buf.append("unacked inbound? ").append(getUnackedPacketsReceived());
|
||||
buf.append(" [high ").append(_inputStream.getHighestBlockId());
|
||||
long nacks[] = _inputStream.getNacks();
|
||||
if (nacks != null)
|
||||
for (int i = 0; i < nacks.length; i++)
|
||||
buf.append(" ").append(nacks[i]);
|
||||
buf.append("]");
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Coordinate the resends of a given packet
|
||||
*/
|
||||
private class ResendPacketEvent implements SimpleTimer.TimedEvent {
|
||||
private PacketLocal _packet;
|
||||
public ResendPacketEvent(PacketLocal packet) {
|
||||
_packet = packet;
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
if (!_connected) return;
|
||||
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Resend period reached for " + _packet);
|
||||
boolean resend = false;
|
||||
synchronized (_outboundPackets) {
|
||||
if (_outboundPackets.containsKey(new Long(_packet.getSequenceNum())))
|
||||
resend = true;
|
||||
}
|
||||
if ( (resend) && (_packet.getAckTime() < 0) ) {
|
||||
// revamp various fields, in case we need to ack more, etc
|
||||
_inputStream.updateAcks(_packet);
|
||||
_packet.setOptionalDelay(getOptions().getChoke());
|
||||
_packet.setOptionalMaxSize(getOptions().getMaxMessageSize());
|
||||
_packet.setResendDelay(getOptions().getResendDelay());
|
||||
_packet.setReceiveStreamId(_receiveStreamId);
|
||||
_packet.setSendStreamId(_sendStreamId);
|
||||
|
||||
// shrink the window
|
||||
int newWindowSize = getOptions().getWindowSize();
|
||||
newWindowSize /= 2;
|
||||
if (newWindowSize <= 0)
|
||||
newWindowSize = 1;
|
||||
getOptions().setWindowSize(newWindowSize);
|
||||
|
||||
int numSends = _packet.getNumSends() + 1;
|
||||
|
||||
// in case things really suck, the other side may have lost thier
|
||||
// session tags (e.g. they restarted), so jump back to ElGamal.
|
||||
if ( (newWindowSize == 1) && (numSends > 2) )
|
||||
_context.sessionKeyManager().failTags(_remotePeer.getPublicKey());
|
||||
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Resend packet " + _packet + " time " + numSends + " (wsize "
|
||||
+ newWindowSize + " lifetime "
|
||||
+ (_context.clock().now() - _packet.getCreatedOn()) + "ms)");
|
||||
_outboundQueue.enqueue(_packet);
|
||||
|
||||
if (numSends > _options.getMaxResends()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Too many resends");
|
||||
disconnect(false);
|
||||
} else {
|
||||
//long timeout = _options.getResendDelay() << numSends;
|
||||
long timeout = _options.getRTT() << (numSends-1);
|
||||
if ( (timeout > MAX_RESEND_DELAY) || (timeout <= 0) )
|
||||
timeout = MAX_RESEND_DELAY;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Scheduling resend in " + timeout + "ms for " + _packet);
|
||||
SimpleTimer.getInstance().addEvent(ResendPacketEvent.this, timeout);
|
||||
}
|
||||
} else {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Packet acked before resend (resend="+ resend + "): "
|
||||
// + _packet + " on " + Connection.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.IOException;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private Connection _connection;
|
||||
private MessageOutputStream.WriteStatus _dummyStatus;
|
||||
|
||||
public ConnectionDataReceiver(I2PAppContext ctx, Connection con) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(ConnectionDataReceiver.class);
|
||||
_connection = con;
|
||||
_dummyStatus = new DummyStatus();
|
||||
}
|
||||
|
||||
public MessageOutputStream.WriteStatus writeData(byte[] buf, int off, int size) {
|
||||
boolean doSend = true;
|
||||
if ( (size <= 0) && (_connection.getLastSendId() >= 0) ) {
|
||||
if (_connection.getOutputStream().getClosed()) {
|
||||
if (_connection.getCloseSentOn() < 0) {
|
||||
doSend = true;
|
||||
} else {
|
||||
// closed, no new data, and we've already sent a close packet
|
||||
doSend = false;
|
||||
}
|
||||
} else {
|
||||
// no new data, not closed, already synchronized
|
||||
doSend = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_connection.getUnackedPacketsReceived() > 0)
|
||||
doSend = true;
|
||||
|
||||
if (_log.shouldLog(Log.ERROR) && !doSend)
|
||||
_log.error("writeData called: size="+size + " doSend=" + doSend
|
||||
+ " unackedReceived: " + _connection.getUnackedPacketsReceived()
|
||||
+ " con: " + _connection, new Exception("write called by"));
|
||||
|
||||
if (doSend) {
|
||||
PacketLocal packet = send(buf, off, size);
|
||||
return packet;
|
||||
} else {
|
||||
return _dummyStatus;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public PacketLocal send(byte buf[], int off, int size) {
|
||||
PacketLocal packet = buildPacket(buf, off, size);
|
||||
_connection.sendPacket(packet);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private boolean isAckOnly(int size) {
|
||||
boolean ackOnly = ( (size <= 0) && // no data
|
||||
(_connection.getLastSendId() >= 0) && // not a SYN
|
||||
( (!_connection.getOutputStream().getClosed()) || // not a CLOSE
|
||||
(_connection.getOutputStream().getClosed() &&
|
||||
_connection.getCloseSentOn() > 0) )); // or it is a dup CLOSE
|
||||
return ackOnly;
|
||||
}
|
||||
|
||||
private PacketLocal buildPacket(byte buf[], int off, int size) {
|
||||
boolean ackOnly = isAckOnly(size);
|
||||
PacketLocal packet = new PacketLocal(_context, _connection.getRemotePeer(), _connection);
|
||||
byte data[] = new byte[size];
|
||||
if (size > 0)
|
||||
System.arraycopy(buf, off, data, 0, size);
|
||||
packet.setPayload(data);
|
||||
if (ackOnly)
|
||||
packet.setSequenceNum(0);
|
||||
else
|
||||
packet.setSequenceNum(_connection.getNextOutboundPacketNum());
|
||||
packet.setSendStreamId(_connection.getSendStreamId());
|
||||
packet.setReceiveStreamId(_connection.getReceiveStreamId());
|
||||
|
||||
_connection.getInputStream().updateAcks(packet);
|
||||
packet.setOptionalDelay(_connection.getOptions().getChoke());
|
||||
packet.setOptionalMaxSize(_connection.getOptions().getMaxMessageSize());
|
||||
packet.setResendDelay(_connection.getOptions().getResendDelay());
|
||||
|
||||
if (_connection.getOptions().getProfile() == ConnectionOptions.PROFILE_INTERACTIVE)
|
||||
packet.setFlag(Packet.FLAG_PROFILE_INTERACTIVE, true);
|
||||
else
|
||||
packet.setFlag(Packet.FLAG_PROFILE_INTERACTIVE, false);
|
||||
|
||||
packet.setFlag(Packet.FLAG_SIGNATURE_REQUESTED, _connection.getOptions().getRequireFullySigned());
|
||||
|
||||
if ( (!ackOnly) && (packet.getSequenceNum() <= 0) ) {
|
||||
packet.setFlag(Packet.FLAG_SYNCHRONIZE);
|
||||
packet.setOptionalFrom(_connection.getSession().getMyDestination());
|
||||
}
|
||||
|
||||
if (_connection.getOutputStream().getClosed()) {
|
||||
packet.setFlag(Packet.FLAG_CLOSE);
|
||||
_connection.setCloseSentOn(_context.clock().now());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Closed is set for a new packet on " + _connection + ": " + packet);
|
||||
} else {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Closed is not set for a new packet on " + _connection + ": " + packet);
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
|
||||
private static final class DummyStatus implements MessageOutputStream.WriteStatus {
|
||||
public final void waitForAccept(int maxWaitMs) { return; }
|
||||
public final void waitForCompletion(int maxWaitMs) { return; }
|
||||
public final boolean writeAccepted() { return true; }
|
||||
public final boolean writeFailed() { return false; }
|
||||
public final boolean writeSuccessful() { return true; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
/**
|
||||
* Receive new connection attempts
|
||||
*/
|
||||
class ConnectionHandler {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private ConnectionManager _manager;
|
||||
private List _synQueue;
|
||||
private boolean _active;
|
||||
private int _acceptTimeout;
|
||||
|
||||
/** max time after receiveNewSyn() and before the matched accept() */
|
||||
private static final int DEFAULT_ACCEPT_TIMEOUT = 3*1000;
|
||||
|
||||
/** Creates a new instance of ConnectionHandler */
|
||||
public ConnectionHandler(I2PAppContext context, ConnectionManager mgr) {
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(ConnectionHandler.class);
|
||||
_manager = mgr;
|
||||
_synQueue = new ArrayList(5);
|
||||
_active = false;
|
||||
_acceptTimeout = DEFAULT_ACCEPT_TIMEOUT;
|
||||
}
|
||||
|
||||
public void setActive(boolean active) { _active = active; }
|
||||
public boolean getActive() { return _active; }
|
||||
|
||||
public void receiveNewSyn(Packet packet) {
|
||||
if (!_active) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Dropping new SYN request, as we're not listening");
|
||||
sendReset(packet);
|
||||
return;
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Receive new SYN: " + packet + ": timeout in " + _acceptTimeout);
|
||||
SimpleTimer.getInstance().addEvent(new TimeoutSyn(packet), _acceptTimeout);
|
||||
synchronized (_synQueue) {
|
||||
_synQueue.add(packet);
|
||||
_synQueue.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public Connection accept(long timeoutMs) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Accept("+ timeoutMs+") called");
|
||||
|
||||
long expiration = timeoutMs;
|
||||
if (expiration > 0)
|
||||
expiration += _context.clock().now();
|
||||
Packet syn = null;
|
||||
synchronized (_synQueue) {
|
||||
while ( _active && (_synQueue.size() <= 0) ) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Accept("+ timeoutMs+"): active=" + _active + " queue: " + _synQueue.size());
|
||||
if (timeoutMs <= 0) {
|
||||
try { _synQueue.wait(); } catch (InterruptedException ie) {}
|
||||
} else {
|
||||
long remaining = expiration - _context.clock().now();
|
||||
if (remaining < 0)
|
||||
break;
|
||||
try { _synQueue.wait(remaining); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
if (_active && _synQueue.size() > 0) {
|
||||
syn = (Packet)_synQueue.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (syn != null) {
|
||||
// deal with forged / invalid syn packets
|
||||
Connection con = _manager.receiveConnection(syn);
|
||||
if (con != null) {
|
||||
return con;
|
||||
} else if (timeoutMs > 0) {
|
||||
long remaining = expiration - _context.clock().now();
|
||||
if (remaining <= 0) {
|
||||
return null;
|
||||
} else {
|
||||
return accept(remaining);
|
||||
}
|
||||
} else {
|
||||
return accept(timeoutMs);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendReset(Packet packet) {
|
||||
boolean ok = packet.verifySignature(_context, packet.getOptionalFrom(), null);
|
||||
if (!ok) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Received a spoofed SYN packet: they said they were " + packet.getOptionalFrom());
|
||||
return;
|
||||
}
|
||||
PacketLocal reply = new PacketLocal(_context, packet.getOptionalFrom());
|
||||
reply.setFlag(Packet.FLAG_RESET);
|
||||
reply.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
|
||||
reply.setAckThrough(packet.getSequenceNum());
|
||||
reply.setSendStreamId(packet.getReceiveStreamId());
|
||||
reply.setReceiveStreamId(null);
|
||||
reply.setOptionalFrom(_manager.getSession().getMyDestination());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Sending RST: " + reply + " because of " + packet);
|
||||
// this just sends the packet - no retries or whatnot
|
||||
_manager.getPacketQueue().enqueue(reply);
|
||||
}
|
||||
|
||||
private class TimeoutSyn implements SimpleTimer.TimedEvent {
|
||||
private Packet _synPacket;
|
||||
public TimeoutSyn(Packet packet) {
|
||||
_synPacket = packet;
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
boolean removed = false;
|
||||
synchronized (_synQueue) {
|
||||
removed = _synQueue.remove(_synPacket);
|
||||
}
|
||||
|
||||
if (removed) {
|
||||
// timeout - send RST
|
||||
sendReset(_synPacket);
|
||||
} else {
|
||||
// handled. noop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Coordinate all of the connections for a single local destination.
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class ConnectionManager {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private I2PSession _session;
|
||||
private MessageHandler _messageHandler;
|
||||
private PacketHandler _packetHandler;
|
||||
private ConnectionHandler _connectionHandler;
|
||||
private PacketQueue _outboundQueue;
|
||||
private SchedulerChooser _schedulerChooser;
|
||||
private ConnectionPacketHandler _conPacketHandler;
|
||||
/** Inbound stream ID (ByteArray) to Connection map */
|
||||
private Map _connectionByInboundId;
|
||||
/** Ping ID (ByteArray) to PingRequest */
|
||||
private Map _pendingPings;
|
||||
private boolean _allowIncoming;
|
||||
private Object _connectionLock;
|
||||
|
||||
public ConnectionManager(I2PAppContext context, I2PSession session) {
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(ConnectionManager.class);
|
||||
_connectionByInboundId = new HashMap(32);
|
||||
_pendingPings = new HashMap(4);
|
||||
_connectionLock = new Object();
|
||||
_messageHandler = new MessageHandler(context, this);
|
||||
_packetHandler = new PacketHandler(context, this);
|
||||
_connectionHandler = new ConnectionHandler(context, this);
|
||||
_schedulerChooser = new SchedulerChooser(context);
|
||||
_conPacketHandler = new ConnectionPacketHandler(context);
|
||||
_session = session;
|
||||
session.setSessionListener(_messageHandler);
|
||||
_outboundQueue = new PacketQueue(context, session);
|
||||
_allowIncoming = false;
|
||||
}
|
||||
|
||||
Connection getConnectionByInboundId(byte[] id) {
|
||||
synchronized (_connectionLock) {
|
||||
return (Connection)_connectionByInboundId.get(new ByteArray(id));
|
||||
}
|
||||
}
|
||||
|
||||
public void setAllowIncomingConnections(boolean allow) {
|
||||
_connectionHandler.setActive(allow);
|
||||
}
|
||||
public boolean getAllowIncomingConnections() {
|
||||
return _connectionHandler.getActive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new connection based on the SYN packet we received.
|
||||
*
|
||||
* @return created Connection with the packet's data already delivered to
|
||||
* it, or null if the syn's streamId was already taken
|
||||
*/
|
||||
public Connection receiveConnection(Packet synPacket) {
|
||||
Connection con = new Connection(_context, this, _schedulerChooser, _outboundQueue, _conPacketHandler);
|
||||
byte receiveId[] = new byte[4];
|
||||
_context.random().nextBytes(receiveId);
|
||||
synchronized (_connectionLock) {
|
||||
while (true) {
|
||||
Connection oldCon = (Connection)_connectionByInboundId.put(new ByteArray(receiveId), con);
|
||||
if (oldCon == null) {
|
||||
break;
|
||||
} else {
|
||||
_connectionByInboundId.put(new ByteArray(receiveId), oldCon);
|
||||
// receiveId already taken, try another
|
||||
_context.random().nextBytes(receiveId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
con.setReceiveStreamId(receiveId);
|
||||
try {
|
||||
con.getPacketHandler().receivePacket(synPacket, con);
|
||||
} catch (I2PException ie) {
|
||||
synchronized (_connectionLock) {
|
||||
_connectionByInboundId.remove(new ByteArray(receiveId));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return con;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new connection to the given peer
|
||||
*/
|
||||
public Connection connect(Destination peer, ConnectionOptions opts) {
|
||||
Connection con = new Connection(_context, this, _schedulerChooser, _outboundQueue, _conPacketHandler, opts);
|
||||
con.setRemotePeer(peer);
|
||||
byte receiveId[] = new byte[4];
|
||||
_context.random().nextBytes(receiveId);
|
||||
synchronized (_connectionLock) {
|
||||
ByteArray ba = new ByteArray(receiveId);
|
||||
while (_connectionByInboundId.containsKey(ba)) {
|
||||
_context.random().nextBytes(receiveId);
|
||||
}
|
||||
_connectionByInboundId.put(ba, con);
|
||||
}
|
||||
|
||||
con.setReceiveStreamId(receiveId);
|
||||
con.eventOccurred();
|
||||
return con;
|
||||
}
|
||||
|
||||
public MessageHandler getMessageHandler() { return _messageHandler; }
|
||||
public PacketHandler getPacketHandler() { return _packetHandler; }
|
||||
public ConnectionHandler getConnectionHandler() { return _connectionHandler; }
|
||||
public I2PSession getSession() { return _session; }
|
||||
public PacketQueue getPacketQueue() { return _outboundQueue; }
|
||||
|
||||
/**
|
||||
* Something b0rked hard, so kill all of our connections without mercy.
|
||||
* Don't bother sending close packets.
|
||||
*
|
||||
*/
|
||||
public void disconnectAllHard() {
|
||||
synchronized (_connectionLock) {
|
||||
for (Iterator iter = _connectionByInboundId.values().iterator(); iter.hasNext(); ) {
|
||||
Connection con = (Connection)iter.next();
|
||||
con.disconnect(false, false);
|
||||
}
|
||||
_connectionByInboundId.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeConnection(Connection con) {
|
||||
synchronized (_connectionLock) {
|
||||
_connectionByInboundId.remove(new ByteArray(con.getReceiveStreamId()));
|
||||
}
|
||||
}
|
||||
|
||||
public Set listConnections() {
|
||||
synchronized (_connectionLock) {
|
||||
return new HashSet(_connectionByInboundId.values());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean ping(Destination peer, long timeoutMs) {
|
||||
return ping(peer, timeoutMs, true);
|
||||
}
|
||||
public boolean ping(Destination peer, long timeoutMs, boolean blocking) {
|
||||
return ping(peer, timeoutMs, blocking, null, null, null);
|
||||
}
|
||||
public boolean ping(Destination peer, long timeoutMs, boolean blocking, SessionKey keyToUse, Set tagsToSend, PingNotifier notifier) {
|
||||
byte id[] = new byte[4];
|
||||
_context.random().nextBytes(id);
|
||||
ByteArray ba = new ByteArray(id);
|
||||
PacketLocal packet = new PacketLocal(_context, peer);
|
||||
packet.setSendStreamId(id);
|
||||
packet.setFlag(Packet.FLAG_ECHO);
|
||||
packet.setFlag(Packet.FLAG_SIGNATURE_INCLUDED);
|
||||
packet.setOptionalFrom(_session.getMyDestination());
|
||||
if ( (keyToUse != null) && (tagsToSend != null) ) {
|
||||
packet.setKeyUsed(keyToUse);
|
||||
packet.setTagsSent(tagsToSend);
|
||||
}
|
||||
|
||||
PingRequest req = new PingRequest(peer, packet, notifier);
|
||||
|
||||
synchronized (_pendingPings) {
|
||||
_pendingPings.put(ba, req);
|
||||
}
|
||||
|
||||
_outboundQueue.enqueue(packet);
|
||||
|
||||
if (blocking) {
|
||||
synchronized (req) {
|
||||
if (!req.pongReceived())
|
||||
try { req.wait(timeoutMs); } catch (InterruptedException ie) {}
|
||||
}
|
||||
|
||||
synchronized (_pendingPings) {
|
||||
_pendingPings.remove(ba);
|
||||
}
|
||||
} else {
|
||||
SimpleTimer.getInstance().addEvent(new PingFailed(ba, notifier), timeoutMs);
|
||||
}
|
||||
|
||||
boolean ok = req.pongReceived();
|
||||
return ok;
|
||||
}
|
||||
|
||||
interface PingNotifier {
|
||||
public void pingComplete(boolean ok);
|
||||
}
|
||||
|
||||
private class PingFailed implements SimpleTimer.TimedEvent {
|
||||
private ByteArray _ba;
|
||||
private PingNotifier _notifier;
|
||||
public PingFailed(ByteArray ba, PingNotifier notifier) {
|
||||
_ba = ba;
|
||||
_notifier = notifier;
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
boolean removed = false;
|
||||
synchronized (_pendingPings) {
|
||||
Object o = _pendingPings.remove(_ba);
|
||||
if (o != null)
|
||||
removed = true;
|
||||
}
|
||||
if (removed) {
|
||||
if (_notifier != null)
|
||||
_notifier.pingComplete(false);
|
||||
_log.error("Ping failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PingRequest {
|
||||
private boolean _ponged;
|
||||
private Destination _peer;
|
||||
private PacketLocal _packet;
|
||||
private PingNotifier _notifier;
|
||||
public PingRequest(Destination peer, PacketLocal packet, PingNotifier notifier) {
|
||||
_ponged = false;
|
||||
_peer = peer;
|
||||
_packet = packet;
|
||||
_notifier = notifier;
|
||||
}
|
||||
public void pong() {
|
||||
_log.debug("Ping successful");
|
||||
_context.sessionKeyManager().tagsDelivered(_peer.getPublicKey(), _packet.getKeyUsed(), _packet.getTagsSent());
|
||||
synchronized (ConnectionManager.PingRequest.this) {
|
||||
_ponged = true;
|
||||
ConnectionManager.PingRequest.this.notifyAll();
|
||||
}
|
||||
if (_notifier != null)
|
||||
_notifier.pingComplete(true);
|
||||
}
|
||||
public boolean pongReceived() { return _ponged; }
|
||||
}
|
||||
|
||||
void receivePong(byte pingId[]) {
|
||||
ByteArray ba = new ByteArray(pingId);
|
||||
PingRequest req = null;
|
||||
synchronized (_pendingPings) {
|
||||
req = (PingRequest)_pendingPings.remove(ba);
|
||||
}
|
||||
if (req != null)
|
||||
req.pong();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Define the current options for the con (and allow custom tweaking midstream)
|
||||
*
|
||||
*/
|
||||
public class ConnectionOptions extends I2PSocketOptions {
|
||||
private int _connectDelay;
|
||||
private boolean _fullySigned;
|
||||
private int _windowSize;
|
||||
private int _receiveWindow;
|
||||
private int _profile;
|
||||
private int _rtt;
|
||||
private int _resendDelay;
|
||||
private int _sendAckDelay;
|
||||
private int _maxMessageSize;
|
||||
private int _choke;
|
||||
private int _maxResends;
|
||||
|
||||
public static final int PROFILE_BULK = 1;
|
||||
public static final int PROFILE_INTERACTIVE = 2;
|
||||
|
||||
public ConnectionOptions() {
|
||||
super();
|
||||
init(null);
|
||||
}
|
||||
|
||||
public ConnectionOptions(I2PSocketOptions opts) {
|
||||
super(opts);
|
||||
init(null);
|
||||
}
|
||||
|
||||
public ConnectionOptions(ConnectionOptions opts) {
|
||||
super(opts);
|
||||
init(opts);
|
||||
}
|
||||
|
||||
private void init(ConnectionOptions opts) {
|
||||
if (opts != null) {
|
||||
setConnectDelay(opts.getConnectDelay());
|
||||
setProfile(opts.getProfile());
|
||||
setRTT(opts.getRTT());
|
||||
setRequireFullySigned(opts.getRequireFullySigned());
|
||||
setWindowSize(opts.getWindowSize());
|
||||
setResendDelay(opts.getResendDelay());
|
||||
setMaxMessageSize(opts.getMaxMessageSize());
|
||||
setChoke(opts.getChoke());
|
||||
setMaxResends(opts.getMaxResends());
|
||||
} else {
|
||||
setConnectDelay(2*1000);
|
||||
setProfile(PROFILE_BULK);
|
||||
setMaxMessageSize(Packet.MAX_PAYLOAD_SIZE);
|
||||
setRTT(30*1000);
|
||||
setReceiveWindow(1);
|
||||
setResendDelay(5*1000);
|
||||
setSendAckDelay(2*1000);
|
||||
setWindowSize(1);
|
||||
setMaxResends(5);
|
||||
setWriteTimeout(-1);
|
||||
}
|
||||
}
|
||||
|
||||
public ConnectionOptions(Properties opts) {
|
||||
super(opts);
|
||||
// load the options;
|
||||
}
|
||||
|
||||
/**
|
||||
* how long will we wait after instantiating a new con
|
||||
* before actually attempting to connect. If this is
|
||||
* set to 0, connect ASAP. If it is greater than 0, wait
|
||||
* until the output stream is flushed, the buffer fills,
|
||||
* or that many milliseconds pass.
|
||||
*
|
||||
*/
|
||||
public int getConnectDelay() { return _connectDelay; }
|
||||
public void setConnectDelay(int delayMs) { _connectDelay = delayMs; }
|
||||
|
||||
/**
|
||||
* Do we want all packets in both directions to be signed,
|
||||
* or can we deal with signatures on the SYN and FIN packets
|
||||
* only?
|
||||
*
|
||||
*/
|
||||
public boolean getRequireFullySigned() { return _fullySigned; }
|
||||
public void setRequireFullySigned(boolean sign) { _fullySigned = sign; }
|
||||
|
||||
private static final int MAX_WINDOW_SIZE = 32;
|
||||
/**
|
||||
* How many messages will we send before waiting for an ACK?
|
||||
*
|
||||
*/
|
||||
public int getWindowSize() { return _windowSize; }
|
||||
public void setWindowSize(int numMsgs) {
|
||||
if (numMsgs > MAX_WINDOW_SIZE)
|
||||
numMsgs = MAX_WINDOW_SIZE;
|
||||
_windowSize = numMsgs;
|
||||
}
|
||||
|
||||
/** after how many consecutive messages should we ack? */
|
||||
public int getReceiveWindow() { return _receiveWindow; }
|
||||
public void setReceiveWindow(int numMsgs) { _receiveWindow = numMsgs; }
|
||||
|
||||
/**
|
||||
* What to set the round trip time estimate to (in milliseconds)
|
||||
*/
|
||||
public int getRTT() { return _rtt; }
|
||||
public void setRTT(int ms) {
|
||||
_rtt = ms;
|
||||
if (_rtt > 60*1000)
|
||||
_rtt = 60*1000;
|
||||
}
|
||||
|
||||
/** rtt = rtt*RTT_DAMPENING + (1-RTT_DAMPENING)*currentPacketRTT */
|
||||
private static final double RTT_DAMPENING = 0.9;
|
||||
|
||||
public void updateRTT(int measuredValue) {
|
||||
setRTT((int)(RTT_DAMPENING*_rtt + (1-RTT_DAMPENING)*measuredValue));
|
||||
}
|
||||
|
||||
/** How long after sending a packet will we wait before resending? */
|
||||
public int getResendDelay() { return _resendDelay; }
|
||||
public void setResendDelay(int ms) { _resendDelay = ms; }
|
||||
|
||||
/**
|
||||
* if there are packets we haven't ACKed yet and we don't
|
||||
* receive _receiveWindow messages before
|
||||
* (_lastSendTime+_sendAckDelay), send an ACK of what
|
||||
* we have received so far.
|
||||
*
|
||||
*/
|
||||
public int getSendAckDelay() { return _sendAckDelay; }
|
||||
public void setSendAckDelay(int delayMs) { _sendAckDelay = delayMs; }
|
||||
|
||||
/** What is the largest message we want to send or receive? */
|
||||
public int getMaxMessageSize() { return _maxMessageSize; }
|
||||
public void setMaxMessageSize(int bytes) { _maxMessageSize = bytes; }
|
||||
|
||||
/**
|
||||
* how long we want to wait before any data is transferred on the
|
||||
* connection in either direction
|
||||
*
|
||||
*/
|
||||
public int getChoke() { return _choke; }
|
||||
public void setChoke(int ms) { _choke = ms; }
|
||||
|
||||
/**
|
||||
* What profile do we want to use for this connection?
|
||||
*
|
||||
*/
|
||||
public int getProfile() { return _profile; }
|
||||
public void setProfile(int profile) { _profile = profile; }
|
||||
|
||||
/**
|
||||
* How many times will we try to send a message before giving up?
|
||||
*
|
||||
*/
|
||||
public int getMaxResends() { return _maxResends; }
|
||||
public void setMaxResends(int numSends) { _maxResends = numSends; }
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Receive a packet for a particular connection - placing the data onto the
|
||||
* queue, marking packets as acked, updating various fields, etc.
|
||||
*
|
||||
*/
|
||||
public class ConnectionPacketHandler {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
|
||||
public ConnectionPacketHandler(I2PAppContext context) {
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(ConnectionPacketHandler.class);
|
||||
}
|
||||
|
||||
/** distribute a packet to the connection specified */
|
||||
void receivePacket(Packet packet, Connection con) throws I2PException {
|
||||
boolean ok = verifyPacket(packet, con);
|
||||
if (!ok) return;
|
||||
boolean isNew = con.getInputStream().messageReceived(packet.getSequenceNum(), packet.getPayload());
|
||||
|
||||
// close *after* receiving the data, as well as after verifying the signatures / etc
|
||||
if (packet.isFlagSet(Packet.FLAG_CLOSE) && packet.isFlagSet(Packet.FLAG_SIGNATURE_INCLUDED))
|
||||
con.closeReceived();
|
||||
|
||||
if (isNew) {
|
||||
con.incrementUnackedPacketsReceived();
|
||||
if (packet.isFlagSet(Packet.FLAG_DELAY_REQUESTED) && (packet.getOptionalDelay() <= 0) ) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Scheduling immediate ack for " + packet);
|
||||
con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
|
||||
} else {
|
||||
int delay = con.getOptions().getSendAckDelay();
|
||||
if (packet.isFlagSet(Packet.FLAG_DELAY_REQUESTED)) // delayed ACK requested
|
||||
delay += packet.getOptionalDelay();
|
||||
con.setNextSendTime(delay + _context.clock().now());
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Scheduling ack in " + delay + "ms for received packet " + packet);
|
||||
}
|
||||
} else {
|
||||
if (packet.getSequenceNum() > 0) {
|
||||
// take note of congestion
|
||||
con.getOptions().setResendDelay(con.getOptions().getResendDelay()*2);
|
||||
//con.getOptions().setWindowSize(con.getOptions().getWindowSize()/2);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("congestion.. dup " + packet);
|
||||
con.incrementUnackedPacketsReceived();
|
||||
con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
|
||||
} else {
|
||||
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
|
||||
con.incrementUnackedPacketsReceived();
|
||||
con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("ACK only packet received: " + packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int numResends = 0;
|
||||
List acked = con.ackPackets(packet.getAckThrough(), packet.getNacks());
|
||||
if ( (acked != null) && (acked.size() > 0) ) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(acked.size() + " of our packets acked with " + packet);
|
||||
// use the highest RTT, since these would likely be bunched together,
|
||||
// and the highest rtt lets us set our resend delay properly
|
||||
int highestRTT = -1;
|
||||
for (int i = 0; i < acked.size(); i++) {
|
||||
PacketLocal p = (PacketLocal)acked.get(i);
|
||||
if (p.getAckTime() > highestRTT) {
|
||||
//if (p.getNumSends() <= 1)
|
||||
highestRTT = p.getAckTime();
|
||||
}
|
||||
|
||||
if (p.getNumSends() > 1)
|
||||
numResends++;
|
||||
|
||||
// ACK the tags we delivered so we can use them
|
||||
if ( (p.getKeyUsed() != null) && (p.getTagsSent() != null)
|
||||
&& (p.getTagsSent().size() > 0) ) {
|
||||
_context.sessionKeyManager().tagsDelivered(p.getTo().getPublicKey(),
|
||||
p.getKeyUsed(),
|
||||
p.getTagsSent());
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Packet acked after " + p.getAckTime() + "ms: " + p);
|
||||
}
|
||||
if (highestRTT > 0) {
|
||||
con.getOptions().updateRTT(highestRTT);
|
||||
}
|
||||
}
|
||||
|
||||
boolean fastAck = adjustWindow(con, isNew, packet.getSequenceNum(), numResends, (acked != null ? acked.size() : 0));
|
||||
con.eventOccurred();
|
||||
if (fastAck) {
|
||||
if (con.getLastSendTime() + con.getOptions().getRTT() < _context.clock().now()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Fast ack for dup " + packet);
|
||||
con.ackImmediately();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean adjustWindow(Connection con, boolean isNew, long sequenceNum, int numResends, int acked) {
|
||||
if ( (!isNew) && (sequenceNum > 0) ) {
|
||||
// dup real packet
|
||||
int oldSize = con.getOptions().getWindowSize();
|
||||
oldSize >>>= 1;
|
||||
if (oldSize <= 0)
|
||||
oldSize = 1;
|
||||
con.getOptions().setWindowSize(oldSize);
|
||||
return true;
|
||||
} else if (numResends > 0) {
|
||||
// window sizes are shrunk on resend, not on ack
|
||||
} else {
|
||||
if (acked > 0) {
|
||||
long lowest = con.getHighestAckedThrough();
|
||||
if (lowest >= con.getCongestionWindowEnd()) {
|
||||
// new packet that ack'ed uncongested data, or an empty ack
|
||||
int newWindowSize = con.getOptions().getWindowSize();
|
||||
newWindowSize += 1; // acked; // 1
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("New window size " + newWindowSize + " (#resends: " + numResends
|
||||
+ ") for " + con);
|
||||
con.getOptions().setWindowSize(newWindowSize);
|
||||
con.setCongestionWindowEnd(newWindowSize + lowest);
|
||||
}
|
||||
} else {
|
||||
// received a message that doesn't contain a new ack
|
||||
|
||||
// ehh. cant do this, as we SACK and the acks may be
|
||||
// received out of order:
|
||||
// Alice: RECEIVE 2
|
||||
// Alice: SEND ack 2 nack 1
|
||||
// Alice: RECEIVE 1
|
||||
// Alice: SEND ack 2
|
||||
// Bob: RECEIVE ack 2
|
||||
// Bob: RECEIVE ack 2 nack 1 <-- NOT bad
|
||||
|
||||
/*
|
||||
if (con.getUnackedPacketsSent() > 0) {
|
||||
// peer got a dup
|
||||
int oldSize = con.getOptions().getWindowSize();
|
||||
oldSize >>>= 1;
|
||||
if (oldSize <= 0)
|
||||
oldSize = 1;
|
||||
con.getOptions().setWindowSize(oldSize);
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure this packet is ok and that we can continue processing its data.
|
||||
*
|
||||
* @return true if the packet is ok for this connection, false if we shouldn't
|
||||
* continue processing.
|
||||
*/
|
||||
private boolean verifyPacket(Packet packet, Connection con) throws I2PException {
|
||||
if (packet.isFlagSet(Packet.FLAG_RESET)) {
|
||||
verifyReset(packet, con);
|
||||
return false;
|
||||
} else {
|
||||
verifySignature(packet, con);
|
||||
|
||||
if (con.getSendStreamId() == null) {
|
||||
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
|
||||
con.setSendStreamId(packet.getReceiveStreamId());
|
||||
con.setRemotePeer(packet.getOptionalFrom());
|
||||
return true;
|
||||
} else {
|
||||
// neither RST nor SYN and we dont have the stream id yet? nuh uh
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Packet without RST or SYN where we dont know stream ID: "
|
||||
+ packet);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!DataHelper.eq(con.getSendStreamId(), packet.getReceiveStreamId())) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Packet received with the wrong reply stream id: "
|
||||
+ con + " / " + packet);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure this RST packet is valid, and if it is, act on it.
|
||||
*/
|
||||
private void verifyReset(Packet packet, Connection con) {
|
||||
if (DataHelper.eq(con.getReceiveStreamId(), packet.getSendStreamId())) {
|
||||
boolean ok = packet.verifySignature(_context, packet.getOptionalFrom(), null);
|
||||
if (!ok) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Received unsigned / forged RST on " + con);
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Reset received");
|
||||
// ok, valid RST
|
||||
con.resetReceived();
|
||||
con.eventOccurred();
|
||||
|
||||
// no further processing
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Received a packet for the wrong connection? wtf: "
|
||||
+ con + " / " + packet);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the signature if necessary.
|
||||
*
|
||||
* @throws I2PException if the signature was necessary and it was invalid
|
||||
*/
|
||||
private void verifySignature(Packet packet, Connection con) throws I2PException {
|
||||
// verify the signature if necessary
|
||||
if (con.getOptions().getRequireFullySigned() ||
|
||||
packet.isFlagSet(Packet.FLAG_SYNCHRONIZE) ||
|
||||
packet.isFlagSet(Packet.FLAG_CLOSE) ) {
|
||||
// we need a valid signature
|
||||
Destination from = con.getRemotePeer();
|
||||
if (from == null)
|
||||
from = packet.getOptionalFrom();
|
||||
boolean sigOk = packet.verifySignature(_context, from, null);
|
||||
if (!sigOk) {
|
||||
throw new I2PException("Received unsigned / forged packet: " + packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import net.i2p.I2PException;
|
||||
|
||||
/**
|
||||
* Bridge to allow accepting new connections
|
||||
*
|
||||
*/
|
||||
public class I2PServerSocketFull implements I2PServerSocket {
|
||||
private I2PSocketManagerFull _socketManager;
|
||||
|
||||
public I2PServerSocketFull(I2PSocketManagerFull mgr) {
|
||||
_socketManager = mgr;
|
||||
}
|
||||
|
||||
public I2PSocket accept() throws I2PException {
|
||||
return _socketManager.receiveSocket();
|
||||
}
|
||||
|
||||
public void close() { _socketManager.getConnectionManager().setAllowIncomingConnections(false); }
|
||||
|
||||
public I2PSocketManager getManager() { return _socketManager; }
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
/**
|
||||
* Bridge between the full streaming lib and the I2PSocket API
|
||||
*
|
||||
*/
|
||||
public class I2PSocketFull implements I2PSocket {
|
||||
private Connection _connection;
|
||||
private I2PSocket.SocketErrorListener _listener;
|
||||
|
||||
public I2PSocketFull(Connection con) {
|
||||
_connection = con;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
if (_connection.getIsConnected()) {
|
||||
_connection.getOutputStream().close();
|
||||
_connection.disconnect(true);
|
||||
} else {
|
||||
throw new IOException("Not connected");
|
||||
}
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
return _connection.getInputStream();
|
||||
}
|
||||
|
||||
public I2PSocketOptions getOptions() {
|
||||
return _connection.getOptions();
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
return _connection.getOutputStream();
|
||||
}
|
||||
|
||||
public Destination getPeerDestination() {
|
||||
return _connection.getRemotePeer();
|
||||
}
|
||||
|
||||
public long getReadTimeout() {
|
||||
return _connection.getOptions().getReadTimeout();
|
||||
}
|
||||
|
||||
public Destination getThisDestination() {
|
||||
return _connection.getSession().getMyDestination();
|
||||
}
|
||||
|
||||
public void setOptions(I2PSocketOptions options) {
|
||||
if (options instanceof ConnectionOptions)
|
||||
_connection.setOptions((ConnectionOptions)options);
|
||||
else
|
||||
_connection.setOptions(new ConnectionOptions(options));
|
||||
}
|
||||
|
||||
public void setReadTimeout(long ms) {
|
||||
_connection.getOptions().setReadTimeout(ms);
|
||||
}
|
||||
|
||||
public void setSocketErrorListener(I2PSocket.SocketErrorListener lsnr) {
|
||||
_listener = lsnr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
|
||||
/**
|
||||
* Centralize the coordination and multiplexing of the local client's streaming.
|
||||
* There should be one I2PSocketManager for each I2PSession, and if an application
|
||||
* is sending and receiving data through the streaming library using an
|
||||
* I2PSocketManager, it should not attempt to call I2PSession's setSessionListener
|
||||
* or receive any messages with its .receiveMessage
|
||||
*
|
||||
*/
|
||||
public class I2PSocketManagerFull implements I2PSocketManager {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private I2PSession _session;
|
||||
private I2PServerSocketFull _serverSocket;
|
||||
private ConnectionOptions _defaultOptions;
|
||||
private long _acceptTimeout;
|
||||
private String _name;
|
||||
private static int __managerId = 0;
|
||||
private ConnectionManager _connectionManager;
|
||||
|
||||
/**
|
||||
* How long to wait for the client app to accept() before sending back CLOSE?
|
||||
* This includes the time waiting in the queue. Currently set to 5 seconds.
|
||||
*/
|
||||
private static final long ACCEPT_TIMEOUT_DEFAULT = 5*1000;
|
||||
|
||||
public I2PSocketManagerFull() {
|
||||
_context = null;
|
||||
_session = null;
|
||||
}
|
||||
public I2PSocketManagerFull(I2PAppContext context, I2PSession session, Properties opts, String name) {
|
||||
this();
|
||||
init(context, session, opts, name);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void init(I2PAppContext context, I2PSession session, Properties opts, String name) {
|
||||
_context = context;
|
||||
_session = session;
|
||||
_log = _context.logManager().getLog(I2PSocketManagerFull.class);
|
||||
_connectionManager = new ConnectionManager(_context, _session);
|
||||
_name = name + " " + (++__managerId);
|
||||
_acceptTimeout = ACCEPT_TIMEOUT_DEFAULT;
|
||||
_defaultOptions = new ConnectionOptions(opts);
|
||||
_serverSocket = new I2PServerSocketFull(this);
|
||||
}
|
||||
|
||||
public I2PSession getSession() {
|
||||
return _session;
|
||||
}
|
||||
|
||||
public ConnectionManager getConnectionManager() {
|
||||
return _connectionManager;
|
||||
}
|
||||
|
||||
public I2PSocket receiveSocket() throws I2PException {
|
||||
if (_session.isClosed()) throw new I2PException("Session closed");
|
||||
Connection con = _connectionManager.getConnectionHandler().accept(-1);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("receiveSocket() called: " + con);
|
||||
if (con != null) {
|
||||
I2PSocketFull sock = new I2PSocketFull(con);
|
||||
con.setSocket(sock);
|
||||
return sock;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ping the specified peer, returning true if they replied to the ping within
|
||||
* the timeout specified, false otherwise. This call blocks.
|
||||
*
|
||||
*/
|
||||
public boolean ping(Destination peer, long timeoutMs) {
|
||||
return _connectionManager.ping(peer, timeoutMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* How long should we wait for the client to .accept() a socket before
|
||||
* sending back a NACK/Close?
|
||||
*
|
||||
* @param ms milliseconds to wait, maximum
|
||||
*/
|
||||
public void setAcceptTimeout(long ms) { _acceptTimeout = ms; }
|
||||
public long getAcceptTimeout() { return _acceptTimeout; }
|
||||
|
||||
public void setDefaultOptions(I2PSocketOptions options) {
|
||||
_defaultOptions = new ConnectionOptions(options);
|
||||
}
|
||||
|
||||
public I2PSocketOptions getDefaultOptions() {
|
||||
return _defaultOptions;
|
||||
}
|
||||
|
||||
public I2PServerSocket getServerSocket() {
|
||||
_connectionManager.setAllowIncomingConnections(true);
|
||||
return _serverSocket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new connected socket (block until the socket is created)
|
||||
*
|
||||
* @param peer Destination to connect to
|
||||
* @param options I2P socket options to be used for connecting
|
||||
*
|
||||
* @throws NoRouteToHostException if the peer is not found or not reachable
|
||||
* @throws I2PException if there is some other I2P-related problem
|
||||
*/
|
||||
public I2PSocket connect(Destination peer, I2PSocketOptions options)
|
||||
throws I2PException, NoRouteToHostException {
|
||||
if (_connectionManager.getSession().isClosed())
|
||||
throw new I2PException("Session is closed");
|
||||
Connection con = _connectionManager.connect(peer, new ConnectionOptions(options));
|
||||
I2PSocketFull socket = new I2PSocketFull(con);
|
||||
con.setSocket(socket);
|
||||
if (con.getConnectionError() != null) {
|
||||
con.disconnect(false);
|
||||
throw new NoRouteToHostException(con.getConnectionError());
|
||||
}
|
||||
return socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new connected socket (block until the socket is created)
|
||||
*
|
||||
* @param peer Destination to connect to
|
||||
*
|
||||
* @throws NoRouteToHostException if the peer is not found or not reachable
|
||||
* @throws I2PException if there is some other I2P-related problem
|
||||
*/
|
||||
public I2PSocket connect(Destination peer) throws I2PException, NoRouteToHostException {
|
||||
return connect(peer, _defaultOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the socket manager, freeing all the associated resources. This
|
||||
* method will block untill all the managed sockets are closed.
|
||||
*
|
||||
*/
|
||||
public void destroySocketManager() {
|
||||
_connectionManager.disconnectAllHard();
|
||||
_connectionManager.setAllowIncomingConnections(false);
|
||||
// should we destroy the _session too?
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a set of currently connected I2PSockets, either initiated locally or remotely.
|
||||
*
|
||||
*/
|
||||
public Set listSockets() {
|
||||
Set connections = _connectionManager.listConnections();
|
||||
Set rv = new HashSet(connections.size());
|
||||
for (Iterator iter = connections.iterator(); iter.hasNext(); ) {
|
||||
Connection con = (Connection)iter.next();
|
||||
if (con.getSocket() != null)
|
||||
rv.add(con.getSocket());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
public String getName() { return _name; }
|
||||
public void setName(String name) { _name = name; }
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class MessageHandler implements I2PSessionListener {
|
||||
private ConnectionManager _manager;
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
|
||||
public MessageHandler(I2PAppContext ctx, ConnectionManager mgr) {
|
||||
_manager = mgr;
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(MessageHandler.class);
|
||||
}
|
||||
|
||||
/** Instruct the client that the given session has received a message with
|
||||
* size # of bytes.
|
||||
* @param session session to notify
|
||||
* @param msgId message number available
|
||||
* @param size size of the message
|
||||
*/
|
||||
public void messageAvailable(I2PSession session, int msgId, long size) {
|
||||
byte data[] = null;
|
||||
try {
|
||||
data = session.receiveMessage(msgId);
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error receiving the message", ise);
|
||||
return;
|
||||
}
|
||||
Packet packet = new Packet();
|
||||
try {
|
||||
packet.readPacket(data, 0, data.length);
|
||||
_manager.getPacketHandler().receivePacket(packet);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Received an invalid packet", iae);
|
||||
}
|
||||
}
|
||||
|
||||
/** Instruct the client that the session specified seems to be under attack
|
||||
* and that the client may wish to move its destination to another router.
|
||||
* @param session session to report abuse to
|
||||
* @param severity how bad the abuse is
|
||||
*/
|
||||
public void reportAbuse(I2PSession session, int severity) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Abuse reported with severity " + severity);
|
||||
_manager.disconnectAllHard();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the client that the session has been terminated
|
||||
*
|
||||
*/
|
||||
public void disconnected(I2PSession session) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("I2PSession disconnected");
|
||||
_manager.disconnectAllHard();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the client that some error occurred
|
||||
*
|
||||
*/
|
||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("error occurred: " + message, error);
|
||||
//_manager.disconnectAllHard();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Stream that can be given messages out of order
|
||||
* yet present them in order.
|
||||
*
|
||||
*/
|
||||
public class MessageInputStream extends InputStream {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
/**
|
||||
* List of ByteArray objects of data ready to be read,
|
||||
* with the first ByteArray at index 0, and the next
|
||||
* actual byte to be read at _readyDataBlockIndex of
|
||||
* that array.
|
||||
*
|
||||
*/
|
||||
private List _readyDataBlocks;
|
||||
private int _readyDataBlockIndex;
|
||||
/** highest message ID used in the readyDataBlocks */
|
||||
private long _highestReadyBlockId;
|
||||
/** highest overall message ID */
|
||||
private long _highestBlockId;
|
||||
/**
|
||||
* Message ID (Long) to ByteArray for blocks received
|
||||
* out of order when there are lower IDs not yet
|
||||
* received
|
||||
*/
|
||||
private Map _notYetReadyBlocks;
|
||||
/**
|
||||
* if we have received a flag saying there won't be later messages, EOF
|
||||
* after we have cleared what we have received.
|
||||
*/
|
||||
private boolean _closeReceived;
|
||||
/** if we don't want any more data, ignore the data */
|
||||
private boolean _locallyClosed;
|
||||
private int _readTimeout;
|
||||
private IOException _streamError;
|
||||
private long _readTotal;
|
||||
|
||||
private Object _dataLock;
|
||||
|
||||
public MessageInputStream(I2PAppContext ctx) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(MessageInputStream.class);
|
||||
_readyDataBlocks = new ArrayList(4);
|
||||
_readyDataBlockIndex = 0;
|
||||
_highestReadyBlockId = -1;
|
||||
_highestBlockId = -1;
|
||||
_readTimeout = -1;
|
||||
_readTotal = 0;
|
||||
_notYetReadyBlocks = new HashMap(4);
|
||||
_dataLock = new Object();
|
||||
_closeReceived = false;
|
||||
_locallyClosed = false;
|
||||
}
|
||||
|
||||
/** What is the highest block ID we've completely received through? */
|
||||
public long getHighestReadyBockId() {
|
||||
synchronized (_dataLock) {
|
||||
return _highestReadyBlockId;
|
||||
}
|
||||
}
|
||||
|
||||
public long getHighestBlockId() {
|
||||
synchronized (_dataLock) {
|
||||
return _highestBlockId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the message IDs that are holes in our sequence - ones
|
||||
* past the highest ready ID and below the highest received message
|
||||
* ID. This may return null if there are no such IDs.
|
||||
*
|
||||
*/
|
||||
public long[] getNacks() {
|
||||
synchronized (_dataLock) {
|
||||
return locked_getNacks();
|
||||
}
|
||||
}
|
||||
private long[] locked_getNacks() {
|
||||
List ids = null;
|
||||
for (long i = _highestReadyBlockId + 1; i < _highestBlockId; i++) {
|
||||
Long l = new Long(i);
|
||||
if (_notYetReadyBlocks.containsKey(l)) {
|
||||
// ACK
|
||||
} else {
|
||||
if (ids == null)
|
||||
ids = new ArrayList(4);
|
||||
ids.add(l);
|
||||
}
|
||||
}
|
||||
if (ids != null) {
|
||||
long rv[] = new long[ids.size()];
|
||||
for (int i = 0; i < rv.length; i++)
|
||||
rv[i] = ((Long)ids.get(i)).longValue();
|
||||
return rv;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void updateAcks(PacketLocal packet) {
|
||||
synchronized (_dataLock) {
|
||||
packet.setAckThrough(_highestBlockId);
|
||||
packet.setNacks(locked_getNacks());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ascending list of block IDs greater than the highest
|
||||
* ready block ID, or null if there aren't any.
|
||||
*
|
||||
*/
|
||||
public long[] getOutOfOrderBlocks() {
|
||||
long blocks[] = null;
|
||||
synchronized (_dataLock) {
|
||||
int num = _notYetReadyBlocks.size();
|
||||
if (num <= 0) return null;
|
||||
blocks = new long[num];
|
||||
int i = 0;
|
||||
for (Iterator iter = _notYetReadyBlocks.keySet().iterator(); iter.hasNext(); ) {
|
||||
Long id = (Long)iter.next();
|
||||
blocks[i] = id.longValue();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
Arrays.sort(blocks);
|
||||
return blocks;
|
||||
}
|
||||
|
||||
/** how many blocks have we received that we still have holes before? */
|
||||
public int getOutOfOrderBlockCount() {
|
||||
synchronized (_dataLock) {
|
||||
return _notYetReadyBlocks.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* how long a read() call should block (if less than 0, block indefinitely,
|
||||
* but if it is 0, do not block at all)
|
||||
*/
|
||||
public int getReadTimeout() { return _readTimeout; }
|
||||
public void setReadTimeout(int timeout) { _readTimeout = timeout; }
|
||||
|
||||
public void closeReceived() {
|
||||
synchronized (_dataLock) {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("Close received, ready bytes: ");
|
||||
long available = 0;
|
||||
for (int i = 0; i < _readyDataBlocks.size(); i++)
|
||||
available += ((ByteArray)_readyDataBlocks.get(i)).getData().length;
|
||||
available -= _readyDataBlockIndex;
|
||||
buf.append(available);
|
||||
buf.append(" blocks: ").append(_readyDataBlocks.size());
|
||||
|
||||
buf.append(" not ready blocks: ");
|
||||
long notAvailable = 0;
|
||||
for (Iterator iter = _notYetReadyBlocks.keySet().iterator(); iter.hasNext(); ) {
|
||||
Long id = (Long)iter.next();
|
||||
ByteArray ba = (ByteArray)_notYetReadyBlocks.get(id);
|
||||
buf.append(id).append(" ");
|
||||
|
||||
if (ba.getData() != null)
|
||||
notAvailable += ba.getData().length;
|
||||
}
|
||||
|
||||
buf.append("not ready bytes: ").append(notAvailable);
|
||||
buf.append(" highest ready block: ").append(_highestReadyBlockId);
|
||||
|
||||
_log.debug(buf.toString(), new Exception("closed"));
|
||||
}
|
||||
_closeReceived = true;
|
||||
_dataLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A new message has arrived - toss it on the appropriate queue (moving
|
||||
* previously pending messages to the ready queue if it fills the gap, etc).
|
||||
*
|
||||
* @return true if this is a new packet, false if it is a dup
|
||||
*/
|
||||
public boolean messageReceived(long messageId, byte payload[]) {
|
||||
synchronized (_dataLock) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("received " + messageId + " with " + payload.length);
|
||||
if (messageId <= _highestReadyBlockId) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("ignoring dup message " + messageId);
|
||||
return false; // already received
|
||||
}
|
||||
if (messageId > _highestBlockId)
|
||||
_highestBlockId = messageId;
|
||||
|
||||
if (_highestReadyBlockId + 1 == messageId) {
|
||||
if (!_locallyClosed && payload.length > 0) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("accepting bytes as ready: " + payload.length);
|
||||
_readyDataBlocks.add(new ByteArray(payload));
|
||||
}
|
||||
_highestReadyBlockId = messageId;
|
||||
long cur = _highestReadyBlockId + 1;
|
||||
// now pull in any previously pending blocks
|
||||
while (_notYetReadyBlocks.containsKey(new Long(cur))) {
|
||||
ByteArray ba = (ByteArray)_notYetReadyBlocks.remove(new Long(cur));
|
||||
if ( (ba != null) && (ba.getData() != null) && (ba.getData().length > 0) ) {
|
||||
_readyDataBlocks.add(ba);
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("making ready the block " + cur);
|
||||
cur++;
|
||||
_highestReadyBlockId++;
|
||||
}
|
||||
_dataLock.notifyAll();
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("message is out of order: " + messageId);
|
||||
if (_locallyClosed) // dont need the payload, just the msgId in order
|
||||
_notYetReadyBlocks.put(new Long(messageId), new ByteArray(null));
|
||||
else
|
||||
_notYetReadyBlocks.put(new Long(messageId), new ByteArray(payload));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
if (_locallyClosed) throw new IOException("Already locally closed");
|
||||
throwAnyError();
|
||||
long expiration = -1;
|
||||
if (_readTimeout > 0)
|
||||
expiration = _readTimeout + System.currentTimeMillis();
|
||||
synchronized (_dataLock) {
|
||||
while (_readyDataBlocks.size() <= 0) {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("read() with readyBlocks.size = " + _readyDataBlocks.size() + " on " + toString());
|
||||
|
||||
if ( (_notYetReadyBlocks.size() <= 0) && (_closeReceived) ) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("read() got EOF after " + _readTotal + " " + toString());
|
||||
return -1;
|
||||
} else {
|
||||
if (_readTimeout < 0) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("read() with no timeout: " + toString());
|
||||
try { _dataLock.wait(); } catch (InterruptedException ie) { }
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("read() with no timeout complete: " + toString());
|
||||
throwAnyError();
|
||||
} else if (_readTimeout > 0) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("read() with timeout: " + _readTimeout + ": " + toString());
|
||||
try { _dataLock.wait(_readTimeout); } catch (InterruptedException ie) { }
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("read() with timeout complete: " + _readTimeout + ": " + toString());
|
||||
throwAnyError();
|
||||
} else { // readTimeout == 0
|
||||
// noop, don't block
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("read() with nonblocking setup: " + toString());
|
||||
}
|
||||
if (_readyDataBlocks.size() <= 0) {
|
||||
if ( (_readTimeout > 0) && (expiration > System.currentTimeMillis()) )
|
||||
throw new InterruptedIOException("Timeout reading (timeout=" + _readTimeout + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("read() readyBlocks = " + _readyDataBlocks.size() + ": " + toString());
|
||||
|
||||
// either was already ready, or we wait()ed and it arrived
|
||||
ByteArray cur = (ByteArray)_readyDataBlocks.get(0);
|
||||
byte rv = cur.getData()[_readyDataBlockIndex];
|
||||
_readyDataBlockIndex++;
|
||||
if (cur.getData().length <= _readyDataBlockIndex) {
|
||||
_readyDataBlockIndex = 0;
|
||||
_readyDataBlocks.remove(0);
|
||||
}
|
||||
_readTotal++;
|
||||
return (rv < 0 ? rv + 256 : rv);
|
||||
}
|
||||
}
|
||||
|
||||
public int available() throws IOException {
|
||||
if (_locallyClosed) throw new IOException("Already closed, you wanker");
|
||||
throwAnyError();
|
||||
synchronized (_dataLock) {
|
||||
if (_readyDataBlocks.size() <= 0)
|
||||
return 0;
|
||||
int numBytes = 0;
|
||||
for (int i = 0; i < _readyDataBlocks.size(); i++) {
|
||||
ByteArray cur = (ByteArray)_readyDataBlocks.get(i);
|
||||
if (i == 0)
|
||||
numBytes += cur.getData().length - _readyDataBlockIndex;
|
||||
else
|
||||
numBytes += cur.getData().length;
|
||||
}
|
||||
return numBytes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* How many bytes are queued up for reading (or sitting in the out-of-order
|
||||
* buffer)?
|
||||
*
|
||||
*/
|
||||
public int getTotalQueuedSize() {
|
||||
synchronized (_dataLock) {
|
||||
if (_locallyClosed) return 0;
|
||||
int numBytes = 0;
|
||||
for (int i = 0; i < _readyDataBlocks.size(); i++) {
|
||||
ByteArray cur = (ByteArray)_readyDataBlocks.get(i);
|
||||
if (i == 0)
|
||||
numBytes += cur.getData().length - _readyDataBlockIndex;
|
||||
else
|
||||
numBytes += cur.getData().length;
|
||||
}
|
||||
for (Iterator iter = _notYetReadyBlocks.values().iterator(); iter.hasNext(); ) {
|
||||
ByteArray cur = (ByteArray)iter.next();
|
||||
numBytes += cur.getData().length;
|
||||
}
|
||||
return numBytes;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
synchronized (_dataLock) {
|
||||
_readyDataBlocks.clear();
|
||||
|
||||
// we don't need the data, but we do need to keep track of the messageIds
|
||||
// received, so we can ACK accordingly
|
||||
for (Iterator iter = _notYetReadyBlocks.values().iterator(); iter.hasNext(); ) {
|
||||
ByteArray ba = (ByteArray)iter.next();
|
||||
ba.setData(null);
|
||||
}
|
||||
_locallyClosed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream b0rked, die with the given error
|
||||
*
|
||||
*/
|
||||
void streamErrorOccurred(IOException ioe) {
|
||||
_streamError = ioe;
|
||||
synchronized (_dataLock) {
|
||||
_dataLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void throwAnyError() throws IOException {
|
||||
if (_streamError != null) {
|
||||
IOException ioe = _streamError;
|
||||
_streamError = null;
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class MessageOutputStream extends OutputStream {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private byte _buf[];
|
||||
private int _valid;
|
||||
private Object _dataLock;
|
||||
private DataReceiver _dataReceiver;
|
||||
private IOException _streamError;
|
||||
private boolean _closed;
|
||||
private long _written;
|
||||
private int _writeTimeout;
|
||||
|
||||
public MessageOutputStream(I2PAppContext ctx, DataReceiver receiver) {
|
||||
this(ctx, receiver, Packet.MAX_PAYLOAD_SIZE);
|
||||
}
|
||||
public MessageOutputStream(I2PAppContext ctx, DataReceiver receiver, int bufSize) {
|
||||
super();
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(MessageOutputStream.class);
|
||||
_buf = new byte[bufSize];
|
||||
_dataReceiver = receiver;
|
||||
_dataLock = new Object();
|
||||
_written = 0;
|
||||
_closed = false;
|
||||
_writeTimeout = -1;
|
||||
}
|
||||
|
||||
public void setWriteTimeout(int ms) { _writeTimeout = ms; }
|
||||
public int getWriteTimeout() { return _writeTimeout; }
|
||||
|
||||
public void write(byte b[]) throws IOException {
|
||||
write(b, 0, b.length);
|
||||
}
|
||||
|
||||
public void write(byte b[], int off, int len) throws IOException {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("write(b[], " + off + ", " + len + ")");
|
||||
int cur = off;
|
||||
int remaining = len;
|
||||
while (remaining > 0) {
|
||||
WriteStatus ws = null;
|
||||
// we do any waiting outside the synchronized() block because we
|
||||
// want to allow other threads to flushAvailable() whenever they want.
|
||||
// this is the only method that *adds* to the _buf, and all
|
||||
// code that reads from it is synchronized
|
||||
synchronized (_dataLock) {
|
||||
if (_valid + remaining < _buf.length) {
|
||||
// simply buffer the data, no flush
|
||||
System.arraycopy(b, cur, _buf, _valid, remaining);
|
||||
_valid += remaining;
|
||||
cur += remaining;
|
||||
_written += remaining;
|
||||
remaining = 0;
|
||||
} else {
|
||||
// buffer whatever we can fit then flush,
|
||||
// repeating until we've pushed all of the
|
||||
// data through
|
||||
int toWrite = _buf.length - _valid;
|
||||
System.arraycopy(b, cur, _buf, _valid, toWrite);
|
||||
remaining -= toWrite;
|
||||
cur += toWrite;
|
||||
_valid = _buf.length;
|
||||
ws = _dataReceiver.writeData(_buf, 0, _valid);
|
||||
_written += _valid;
|
||||
_valid = 0;
|
||||
throwAnyError();
|
||||
}
|
||||
}
|
||||
if (ws != null) {
|
||||
// ok, we've actually added a new packet - lets wait until
|
||||
// its accepted into the queue before moving on (so that we
|
||||
// dont fill our buffer instantly)
|
||||
ws.waitForAccept(_writeTimeout);
|
||||
if (!ws.writeAccepted()) {
|
||||
if (_writeTimeout > 0)
|
||||
throw new InterruptedIOException("Write not accepted within timeout");
|
||||
else
|
||||
throw new IOException("Write not accepted into the queue");
|
||||
}
|
||||
}
|
||||
}
|
||||
throwAnyError();
|
||||
}
|
||||
|
||||
public void write(int b) throws IOException {
|
||||
write(new byte[] { (byte)b }, 0, 1);
|
||||
throwAnyError();
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
WriteStatus ws = null;
|
||||
synchronized (_dataLock) {
|
||||
ws = _dataReceiver.writeData(_buf, 0, _valid);
|
||||
_written += _valid;
|
||||
_valid = 0;
|
||||
_dataLock.notifyAll();
|
||||
}
|
||||
|
||||
ws.waitForCompletion(_writeTimeout);
|
||||
if (ws.writeFailed() && (_writeTimeout > 0) )
|
||||
throw new InterruptedIOException("Timed out during write");
|
||||
else if (ws.writeFailed())
|
||||
throw new IOException("Write failed");
|
||||
throwAnyError();
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
_closed = true;
|
||||
flush();
|
||||
_log.debug("Output stream closed after writing " + _written);
|
||||
}
|
||||
public void closeInternal() {
|
||||
_closed = true;
|
||||
_streamError = new IOException("Closed internally");
|
||||
synchronized (_dataLock) {
|
||||
// flush any data, but don't wait for it
|
||||
if (_valid > 0) {
|
||||
_dataReceiver.writeData(_buf, 0, _valid);
|
||||
_written += _valid;
|
||||
_valid = 0;
|
||||
}
|
||||
_dataLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getClosed() { return _closed; }
|
||||
|
||||
private void throwAnyError() throws IOException {
|
||||
if (_streamError != null) {
|
||||
IOException ioe = _streamError;
|
||||
_streamError = null;
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
|
||||
void streamErrorOccurred(IOException ioe) {
|
||||
_streamError = ioe;
|
||||
}
|
||||
|
||||
/**
|
||||
* called whenever the engine wants to push more data to the
|
||||
* peer
|
||||
*
|
||||
* @return true if the data was flushed
|
||||
*/
|
||||
void flushAvailable(DataReceiver target) throws IOException {
|
||||
flushAvailable(target, true);
|
||||
}
|
||||
void flushAvailable(DataReceiver target, boolean blocking) throws IOException {
|
||||
WriteStatus ws = null;
|
||||
synchronized (_dataLock) {
|
||||
ws = target.writeData(_buf, 0, _valid);
|
||||
_written += _valid;
|
||||
_valid = 0;
|
||||
_dataLock.notifyAll();
|
||||
}
|
||||
if (blocking) {
|
||||
ws.waitForAccept(_writeTimeout);
|
||||
if (ws.writeFailed())
|
||||
throw new IOException("Flush available failed");
|
||||
else if (!ws.writeAccepted())
|
||||
throw new InterruptedIOException("Flush available timed out");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
public interface DataReceiver {
|
||||
/**
|
||||
* Nonblocking write
|
||||
*/
|
||||
public WriteStatus writeData(byte buf[], int off, int size);
|
||||
}
|
||||
|
||||
public interface WriteStatus {
|
||||
/** wait until the data written either fails or succeeds */
|
||||
public void waitForCompletion(int maxWaitMs);
|
||||
/**
|
||||
* wait until the data written is accepted into the outbound pool,
|
||||
* which we throttle rather than accept arbitrary data and queue
|
||||
*/
|
||||
public void waitForAccept(int maxWaitMs);
|
||||
/** the write was accepted. aka did the socket not close? */
|
||||
public boolean writeAccepted();
|
||||
/** did the write fail? */
|
||||
public boolean writeFailed();
|
||||
/** did the write succeed? */
|
||||
public boolean writeSuccessful();
|
||||
}
|
||||
}
|
||||
559
apps/streaming/java/src/net/i2p/client/streaming/Packet.java
Normal file
559
apps/streaming/java/src/net/i2p/client/streaming/Packet.java
Normal file
@@ -0,0 +1,559 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.util.Arrays;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
|
||||
/**
|
||||
* Contain a single packet transferred as part of a streaming connection.
|
||||
* The data format is as follows:<ul>
|
||||
* <li>{@link #getSendStreamId sendStreamId} [4 byte value]</li>
|
||||
* <li>{@link #getReceiveStreamId receiveStreamId} [4 byte value]</li>
|
||||
* <li>{@link #getSequenceNum sequenceNum} [4 byte unsigned integer]</li>
|
||||
* <li>{@link #getAckThrough ackThrough} [4 byte unsigned integer]</li>
|
||||
* <li>number of NACKs [1 byte unsigned integer]</li>
|
||||
* <li>that many {@link #getNacks NACKs}</li>
|
||||
* <li>{@link #getResendDelay resendDelay} [1 byte integer]</li>
|
||||
* <li>flags [2 byte value]</li>
|
||||
* <li>option data size [2 byte integer]</li>
|
||||
* <li>option data specified by those flags [0 or more bytes]</li>
|
||||
* <li>payload [remaining packet size]</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>The flags field above specifies some metadata about the packet, and in
|
||||
* turn may require certain additional data to be included. The flags are
|
||||
* as follows (with any data structures specified added to the options area
|
||||
* in the given order):</p><ol>
|
||||
* <li>{@link #FLAG_SYNCHRONIZE}: no option data</li>
|
||||
* <li>{@link #FLAG_CLOSE}: no option data</li>
|
||||
* <li>{@link #FLAG_RESET}: no option data</li>
|
||||
* <li>{@link #FLAG_SIGNATURE_INCLUDED}: {@link net.i2p.data.Signature}</li>
|
||||
* <li>{@link #FLAG_SIGNATURE_REQUESTED}: no option data</li>
|
||||
* <li>{@link #FLAG_FROM_INCLUDED}: {@link net.i2p.data.Destination}</li>
|
||||
* <li>{@link #FLAG_DELAY_REQUESTED}: 1 byte integer</li>
|
||||
* <li>{@link #FLAG_MAX_PACKET_SIZE_INCLUDED}: 2 byte integer</li>
|
||||
* <li>{@link #FLAG_PROFILE_INTERACTIVE}: no option data</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>If the signature is included, it uses the Destination's DSA key
|
||||
* to sign the entire header and payload with the space in the options
|
||||
* for the signature being set to all zeroes.</p>
|
||||
*
|
||||
* <p>If the sequenceNum is 0 and the SYN is not set, this is a plain ACK
|
||||
* packet that should not be ACKed</p>
|
||||
*
|
||||
*/
|
||||
public class Packet {
|
||||
private byte _sendStreamId[];
|
||||
private byte _receiveStreamId[];
|
||||
private long _sequenceNum;
|
||||
private long _ackThrough;
|
||||
private long _nacks[];
|
||||
private int _resendDelay;
|
||||
private int _flags;
|
||||
private byte _payload[];
|
||||
// the next four are set only if the flags say so
|
||||
private Signature _optionSignature;
|
||||
private Destination _optionFrom;
|
||||
private int _optionDelay;
|
||||
private int _optionMaxSize;
|
||||
|
||||
/**
|
||||
* The receiveStreamId will be set to this when the packet doesn't know
|
||||
* what ID will be assigned by the remote peer (aka this is the initial
|
||||
* synchronize packet)
|
||||
*
|
||||
*/
|
||||
public static final byte STREAM_ID_UNKNOWN[] = new byte[] { 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
/**
|
||||
* This packet is creating a new socket connection (if the receiveStreamId
|
||||
* is STREAM_ID_UNKNOWN) or it is acknowledging a request to
|
||||
* create a connection and in turn is accepting the socket.
|
||||
*
|
||||
*/
|
||||
public static final int FLAG_SYNCHRONIZE = (1 << 0);
|
||||
/**
|
||||
* The sender of this packet will not be sending any more payload data.
|
||||
*/
|
||||
public static final int FLAG_CLOSE = (1 << 1);
|
||||
/**
|
||||
* This packet is being sent to signify that the socket does not exist
|
||||
* (or, if in response to an initial synchronize packet, that the
|
||||
* connection was refused).
|
||||
*
|
||||
*/
|
||||
public static final int FLAG_RESET = (1 << 2);
|
||||
/**
|
||||
* This packet contains a DSA signature from the packet's sender. This
|
||||
* signature is within the packet options. All synchronize packets must
|
||||
* have this flag set.
|
||||
*
|
||||
*/
|
||||
public static final int FLAG_SIGNATURE_INCLUDED = (1 << 3);
|
||||
/**
|
||||
* This packet wants the recipient to include signatures on subsequent
|
||||
* packets sent to the creator of this packet.
|
||||
*/
|
||||
public static final int FLAG_SIGNATURE_REQUESTED = (1 << 4);
|
||||
/**
|
||||
* This packet includes the full I2P destination of the packet's sender.
|
||||
* The initial synchronize packet must have this flag set.
|
||||
*/
|
||||
public static final int FLAG_FROM_INCLUDED = (1 << 5);
|
||||
/**
|
||||
* This packet includes an explicit request for the recipient to delay
|
||||
* sending any packets with data for a given amount of time.
|
||||
*
|
||||
*/
|
||||
public static final int FLAG_DELAY_REQUESTED = (1 << 6);
|
||||
/**
|
||||
* This packet includes a request that the recipient not send any
|
||||
* subsequent packets with payloads greater than a specific size.
|
||||
* If not set and no prior value was delivered, the maximum value
|
||||
* will be assumed (approximately 32KB).
|
||||
*
|
||||
*/
|
||||
public static final int FLAG_MAX_PACKET_SIZE_INCLUDED = (1 << 7);
|
||||
/**
|
||||
* If set, this packet is travelling as part of an interactive flow,
|
||||
* meaning it is more lag sensitive than throughput sensitive. aka
|
||||
* send data ASAP rather than waiting around to send full packets.
|
||||
*
|
||||
*/
|
||||
public static final int FLAG_PROFILE_INTERACTIVE = (1 << 8);
|
||||
/**
|
||||
* If set, this packet is a ping (if sendStreamId is set) or a
|
||||
* ping reply (if receiveStreamId is set).
|
||||
*/
|
||||
public static final int FLAG_ECHO = (1 << 9);
|
||||
|
||||
public static final int DEFAULT_MAX_SIZE = 32*1024;
|
||||
private static final int MAX_DELAY_REQUEST = 65535;
|
||||
|
||||
/** what stream is this packet a part of? */
|
||||
public byte[] getSendStreamId() {
|
||||
if ( (_sendStreamId == null) || (DataHelper.eq(_sendStreamId, STREAM_ID_UNKNOWN)) )
|
||||
return null;
|
||||
else
|
||||
return _sendStreamId;
|
||||
}
|
||||
public void setSendStreamId(byte[] id) {
|
||||
_sendStreamId = id;
|
||||
if ( (id != null) && (DataHelper.eq(id, STREAM_ID_UNKNOWN)) )
|
||||
_sendStreamId = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream that replies should be sent on. if the
|
||||
* connection is still being built, this should be
|
||||
* null.
|
||||
*
|
||||
*/
|
||||
public byte[] getReceiveStreamId() {
|
||||
if ( (_receiveStreamId == null) || (DataHelper.eq(_receiveStreamId, STREAM_ID_UNKNOWN)) )
|
||||
return null;
|
||||
else
|
||||
return _receiveStreamId;
|
||||
}
|
||||
public void setReceiveStreamId(byte[] id) {
|
||||
_receiveStreamId = id;
|
||||
if ( (id != null) && (DataHelper.eq(id, STREAM_ID_UNKNOWN)) )
|
||||
_receiveStreamId = null;
|
||||
}
|
||||
|
||||
/** 0-indexed sequence number for this Packet in the sendStream */
|
||||
public long getSequenceNum() { return _sequenceNum; }
|
||||
public void setSequenceNum(long num) { _sequenceNum = num; }
|
||||
|
||||
/**
|
||||
* The highest packet sequence number that received
|
||||
* on the receiveStreamId. This field is ignored on the initial
|
||||
* connection packet (where receiveStreamId is the unknown id).
|
||||
*
|
||||
*/
|
||||
public long getAckThrough() { return _ackThrough; }
|
||||
public void setAckThrough(long id) { _ackThrough = id; }
|
||||
|
||||
/**
|
||||
* List of packet sequence numbers below the getAckThrough() value
|
||||
* have not been received. this may be null.
|
||||
*
|
||||
*/
|
||||
public long[] getNacks() { return _nacks; }
|
||||
public void setNacks(long nacks[]) { _nacks = nacks; }
|
||||
|
||||
/**
|
||||
* How long is the creator of this packet going to wait before
|
||||
* resending this packet (if it hasn't yet been ACKed). The
|
||||
* value is seconds since the packet was created.
|
||||
*
|
||||
*/
|
||||
public int getResendDelay() { return _resendDelay; }
|
||||
public void setResendDelay(int numSeconds) { _resendDelay = numSeconds; }
|
||||
|
||||
public static final int MAX_PAYLOAD_SIZE = 32*1024;
|
||||
|
||||
/** get the actual payload of the message. may be null */
|
||||
public byte[] getPayload() { return _payload; }
|
||||
public void setPayload(byte payload[]) {
|
||||
_payload = payload;
|
||||
if ( (payload != null) && (payload.length > MAX_PAYLOAD_SIZE) )
|
||||
throw new IllegalArgumentException("Too large payload: " + payload.length);
|
||||
}
|
||||
|
||||
/** is a particular flag set on this packet? */
|
||||
public boolean isFlagSet(int flag) { return 0 != (_flags & flag); }
|
||||
public void setFlag(int flag) { _flags |= flag; }
|
||||
public void setFlag(int flag, boolean set) {
|
||||
if (set)
|
||||
_flags |= flag;
|
||||
else
|
||||
_flags &= ~flag;
|
||||
}
|
||||
|
||||
/** the signature on the packet (only included if the flag for it is set) */
|
||||
public Signature getOptionalSignature() { return _optionSignature; }
|
||||
public void setOptionalSignature(Signature sig) {
|
||||
setFlag(FLAG_SIGNATURE_INCLUDED, sig != null);
|
||||
_optionSignature = sig;
|
||||
}
|
||||
|
||||
/** the sender of the packet (only included if the flag for it is set) */
|
||||
public Destination getOptionalFrom() { return _optionFrom; }
|
||||
public void setOptionalFrom(Destination from) {
|
||||
setFlag(FLAG_FROM_INCLUDED, from != null);
|
||||
if (from == null) throw new RuntimeException("from is null!?");
|
||||
_optionFrom = from;
|
||||
}
|
||||
|
||||
/**
|
||||
* How many milliseconds the sender of this packet wants the recipient
|
||||
* to wait before sending any more data (only valid if the flag for it is
|
||||
* set)
|
||||
*/
|
||||
public int getOptionalDelay() { return _optionDelay; }
|
||||
public void setOptionalDelay(int delayMs) {
|
||||
setFlag(FLAG_DELAY_REQUESTED, delayMs > 0);
|
||||
if (delayMs > MAX_DELAY_REQUEST)
|
||||
_optionDelay = MAX_DELAY_REQUEST;
|
||||
else if (delayMs < 0)
|
||||
_optionDelay = 0;
|
||||
else
|
||||
_optionDelay = delayMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* What is the largest payload the sender of this packet wants to receive?
|
||||
*
|
||||
*/
|
||||
public int getOptionalMaxSize() { return _optionMaxSize; }
|
||||
public void setOptionalMaxSize(int numBytes) {
|
||||
setFlag(FLAG_MAX_PACKET_SIZE_INCLUDED, numBytes > 0);
|
||||
_optionMaxSize = numBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the packet to the buffer (starting at the offset) and return
|
||||
* the number of bytes written.
|
||||
*
|
||||
* @throws IllegalStateException if there is data missing or otherwise b0rked
|
||||
*/
|
||||
public int writePacket(byte buffer[], int offset) throws IllegalStateException {
|
||||
return writePacket(buffer, offset, true);
|
||||
}
|
||||
/**
|
||||
* @param includeSig if true, include the real signature, otherwise put zeroes
|
||||
* in its place.
|
||||
*/
|
||||
private int writePacket(byte buffer[], int offset, boolean includeSig) throws IllegalStateException {
|
||||
int cur = offset;
|
||||
if (_sendStreamId != null)
|
||||
System.arraycopy(_sendStreamId, 0, buffer, cur, _sendStreamId.length);
|
||||
else
|
||||
System.arraycopy(STREAM_ID_UNKNOWN, 0, buffer, cur, STREAM_ID_UNKNOWN.length);
|
||||
cur += 4;
|
||||
if (_receiveStreamId != null)
|
||||
System.arraycopy(_receiveStreamId, 0, buffer, cur, _receiveStreamId.length);
|
||||
else
|
||||
System.arraycopy(STREAM_ID_UNKNOWN, 0, buffer, cur, STREAM_ID_UNKNOWN.length);
|
||||
cur += 4;
|
||||
DataHelper.toLong(buffer, cur, 4, _sequenceNum > 0 ? _sequenceNum : 0);
|
||||
cur += 4;
|
||||
DataHelper.toLong(buffer, cur, 4, _ackThrough > 0 ? _ackThrough : 0);
|
||||
cur += 4;
|
||||
if (_nacks != null) {
|
||||
DataHelper.toLong(buffer, cur, 1, _nacks.length);
|
||||
cur++;
|
||||
for (int i = 0; i < _nacks.length; i++) {
|
||||
DataHelper.toLong(buffer, cur, 4, _nacks[i]);
|
||||
cur += 4;
|
||||
}
|
||||
} else {
|
||||
DataHelper.toLong(buffer, cur, 1, 0);
|
||||
cur++;
|
||||
}
|
||||
DataHelper.toLong(buffer, cur, 1, _resendDelay > 0 ? _resendDelay : 0);
|
||||
cur++;
|
||||
DataHelper.toLong(buffer, cur, 2, _flags);
|
||||
cur += 2;
|
||||
|
||||
int optionSize = 0;
|
||||
if (isFlagSet(FLAG_DELAY_REQUESTED))
|
||||
optionSize += 2;
|
||||
if (isFlagSet(FLAG_FROM_INCLUDED))
|
||||
optionSize += _optionFrom.size();
|
||||
if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED))
|
||||
optionSize += 2;
|
||||
if (isFlagSet(FLAG_SIGNATURE_INCLUDED))
|
||||
optionSize += Signature.SIGNATURE_BYTES;
|
||||
|
||||
DataHelper.toLong(buffer, cur, 2, optionSize);
|
||||
cur += 2;
|
||||
|
||||
if (isFlagSet(FLAG_DELAY_REQUESTED)) {
|
||||
DataHelper.toLong(buffer, cur, 2, _optionDelay > 0 ? _optionDelay : 0);
|
||||
cur += 2;
|
||||
}
|
||||
if (isFlagSet(FLAG_FROM_INCLUDED)) {
|
||||
cur += _optionFrom.writeBytes(buffer, cur);
|
||||
}
|
||||
if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) {
|
||||
DataHelper.toLong(buffer, cur, 2, _optionMaxSize > 0 ? _optionMaxSize : DEFAULT_MAX_SIZE);
|
||||
cur += 2;
|
||||
}
|
||||
if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) {
|
||||
if (includeSig)
|
||||
System.arraycopy(_optionSignature.getData(), 0, buffer, cur, Signature.SIGNATURE_BYTES);
|
||||
else // we're signing (or validating)
|
||||
Arrays.fill(buffer, cur, cur+Signature.SIGNATURE_BYTES, (byte)0x0);
|
||||
cur += Signature.SIGNATURE_BYTES;
|
||||
}
|
||||
|
||||
if (_payload != null) {
|
||||
try {
|
||||
System.arraycopy(_payload, 0, buffer, cur, _payload.length);
|
||||
} catch (ArrayIndexOutOfBoundsException aioobe) {
|
||||
System.err.println("payload.length: " + _payload.length + " buffer.length: " + buffer.length + " cur: " + cur);
|
||||
throw aioobe;
|
||||
}
|
||||
cur += _payload.length;
|
||||
}
|
||||
|
||||
return cur - offset;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* how large would this packet be if we wrote it
|
||||
*/
|
||||
public int writtenSize() throws IllegalStateException {
|
||||
int size = 0;
|
||||
size += _sendStreamId.length;
|
||||
size += _receiveStreamId.length;
|
||||
size += 4; // sequenceNum
|
||||
size += 4; // ackThrough
|
||||
if (_nacks != null) {
|
||||
size++; // nacks length
|
||||
size += 4 * _nacks.length;
|
||||
} else {
|
||||
size++; // nacks length
|
||||
}
|
||||
size++; // resendDelay
|
||||
size += 2; // flags
|
||||
|
||||
if (isFlagSet(FLAG_DELAY_REQUESTED))
|
||||
size += 2;
|
||||
if (isFlagSet(FLAG_FROM_INCLUDED))
|
||||
size += _optionFrom.size();
|
||||
if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED))
|
||||
size += 2;
|
||||
if (isFlagSet(FLAG_SIGNATURE_INCLUDED))
|
||||
size += Signature.SIGNATURE_BYTES;
|
||||
|
||||
size += 2; // option size
|
||||
|
||||
if (_payload != null) {
|
||||
size += _payload.length;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
/**
|
||||
* Read the packet from the buffer (starting at the offset) and return
|
||||
* the number of bytes read.
|
||||
*
|
||||
* @param buffer packet buffer containing the data
|
||||
* @param offset index into the buffer to start readign
|
||||
* @param length how many bytes within the buffer past the offset are
|
||||
* part of the packet?
|
||||
*
|
||||
* @throws IllegalArgumentException if the data is b0rked
|
||||
*/
|
||||
public void readPacket(byte buffer[], int offset, int length) throws IllegalArgumentException {
|
||||
int cur = offset;
|
||||
_sendStreamId = new byte[4];
|
||||
System.arraycopy(buffer, cur, _sendStreamId, 0, 4);
|
||||
cur += 4;
|
||||
_receiveStreamId = new byte[4];
|
||||
System.arraycopy(buffer, cur, _receiveStreamId, 0, 4);
|
||||
cur += 4;
|
||||
_sequenceNum = DataHelper.fromLong(buffer, cur, 4);
|
||||
cur += 4;
|
||||
_ackThrough = DataHelper.fromLong(buffer, cur, 4);
|
||||
cur += 4;
|
||||
int numNacks = (int)DataHelper.fromLong(buffer, cur, 1);
|
||||
cur++;
|
||||
if (numNacks > 0) {
|
||||
_nacks = new long[numNacks];
|
||||
for (int i = 0; i < numNacks; i++) {
|
||||
_nacks[i] = DataHelper.fromLong(buffer, cur, 4);
|
||||
cur += 4;
|
||||
}
|
||||
} else {
|
||||
_nacks = null;
|
||||
}
|
||||
_resendDelay = (int)DataHelper.fromLong(buffer, cur, 1);
|
||||
cur++;
|
||||
_flags = (int)DataHelper.fromLong(buffer, cur, 2);
|
||||
cur += 2;
|
||||
|
||||
int optionSize = (int)DataHelper.fromLong(buffer, cur, 2);
|
||||
cur += 2;
|
||||
int payloadBegin = cur + optionSize;
|
||||
|
||||
// skip ahead to the payload
|
||||
_payload = new byte[offset + length - payloadBegin];
|
||||
if (_payload.length > MAX_PAYLOAD_SIZE)
|
||||
throw new IllegalArgumentException("length: " + length + " offset: " + offset + " begin: " + payloadBegin);
|
||||
System.arraycopy(buffer, payloadBegin, _payload, 0, _payload.length);
|
||||
|
||||
// ok now lets go back and deal with the options
|
||||
if (isFlagSet(FLAG_DELAY_REQUESTED)) {
|
||||
_optionDelay = (int)DataHelper.fromLong(buffer, cur, 2);
|
||||
cur += 2;
|
||||
}
|
||||
if (isFlagSet(FLAG_FROM_INCLUDED)) {
|
||||
_optionFrom = new Destination();
|
||||
try {
|
||||
cur += _optionFrom.readBytes(buffer, cur);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new IllegalArgumentException("Bad from field: " + dfe.getMessage());
|
||||
}
|
||||
}
|
||||
if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) {
|
||||
_optionMaxSize = (int)DataHelper.fromLong(buffer, cur, 2);
|
||||
cur += 2;
|
||||
}
|
||||
if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) {
|
||||
_optionSignature = new Signature();
|
||||
byte buf[] = new byte[Signature.SIGNATURE_BYTES];
|
||||
System.arraycopy(buffer, cur, buf, 0, Signature.SIGNATURE_BYTES);
|
||||
_optionSignature.setData(buf);
|
||||
cur += Signature.SIGNATURE_BYTES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the signature on the data is valid.
|
||||
*
|
||||
* @return true if the signature exists and validates against the data,
|
||||
* false otherwise.
|
||||
*/
|
||||
public boolean verifySignature(I2PAppContext ctx, Destination from, byte buffer[]) {
|
||||
if (!isFlagSet(FLAG_SIGNATURE_INCLUDED)) return false;
|
||||
if (_optionSignature == null) return false;
|
||||
|
||||
int size = writtenSize();
|
||||
|
||||
if (buffer == null)
|
||||
buffer = new byte[size];
|
||||
int written = writePacket(buffer, 0, false);
|
||||
if (written != size) {
|
||||
ctx.logManager().getLog(Packet.class).error("Written " + written + " size " + size + " for " + toString(), new Exception("moo"));
|
||||
return false;
|
||||
}
|
||||
boolean ok = ctx.dsa().verifySignature(_optionSignature, buffer, 0, size, from.getSigningPublicKey());
|
||||
if (!ok) {
|
||||
ctx.logManager().getLog(Packet.class).error("Signature failed with sig " + Base64.encode(_optionSignature.getData()), new Exception("moo"));
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign and write the packet to the buffer (starting at the offset) and return
|
||||
* the number of bytes written.
|
||||
*
|
||||
* @throws IllegalStateException if there is data missing or otherwise b0rked
|
||||
*/
|
||||
public int writeSignedPacket(byte buffer[], int offset, I2PAppContext ctx, SigningPrivateKey key) throws IllegalStateException {
|
||||
setFlag(FLAG_SIGNATURE_INCLUDED);
|
||||
int size = writePacket(buffer, offset, false);
|
||||
_optionSignature = ctx.dsa().sign(buffer, offset, size, key);
|
||||
// jump into the signed data and inject the signature where we
|
||||
// previously placed a bunch of zeroes
|
||||
int signatureOffset = offset
|
||||
+ 4 // sendStreamId
|
||||
+ 4 // receiveStreamId
|
||||
+ 4 // sequenceNum
|
||||
+ 4 // ackThrough
|
||||
+ (_nacks != null ? 4*_nacks.length + 1 : 1)
|
||||
+ 1 // resendDelay
|
||||
+ 2 // flags
|
||||
+ 2 // optionSize
|
||||
+ (isFlagSet(FLAG_DELAY_REQUESTED) ? 2 : 0)
|
||||
+ (isFlagSet(FLAG_FROM_INCLUDED) ? _optionFrom.size() : 0)
|
||||
+ (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED) ? 2 : 0);
|
||||
System.arraycopy(_optionSignature.getData(), 0, buffer, signatureOffset, Signature.SIGNATURE_BYTES);
|
||||
return size;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer(64);
|
||||
buf.append(toId(_sendStreamId));
|
||||
//buf.append("<-->");
|
||||
buf.append(toId(_receiveStreamId)).append(": #").append(_sequenceNum);
|
||||
if (_sequenceNum < 10)
|
||||
buf.append(" \t"); // so the tab lines up right
|
||||
else
|
||||
buf.append('\t');
|
||||
buf.append(toFlagString());
|
||||
buf.append(" ACK ").append(_ackThrough);
|
||||
if (_nacks != null) {
|
||||
buf.append(" NACK");
|
||||
for (int i = 0; i < _nacks.length; i++) {
|
||||
buf.append(" ").append(_nacks[i]);
|
||||
}
|
||||
}
|
||||
if ( (_payload != null) && (_payload.length > 0) )
|
||||
buf.append(" data: ").append(_payload.length);
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static final String toId(byte id[]) {
|
||||
if (id == null)
|
||||
return Base64.encode(STREAM_ID_UNKNOWN);
|
||||
else
|
||||
return Base64.encode(id);
|
||||
}
|
||||
|
||||
private final String toFlagString() {
|
||||
StringBuffer buf = new StringBuffer(32);
|
||||
if (isFlagSet(FLAG_CLOSE)) buf.append(" CLOSE");
|
||||
if (isFlagSet(FLAG_DELAY_REQUESTED)) buf.append(" DELAY ").append(_optionDelay);
|
||||
if (isFlagSet(FLAG_ECHO)) buf.append(" ECHO");
|
||||
if (isFlagSet(FLAG_FROM_INCLUDED)) buf.append(" FROM");
|
||||
if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) buf.append(" MS");
|
||||
if (isFlagSet(FLAG_PROFILE_INTERACTIVE)) buf.append(" INTERACTIVE");
|
||||
if (isFlagSet(FLAG_RESET)) buf.append(" RESET");
|
||||
if (isFlagSet(FLAG_SIGNATURE_INCLUDED)) buf.append(" SIG");
|
||||
if (isFlagSet(FLAG_SIGNATURE_REQUESTED)) buf.append(" SIGREQ");
|
||||
if (isFlagSet(FLAG_SYNCHRONIZE)) buf.append(" SYN");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
/**
|
||||
* receive a packet and dispatch it correctly to the connection specified,
|
||||
* the server socket, or queue a reply RST packet.
|
||||
*
|
||||
*/
|
||||
public class PacketHandler {
|
||||
private ConnectionManager _manager;
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private int _lastDelay;
|
||||
|
||||
public PacketHandler(I2PAppContext ctx, ConnectionManager mgr) {
|
||||
_manager = mgr;
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(PacketHandler.class);
|
||||
_lastDelay = _context.random().nextInt(30*1000);
|
||||
}
|
||||
|
||||
private boolean choke(Packet packet) {
|
||||
if (false) {
|
||||
// artificial choke: 2% random drop and a 0-30s
|
||||
// random tiered delay from 0-30s
|
||||
if (_context.random().nextInt(100) >= 95) {
|
||||
displayPacket(packet, "DROP");
|
||||
return false;
|
||||
} else {
|
||||
// if (true) return true; // no lag, just drop
|
||||
/*
|
||||
int delay = _context.random().nextInt(5*1000);
|
||||
*/
|
||||
int delay = _context.random().nextInt(6*1000);
|
||||
int delayFactor = _context.random().nextInt(100);
|
||||
if (delayFactor > 80) {
|
||||
if (delayFactor > 98)
|
||||
delay *= 5;
|
||||
else if (delayFactor > 95)
|
||||
delay *= 4;
|
||||
else if (delayFactor > 90)
|
||||
delay *= 3;
|
||||
else
|
||||
delay *= 2;
|
||||
}
|
||||
|
||||
if (_context.random().nextInt(100) >= 20)
|
||||
delay = _lastDelay;
|
||||
|
||||
_lastDelay = delay;
|
||||
SimpleTimer.getInstance().addEvent(new Reinject(packet, delay), delay);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private class Reinject implements SimpleTimer.TimedEvent {
|
||||
private Packet _packet;
|
||||
private int _delay;
|
||||
public Reinject(Packet packet, int delay) {
|
||||
_packet = packet;
|
||||
_delay = delay;
|
||||
}
|
||||
public void timeReached() {
|
||||
_log.debug("Reinjecting after " + _delay + ": " + _packet);
|
||||
receivePacketDirect(_packet);
|
||||
}
|
||||
}
|
||||
|
||||
void receivePacket(Packet packet) {
|
||||
boolean ok = choke(packet);
|
||||
if (ok)
|
||||
receivePacketDirect(packet);
|
||||
}
|
||||
|
||||
private void receivePacketDirect(Packet packet) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("packet received: " + packet);
|
||||
|
||||
byte sendId[] = packet.getSendStreamId();
|
||||
if (!isNonZero(sendId))
|
||||
sendId = null;
|
||||
|
||||
Connection con = (sendId != null ? _manager.getConnectionByInboundId(sendId) : null);
|
||||
if (con != null) {
|
||||
receiveKnownCon(con, packet);
|
||||
displayPacket(packet, "RECV");
|
||||
} else {
|
||||
receiveUnknownCon(packet, sendId);
|
||||
displayPacket(packet, "UNKN");
|
||||
}
|
||||
}
|
||||
|
||||
private static final SimpleDateFormat _fmt = new SimpleDateFormat("hh:mm:ss.SSS");
|
||||
static void displayPacket(Packet packet, String prefix) {
|
||||
String msg = null;
|
||||
synchronized (_fmt) {
|
||||
msg = _fmt.format(new Date()) + ": " + prefix + " " + packet.toString();
|
||||
}
|
||||
System.out.println(msg);
|
||||
}
|
||||
|
||||
private void receiveKnownCon(Connection con, Packet packet) {
|
||||
// the packet is pointed at a stream ID we're receiving on
|
||||
if (isValidMatch(con.getSendStreamId(), packet.getReceiveStreamId())) {
|
||||
// the packet's receive stream ID also matches what we expect
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("receive valid: " + packet);
|
||||
try {
|
||||
con.getPacketHandler().receivePacket(packet, con);
|
||||
} catch (I2PException ie) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Received forged packet for " + con, ie);
|
||||
}
|
||||
} else {
|
||||
if (packet.isFlagSet(Packet.FLAG_RESET)) {
|
||||
// refused
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("receive reset: " + packet);
|
||||
try {
|
||||
con.getPacketHandler().receivePacket(packet, con);
|
||||
} catch (I2PException ie) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Received forged reset for " + con, ie);
|
||||
}
|
||||
} else if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
|
||||
if ( (con.getSendStreamId() == null) ||
|
||||
(DataHelper.eq(con.getSendStreamId(), packet.getReceiveStreamId())) ) {
|
||||
byte oldId[] =con.getSendStreamId();
|
||||
// con fully established, w00t
|
||||
con.setSendStreamId(packet.getReceiveStreamId());
|
||||
try {
|
||||
con.getPacketHandler().receivePacket(packet, con);
|
||||
} catch (I2PException ie) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Received forged syn for " + con, ie);
|
||||
con.setSendStreamId(oldId);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Receive a syn packet with the wrong IDs: " + packet);
|
||||
}
|
||||
} else {
|
||||
// someone is sending us a packet on the wrong stream
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Received a packet on the wrong stream: " + packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void receiveUnknownCon(Packet packet, byte sendId[]) {
|
||||
if (packet.isFlagSet(Packet.FLAG_ECHO)) {
|
||||
if (packet.getSendStreamId() != null) {
|
||||
receivePing(packet);
|
||||
} else if (packet.getReceiveStreamId() != null) {
|
||||
receivePong(packet);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Echo packet received with no stream IDs: " + packet);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Packet received on an unknown stream (and not a SYN): " + packet);
|
||||
if (sendId == null) {
|
||||
for (Iterator iter = _manager.listConnections().iterator(); iter.hasNext(); ) {
|
||||
Connection con = (Connection)iter.next();
|
||||
if (DataHelper.eq(con.getSendStreamId(), packet.getReceiveStreamId())) {
|
||||
if (con.getAckedPackets() <= 0) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received additional packets before the syn on " + con + ": " + packet);
|
||||
receiveKnownCon(con, packet);
|
||||
return;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("hrmph, received while ack of syn was in flight on " + con + ": " + packet + " acked: " + con.getAckedPackets());
|
||||
receiveKnownCon(con, packet);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) {
|
||||
_manager.getConnectionHandler().receiveNewSyn(packet);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
Set cons = _manager.listConnections();
|
||||
for (Iterator iter = cons.iterator(); iter.hasNext(); ) {
|
||||
Connection con = (Connection)iter.next();
|
||||
buf.append(con.toString()).append(" ");
|
||||
}
|
||||
_log.warn("Packet belongs to no other cons: " + packet + " connections: "
|
||||
+ buf.toString() + " sendId: "
|
||||
+ (sendId != null ? Base64.encode(sendId) : " unknown"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void receivePing(Packet packet) {
|
||||
boolean ok = packet.verifySignature(_context, packet.getOptionalFrom(), null);
|
||||
if (!ok) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
if (packet.getOptionalFrom() == null)
|
||||
_log.warn("Ping with no from (flagged? " + packet.isFlagSet(Packet.FLAG_FROM_INCLUDED) + ")");
|
||||
else if (packet.getOptionalSignature() == null)
|
||||
_log.warn("Ping with no signature (flagged? " + packet.isFlagSet(Packet.FLAG_SIGNATURE_INCLUDED) + ")");
|
||||
else
|
||||
_log.warn("Forged ping, discard (from=" + packet.getOptionalFrom().calculateHash().toBase64()
|
||||
+ " sig=" + packet.getOptionalSignature().toBase64() + ")");
|
||||
}
|
||||
} else {
|
||||
PacketLocal pong = new PacketLocal(_context, packet.getOptionalFrom());
|
||||
pong.setFlag(Packet.FLAG_ECHO, true);
|
||||
pong.setFlag(Packet.FLAG_SIGNATURE_INCLUDED, false);
|
||||
pong.setReceiveStreamId(packet.getSendStreamId());
|
||||
_manager.getPacketQueue().enqueue(pong);
|
||||
}
|
||||
}
|
||||
|
||||
private void receivePong(Packet packet) {
|
||||
_manager.receivePong(packet.getReceiveStreamId());
|
||||
}
|
||||
|
||||
private static final boolean isValidMatch(byte conStreamId[], byte packetStreamId[]) {
|
||||
if ( (conStreamId == null) || (packetStreamId == null) ||
|
||||
(conStreamId.length != packetStreamId.length) )
|
||||
return false;
|
||||
|
||||
boolean nonZeroFound = false;
|
||||
for (int i = 0; i < conStreamId.length; i++) {
|
||||
if (conStreamId[i] != packetStreamId[i]) return false;
|
||||
if (conStreamId[i] != 0x0) nonZeroFound = true;
|
||||
}
|
||||
return nonZeroFound;
|
||||
}
|
||||
|
||||
private static final boolean isNonZero(byte[] b) {
|
||||
boolean nonZeroFound = false;
|
||||
for (int i = 0; b != null && i < b.length; i++) {
|
||||
if (b[i] != 0x0)
|
||||
nonZeroFound = true;
|
||||
}
|
||||
return nonZeroFound;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.SessionKey;
|
||||
|
||||
/**
|
||||
* coordinate local attributes about a packet - send time, ack time, number of
|
||||
* retries, etc.
|
||||
*/
|
||||
public class PacketLocal extends Packet implements MessageOutputStream.WriteStatus {
|
||||
private I2PAppContext _context;
|
||||
private Connection _connection;
|
||||
private Destination _to;
|
||||
private SessionKey _keyUsed;
|
||||
private Set _tagsSent;
|
||||
private long _createdOn;
|
||||
private int _numSends;
|
||||
private long _lastSend;
|
||||
private long _acceptedOn;
|
||||
private long _ackOn;
|
||||
private long _cancelledOn;
|
||||
|
||||
public PacketLocal(I2PAppContext ctx, Destination to) {
|
||||
this(ctx, to, null);
|
||||
}
|
||||
public PacketLocal(I2PAppContext ctx, Destination to, Connection con) {
|
||||
_context = ctx;
|
||||
_createdOn = ctx.clock().now();
|
||||
_to = to;
|
||||
_connection = con;
|
||||
_lastSend = -1;
|
||||
_cancelledOn = -1;
|
||||
}
|
||||
|
||||
public Destination getTo() { return _to; }
|
||||
public void setTo(Destination to) { _to = to; }
|
||||
|
||||
public SessionKey getKeyUsed() { return _keyUsed; }
|
||||
public void setKeyUsed(SessionKey key) { _keyUsed = key; }
|
||||
|
||||
public Set getTagsSent() { return _tagsSent; }
|
||||
public void setTagsSent(Set tags) {
|
||||
if ( (_tagsSent != null) && (_tagsSent.size() > 0) && (tags.size() > 0) ) {
|
||||
//int old = _tagsSent.size();
|
||||
//_tagsSent.addAll(tags);
|
||||
if (!_tagsSent.equals(tags))
|
||||
System.out.println("ERROR: dup tags: old=" + _tagsSent.size() + " new=" + tags.size() + " packet: " + toString());
|
||||
} else {
|
||||
_tagsSent = tags;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldSign() {
|
||||
return isFlagSet(FLAG_SIGNATURE_INCLUDED) ||
|
||||
isFlagSet(FLAG_SYNCHRONIZE) ||
|
||||
isFlagSet(FLAG_CLOSE);
|
||||
}
|
||||
|
||||
/** last minute update of ack fields, just before write/sign */
|
||||
public void prepare() {
|
||||
if (_connection != null)
|
||||
_connection.getInputStream().updateAcks(this);
|
||||
}
|
||||
|
||||
public long getCreatedOn() { return _createdOn; }
|
||||
public void incrementSends() {
|
||||
_numSends++;
|
||||
_lastSend = _context.clock().now();
|
||||
}
|
||||
public void ackReceived() {
|
||||
synchronized (this) {
|
||||
if (_ackOn <= 0)
|
||||
_ackOn = _context.clock().now();
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
public void cancelled() {
|
||||
synchronized (this) {
|
||||
_cancelledOn = _context.clock().now();
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
/** how long after packet creation was it acked? */
|
||||
public int getAckTime() {
|
||||
if (_ackOn <= 0)
|
||||
return -1;
|
||||
else
|
||||
return (int)(_ackOn - _createdOn);
|
||||
}
|
||||
public int getNumSends() { return _numSends; }
|
||||
public long getLastSend() { return _lastSend; }
|
||||
public Connection getConnection() { return _connection; }
|
||||
|
||||
public String toString() {
|
||||
String str = super.toString();
|
||||
if (_ackOn > 0)
|
||||
return str + " ack after " + getAckTime();
|
||||
else
|
||||
return str;
|
||||
}
|
||||
|
||||
public void waitForAccept(int maxWaitMs) {
|
||||
if (_connection == null)
|
||||
throw new IllegalStateException("Cannot wait for accept with no connection");
|
||||
long expiration = _context.clock().now()+maxWaitMs;
|
||||
boolean accepted = _connection.packetSendChoke(maxWaitMs);
|
||||
if (accepted)
|
||||
_acceptedOn = _context.clock().now();
|
||||
else
|
||||
_acceptedOn = -1;
|
||||
}
|
||||
|
||||
public void waitForCompletion(int maxWaitMs) {
|
||||
long expiration = _context.clock().now()+maxWaitMs;
|
||||
while ((maxWaitMs <= 0) || (expiration < _context.clock().now())) {
|
||||
synchronized (this) {
|
||||
if (_ackOn > 0)
|
||||
return;
|
||||
if (_cancelledOn > 0)
|
||||
return;
|
||||
try { wait(); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean writeAccepted() { return _acceptedOn > 0 && _cancelledOn <= 0; }
|
||||
public boolean writeFailed() { return _cancelledOn > 0; }
|
||||
public boolean writeSuccessful() { return _ackOn > 0 && _cancelledOn <= 0; }
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class PacketQueue {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private I2PSession _session;
|
||||
private byte _buf[];
|
||||
|
||||
public PacketQueue(I2PAppContext context, I2PSession session) {
|
||||
_context = context;
|
||||
_session = session;
|
||||
_buf = new byte[36*1024];
|
||||
_log = context.logManager().getLog(PacketQueue.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new packet to be sent out ASAP
|
||||
*/
|
||||
public void enqueue(PacketLocal packet) {
|
||||
packet.prepare();
|
||||
int size = 0;
|
||||
if (packet.shouldSign())
|
||||
size = packet.writeSignedPacket(_buf, 0, _context, _session.getPrivateKey());
|
||||
else
|
||||
size = packet.writePacket(_buf, 0);
|
||||
|
||||
SessionKey keyUsed = packet.getKeyUsed();
|
||||
if (keyUsed == null)
|
||||
keyUsed = new SessionKey();
|
||||
Set tagsSent = packet.getTagsSent();
|
||||
if (tagsSent == null)
|
||||
tagsSent = new HashSet();
|
||||
try {
|
||||
// cache this from before sendMessage
|
||||
String conStr = (packet.getConnection() != null ? packet.getConnection().toString() : "");
|
||||
if (packet.getAckTime() > 0) {
|
||||
_log.debug("Not resending " + packet);
|
||||
return;
|
||||
} else {
|
||||
_log.debug("Sending... " + packet);
|
||||
}
|
||||
// this should not block!
|
||||
long begin = _context.clock().now();
|
||||
boolean sent = _session.sendMessage(packet.getTo(), _buf, 0, size, keyUsed, tagsSent);
|
||||
long end = _context.clock().now();
|
||||
if (!sent) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Send failed for " + packet);
|
||||
packet.getConnection().disconnect(false);
|
||||
} else {
|
||||
packet.setKeyUsed(keyUsed);
|
||||
packet.setTagsSent(tagsSent);
|
||||
packet.incrementSends();
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
String msg = "SEND " + packet + (tagsSent.size() > 0
|
||||
? " with " + tagsSent.size() + " tags"
|
||||
: "")
|
||||
+ " send # " + packet.getNumSends()
|
||||
+ " sendTime: " + (end-begin)
|
||||
+ " con: " + conStr;
|
||||
_log.debug(msg);
|
||||
}
|
||||
PacketHandler.displayPacket(packet, "SEND");
|
||||
}
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unable to send the packet " + packet, ise);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Examine a connection's state and pick the right scheduler for it.
|
||||
*
|
||||
*/
|
||||
class SchedulerChooser {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private TaskScheduler _nullScheduler;
|
||||
/** list of TaskScheduler objects */
|
||||
private List _schedulers;
|
||||
|
||||
public SchedulerChooser(I2PAppContext context) {
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(SchedulerChooser.class);
|
||||
_schedulers = createSchedulers();
|
||||
_nullScheduler = new NullScheduler();
|
||||
}
|
||||
|
||||
public TaskScheduler getScheduler(Connection con) {
|
||||
for (int i = 0; i < _schedulers.size(); i++) {
|
||||
TaskScheduler scheduler = (TaskScheduler)_schedulers.get(i);
|
||||
if (scheduler.accept(con)) {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Scheduling for " + con + " with " + scheduler.getClass().getName());
|
||||
return scheduler;
|
||||
}
|
||||
}
|
||||
return _nullScheduler;
|
||||
}
|
||||
|
||||
private List createSchedulers() {
|
||||
List rv = new ArrayList(8);
|
||||
rv.add(new SchedulerPreconnect(_context));
|
||||
rv.add(new SchedulerConnecting(_context));
|
||||
rv.add(new SchedulerReceived(_context));
|
||||
rv.add(new SchedulerConnectedBulk(_context));
|
||||
rv.add(new SchedulerClosing(_context));
|
||||
rv.add(new SchedulerClosed(_context));
|
||||
rv.add(new SchedulerDead(_context));
|
||||
return rv;
|
||||
}
|
||||
private class NullScheduler implements TaskScheduler {
|
||||
private Log _log;
|
||||
public NullScheduler() {
|
||||
_log = _context.logManager().getLog(NullScheduler.class);
|
||||
}
|
||||
|
||||
public void eventOccurred(Connection con) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Event occurred on " + con, new Exception("source"));
|
||||
}
|
||||
public boolean accept(Connection con) { return true; }
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* <p>Scheduler used for after both sides have had their close packets
|
||||
* ACKed, but the final timeout hasn't passed.</p>
|
||||
*
|
||||
* <h2>Entry conditions:</h2><ul>
|
||||
* <li>Both sides have closed and ACKed.</li>
|
||||
* <li>Less than the final timeout period has passed since the last ACK.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Events:</h2><ul>
|
||||
* <li>Packets received</li>
|
||||
* <li>RESET received</li>
|
||||
* <li>Message sending fails (error talking to the session)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Next states:</h2>
|
||||
* <li>{@link SchedulerDead dead} - after the final timeout passes</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
*/
|
||||
class SchedulerClosed extends SchedulerImpl {
|
||||
private Log _log;
|
||||
public SchedulerClosed(I2PAppContext ctx) {
|
||||
super(ctx);
|
||||
_log = ctx.logManager().getLog(SchedulerClosed.class);
|
||||
}
|
||||
|
||||
static final long CLOSE_TIMEOUT = 30*1000;
|
||||
|
||||
public boolean accept(Connection con) {
|
||||
boolean ok = (con != null) &&
|
||||
(con.getCloseSentOn() > 0) &&
|
||||
(con.getCloseReceivedOn() > 0) &&
|
||||
(con.getUnackedPacketsReceived() <= 0) &&
|
||||
(con.getUnackedPacketsSent() <= 0) &&
|
||||
(!con.getResetReceived()) &&
|
||||
(con.getCloseSentOn() + CLOSE_TIMEOUT > _context.clock().now());
|
||||
return ok;
|
||||
}
|
||||
|
||||
public void eventOccurred(Connection con) {
|
||||
long timeLeft = con.getCloseSentOn() + CLOSE_TIMEOUT - _context.clock().now();
|
||||
reschedule(timeLeft, con);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* <p>Scheduler used for after both SYNs have been ACKed and both sides
|
||||
* have closed the stream, but either we haven't ACKed their close or
|
||||
* they haven't ACKed ours.</p>
|
||||
*
|
||||
* <h2>Entry conditions:</h2><ul>
|
||||
* <li>Both sides have closed.</li>
|
||||
* <li>At least one direction has not ACKed the close.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Events:</h2><ul>
|
||||
* <li>Packets received (which may or may not ACK the ones sent)</li>
|
||||
* <li>RESET received</li>
|
||||
* <li>Message sending fails (error talking to the session)</li>
|
||||
* <li>Message sending fails (too many resends)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Next states:</h2>
|
||||
* <li>{@link SchedulerClosed closed} - after both sending and receiving ACKs on the CLOSE</li>
|
||||
* <li>{@link SchedulerDead dead} - after sending or receiving a RESET</li>
|
||||
* </ul>
|
||||
*
|
||||
*/
|
||||
class SchedulerClosing extends SchedulerImpl {
|
||||
private Log _log;
|
||||
public SchedulerClosing(I2PAppContext ctx) {
|
||||
super(ctx);
|
||||
_log = ctx.logManager().getLog(SchedulerClosing.class);
|
||||
}
|
||||
|
||||
public boolean accept(Connection con) {
|
||||
boolean ok = (con != null) &&
|
||||
(con.getCloseSentOn() > 0) &&
|
||||
(con.getCloseReceivedOn() > 0) &&
|
||||
( (con.getUnackedPacketsReceived() > 0) || (con.getUnackedPacketsSent() > 0) );
|
||||
return ok;
|
||||
}
|
||||
|
||||
public void eventOccurred(Connection con) {
|
||||
if (con.getNextSendTime() <= 0)
|
||||
con.setNextSendTime(_context.clock().now() + con.getOptions().getSendAckDelay());
|
||||
long remaining = con.getNextSendTime() - _context.clock().now();
|
||||
if (remaining <= 0)
|
||||
con.sendAvailable();
|
||||
else
|
||||
reschedule(remaining, con);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* <p>Scheduler used for after our SYN has been sent and ACKed but one
|
||||
* (or more) sides haven't closed the stream yet. In addition, the
|
||||
* stream must be using the BULK profile, rather than the INTERACTIVE
|
||||
* profile.</p>
|
||||
*
|
||||
* <h2>Entry conditions:</h2><ul>
|
||||
* <li>Packets sent and ACKs received.</li>
|
||||
* <li>At least one direction is not closed</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Events:</h2><ul>
|
||||
* <li>Packets received (which may or may not ACK the ones sent)</li>
|
||||
* <li>Message flush (explicitly, through a full buffer, or stream closure)</li>
|
||||
* <li>RESET received</li>
|
||||
* <li>Message sending fails (error talking to the session)</li>
|
||||
* <li>Message sending fails (too many resends)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Next states:</h2>
|
||||
* <li>{@link SchedulerClosing closing} - after both sending and receiving a CLOSE</li>
|
||||
* <li>{@link SchedulerClosed closed} - after both sending and receiving ACKs on the CLOSE</li>
|
||||
* <li>{@link SchedulerDead dead} - after sending or receiving a RESET</li>
|
||||
* </ul>
|
||||
*
|
||||
*/
|
||||
class SchedulerConnectedBulk extends SchedulerImpl {
|
||||
private Log _log;
|
||||
public SchedulerConnectedBulk(I2PAppContext ctx) {
|
||||
super(ctx);
|
||||
_log = ctx.logManager().getLog(SchedulerConnectedBulk.class);
|
||||
}
|
||||
|
||||
public boolean accept(Connection con) {
|
||||
boolean ok = (con != null) &&
|
||||
(con.getAckedPackets() > 0) &&
|
||||
(con.getOptions().getProfile() == ConnectionOptions.PROFILE_BULK) &&
|
||||
(!con.getResetReceived()) &&
|
||||
( (con.getCloseSentOn() <= 0) || (con.getCloseReceivedOn() <= 0) );
|
||||
if (!ok) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("con: " + con + " closeSentOn: " + con.getCloseSentOn()
|
||||
+ " closeReceivedOn: " + con.getCloseReceivedOn());
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
public void eventOccurred(Connection con) {
|
||||
if (con.getNextSendTime() <= 0)
|
||||
return;
|
||||
|
||||
long timeTillSend = con.getNextSendTime() - _context.clock().now();
|
||||
|
||||
if (timeTillSend <= 0) {
|
||||
con.setNextSendTime(-1);
|
||||
con.sendAvailable();
|
||||
} else {
|
||||
reschedule(timeTillSend, con);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* <p>Scheduler used once we've sent our SYN but it hasn't been ACKed yet.
|
||||
* This connection may or may not be locally created.</p>
|
||||
*
|
||||
* <h2>Entry conditions:</h2><ul>
|
||||
* <li>Packets sent but none ACKed</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Events:</h2><ul>
|
||||
* <li>Packets received (which may or may not ACK the ones sent)</li>
|
||||
* <li>Message flush (explicitly, through a full buffer, or stream closure)</li>
|
||||
* <li>Connection establishment timeout</li>
|
||||
* <li>RESET received</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Next states:</h2>
|
||||
* <li>{@link SchedulerConnectedBulk connected} - after receiving an ACK</li>
|
||||
* <li>{@link SchedulerClosing closing} - after both sending and receiving a CLOSE</li>
|
||||
* <li>{@link SchedulerClosed closed} - after both sending and receiving ACKs on the CLOSE</li>
|
||||
* <li>{@link SchedulerDead dead} - after sending or receiving a RESET</li>
|
||||
* </ul>
|
||||
*
|
||||
*/
|
||||
class SchedulerConnecting extends SchedulerImpl {
|
||||
private Log _log;
|
||||
|
||||
public SchedulerConnecting(I2PAppContext ctx) {
|
||||
super(ctx);
|
||||
_log = ctx.logManager().getLog(SchedulerConnecting.class);
|
||||
}
|
||||
|
||||
public boolean accept(Connection con) {
|
||||
return (con != null) &&
|
||||
(con.getLastSendId() >= 0) &&
|
||||
(con.getAckedPackets() <= 0) &&
|
||||
(!con.getResetReceived());
|
||||
}
|
||||
|
||||
public void eventOccurred(Connection con) {
|
||||
long waited = _context.clock().now() - con.getCreatedOn();
|
||||
if ( (con.getOptions().getConnectTimeout() > 0) &&
|
||||
(con.getOptions().getConnectTimeout() <= waited) ) {
|
||||
con.setConnectionError("Timeout waiting for ack (waited " + waited + "ms)");
|
||||
con.disconnect(false);
|
||||
reschedule(0, con);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("waited too long: " + waited);
|
||||
return;
|
||||
} else {
|
||||
if (con.getOptions().getConnectTimeout() > 0)
|
||||
reschedule(con.getOptions().getConnectTimeout(), con);
|
||||
}
|
||||
/*
|
||||
long timeTillSend = con.getNextSendTime() - _context.clock().now();
|
||||
if ( (timeTillSend <= 0) && (con.getNextSendTime() > 0) ) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("send next on " + con);
|
||||
con.sendAvailable();
|
||||
con.setNextSendTime(-1);
|
||||
} else {
|
||||
if (con.getNextSendTime() > 0) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("time till send: " + timeTillSend + " on " + con);
|
||||
reschedule(timeTillSend, con);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* <p>Scheduler used for after the final timeout has passed or the
|
||||
* connection was reset.</p>
|
||||
*
|
||||
* <h2>Entry conditions:</h2><ul>
|
||||
* <li>Both sides have closed and ACKed and the timeout has passed. <br />
|
||||
* <b>or</b></li>
|
||||
* <li>A RESET was received</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Events:</h2><ul>
|
||||
* <li>None</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Next states:</h2>
|
||||
* <li>None</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
*/
|
||||
class SchedulerDead extends SchedulerImpl {
|
||||
private Log _log;
|
||||
public SchedulerDead(I2PAppContext ctx) {
|
||||
super(ctx);
|
||||
_log = ctx.logManager().getLog(SchedulerDead.class);
|
||||
}
|
||||
|
||||
public boolean accept(Connection con) {
|
||||
boolean ok = (con != null) &&
|
||||
(con.getResetReceived()) ||
|
||||
((con.getCloseSentOn() > 0) &&
|
||||
(con.getCloseReceivedOn() > 0) &&
|
||||
(con.getUnackedPacketsReceived() <= 0) &&
|
||||
(con.getUnackedPacketsSent() <= 0) &&
|
||||
(con.getCloseSentOn() + SchedulerClosed.CLOSE_TIMEOUT <= _context.clock().now()));
|
||||
return ok;
|
||||
}
|
||||
|
||||
public void eventOccurred(Connection con) {
|
||||
con.disconnectComplete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Base scheduler
|
||||
*/
|
||||
abstract class SchedulerImpl implements TaskScheduler {
|
||||
protected I2PAppContext _context;
|
||||
private Log _log;
|
||||
|
||||
public SchedulerImpl(I2PAppContext ctx) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(SchedulerImpl.class);
|
||||
}
|
||||
|
||||
protected void reschedule(long msToWait, Connection con) {
|
||||
SimpleTimer.getInstance().addEvent(new ConEvent(con), msToWait);
|
||||
}
|
||||
|
||||
private class ConEvent implements SimpleTimer.TimedEvent {
|
||||
private Connection _connection;
|
||||
private Exception _addedBy;
|
||||
public ConEvent(Connection con) {
|
||||
_connection = con;
|
||||
//_addedBy = new Exception("added by");
|
||||
}
|
||||
public void timeReached() {
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("firing event on " + _connection, _addedBy);
|
||||
_connection.eventOccurred();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* <p>Scheduler used for locally created connections where we have not yet
|
||||
* sent the initial SYN packet.</p>
|
||||
*
|
||||
* <h2>Entry conditions:</h2><ul>
|
||||
* <li>Locally created</li>
|
||||
* <li>No packets sent or received</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Events:</h2><ul>
|
||||
* <li>Message flush (explicitly, through a full buffer, or stream closure)</li>
|
||||
* <li>Initial delay timeout (causing implicit flush of any data available)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Next states:</h2>
|
||||
* <li>{@link SchedulerConnecting connecting} - after sending a packet</li>
|
||||
* </ul>
|
||||
*/
|
||||
class SchedulerPreconnect extends SchedulerImpl {
|
||||
private Log _log;
|
||||
|
||||
public SchedulerPreconnect(I2PAppContext ctx) {
|
||||
super(ctx);
|
||||
_log = ctx.logManager().getLog(SchedulerPreconnect.class);
|
||||
}
|
||||
|
||||
public boolean accept(Connection con) {
|
||||
return (con != null) &&
|
||||
(con.getSendStreamId() == null) &&
|
||||
(con.getLastSendId() < 0);
|
||||
}
|
||||
|
||||
public void eventOccurred(Connection con) {
|
||||
if (con.getNextSendTime() < 0)
|
||||
con.setNextSendTime(_context.clock().now() + con.getOptions().getConnectDelay());
|
||||
|
||||
long timeTillSend = con.getNextSendTime() - _context.clock().now();
|
||||
if (timeTillSend <= 0) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Send available for the SYN on " + con);
|
||||
con.sendAvailable();
|
||||
con.setNextSendTime(-1);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Wait " + timeTillSend + " before sending the SYN on " + con);
|
||||
reschedule(timeTillSend, con);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Scheduler used after receiving an inbound connection but before
|
||||
* we have sent our own SYN.
|
||||
*
|
||||
*/
|
||||
class SchedulerReceived extends SchedulerImpl {
|
||||
private Log _log;
|
||||
|
||||
public SchedulerReceived(I2PAppContext ctx) {
|
||||
super(ctx);
|
||||
_log = ctx.logManager().getLog(SchedulerReceived.class);
|
||||
}
|
||||
|
||||
public boolean accept(Connection con) {
|
||||
return (con != null) &&
|
||||
(con.getLastSendId() < 0) &&
|
||||
(con.getSendStreamId() != null);
|
||||
}
|
||||
|
||||
public void eventOccurred(Connection con) {
|
||||
if (con.getUnackedPacketsReceived() <= 0) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("hmm, state is received, but no unacked packets received?");
|
||||
return;
|
||||
}
|
||||
|
||||
long timeTillSend = con.getNextSendTime() - _context.clock().now();
|
||||
if (timeTillSend <= 0) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("received con... send a packet");
|
||||
con.sendAvailable();
|
||||
con.setNextSendTime(-1);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("received con... time till next send: " + timeTillSend);
|
||||
reschedule(timeTillSend, con);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
/**
|
||||
* Coordinates what we do 'next'. The scheduler used by a connection is
|
||||
* selected based upon its current state.
|
||||
*
|
||||
*/
|
||||
interface TaskScheduler {
|
||||
/**
|
||||
* An event has occurred (timeout, message sent, or message received),
|
||||
* so schedule what to do next based on our current state.
|
||||
*
|
||||
*/
|
||||
public void eventOccurred(Connection con);
|
||||
|
||||
/**
|
||||
* Determine whether this scheduler is fit to operate against the
|
||||
* given connection
|
||||
*
|
||||
*/
|
||||
public boolean accept(Connection con);
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ConnectTest {
|
||||
private Log _log;
|
||||
private I2PSession _client;
|
||||
private I2PSession _server;
|
||||
public void test() {
|
||||
try {
|
||||
I2PAppContext context = I2PAppContext.getGlobalContext();
|
||||
_log = context.logManager().getLog(ConnectTest.class);
|
||||
_log.debug("creating server session");
|
||||
_server = createSession();
|
||||
_log.debug("running server");
|
||||
runServer(context, _server);
|
||||
_log.debug("creating client session");
|
||||
_client = createSession();
|
||||
_log.debug("running client");
|
||||
runClient(context, _client);
|
||||
} catch (Exception e) {
|
||||
_log.error("error running", e);
|
||||
}
|
||||
try { Thread.sleep(30*1000); } catch (Exception e) {}
|
||||
}
|
||||
|
||||
private void runClient(I2PAppContext ctx, I2PSession session) {
|
||||
Thread t = new Thread(new ClientRunner(ctx, session));
|
||||
t.setName("client");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
private void runServer(I2PAppContext ctx, I2PSession session) {
|
||||
Thread t = new Thread(new ServerRunner(ctx, session));
|
||||
t.setName("server");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
private class ServerRunner implements Runnable {
|
||||
private I2PAppContext _context;
|
||||
private I2PSession _session;
|
||||
private Log _log;
|
||||
public ServerRunner(I2PAppContext ctx, I2PSession session) {
|
||||
_context = ctx;
|
||||
_session = session;
|
||||
_log = ctx.logManager().getLog(ServerRunner.class);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
Properties opts = new Properties();
|
||||
I2PSocketManager mgr = new I2PSocketManagerFull(_context, _session, opts, "client");
|
||||
_log.debug("manager created");
|
||||
I2PServerSocket ssocket = mgr.getServerSocket();
|
||||
_log.debug("server socket created");
|
||||
while (true) {
|
||||
I2PSocket socket = ssocket.accept();
|
||||
_log.debug("socket accepted: " + socket);
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
socket.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_log.error("error running", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ClientRunner implements Runnable {
|
||||
private I2PAppContext _context;
|
||||
private I2PSession _session;
|
||||
private Log _log;
|
||||
public ClientRunner(I2PAppContext ctx, I2PSession session) {
|
||||
_context = ctx;
|
||||
_session = session;
|
||||
_log = ctx.logManager().getLog(ClientRunner.class);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
Properties opts = new Properties();
|
||||
I2PSocketManager mgr = new I2PSocketManagerFull(_context, _session, opts, "client");
|
||||
_log.debug("manager created");
|
||||
I2PSocket socket = mgr.connect(_server.getMyDestination());
|
||||
_log.debug("socket created");
|
||||
socket.close();
|
||||
_log.debug("socket closed");
|
||||
} catch (Exception e) {
|
||||
_log.error("error running", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private I2PSession createSession() {
|
||||
try {
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
|
||||
Destination dest = client.createDestination(baos);
|
||||
I2PSession sess = client.createSession(new ByteArrayInputStream(baos.toByteArray()), new Properties());
|
||||
sess.connect();
|
||||
return sess;
|
||||
} catch (Exception e) {
|
||||
_log.error("error running", e);
|
||||
throw new RuntimeException("b0rk b0rk b0rk");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
ConnectTest ct = new ConnectTest();
|
||||
ct.test();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Date;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class EchoLargeTest {
|
||||
private Log _log;
|
||||
private I2PSession _client;
|
||||
private I2PSession _server;
|
||||
|
||||
public void test() {
|
||||
try {
|
||||
I2PAppContext context = I2PAppContext.getGlobalContext();
|
||||
_log = context.logManager().getLog(ConnectTest.class);
|
||||
_log.debug("creating server session");
|
||||
_server = createSession();
|
||||
_log.debug("running server");
|
||||
runServer(context, _server);
|
||||
_log.debug("creating client session");
|
||||
_client = createSession();
|
||||
_log.debug("running client");
|
||||
runClient(context, _client);
|
||||
} catch (Exception e) {
|
||||
_log.error("error running", e);
|
||||
}
|
||||
try { Thread.sleep(300*1000); } catch (Exception e) {}
|
||||
}
|
||||
|
||||
private void runClient(I2PAppContext ctx, I2PSession session) {
|
||||
Thread t = new Thread(new ClientRunner(ctx, session));
|
||||
t.setName("client");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
private void runServer(I2PAppContext ctx, I2PSession session) {
|
||||
Thread t = new Thread(new ServerRunner(ctx, session));
|
||||
t.setName("server");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
private class ServerRunner implements Runnable {
|
||||
private I2PAppContext _context;
|
||||
private I2PSession _session;
|
||||
private Log _log;
|
||||
public ServerRunner(I2PAppContext ctx, I2PSession session) {
|
||||
_context = ctx;
|
||||
_session = session;
|
||||
_log = ctx.logManager().getLog(ServerRunner.class);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
Properties opts = new Properties();
|
||||
I2PSocketManager mgr = new I2PSocketManagerFull(_context, _session, opts, "client");
|
||||
_log.debug("manager created");
|
||||
I2PServerSocket ssocket = mgr.getServerSocket();
|
||||
_log.debug("server socket created");
|
||||
while (true) {
|
||||
I2PSocket socket = ssocket.accept();
|
||||
_log.debug("socket accepted: " + socket);
|
||||
InputStream in = socket.getInputStream();
|
||||
OutputStream out = socket.getOutputStream();
|
||||
_log.debug("server streams built");
|
||||
byte buf[] = new byte[128*1024];
|
||||
while (buf != null) {
|
||||
for (int i = 0; i < buf.length; i++) {
|
||||
int c = in.read();
|
||||
if (c == -1) {
|
||||
buf = null;
|
||||
break;
|
||||
} else {
|
||||
buf[i] = (byte)(c & 0xFF);
|
||||
}
|
||||
}
|
||||
if (buf != null) {
|
||||
_log.debug("* server read the full buffer");
|
||||
out.write(buf);
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Closing the received server socket");
|
||||
socket.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_log.error("error running", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ClientRunner implements Runnable {
|
||||
private I2PAppContext _context;
|
||||
private I2PSession _session;
|
||||
private Log _log;
|
||||
public ClientRunner(I2PAppContext ctx, I2PSession session) {
|
||||
_context = ctx;
|
||||
_session = session;
|
||||
_log = ctx.logManager().getLog(ClientRunner.class);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
Properties opts = new Properties();
|
||||
I2PSocketManager mgr = new I2PSocketManagerFull(_context, _session, opts, "client");
|
||||
_log.debug("manager created");
|
||||
I2PSocket socket = mgr.connect(_server.getMyDestination());
|
||||
_log.debug("socket created");
|
||||
InputStream in = socket.getInputStream();
|
||||
OutputStream out = socket.getOutputStream();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
byte buf[] = new byte[128*1024];
|
||||
_context.random().nextBytes(buf);
|
||||
byte orig[] = new byte[buf.length];
|
||||
System.arraycopy(buf, 0, orig, 0, buf.length);
|
||||
out.write(buf);
|
||||
_log.debug("client wrote a buffer");
|
||||
out.flush();
|
||||
_log.debug("client flushed");
|
||||
|
||||
byte rbuf[] = new byte[buf.length];
|
||||
for (int j = 0; j < buf.length; j++) {
|
||||
int c = in.read();
|
||||
if (c == -1) {
|
||||
buf = null;
|
||||
break;
|
||||
} else {
|
||||
//_log.debug("client read: " + ((char)c));
|
||||
if (c < 0) c += 256;
|
||||
rbuf[j] = (byte)(c & 0xFF);
|
||||
}
|
||||
}
|
||||
if (buf != null) {
|
||||
_log.debug("* client read a full buffer");
|
||||
int firstOff = -1;
|
||||
for (int k = 0; k < orig.length; k++) {
|
||||
if (orig[k] != rbuf[k]) {
|
||||
firstOff = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (firstOff < 0) {
|
||||
System.out.println("** Read match");
|
||||
} else {
|
||||
System.out.println("** Read does not match: first off = " + firstOff);
|
||||
_log.error("read does not match (first off = " + firstOff + "): \n"
|
||||
+ Base64.encode(orig) + "\n"
|
||||
+ Base64.encode(rbuf));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Closing the client socket");
|
||||
socket.close();
|
||||
_log.debug("socket closed");
|
||||
|
||||
Thread.sleep(5*1000);
|
||||
System.exit(0);
|
||||
} catch (Exception e) {
|
||||
_log.error("error running", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private I2PSession createSession() {
|
||||
try {
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
|
||||
Destination dest = client.createDestination(baos);
|
||||
I2PSession sess = client.createSession(new ByteArrayInputStream(baos.toByteArray()), new Properties());
|
||||
sess.connect();
|
||||
return sess;
|
||||
} catch (Exception e) {
|
||||
_log.error("error running", e);
|
||||
throw new RuntimeException("b0rk b0rk b0rk");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
EchoLargeTest et = new EchoLargeTest();
|
||||
et.test();
|
||||
}
|
||||
}
|
||||
179
apps/streaming/java/test/net/i2p/client/streaming/EchoTest.java
Normal file
179
apps/streaming/java/test/net/i2p/client/streaming/EchoTest.java
Normal file
@@ -0,0 +1,179 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Date;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class EchoTest {
|
||||
private Log _log;
|
||||
private I2PSession _client;
|
||||
private I2PSession _server;
|
||||
public void test() {
|
||||
try {
|
||||
I2PAppContext context = I2PAppContext.getGlobalContext();
|
||||
_log = context.logManager().getLog(ConnectTest.class);
|
||||
_log.debug("creating server session");
|
||||
_server = createSession();
|
||||
_log.debug("running server");
|
||||
runServer(context, _server);
|
||||
_log.debug("creating client session");
|
||||
_client = createSession();
|
||||
_log.debug("running client");
|
||||
runClient(context, _client);
|
||||
} catch (Exception e) {
|
||||
_log.error("error running", e);
|
||||
}
|
||||
try { Thread.sleep(300*1000); } catch (Exception e) {}
|
||||
}
|
||||
|
||||
private void runClient(I2PAppContext ctx, I2PSession session) {
|
||||
Thread t = new Thread(new ClientRunner(ctx, session));
|
||||
t.setName("client");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
private void runServer(I2PAppContext ctx, I2PSession session) {
|
||||
Thread t = new Thread(new ServerRunner(ctx, session));
|
||||
t.setName("server");
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
private class ServerRunner implements Runnable {
|
||||
private I2PAppContext _context;
|
||||
private I2PSession _session;
|
||||
private Log _log;
|
||||
public ServerRunner(I2PAppContext ctx, I2PSession session) {
|
||||
_context = ctx;
|
||||
_session = session;
|
||||
_log = ctx.logManager().getLog(ServerRunner.class);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
Properties opts = new Properties();
|
||||
I2PSocketManager mgr = new I2PSocketManagerFull(_context, _session, opts, "client");
|
||||
_log.debug("manager created");
|
||||
I2PServerSocket ssocket = mgr.getServerSocket();
|
||||
_log.debug("server socket created");
|
||||
while (true) {
|
||||
I2PSocket socket = ssocket.accept();
|
||||
_log.debug("socket accepted: " + socket);
|
||||
InputStream in = socket.getInputStream();
|
||||
OutputStream out = socket.getOutputStream();
|
||||
_log.debug("server streams built");
|
||||
byte buf[] = new byte[5];
|
||||
while (buf != null) {
|
||||
for (int i = 0; i < buf.length; i++) {
|
||||
int c = in.read();
|
||||
if (c == -1) {
|
||||
buf = null;
|
||||
break;
|
||||
} else {
|
||||
buf[i] = (byte)(c & 0xFF);
|
||||
}
|
||||
}
|
||||
if (buf != null) {
|
||||
_log.debug("* server read: " + new String(buf));
|
||||
out.write(buf);
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Closing the received server socket");
|
||||
socket.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_log.error("error running", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ClientRunner implements Runnable {
|
||||
private I2PAppContext _context;
|
||||
private I2PSession _session;
|
||||
private Log _log;
|
||||
public ClientRunner(I2PAppContext ctx, I2PSession session) {
|
||||
_context = ctx;
|
||||
_session = session;
|
||||
_log = ctx.logManager().getLog(ClientRunner.class);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
Properties opts = new Properties();
|
||||
I2PSocketManager mgr = new I2PSocketManagerFull(_context, _session, opts, "client");
|
||||
_log.debug("manager created");
|
||||
I2PSocket socket = mgr.connect(_server.getMyDestination());
|
||||
_log.debug("socket created");
|
||||
InputStream in = socket.getInputStream();
|
||||
OutputStream out = socket.getOutputStream();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
out.write("blah!".getBytes());
|
||||
_log.debug("client wrote a line");
|
||||
out.flush();
|
||||
_log.debug("client flushed");
|
||||
byte buf[] = new byte[5];
|
||||
|
||||
for (int j = 0; j < buf.length; j++) {
|
||||
int c = in.read();
|
||||
if (c == -1) {
|
||||
buf = null;
|
||||
break;
|
||||
} else {
|
||||
//_log.debug("client read: " + ((char)c));
|
||||
buf[j] = (byte)(c & 0xFF);
|
||||
}
|
||||
}
|
||||
if (buf != null) {
|
||||
_log.debug("* client read: " + new String(buf));
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Closing the client socket");
|
||||
socket.close();
|
||||
_log.debug("socket closed");
|
||||
|
||||
Thread.sleep(5*1000);
|
||||
System.exit(0);
|
||||
} catch (Exception e) {
|
||||
_log.error("error running", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private I2PSession createSession() {
|
||||
try {
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
|
||||
Destination dest = client.createDestination(baos);
|
||||
I2PSession sess = client.createSession(new ByteArrayInputStream(baos.toByteArray()), new Properties());
|
||||
sess.connect();
|
||||
return sess;
|
||||
} catch (Exception e) {
|
||||
_log.error("error running", e);
|
||||
throw new RuntimeException("b0rk b0rk b0rk");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
EchoTest et = new EchoTest();
|
||||
et.test();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Stress test the MessageInputStream
|
||||
*/
|
||||
public class MessageInputStreamTest {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
|
||||
public MessageInputStreamTest() {
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(MessageInputStreamTest.class);
|
||||
}
|
||||
|
||||
public void testInOrder() {
|
||||
byte orig[] = new byte[256*1024];
|
||||
_context.random().nextBytes(orig);
|
||||
|
||||
MessageInputStream in = new MessageInputStream(_context);
|
||||
for (int i = 0; i < orig.length / 1024; i++) {
|
||||
byte msg[] = new byte[1024];
|
||||
System.arraycopy(orig, i*1024, msg, 0, 1024);
|
||||
in.messageReceived(i, msg);
|
||||
}
|
||||
|
||||
byte read[] = new byte[orig.length];
|
||||
try {
|
||||
int howMany = DataHelper.read(in, read);
|
||||
if (howMany != orig.length)
|
||||
throw new RuntimeException("Failed test: not enough bytes read [" + howMany + "]");
|
||||
if (!DataHelper.eq(orig, read))
|
||||
throw new RuntimeException("Failed test: data read is not equal");
|
||||
|
||||
_log.info("Passed test: in order");
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException("IOError reading: " + ioe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testRandomOrder() {
|
||||
byte orig[] = new byte[256*1024];
|
||||
_context.random().nextBytes(orig);
|
||||
|
||||
MessageInputStream in = new MessageInputStream(_context);
|
||||
ArrayList order = new ArrayList(32);
|
||||
for (int i = 0; i < orig.length / 1024; i++)
|
||||
order.add(new Integer(i));
|
||||
Collections.shuffle(order);
|
||||
for (int i = 0; i < orig.length / 1024; i++) {
|
||||
byte msg[] = new byte[1024];
|
||||
Integer cur = (Integer)order.get(i);
|
||||
System.arraycopy(orig, cur.intValue()*1024, msg, 0, 1024);
|
||||
in.messageReceived(cur.intValue(), msg);
|
||||
_log.debug("Injecting " + cur);
|
||||
}
|
||||
|
||||
byte read[] = new byte[orig.length];
|
||||
try {
|
||||
int howMany = DataHelper.read(in, read);
|
||||
if (howMany != orig.length)
|
||||
throw new RuntimeException("Failed test: not enough bytes read [" + howMany + "]");
|
||||
if (!DataHelper.eq(orig, read))
|
||||
throw new RuntimeException("Failed test: data read is not equal");
|
||||
|
||||
_log.info("Passed test: random order");
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException("IOError reading: " + ioe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testRandomDups() {
|
||||
byte orig[] = new byte[256*1024];
|
||||
_context.random().nextBytes(orig);
|
||||
|
||||
MessageInputStream in = new MessageInputStream(_context);
|
||||
for (int n = 0; n < 3; n++) {
|
||||
ArrayList order = new ArrayList(32);
|
||||
for (int i = 0; i < orig.length / 1024; i++)
|
||||
order.add(new Integer(i));
|
||||
Collections.shuffle(order);
|
||||
for (int i = 0; i < orig.length / 1024; i++) {
|
||||
byte msg[] = new byte[1024];
|
||||
Integer cur = (Integer)order.get(i);
|
||||
System.arraycopy(orig, cur.intValue()*1024, msg, 0, 1024);
|
||||
in.messageReceived(cur.intValue(), msg);
|
||||
_log.debug("Injecting " + cur);
|
||||
}
|
||||
}
|
||||
|
||||
byte read[] = new byte[orig.length];
|
||||
try {
|
||||
int howMany = DataHelper.read(in, read);
|
||||
if (howMany != orig.length)
|
||||
throw new RuntimeException("Failed test: not enough bytes read [" + howMany + "]");
|
||||
if (!DataHelper.eq(orig, read))
|
||||
throw new RuntimeException("Failed test: data read is not equal");
|
||||
|
||||
_log.info("Passed test: random dups");
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException("IOError reading: " + ioe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
MessageInputStreamTest t = new MessageInputStreamTest();
|
||||
try {
|
||||
t.testInOrder();
|
||||
t.testRandomOrder();
|
||||
t.testRandomDups();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class MessageOutputStreamTest {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
|
||||
public MessageOutputStreamTest() {
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(MessageOutputStreamTest.class);
|
||||
}
|
||||
|
||||
public void test() {
|
||||
Receiver receiver = new Receiver();
|
||||
MessageOutputStream out = new MessageOutputStream(_context, receiver);
|
||||
byte buf[] = new byte[128*1024];
|
||||
_context.random().nextBytes(buf);
|
||||
try {
|
||||
out.write(buf);
|
||||
out.flush();
|
||||
} catch (IOException ioe) { ioe.printStackTrace(); }
|
||||
byte read[] = receiver.getData();
|
||||
int firstOff = -1;
|
||||
for (int k = 0; k < buf.length; k++) {
|
||||
if (buf[k] != read[k]) {
|
||||
firstOff = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (firstOff < 0) {
|
||||
System.out.println("** Read match");
|
||||
} else {
|
||||
System.out.println("** Read does not match: first off = " + firstOff);
|
||||
_log.error("read does not match (first off = " + firstOff + "): \n"
|
||||
+ Base64.encode(buf) + "\n"
|
||||
+ Base64.encode(read));
|
||||
}
|
||||
}
|
||||
|
||||
private class Receiver implements MessageOutputStream.DataReceiver {
|
||||
private ByteArrayOutputStream _data;
|
||||
public Receiver() {
|
||||
_data = new ByteArrayOutputStream();
|
||||
}
|
||||
public MessageOutputStream.WriteStatus writeData(byte[] buf, int off, int size) {
|
||||
_data.write(buf, off, size);
|
||||
return new DummyWriteStatus();
|
||||
}
|
||||
public byte[] getData() { return _data.toByteArray(); }
|
||||
}
|
||||
|
||||
private static class DummyWriteStatus implements MessageOutputStream.WriteStatus {
|
||||
public void waitForAccept(int maxWaitMs) { return; }
|
||||
public void waitForCompletion(int maxWaitMs) { return; }
|
||||
public boolean writeAccepted() { return true; }
|
||||
public boolean writeFailed() { return false; }
|
||||
public boolean writeSuccessful() { return true; }
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
MessageOutputStreamTest t = new MessageOutputStreamTest();
|
||||
t.test();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class PingTest {
|
||||
public void test() {
|
||||
try {
|
||||
I2PAppContext context = I2PAppContext.getGlobalContext();
|
||||
I2PSession session = createSession();
|
||||
ConnectionManager mgr = new ConnectionManager(context, session);
|
||||
Log log = context.logManager().getLog(PingTest.class);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
log.debug("ping " + i);
|
||||
long before = context.clock().now();
|
||||
boolean ponged = mgr.ping(session.getMyDestination(), 2*1000);
|
||||
long after = context.clock().now();
|
||||
log.debug("ponged? " + ponged + " after " + (after-before) + "ms");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try { Thread.sleep(30*1000); } catch (Exception e) {}
|
||||
|
||||
}
|
||||
|
||||
private I2PSession createSession() {
|
||||
try {
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
|
||||
Destination dest = client.createDestination(baos);
|
||||
I2PSession sess = client.createSession(new ByteArrayInputStream(baos.toByteArray()), new Properties());
|
||||
sess.connect();
|
||||
return sess;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("b0rk b0rk b0rk");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
PingTest pt = new PingTest();
|
||||
pt.test();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
/**
|
||||
* Usage: StreamSinkTest [(old|new) [#hops [#kb]]]
|
||||
*/
|
||||
public class StreamSinkTest {
|
||||
/* private static String HOST1 = "dev.i2p.net";
|
||||
private static String HOST2 = "dev.i2p.net";
|
||||
private static String PORT1 = "4101";
|
||||
private static String PORT2 = "4501";
|
||||
/*
|
||||
private static String HOST1 = "localhost";
|
||||
private static String HOST2 = "localhost";
|
||||
private static String PORT1 = "7654";
|
||||
private static String PORT2 = "7654";
|
||||
*/
|
||||
private static String HOST1 = "localhost";
|
||||
private static String HOST2 = "localhost";
|
||||
private static String PORT1 = "10001";
|
||||
private static String PORT2 = "11001";
|
||||
/* */
|
||||
|
||||
public static void main(String args[]) {
|
||||
boolean old = false;
|
||||
int hops = 0;
|
||||
int kb = 32*1024;
|
||||
if (args.length > 0) {
|
||||
if ("old".equals(args[0]))
|
||||
old = true;
|
||||
}
|
||||
if (args.length > 1) {
|
||||
try {
|
||||
hops = Integer.parseInt(args[1]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
hops = 0;
|
||||
}
|
||||
}
|
||||
if (args.length > 2) {
|
||||
try {
|
||||
kb = Integer.parseInt(args[2]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
kb = 32*1024;
|
||||
}
|
||||
}
|
||||
|
||||
if (!old)
|
||||
System.setProperty(I2PSocketManagerFactory.PROP_MANAGER, I2PSocketManagerFull.class.getName());
|
||||
System.setProperty("tunnels.depthInbound", ""+hops);
|
||||
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
StreamSinkServer.main(new String[] { HOST1, PORT1, "streamSinkTestDir", "streamSinkTestServer.key" });
|
||||
}
|
||||
}, "server").start();
|
||||
|
||||
try { Thread.sleep(60*1000); } catch (Exception e) {}
|
||||
|
||||
//run(256, 1);
|
||||
//run(256, 1000);
|
||||
//run(4*1024, 10);
|
||||
run(kb, 1);
|
||||
//run(1*1024, 1);
|
||||
//run("/home/jrandom/streamSinkTestDir/clientSink36766.dat", 1);
|
||||
//run(512*1024, 1);
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException e) {}
|
||||
System.out.println("Shutting down");
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
private static void run(final int kb, final int msBetweenWrites) {
|
||||
Thread t = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
StreamSinkClient.main(new String[] { HOST2, PORT2, kb+"", msBetweenWrites+"", "streamSinkTestServer.key" });
|
||||
}
|
||||
});
|
||||
t.start();
|
||||
|
||||
System.out.println("client and server started: size = " + kb + "KB, delay = " + msBetweenWrites);
|
||||
try {
|
||||
t.join();
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
|
||||
private static void run(final String filename, final int msBetweenWrites) {
|
||||
Thread t = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
StreamSinkSend.main(new String[] { filename, msBetweenWrites+"", "streamSinkTestServer.key" });
|
||||
}
|
||||
});
|
||||
t.start();
|
||||
|
||||
System.out.println("client and server started: file " + filename + ", delay = " + msBetweenWrites);
|
||||
try {
|
||||
t.join();
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import net.i2p.client.I2PClient;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class StreamSinkTestClient {
|
||||
public static void main(String args[]) {
|
||||
System.setProperty(I2PSocketManagerFactory.PROP_MANAGER, I2PSocketManagerFull.class.getName());
|
||||
//System.setProperty(I2PClient.PROP_TCP_HOST, "dev.i2p.net");
|
||||
//System.setProperty(I2PClient.PROP_TCP_PORT, "4501");
|
||||
System.setProperty("tunnels.depthInbound", "0");
|
||||
|
||||
if (args.length <= 0) {
|
||||
send("/home/jrandom/libjbigi.so");
|
||||
} else {
|
||||
for (int i = 0; i < args.length; i++)
|
||||
send(args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private static void send(final String filename) {
|
||||
Thread t = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
StreamSinkSend.main(new String[] { filename, "0", "streamSinkTestLiveServer.key" });
|
||||
}
|
||||
}, "client " + filename);
|
||||
t.start();
|
||||
try { t.join(); } catch (Exception e) {}
|
||||
System.err.println("Done sending");
|
||||
try { Thread.sleep(120*1000); } catch (Exception e) {}
|
||||
//System.exit(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package net.i2p.client.streaming;
|
||||
|
||||
import net.i2p.client.I2PClient;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class StreamSinkTestServer {
|
||||
public static void main(String args[]) {
|
||||
System.setProperty(I2PSocketManagerFactory.PROP_MANAGER, I2PSocketManagerFull.class.getName());
|
||||
//System.setProperty(I2PClient.PROP_TCP_HOST, "dev.i2p.net");
|
||||
//System.setProperty(I2PClient.PROP_TCP_PORT, "4101");
|
||||
System.setProperty("tunnels.depthInbound", "0");
|
||||
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
StreamSinkServer.main(new String[] { "streamSinkTestLiveDir", "streamSinkTestLiveServer.key" });
|
||||
}
|
||||
}, "server").start();
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ public class SysTray implements SysTrayMenuListener {
|
||||
if (!(new File("router.config")).exists())
|
||||
openRouterConsole("http://localhost:" + _portString + "/index.jsp");
|
||||
|
||||
if (System.getProperty("os.name").startsWith("Windows"))
|
||||
if ( (System.getProperty("os.name").startsWith("Windows")) && (!Boolean.getBoolean("systray.disable")) )
|
||||
_instance = new SysTray();
|
||||
}
|
||||
|
||||
|
||||
37
build.xml
37
build.xml
@@ -6,11 +6,11 @@
|
||||
<echo message=" dist: distclean then package everything up (installer, clean tarball, update tarball)" />
|
||||
<echo message=" installer: build the GUI installer" />
|
||||
<echo message=" tarball: tar the full install into i2p.tar.bz2 (extracts to build a new clean install)" />
|
||||
<echo message=" updater: tar the built i2p specific files into an i2pupdate.tar.bz2 (extracts safely over existing installs)" />
|
||||
<echo message=" updater: tar the built i2p specific files into an i2pupdate.zip (extracts safely over existing installs)" />
|
||||
<echo message=" distclean: clean up all derived files" />
|
||||
<echo message=" javadoc: generate javadoc for the entire project into ./build/javadoc" />
|
||||
</target>
|
||||
<target name="dist" depends="distclean, pkg, javadoc">
|
||||
<target name="dist" depends="pkg, javadoc">
|
||||
</target>
|
||||
<target name="build" depends="builddep, jar, buildWEB" />
|
||||
<target name="buildclean" depends="distclean, build" />
|
||||
@@ -19,6 +19,7 @@
|
||||
<ant dir="core/java/" target="jar" />
|
||||
<ant dir="router/java/" target="jar" />
|
||||
<ant dir="apps/ministreaming/java/" target="jar" />
|
||||
<ant dir="apps/streaming/java/" target="jar" />
|
||||
<ant dir="apps/i2ptunnel/java/" target="jar" />
|
||||
<ant dir="apps/sam/java/" target="jar" />
|
||||
<ant dir="apps/heartbeat/java/" target="jar" />
|
||||
@@ -46,6 +47,7 @@
|
||||
<copy file="core/java/build/i2p.jar" todir="build/" />
|
||||
<copy file="router/java/build/router.jar" todir="build/" />
|
||||
<copy file="apps/ministreaming/java/build/mstreaming.jar" todir="build/" />
|
||||
<copy file="apps/streaming/java/build/streaming.jar" todir="build/" />
|
||||
<copy file="apps/i2ptunnel/java/build/i2ptunnel.jar" todir="build/" />
|
||||
<copy file="apps/i2ptunnel/java/build/i2ptunnel.war" todir="build/" />
|
||||
<copy file="apps/sam/java/build/sam.jar" todir="build/" />
|
||||
@@ -57,7 +59,7 @@
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
<javadoc access="package"
|
||||
destdir="./build/javadoc"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
@@ -75,6 +77,7 @@
|
||||
<pathelement location="router/java/src" />
|
||||
<pathelement location="router/java/test" />
|
||||
<pathelement location="apps/ministreaming/java/src" />
|
||||
<pathelement location="apps/streaming/java/src" />
|
||||
<pathelement location="apps/i2ptunnel/java/src" />
|
||||
<pathelement location="apps/systray/java/src" />
|
||||
<pathelement location="apps/routerconsole/java/src" />
|
||||
@@ -93,6 +96,7 @@
|
||||
<ant dir="core/java/" target="distclean" />
|
||||
<ant dir="router/java/" target="distclean" />
|
||||
<ant dir="apps/ministreaming/java/" target="distclean" />
|
||||
<ant dir="apps/streaming/java/" target="distclean" />
|
||||
<ant dir="apps/i2ptunnel/java/" target="distclean" />
|
||||
<ant dir="apps/sam/java/" target="distclean" />
|
||||
<ant dir="apps/heartbeat/java/" target="distclean" />
|
||||
@@ -143,7 +147,7 @@
|
||||
<target name="pkgclean">
|
||||
<delete dir="pkg-temp" />
|
||||
<delete>
|
||||
<fileset dir="." includes="i2p.tar.bz2 install.jar i2pupdate.tar.bz2" />
|
||||
<fileset dir="." includes="i2p.tar.bz2 install.jar i2pupdate.zip" />
|
||||
</delete>
|
||||
</target>
|
||||
<target name="preppkg" depends="build">
|
||||
@@ -158,6 +162,7 @@
|
||||
<copy file="build/jbigi.jar" todir="pkg-temp/lib" />
|
||||
<copy file="build/jnet.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/mstreaming.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/streaming.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/netmonitor.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/org.mortbay.jetty-jdk1.2.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/org.mortbay.jetty.jar" todir="pkg-temp/lib/" />
|
||||
@@ -207,34 +212,34 @@
|
||||
<copy file="history.txt" todir="pkg-temp/" />
|
||||
<mkdir dir="pkg-temp/docs" />
|
||||
<copy file="readme.html" todir="pkg-temp/docs/" />
|
||||
<mkdir dir="pkg-temp/work" />
|
||||
<touch file="pkg-temp/work/ignore.this" />
|
||||
<mkdir dir="pkg-temp/eepsite" />
|
||||
<mkdir dir="pkg-temp/eepsite/webapps" />
|
||||
<mkdir dir="pkg-temp/eepsite/logs" />
|
||||
<mkdir dir="pkg-temp/eepsite/docroot" />
|
||||
<copy file="installer/resources/eepsite_index.html" tofile="pkg-temp/eepsite/docroot/index.html" />
|
||||
<copy file="installer/resources/jetty.xml" tofile="pkg-temp/eepsite/jetty.xml" />
|
||||
</target>
|
||||
<target name="tarball" depends="preppkg">
|
||||
<tar compression="bzip2" destfile="i2p.tar.bz2">
|
||||
<tarfileset dir="pkg-temp" includes="**/*" prefix="i2p" />
|
||||
</tar>
|
||||
</target>
|
||||
<target name="updater" depends="build">
|
||||
<target name="updater" depends="distclean, build">
|
||||
<delete dir="pkg-temp" />
|
||||
<copy file="build/heartbeat.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/i2p.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/i2ptunnel.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/mstreaming.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/streaming.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/sam.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/router.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/routerconsole.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/sam.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/systray.jar" todir="pkg-temp/lib" />
|
||||
<copy file="build/i2ptunnel.war" todir="pkg-temp/webapps/" />
|
||||
<copy file="build/routerconsole.war" todir="pkg-temp/webapps/" />
|
||||
<copy file="history.txt" todir="pkg-temp/" />
|
||||
<copy file="hosts.txt" todir="pkg-temp/" />
|
||||
<copy file="installer/resources/install_i2p_service_winnt.bat" todir="pkg-temp/" />
|
||||
<copy file="installer/resources/uninstall_i2p_service_winnt.bat" todir="pkg-temp/" />
|
||||
<copy file="installer/resources/i2prouter" todir="pkg-temp/" />
|
||||
<copy file="installer/resources/i2prouter.bat" todir="pkg-temp/" />
|
||||
<copy file="installer/resources/i2prouter_win9x.bat" todir="pkg-temp/" />
|
||||
<copy file="installer/resources/wrapper.config" todir="pkg-temp/" />
|
||||
<mkdir dir="pkg-temp/eepsite" />
|
||||
<mkdir dir="pkg-temp/eepsite/webapps" />
|
||||
<copy file="installer/resources/jetty.xml" tofile="pkg-temp/eepsite/jetty.xml" />
|
||||
<zip destfile="i2pupdate.zip" basedir="pkg-temp" />
|
||||
</target>
|
||||
<taskdef name="izpack" classpath="${basedir}/installer/lib/izpack/standalone-compiler.jar" classname="com.izforge.izpack.ant.IzPackTask" />
|
||||
|
||||
@@ -14,8 +14,8 @@ package net.i2p;
|
||||
*
|
||||
*/
|
||||
public class CoreVersion {
|
||||
public final static String ID = "$Revision: 1.19 $ $Date: 2004/09/03 14:46:07 $";
|
||||
public final static String VERSION = "0.4.0.1";
|
||||
public final static String ID = "$Revision: 1.24 $ $Date: 2004/10/18 14:08:00 $";
|
||||
public final static String VERSION = "0.4.1.4";
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println("I2P Core version: " + VERSION);
|
||||
|
||||
@@ -284,7 +284,7 @@ public class I2PAppContext {
|
||||
* matter. Though for the crazy people out there, we do expose a way to
|
||||
* disable it.
|
||||
*/
|
||||
public AESEngine AESEngine() {
|
||||
public AESEngine aes() {
|
||||
if (!_AESEngineInitialized) initializeAESEngine();
|
||||
return _AESEngine;
|
||||
}
|
||||
|
||||
@@ -109,8 +109,8 @@ class I2CPMessageProducer {
|
||||
// generateNewTags would only generate tags if necessary
|
||||
|
||||
data.setEncryptedData(encr);
|
||||
_log.debug("Encrypting the payload to public key " + dest.getPublicKey().toBase64() + "\nPayload: "
|
||||
+ data.calculateHash());
|
||||
//_log.debug("Encrypting the payload to public key " + dest.getPublicKey().toBase64() + "\nPayload: "
|
||||
// + data.calculateHash());
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ public interface I2PSession {
|
||||
* @return whether it was accepted by the router for delivery or not
|
||||
*/
|
||||
public boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException;
|
||||
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size) throws I2PSessionException;
|
||||
|
||||
/**
|
||||
* Like sendMessage above, except the key used and the tags sent are exposed to the
|
||||
@@ -66,8 +67,8 @@ public interface I2PSession {
|
||||
* the contents of the set is ignored during the call, but afterwards it contains a set of SessionTag
|
||||
* objects that were sent along side the given keyUsed.
|
||||
*/
|
||||
public boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set tagsSent)
|
||||
throws I2PSessionException;
|
||||
public boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set tagsSent) throws I2PSessionException;
|
||||
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent) throws I2PSessionException;
|
||||
|
||||
/** Receive a message that the router has notified the client about, returning
|
||||
* the payload.
|
||||
|
||||
@@ -314,6 +314,13 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
public abstract void receiveStatus(int msgId, long nonce, int status);
|
||||
|
||||
protected boolean isGuaranteed() {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
String str = _options.getProperty(I2PClient.PROP_RELIABILITY);
|
||||
if (str == null)
|
||||
_log.debug("reliability is not specified, fallback");
|
||||
else
|
||||
_log.debug("reliability is specified: " + str);
|
||||
}
|
||||
String reliability = _options.getProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
|
||||
return I2PClient.PROP_RELIABILITY_GUARANTEED.equals(reliability);
|
||||
}
|
||||
@@ -386,8 +393,13 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
}
|
||||
}
|
||||
if ( (msgId != null) && (size != null) ) {
|
||||
if (_sessionListener != null)
|
||||
_sessionListener.messageAvailable(I2PSessionImpl.this, msgId.intValue(), size.intValue());
|
||||
if (_sessionListener != null) {
|
||||
try {
|
||||
_sessionListener.messageAvailable(I2PSessionImpl.this, msgId.intValue(), size.intValue());
|
||||
} catch (Exception e) {
|
||||
_log.log(Log.CRIT, "Error notifying app of message availability", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -580,7 +592,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
while (true) {
|
||||
long delay = BASE_RECONNECT_DELAY << i;
|
||||
i++;
|
||||
if (delay > MAX_RECONNECT_DELAY)
|
||||
if ( (delay > MAX_RECONNECT_DELAY) || (delay <= 0) )
|
||||
delay = MAX_RECONNECT_DELAY;
|
||||
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
|
||||
|
||||
|
||||
@@ -62,19 +62,28 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
}
|
||||
|
||||
public boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException {
|
||||
return sendMessage(dest, payload, new SessionKey(), new HashSet(64));
|
||||
return sendMessage(dest, payload, 0, payload.length);
|
||||
}
|
||||
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size) throws I2PSessionException {
|
||||
return sendMessage(dest, payload, offset, size, new SessionKey(), new HashSet(64));
|
||||
}
|
||||
|
||||
public boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set tagsSent)
|
||||
public boolean sendMessage(Destination dest, byte[] payload, SessionKey keyUsed, Set tagsSent) throws I2PSessionException {
|
||||
return sendMessage(dest, payload, 0, payload.length, keyUsed, tagsSent);
|
||||
}
|
||||
public boolean sendMessage(Destination dest, byte[] payload, int offset, int size, SessionKey keyUsed, Set tagsSent)
|
||||
throws I2PSessionException {
|
||||
if (isClosed()) throw new I2PSessionException("Already closed");
|
||||
if (SHOULD_COMPRESS) payload = DataHelper.compress(payload);
|
||||
if (SHOULD_COMPRESS) payload = DataHelper.compress(payload, offset, size);
|
||||
else throw new IllegalStateException("we need to update sendGuaranteed to support partial send");
|
||||
|
||||
// we always send as guaranteed (so we get the session keys/tags acked),
|
||||
// but only block until the appropriate event has been reached (guaranteed
|
||||
// success or accepted). we may want to break this out into a seperate
|
||||
// attribute, allowing both nonblocking sends and transparently managed keys,
|
||||
// as well as the nonblocking sends with application managed keys. Later.
|
||||
if (isGuaranteed() || true) {
|
||||
if (isGuaranteed() || false) {
|
||||
//_log.error("sendGuaranteed");
|
||||
return sendGuaranteed(dest, payload, keyUsed, tagsSent);
|
||||
}
|
||||
return sendBestEffort(dest, payload, keyUsed, tagsSent);
|
||||
@@ -103,16 +112,32 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
if (key == null) key = _context.sessionKeyManager().createSession(dest.getPublicKey());
|
||||
SessionTag tag = _context.sessionKeyManager().consumeNextAvailableTag(dest.getPublicKey(), key);
|
||||
Set sentTags = null;
|
||||
if (_context.sessionKeyManager().getAvailableTags(dest.getPublicKey(), key) < 10) {
|
||||
sentTags = createNewTags(50);
|
||||
} else if (_context.sessionKeyManager().getAvailableTimeLeft(dest.getPublicKey(), key) < 30 * 1000) {
|
||||
// if we have > 10 tags, but they expire in under 30 seconds, we want more
|
||||
sentTags = createNewTags(50);
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Tags are almost expired, adding 50 new ones");
|
||||
int oldTags = _context.sessionKeyManager().getAvailableTags(dest.getPublicKey(), key);
|
||||
long availTimeLeft = _context.sessionKeyManager().getAvailableTimeLeft(dest.getPublicKey(), key);
|
||||
|
||||
if ( (tagsSent == null) || (tagsSent.size() <= 0) ) {
|
||||
if (oldTags < 10) {
|
||||
sentTags = createNewTags(50);
|
||||
//_log.error("** sendBestEffort only had " + oldTags + " adding 50");
|
||||
} else if (availTimeLeft < 30 * 1000) {
|
||||
// if we have > 10 tags, but they expire in under 30 seconds, we want more
|
||||
sentTags = createNewTags(50);
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Tags are almost expired, adding 50 new ones");
|
||||
//_log.error("** sendBestEffort available time left " + availTimeLeft);
|
||||
} else {
|
||||
//_log.error("sendBestEffort old tags: " + oldTags + " available time left: " + availTimeLeft);
|
||||
}
|
||||
}
|
||||
|
||||
SessionKey newKey = null;
|
||||
if (false) // rekey
|
||||
newKey = _context.keyGenerator().generateSessionKey();
|
||||
|
||||
if ( (tagsSent != null) && (tagsSent.size() > 0) ) {
|
||||
if (sentTags == null)
|
||||
sentTags = new HashSet();
|
||||
sentTags.addAll(tagsSent);
|
||||
}
|
||||
|
||||
long nonce = _context.random().nextInt(Integer.MAX_VALUE);
|
||||
MessageState state = new MessageState(nonce, getPrefix());
|
||||
@@ -143,7 +168,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
long afterSendingSync = _context.clock().now();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix() + "Adding sending state " + state.getMessageId() + " / "
|
||||
+ state.getNonce()
|
||||
+ state.getNonce() + " for best effort "
|
||||
+ " sync took " + (inSendingSync-beforeSendingSync)
|
||||
+ " add took " + (afterSendingSync-inSendingSync));
|
||||
_producer.sendMessage(this, dest, nonce, payload, tag, key, sentTags, newKey);
|
||||
@@ -223,7 +248,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
|
||||
long afterSendingSync = _context.clock().now();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix() + "Adding sending state " + state.getMessageId() + " / "
|
||||
+ state.getNonce()
|
||||
+ state.getNonce() + " for guaranteed "
|
||||
+ " sync took " + (inSendingSync-beforeSendingSync)
|
||||
+ " add took " + (afterSendingSync-inSendingSync));
|
||||
_producer.sendMessage(this, dest, nonce, payload, tag, key, sentTags, newKey);
|
||||
|
||||
@@ -29,7 +29,6 @@ class MessageState {
|
||||
private Destination _to;
|
||||
private boolean _cancelled;
|
||||
private long _created;
|
||||
private Object _lock = new Object();
|
||||
|
||||
private static long __stateId = 0;
|
||||
private long _stateId;
|
||||
@@ -51,9 +50,7 @@ class MessageState {
|
||||
public void receive(int status) {
|
||||
synchronized (_receivedStatus) {
|
||||
_receivedStatus.add(new Integer(status));
|
||||
}
|
||||
synchronized (_lock) {
|
||||
_lock.notifyAll();
|
||||
_receivedStatus.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,150 +113,140 @@ class MessageState {
|
||||
_log.warn(_prefix + "Expired waiting for the status [" + status + "]");
|
||||
return;
|
||||
}
|
||||
if (isSuccess(status) || isFailure(status)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Received a confirm (one way or the other)");
|
||||
return;
|
||||
}
|
||||
if (timeToWait > 5000) {
|
||||
timeToWait = 5000;
|
||||
}
|
||||
synchronized (_lock) {
|
||||
synchronized (_receivedStatus) {
|
||||
if (locked_isSuccess(status) || locked_isFailure(status)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Received a confirm (one way or the other)");
|
||||
return;
|
||||
}
|
||||
if (timeToWait > 5000) {
|
||||
timeToWait = 5000;
|
||||
}
|
||||
try {
|
||||
_lock.wait(timeToWait);
|
||||
_receivedStatus.wait(timeToWait);
|
||||
} catch (InterruptedException ie) { // nop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSuccess(int wantedStatus) {
|
||||
List received = null;
|
||||
synchronized (_receivedStatus) {
|
||||
received = new ArrayList(_receivedStatus);
|
||||
//_receivedStatus.clear();
|
||||
}
|
||||
|
||||
private boolean locked_isSuccess(int wantedStatus) {
|
||||
boolean rv = false;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "isSuccess(" + wantedStatus + "): " + received);
|
||||
for (Iterator iter = received.iterator(); iter.hasNext();) {
|
||||
_log.debug(_prefix + "isSuccess(" + wantedStatus + "): " + _receivedStatus);
|
||||
for (Iterator iter = _receivedStatus.iterator(); iter.hasNext();) {
|
||||
Integer val = (Integer) iter.next();
|
||||
int recv = val.intValue();
|
||||
switch (recv) {
|
||||
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_FAILURE:
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(_prefix + "Received best effort failure after " + getElapsed() + " from "
|
||||
+ toString());
|
||||
rv = false;
|
||||
break;
|
||||
case MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE:
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(_prefix + "Received guaranteed failure after " + getElapsed() + " from "
|
||||
+ toString());
|
||||
rv = false;
|
||||
break;
|
||||
case MessageStatusMessage.STATUS_SEND_ACCEPTED:
|
||||
if (wantedStatus == MessageStatusMessage.STATUS_SEND_ACCEPTED) {
|
||||
return true; // if we're only looking for accepted, take it directly (don't let any GUARANTEED_* override it)
|
||||
}
|
||||
// ignore accepted, as we want something better
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Got accepted, but we're waiting for more from " + toString());
|
||||
continue;
|
||||
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_SUCCESS:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Received best effort success after " + getElapsed()
|
||||
+ " from " + toString());
|
||||
if (wantedStatus == recv) {
|
||||
rv = true;
|
||||
} else {
|
||||
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_FAILURE:
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(_prefix + "Received best effort failure after " + getElapsed() + " from "
|
||||
+ toString());
|
||||
rv = false;
|
||||
break;
|
||||
case MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE:
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(_prefix + "Received guaranteed failure after " + getElapsed() + " from "
|
||||
+ toString());
|
||||
rv = false;
|
||||
break;
|
||||
case MessageStatusMessage.STATUS_SEND_ACCEPTED:
|
||||
if (wantedStatus == MessageStatusMessage.STATUS_SEND_ACCEPTED) {
|
||||
return true; // if we're only looking for accepted, take it directly (don't let any GUARANTEED_* override it)
|
||||
}
|
||||
// ignore accepted, as we want something better
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Not guaranteed success, but best effort after "
|
||||
+ getElapsed() + " will do... from " + toString());
|
||||
_log.debug(_prefix + "Got accepted, but we're waiting for more from " + toString());
|
||||
continue;
|
||||
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_SUCCESS:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Received best effort success after " + getElapsed()
|
||||
+ " from " + toString());
|
||||
if (wantedStatus == recv) {
|
||||
rv = true;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Not guaranteed success, but best effort after "
|
||||
+ getElapsed() + " will do... from " + toString());
|
||||
rv = true;
|
||||
}
|
||||
break;
|
||||
case MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Received guaranteed success after " + getElapsed() + " from "
|
||||
+ toString());
|
||||
// even if we're waiting for best effort success, guaranteed is good enough
|
||||
rv = true;
|
||||
}
|
||||
break;
|
||||
case MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Received guaranteed success after " + getElapsed() + " from "
|
||||
+ toString());
|
||||
// even if we're waiting for best effort success, guaranteed is good enough
|
||||
rv = true;
|
||||
break;
|
||||
case -1:
|
||||
continue;
|
||||
default:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Received something else [" + recv + "]...");
|
||||
break;
|
||||
case -1:
|
||||
continue;
|
||||
default:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Received something else [" + recv + "]...");
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private boolean isFailure(int wantedStatus) {
|
||||
List received = null;
|
||||
synchronized (_receivedStatus) {
|
||||
received = new ArrayList(_receivedStatus);
|
||||
//_receivedStatus.clear();
|
||||
}
|
||||
private boolean locked_isFailure(int wantedStatus) {
|
||||
boolean rv = false;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "isFailure(" + wantedStatus + "): " + received);
|
||||
for (Iterator iter = received.iterator(); iter.hasNext();) {
|
||||
_log.debug(_prefix + "isFailure(" + wantedStatus + "): " + _receivedStatus);
|
||||
|
||||
for (Iterator iter = _receivedStatus.iterator(); iter.hasNext();) {
|
||||
Integer val = (Integer) iter.next();
|
||||
int recv = val.intValue();
|
||||
switch (recv) {
|
||||
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_FAILURE:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.warn(_prefix + "Received best effort failure after " + getElapsed() + " from "
|
||||
+ toString());
|
||||
rv = true;
|
||||
break;
|
||||
case MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.warn(_prefix + "Received guaranteed failure after " + getElapsed() + " from "
|
||||
+ toString());
|
||||
rv = true;
|
||||
break;
|
||||
case MessageStatusMessage.STATUS_SEND_ACCEPTED:
|
||||
if (wantedStatus == MessageStatusMessage.STATUS_SEND_ACCEPTED) {
|
||||
rv = false;
|
||||
} else {
|
||||
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_FAILURE:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Got accepted, but we're waiting for more from "
|
||||
_log.warn(_prefix + "Received best effort failure after " + getElapsed() + " from "
|
||||
+ toString());
|
||||
rv = true;
|
||||
break;
|
||||
case MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.warn(_prefix + "Received guaranteed failure after " + getElapsed() + " from "
|
||||
+ toString());
|
||||
rv = true;
|
||||
break;
|
||||
case MessageStatusMessage.STATUS_SEND_ACCEPTED:
|
||||
if (wantedStatus == MessageStatusMessage.STATUS_SEND_ACCEPTED) {
|
||||
rv = false;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Got accepted, but we're waiting for more from "
|
||||
+ toString());
|
||||
continue;
|
||||
// ignore accepted, as we want something better
|
||||
}
|
||||
break;
|
||||
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_SUCCESS:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Received best effort success after " + getElapsed()
|
||||
+ " from " + toString());
|
||||
if (wantedStatus == recv) {
|
||||
rv = false;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Not guaranteed success, but best effort after "
|
||||
+ getElapsed() + " will do... from " + toString());
|
||||
rv = false;
|
||||
}
|
||||
break;
|
||||
case MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Received guaranteed success after " + getElapsed() + " from "
|
||||
+ toString());
|
||||
// even if we're waiting for best effort success, guaranteed is good enough
|
||||
rv = false;
|
||||
break;
|
||||
case -1:
|
||||
continue;
|
||||
// ignore accepted, as we want something better
|
||||
}
|
||||
break;
|
||||
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_SUCCESS:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Received best effort success after " + getElapsed()
|
||||
+ " from " + toString());
|
||||
if (wantedStatus == recv) {
|
||||
rv = false;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Not guaranteed success, but best effort after "
|
||||
+ getElapsed() + " will do... from " + toString());
|
||||
rv = false;
|
||||
}
|
||||
break;
|
||||
case MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Received guaranteed success after " + getElapsed() + " from "
|
||||
+ toString());
|
||||
// even if we're waiting for best effort success, guaranteed is good enough
|
||||
rv = false;
|
||||
break;
|
||||
case -1:
|
||||
continue;
|
||||
default:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Received something else [" + recv + "]...");
|
||||
default:
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(_prefix + "Received something else [" + recv + "]...");
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
@@ -267,13 +254,15 @@ class MessageState {
|
||||
|
||||
/** true if the given status (or an equivilant) was received */
|
||||
public boolean received(int status) {
|
||||
return isSuccess(status);
|
||||
synchronized (_receivedStatus) {
|
||||
return locked_isSuccess(status);
|
||||
}
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
_cancelled = true;
|
||||
synchronized (_lock) {
|
||||
_lock.notifyAll();
|
||||
synchronized (_receivedStatus) {
|
||||
_receivedStatus.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,13 +13,13 @@ import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.i2cp.I2CPMessage;
|
||||
import net.i2p.data.i2cp.MessageStatusMessage;
|
||||
import net.i2p.data.i2cp.ReceiveMessageBeginMessage;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Handle I2CP MessageStatusMessages from the router. This currently only takes
|
||||
* into account status of available, automatically prefetching them as soon as
|
||||
* possible
|
||||
*
|
||||
* @author jrandom
|
||||
*/
|
||||
class MessageStatusMessageHandler extends HandlerImpl {
|
||||
public MessageStatusMessageHandler(I2PAppContext context) {
|
||||
@@ -28,41 +28,44 @@ class MessageStatusMessageHandler extends HandlerImpl {
|
||||
|
||||
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
|
||||
boolean skipStatus = true;
|
||||
if (I2PClient.PROP_RELIABILITY_GUARANTEED.equals(session.getOptions()
|
||||
.getProperty(I2PClient.PROP_RELIABILITY,
|
||||
I2PClient.PROP_RELIABILITY_BEST_EFFORT)))
|
||||
if (I2PClient.PROP_RELIABILITY_GUARANTEED.equals(session.getOptions().getProperty(I2PClient.PROP_RELIABILITY,
|
||||
I2PClient.PROP_RELIABILITY_BEST_EFFORT)))
|
||||
skipStatus = false;
|
||||
_log.debug("Handle message " + message);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Handle message " + message);
|
||||
MessageStatusMessage msg = (MessageStatusMessage) message;
|
||||
switch (msg.getStatus()) {
|
||||
case MessageStatusMessage.STATUS_AVAILABLE:
|
||||
ReceiveMessageBeginMessage m = new ReceiveMessageBeginMessage();
|
||||
m.setMessageId(msg.getMessageId());
|
||||
m.setSessionId(msg.getSessionId());
|
||||
try {
|
||||
session.sendMessage(m);
|
||||
} catch (I2PSessionException ise) {
|
||||
_log.error("Error asking for the message", ise);
|
||||
}
|
||||
return;
|
||||
case MessageStatusMessage.STATUS_SEND_ACCEPTED:
|
||||
session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus());
|
||||
// noop
|
||||
return;
|
||||
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_SUCCESS:
|
||||
case MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS:
|
||||
_log.info("Message delivery succeeded for message " + msg.getMessageId());
|
||||
//if (!skipStatus)
|
||||
session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus());
|
||||
return;
|
||||
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_FAILURE:
|
||||
case MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE:
|
||||
_log.info("Message delivery FAILED for message " + msg.getMessageId());
|
||||
//if (!skipStatus)
|
||||
session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus());
|
||||
return;
|
||||
default:
|
||||
_log.error("Invalid message delivery status received: " + msg.getStatus());
|
||||
case MessageStatusMessage.STATUS_AVAILABLE:
|
||||
ReceiveMessageBeginMessage m = new ReceiveMessageBeginMessage();
|
||||
m.setMessageId(msg.getMessageId());
|
||||
m.setSessionId(msg.getSessionId());
|
||||
try {
|
||||
session.sendMessage(m);
|
||||
} catch (I2PSessionException ise) {
|
||||
_log.error("Error asking for the message", ise);
|
||||
}
|
||||
return;
|
||||
case MessageStatusMessage.STATUS_SEND_ACCEPTED:
|
||||
session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus());
|
||||
// noop
|
||||
return;
|
||||
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_SUCCESS:
|
||||
case MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS:
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Message delivery succeeded for message " + msg.getMessageId());
|
||||
//if (!skipStatus)
|
||||
session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus());
|
||||
return;
|
||||
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_FAILURE:
|
||||
case MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE:
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Message delivery FAILED for message " + msg.getMessageId());
|
||||
//if (!skipStatus)
|
||||
session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus());
|
||||
return;
|
||||
default:
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Invalid message delivery status received: " + msg.getStatus());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,6 @@ import net.i2p.util.Log;
|
||||
* @author jrandom
|
||||
*/
|
||||
class RequestLeaseSetMessageHandler extends HandlerImpl {
|
||||
private final static Log _log = new Log(RequestLeaseSetMessageHandler.class);
|
||||
private Map _existingLeaseSets;
|
||||
|
||||
public RequestLeaseSetMessageHandler(I2PAppContext context) {
|
||||
|
||||
@@ -10,9 +10,13 @@ package net.i2p.client.naming;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@@ -34,42 +38,51 @@ public class HostsTxtNamingService extends NamingService {
|
||||
* If this system property is specified, the tunnel will read the
|
||||
* given file for hostname=destKey values when resolving names
|
||||
*/
|
||||
public final static String PROP_HOSTS_FILE = "i2p.hostsfile";
|
||||
public final static String PROP_HOSTS_FILE = "i2p.hostsfilelist";
|
||||
|
||||
/** default hosts.txt filename */
|
||||
public final static String DEFAULT_HOSTS_FILE = "hosts.txt";
|
||||
public final static String DEFAULT_HOSTS_FILE = "userhosts.txt,hosts.txt";
|
||||
|
||||
private final static Log _log = new Log(HostsTxtNamingService.class);
|
||||
|
||||
private List getFilenames() {
|
||||
String list = _context.getProperty(PROP_HOSTS_FILE, DEFAULT_HOSTS_FILE);
|
||||
StringTokenizer tok = new StringTokenizer(list, ",");
|
||||
List rv = new ArrayList(tok.countTokens());
|
||||
while (tok.hasMoreTokens())
|
||||
rv.add(tok.nextToken());
|
||||
return rv;
|
||||
}
|
||||
|
||||
public Destination lookup(String hostname) {
|
||||
// Try to look it up in hosts.txt
|
||||
// Reload file each time to catch changes.
|
||||
// (and it's easier :P
|
||||
String hostsfile = _context.getProperty(PROP_HOSTS_FILE, DEFAULT_HOSTS_FILE);
|
||||
Properties hosts = new Properties();
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
File f = new File(hostsfile);
|
||||
if (f.canRead()) {
|
||||
fis = new FileInputStream(f);
|
||||
hosts.load(fis);
|
||||
} else {
|
||||
_log.error("Hosts file " + hostsfile + " does not exist.");
|
||||
}
|
||||
} catch (Exception ioe) {
|
||||
_log.error("Error loading hosts file " + hostsfile, ioe);
|
||||
} finally {
|
||||
if (fis != null) try {
|
||||
fis.close();
|
||||
} catch (IOException ioe) { // nop
|
||||
// check the list each time, reloading the file on each
|
||||
// lookup
|
||||
|
||||
List filenames = getFilenames();
|
||||
for (int i = 0; i < filenames.size(); i++) {
|
||||
String hostsfile = (String)filenames.get(i);
|
||||
Properties hosts = new Properties();
|
||||
try {
|
||||
File f = new File(hostsfile);
|
||||
if ( (f.exists()) && (f.canRead()) ) {
|
||||
DataHelper.loadProps(hosts, f);
|
||||
|
||||
String key = hosts.getProperty(hostname);
|
||||
if ( (key != null) && (key.trim().length() > 0) ) {
|
||||
return lookupBase64(key);
|
||||
}
|
||||
|
||||
} else {
|
||||
_log.warn("Hosts file " + hostsfile + " does not exist.");
|
||||
}
|
||||
} catch (Exception ioe) {
|
||||
_log.error("Error loading hosts file " + hostsfile, ioe);
|
||||
}
|
||||
// not found, continue to the next file
|
||||
}
|
||||
String res = hosts.getProperty(hostname);
|
||||
// If we can't find name in hosts, assume it's a key.
|
||||
if ((res == null) || (res.trim().length() == 0)) {
|
||||
res = hostname;
|
||||
}
|
||||
return lookupBase64(res);
|
||||
// If we can't find name in any of the hosts files,
|
||||
// assume it's a key.
|
||||
return lookupBase64(hostname);
|
||||
}
|
||||
|
||||
public String reverseLookup(Destination dest) {
|
||||
|
||||
@@ -9,8 +9,6 @@ package net.i2p.crypto;
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
@@ -37,92 +35,96 @@ public class AESEngine {
|
||||
|
||||
/** Encrypt the payload with the session key
|
||||
* @param payload data to be encrypted
|
||||
* @param payloadIndex index into the payload to start encrypting
|
||||
* @param out where to store the result
|
||||
* @param outIndex where in out to start writing
|
||||
* @param sessionKey private esession key to encrypt to
|
||||
* @param initializationVector IV for CBC
|
||||
* @return encrypted data
|
||||
* @param iv IV for CBC
|
||||
* @param length how much data to encrypt
|
||||
*/
|
||||
public byte[] encrypt(byte payload[], SessionKey sessionKey, byte initializationVector[]) {
|
||||
if ((initializationVector == null) || (payload == null) || (sessionKey == null)
|
||||
|| (initializationVector.length != 16)) return null;
|
||||
|
||||
byte cyphertext[] = null;
|
||||
if ((payload.length % 16) == 0)
|
||||
cyphertext = new byte[payload.length];
|
||||
else
|
||||
cyphertext = new byte[payload.length + (16 - (payload.length % 16))];
|
||||
System.arraycopy(payload, 0, cyphertext, 0, payload.length);
|
||||
return cyphertext;
|
||||
public void encrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
|
||||
System.arraycopy(payload, payloadIndex, out, outIndex, length);
|
||||
_log.warn("Warning: AES is disabled");
|
||||
}
|
||||
|
||||
public byte[] safeEncrypt(byte payload[], SessionKey sessionKey, byte iv[], int paddedSize) {
|
||||
if ((iv == null) || (payload == null) || (sessionKey == null) || (iv.length != 16)) return null;
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(paddedSize + 64);
|
||||
Hash h = _context.sha().calculateHash(sessionKey.getData());
|
||||
try {
|
||||
h.writeBytes(baos);
|
||||
DataHelper.writeLong(baos, 4, payload.length);
|
||||
baos.write(payload);
|
||||
byte tv[] = baos.toByteArray();
|
||||
baos.write(ElGamalAESEngine.getPadding(_context, tv.length, paddedSize));
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing data", ioe);
|
||||
return null;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error writing data", dfe);
|
||||
return null;
|
||||
}
|
||||
return encrypt(baos.toByteArray(), sessionKey, iv);
|
||||
int size = Hash.HASH_LENGTH
|
||||
+ 4 // sizeof(payload)
|
||||
+ payload.length;
|
||||
int padding = ElGamalAESEngine.getPaddingSize(size, paddedSize);
|
||||
|
||||
byte data[] = new byte[size + padding];
|
||||
SHA256EntryCache.CacheEntry cache = _context.sha().cache().acquire(iv.length);
|
||||
Hash h = _context.sha().calculateHash(iv, cache);
|
||||
|
||||
int cur = 0;
|
||||
System.arraycopy(h.getData(), 0, data, cur, Hash.HASH_LENGTH);
|
||||
cur += Hash.HASH_LENGTH;
|
||||
_context.sha().cache().release(cache);
|
||||
|
||||
DataHelper.toLong(data, cur, 4, payload.length);
|
||||
cur += 4;
|
||||
System.arraycopy(payload, 0, data, cur, payload.length);
|
||||
cur += payload.length;
|
||||
byte paddingData[] = ElGamalAESEngine.getPadding(_context, size, paddedSize);
|
||||
System.arraycopy(paddingData, 0, data, cur, paddingData.length);
|
||||
|
||||
encrypt(data, 0, data, 0, sessionKey, iv, data.length);
|
||||
return data;
|
||||
}
|
||||
|
||||
public byte[] safeDecrypt(byte payload[], SessionKey sessionKey, byte iv[]) {
|
||||
if ((iv == null) || (payload == null) || (sessionKey == null) || (iv.length != 16)) return null;
|
||||
|
||||
byte decr[] = decrypt(payload, sessionKey, iv);
|
||||
byte decr[] = new byte[payload.length];
|
||||
decrypt(payload, 0, decr, 0, sessionKey, iv, payload.length);
|
||||
if (decr == null) {
|
||||
_log.error("Error decrypting the data - payload " + payload.length + " decrypted to null");
|
||||
return null;
|
||||
}
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(decr);
|
||||
Hash h = _context.sha().calculateHash(sessionKey.getData());
|
||||
try {
|
||||
Hash rh = new Hash();
|
||||
rh.readBytes(bais);
|
||||
if (!h.equals(rh)) {
|
||||
|
||||
int cur = 0;
|
||||
SHA256EntryCache.CacheEntry cache = _context.sha().cache().acquire(iv.length);
|
||||
byte h[] = _context.sha().calculateHash(iv, cache).getData();
|
||||
for (int i = 0; i < Hash.HASH_LENGTH; i++) {
|
||||
if (decr[i] != h[i]) {
|
||||
_log.error("Hash does not match [key=" + sessionKey + " / iv =" + DataHelper.toString(iv, iv.length)
|
||||
+ "]", new Exception("Hash error"));
|
||||
_context.sha().cache().release(cache);
|
||||
return null;
|
||||
}
|
||||
long len = DataHelper.readLong(bais, 4);
|
||||
byte data[] = new byte[(int) len];
|
||||
int read = bais.read(data);
|
||||
if (read != len) {
|
||||
_log.error("Not enough to read");
|
||||
return null;
|
||||
}
|
||||
return data;
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error writing data", ioe);
|
||||
return null;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Error writing data", dfe);
|
||||
}
|
||||
cur += Hash.HASH_LENGTH;
|
||||
_context.sha().cache().release(cache);
|
||||
|
||||
long len = DataHelper.fromLong(decr, cur, 4);
|
||||
cur += 4;
|
||||
|
||||
if (cur + len > decr.length) {
|
||||
_log.error("Not enough to read");
|
||||
return null;
|
||||
}
|
||||
|
||||
byte data[] = new byte[(int)len];
|
||||
System.arraycopy(decr, cur, data, 0, (int)len);
|
||||
return data;
|
||||
}
|
||||
|
||||
/** decrypt the data with the session key provided
|
||||
* @param cyphertext encrypted data
|
||||
* @param sessionKey private session key
|
||||
* @param initializationVector IV for CBC
|
||||
* @return unencrypted data
|
||||
*/
|
||||
public byte[] decrypt(byte cyphertext[], SessionKey sessionKey, byte initializationVector[]) {
|
||||
if ((initializationVector == null) || (cyphertext == null) || (sessionKey == null)
|
||||
|| (initializationVector.length != 16)) return null;
|
||||
|
||||
byte payload[] = new byte[cyphertext.length];
|
||||
/** Decrypt the data with the session key
|
||||
* @param payload data to be decrypted
|
||||
* @param payloadIndex index into the payload to start decrypting
|
||||
* @param out where to store the cleartext
|
||||
* @param outIndex where in out to start writing
|
||||
* @param sessionKey private session key to decrypt to
|
||||
* @param iv IV for CBC
|
||||
* @param length how much data to decrypt
|
||||
*/
|
||||
public void decrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
|
||||
System.arraycopy(payload, payloadIndex, out, outIndex, length);
|
||||
_log.warn("Warning: AES is disabled");
|
||||
return cyphertext;
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
@@ -133,14 +135,16 @@ public class AESEngine {
|
||||
|
||||
byte sbuf[] = new byte[16];
|
||||
RandomSource.getInstance().nextBytes(sbuf);
|
||||
byte se[] = ctx.AESEngine().encrypt(sbuf, key, iv);
|
||||
byte sd[] = ctx.AESEngine().decrypt(se, key, iv);
|
||||
byte se[] = new byte[16];
|
||||
ctx.aes().encrypt(sbuf, 0, se, 0, key, iv, sbuf.length);
|
||||
byte sd[] = new byte[16];
|
||||
ctx.aes().decrypt(se, 0, sd, 0, key, iv, se.length);
|
||||
ctx.logManager().getLog(AESEngine.class).debug("Short test: " + DataHelper.eq(sd, sbuf));
|
||||
|
||||
byte lbuf[] = new byte[1024];
|
||||
RandomSource.getInstance().nextBytes(sbuf);
|
||||
byte le[] = ctx.AESEngine().safeEncrypt(lbuf, key, iv, 2048);
|
||||
byte ld[] = ctx.AESEngine().safeDecrypt(le, key, iv);
|
||||
byte le[] = ctx.aes().safeEncrypt(lbuf, key, iv, 2048);
|
||||
byte ld[] = ctx.aes().safeDecrypt(le, key, iv);
|
||||
ctx.logManager().getLog(AESEngine.class).debug("Long test: " + DataHelper.eq(ld, lbuf));
|
||||
}
|
||||
}
|
||||
@@ -46,66 +46,81 @@ public class AESInputStream extends FilterInputStream {
|
||||
private long _cumulativePrepared; // how many bytes decrypted and added to _readyBuf
|
||||
private long _cumulativePaddingStripped; // how many bytes have been stripped
|
||||
|
||||
private ByteArrayOutputStream _encryptedBuf; // read from the stream but not yet decrypted
|
||||
private List _readyBuf; // list of Bytes ready to be consumed, where index 0 is the first
|
||||
/** read but not yet decrypted */
|
||||
private byte _encryptedBuf[];
|
||||
/** how many bytes have been added to the encryptedBuf since it was decrypted? */
|
||||
private int _writesSinceDecrypt;
|
||||
/** decrypted bytes ready for reading (first available == index of 0) */
|
||||
private int _decryptedBuf[];
|
||||
/** how many bytes are available for reading without decrypt? */
|
||||
private int _decryptedSize;
|
||||
|
||||
private final static int BLOCK_SIZE = CryptixRijndael_Algorithm._BLOCK_SIZE;
|
||||
private final static int READ_SIZE = BLOCK_SIZE;
|
||||
private final static int DECRYPT_SIZE = BLOCK_SIZE - 1;
|
||||
|
||||
public AESInputStream(I2PAppContext context, InputStream source, SessionKey key, byte iv[]) {
|
||||
public AESInputStream(I2PAppContext context, InputStream source, SessionKey key, byte[] iv) {
|
||||
super(source);
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(AESInputStream.class);
|
||||
_key = key;
|
||||
_lastBlock = new byte[BLOCK_SIZE];
|
||||
System.arraycopy(iv, 0, _lastBlock, 0, BLOCK_SIZE);
|
||||
_encryptedBuf = new ByteArrayOutputStream(BLOCK_SIZE);
|
||||
_readyBuf = new ArrayList(1024);
|
||||
_encryptedBuf = new byte[BLOCK_SIZE];
|
||||
_writesSinceDecrypt = 0;
|
||||
_decryptedBuf = new int[BLOCK_SIZE-1];
|
||||
_decryptedSize = 0;
|
||||
_cumulativePaddingStripped = 0;
|
||||
_eofFound = false;
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
while ((!_eofFound) && (_readyBuf.size() <= 0)) {
|
||||
refill(READ_SIZE);
|
||||
while ((!_eofFound) && (_decryptedSize <= 0)) {
|
||||
refill();
|
||||
}
|
||||
Integer nval = getNext();
|
||||
if (nval != null) {
|
||||
return nval.intValue();
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("No byte available. eof? " + _eofFound);
|
||||
|
||||
if (_eofFound)
|
||||
if (_decryptedSize > 0) {
|
||||
int c = _decryptedBuf[0];
|
||||
System.arraycopy(_decryptedBuf, 1, _decryptedBuf, 0, _decryptedBuf.length-1);
|
||||
_decryptedSize--;
|
||||
return c;
|
||||
} else if (_eofFound) {
|
||||
return -1;
|
||||
|
||||
throw new IOException("Not EOF, but none available? " + _readyBuf.size() + "/" + _encryptedBuf.size()
|
||||
+ "/" + _cumulativeRead + "... impossible");
|
||||
} else {
|
||||
throw new IOException("Not EOF, but none available? " + _decryptedSize
|
||||
+ "/" + _writesSinceDecrypt
|
||||
+ "/" + _cumulativeRead + "... impossible");
|
||||
}
|
||||
}
|
||||
|
||||
public int read(byte dest[]) throws IOException {
|
||||
for (int i = 0; i < dest.length; i++) {
|
||||
int val = read();
|
||||
if (val == -1) {
|
||||
// no more to read... can they expect more?
|
||||
if (_eofFound && (i == 0)) return -1;
|
||||
|
||||
return i;
|
||||
}
|
||||
dest[i] = (byte) val;
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read the full buffer of size " + dest.length);
|
||||
return dest.length;
|
||||
return read(dest, 0, dest.length);
|
||||
}
|
||||
|
||||
public int read(byte dest[], int off, int len) throws IOException {
|
||||
byte buf[] = new byte[len];
|
||||
int read = read(buf);
|
||||
if (read == -1) return -1;
|
||||
System.arraycopy(buf, 0, dest, off, read);
|
||||
return read;
|
||||
for (int i = 0; i < len; i++) {
|
||||
int val = read();
|
||||
if (val == -1) {
|
||||
// no more to read... can they expect more?
|
||||
if (_eofFound && (i == 0)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.info("EOF? " + _eofFound
|
||||
+ "\nread=" + i + " decryptedSize=" + _decryptedSize
|
||||
+ " \nencryptedSize=" + _writesSinceDecrypt
|
||||
+ " \ntotal=" + _cumulativeRead
|
||||
+ " \npadding=" + _cumulativePaddingStripped
|
||||
+ " \nprepared=" + _cumulativePrepared);
|
||||
return -1;
|
||||
} else {
|
||||
if (i != len)
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.info("non-terminal eof: " + _eofFound + " i=" + i + " len=" + len);
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
dest[off+i] = (byte)val;
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read the full buffer of size " + len);
|
||||
return len;
|
||||
}
|
||||
|
||||
public long skip(long numBytes) throws IOException {
|
||||
@@ -117,25 +132,15 @@ public class AESInputStream extends FilterInputStream {
|
||||
}
|
||||
|
||||
public int available() throws IOException {
|
||||
return _readyBuf.size();
|
||||
return _decryptedSize;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
//_log.debug("We have " + _encryptedBuf.size() + " available to decrypt... doing so");
|
||||
//decrypt();
|
||||
//byte buf[] = new byte[_readyBuf.size()];
|
||||
//for (int i = 0; i < buf.length; i++)
|
||||
// buf[i] = ((Integer)_readyBuf.get(i)).byteValue();
|
||||
//_log.debug("After decrypt: readyBuf.size: " + _readyBuf.size() + "\n val:\t" + Base64.encode(buf));
|
||||
int ready = _readyBuf.size();
|
||||
int encrypted = _readyBuf.size();
|
||||
_readyBuf.clear();
|
||||
_encryptedBuf.reset();
|
||||
in.close();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Cumulative bytes read from source/decrypted/stripped: " + _cumulativeRead + "/"
|
||||
+ _cumulativePrepared + "/" + _cumulativePaddingStripped + "] remaining [" + ready + " ready, "
|
||||
+ encrypted + " still encrypted]");
|
||||
+ _cumulativePrepared + "/" + _cumulativePaddingStripped + "] remaining [" + _decryptedSize + " ready, "
|
||||
+ _writesSinceDecrypt + " still encrypted]");
|
||||
}
|
||||
|
||||
public void mark(int readLimit) { // nop
|
||||
@@ -149,120 +154,66 @@ public class AESInputStream extends FilterInputStream {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the next ready byte, or null if no bytes are ready. this does not refill or block
|
||||
*
|
||||
*/
|
||||
private Integer getNext() {
|
||||
if (_readyBuf.size() > 0) {
|
||||
return (Integer) _readyBuf.remove(0);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read at least one new byte from the underlying stream, and up to max new bytes,
|
||||
* but not necessarily enough for a new decrypted block. This blocks until at least
|
||||
* one new byte is read from the stream
|
||||
*
|
||||
*/
|
||||
private void refill(int max) throws IOException {
|
||||
private void refill() throws IOException {
|
||||
if (!_eofFound) {
|
||||
byte buf[] = new byte[max];
|
||||
int read = in.read(buf);
|
||||
int read = in.read(_encryptedBuf, _writesSinceDecrypt, _encryptedBuf.length - _writesSinceDecrypt);
|
||||
if (read == -1) {
|
||||
_eofFound = true;
|
||||
} else if (read > 0) {
|
||||
//_log.debug("Read from the source stream " + read + " bytes");
|
||||
_cumulativeRead += read;
|
||||
_encryptedBuf.write(buf, 0, read);
|
||||
_writesSinceDecrypt += read;
|
||||
}
|
||||
}
|
||||
if (false) return; // true to keep the data for decrypt/display on close
|
||||
if (_encryptedBuf.size() > 0) {
|
||||
if (_encryptedBuf.size() >= DECRYPT_SIZE) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("We have " + _encryptedBuf.size() + " available to decrypt... doing so");
|
||||
decrypt();
|
||||
if ( (_encryptedBuf.size() > 0) && (_log.shouldLog(Log.DEBUG)) )
|
||||
_log.debug("Bytes left in the encrypted buffer after decrypt: " + _encryptedBuf.size());
|
||||
} else {
|
||||
if (_eofFound) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("EOF and not enough bytes to decrypt [size = " + _encryptedBuf.size()
|
||||
+ " totalCumulative: " + _cumulativeRead + "/"+_cumulativePrepared +"]!");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Not enough bytes to decrypt [size = " + _encryptedBuf.size()
|
||||
+ " totalCumulative: " + _cumulativeRead + "/"+_cumulativePrepared +"]");
|
||||
}
|
||||
}
|
||||
if (_writesSinceDecrypt == BLOCK_SIZE) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("We have " + _writesSinceDecrypt + " available to decrypt... doing so");
|
||||
decryptBlock();
|
||||
if ( (_writesSinceDecrypt > 0) && (_log.shouldLog(Log.DEBUG)) )
|
||||
_log.debug("Bytes left in the encrypted buffer after decrypt: "
|
||||
+ _writesSinceDecrypt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take (n*BLOCK_SIZE) bytes off the _encryptedBuf, decrypt them, and place
|
||||
* them on _readyBuf
|
||||
*
|
||||
* Decrypt the
|
||||
*/
|
||||
private void decrypt() throws IOException {
|
||||
byte encrypted[] = _encryptedBuf.toByteArray();
|
||||
_encryptedBuf.reset();
|
||||
|
||||
if ((encrypted == null) || (encrypted.length <= 0))
|
||||
private void decryptBlock() throws IOException {
|
||||
if (_writesSinceDecrypt != BLOCK_SIZE)
|
||||
throw new IOException("Error decrypting - no data to decrypt");
|
||||
|
||||
if (_decryptedSize != 0)
|
||||
throw new IOException("wtf, decrypted size is not 0? " + _decryptedSize);
|
||||
|
||||
_context.aes().decrypt(_encryptedBuf, 0, _encryptedBuf, 0, _key, _lastBlock, BLOCK_SIZE);
|
||||
DataHelper.xor(_encryptedBuf, 0, _lastBlock, 0, _encryptedBuf, 0, BLOCK_SIZE);
|
||||
int payloadBytes = countBlockPayload(_encryptedBuf, 0);
|
||||
|
||||
int numBlocks = encrypted.length / BLOCK_SIZE;
|
||||
if ((encrypted.length % BLOCK_SIZE) != 0) {
|
||||
// it was flushed / handled off the BLOCK_SIZE segments, so put the excess
|
||||
// back into the _encryptedBuf for later handling
|
||||
int trailing = encrypted.length % BLOCK_SIZE;
|
||||
_encryptedBuf.write(encrypted, encrypted.length - trailing, trailing);
|
||||
byte nencrypted[] = new byte[encrypted.length - trailing];
|
||||
System.arraycopy(encrypted, 0, nencrypted, 0, nencrypted.length);
|
||||
encrypted = nencrypted;
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Decrypt got odd segment - " + trailing
|
||||
+ " bytes pushed back for later decryption - corrupted or slow data stream perhaps?");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(encrypted.length + " bytes makes up " + numBlocks + " blocks to decrypt normally");
|
||||
for (int i = 0; i < payloadBytes; i++) {
|
||||
int c = _encryptedBuf[i];
|
||||
if (c <= 0)
|
||||
c += 256;
|
||||
_decryptedBuf[i] = c;
|
||||
}
|
||||
_decryptedSize = payloadBytes;
|
||||
|
||||
byte block[] = new byte[BLOCK_SIZE];
|
||||
for (int i = 0; i < numBlocks; i++) {
|
||||
System.arraycopy(encrypted, i * BLOCK_SIZE, block, 0, BLOCK_SIZE);
|
||||
byte decrypted[] = _context.AESEngine().decrypt(block, _key, _lastBlock);
|
||||
byte data[] = DataHelper.xor(decrypted, _lastBlock);
|
||||
int cleaned[] = stripPadding(data);
|
||||
for (int j = 0; j < cleaned.length; j++) {
|
||||
if (cleaned[j] <= 0) {
|
||||
cleaned[j] += 256;
|
||||
//_log.error("(modified: " + cleaned[j] + ")");
|
||||
}
|
||||
_readyBuf.add(new Integer(cleaned[j]));
|
||||
}
|
||||
_cumulativePrepared += cleaned.length;
|
||||
//_log.debug("Updating last block for inputStream");
|
||||
System.arraycopy(decrypted, 0, _lastBlock, 0, BLOCK_SIZE);
|
||||
}
|
||||
_cumulativePaddingStripped += BLOCK_SIZE - payloadBytes;
|
||||
_cumulativePrepared += payloadBytes;
|
||||
|
||||
int remaining = encrypted.length % BLOCK_SIZE;
|
||||
if (remaining != 0) {
|
||||
_encryptedBuf.write(encrypted, encrypted.length - remaining, remaining);
|
||||
_log.debug("After pushing " + remaining
|
||||
+ " bytes back onto the buffer, lets delay 1s our action so we don't fast busy until the net transfers data");
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException ie) { // nop
|
||||
}
|
||||
} else {
|
||||
//_log.debug("No remaining encrypted bytes beyond the block size");
|
||||
}
|
||||
System.arraycopy(_encryptedBuf, 0, _lastBlock, 0, BLOCK_SIZE);
|
||||
|
||||
_writesSinceDecrypt = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* How many non-padded bytes are there in the block starting at the given
|
||||
* location.
|
||||
*
|
||||
* PKCS#5 specifies the padding for the block has the # of padding bytes
|
||||
* located in the last byte of the block, and each of the padding bytes are
|
||||
* equal to that value.
|
||||
@@ -275,47 +226,58 @@ public class AESInputStream extends FilterInputStream {
|
||||
*
|
||||
* We use 16 byte blocks in this AES implementation
|
||||
*
|
||||
* @throws IOException if the padding is invalid
|
||||
*/
|
||||
private int[] stripPadding(byte data[]) throws IOException {
|
||||
int numPadBytes = data[data.length - 1];
|
||||
if ((numPadBytes >= data.length) || (numPadBytes <= 0)) {
|
||||
private int countBlockPayload(byte data[], int startIndex) throws IOException {
|
||||
int numPadBytes = data[startIndex + BLOCK_SIZE - 1];
|
||||
if ((numPadBytes >= BLOCK_SIZE) || (numPadBytes <= 0)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("stripPadding from block " + DataHelper.toHexString(data) + " (" + data.length + "bytes): "
|
||||
_log.debug("countBlockPayload on block index " + startIndex
|
||||
+ numPadBytes + " is an invalid # of pad bytes");
|
||||
throw new IOException("Invalid number of pad bytes (" + numPadBytes
|
||||
+ ") for " + data.length + " bytes");
|
||||
+ ") for " + startIndex + " index");
|
||||
}
|
||||
|
||||
int rv[] = new int[data.length - numPadBytes];
|
||||
// optional, but a really good idea: verify the padding
|
||||
if (true) {
|
||||
for (int i = data.length - numPadBytes; i < data.length; i++) {
|
||||
if (data[i] != (byte) numPadBytes) {
|
||||
for (int i = BLOCK_SIZE - numPadBytes; i < BLOCK_SIZE; i++) {
|
||||
if (data[startIndex + i] != (byte) numPadBytes) {
|
||||
throw new IOException("Incorrect padding on decryption: data[" + i
|
||||
+ "] = " + data[i] + " not " + numPadBytes);
|
||||
+ "] = " + data[startIndex + i] + " not " + numPadBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < rv.length; i++)
|
||||
rv[i] = data[i];
|
||||
_cumulativePaddingStripped += numPadBytes;
|
||||
return rv;
|
||||
|
||||
return BLOCK_SIZE - numPadBytes;
|
||||
}
|
||||
|
||||
int remainingBytes() {
|
||||
return _encryptedBuf.size();
|
||||
return _writesSinceDecrypt;
|
||||
}
|
||||
|
||||
int readyBytes() {
|
||||
return _readyBuf.size();
|
||||
return _decryptedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test AESOutputStream/AESInputStream
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
public static void main(String args[]) {
|
||||
I2PAppContext ctx = new I2PAppContext();
|
||||
|
||||
try {
|
||||
System.out.println("pwd=" + new java.io.File(".").getAbsolutePath());
|
||||
System.out.println("Beginning");
|
||||
runTest(ctx);
|
||||
} catch (Throwable e) {
|
||||
ctx.logManager().getLog(AESInputStream.class).error("Fail", e);
|
||||
}
|
||||
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
|
||||
System.out.println("Done");
|
||||
}
|
||||
private static void runTest(I2PAppContext ctx) {
|
||||
Log log = ctx.logManager().getLog(AESInputStream.class);
|
||||
log.setMinimumPriority(Log.DEBUG);
|
||||
byte orig[] = new byte[1024 * 32];
|
||||
RandomSource.getInstance().nextBytes(orig);
|
||||
//byte orig[] = "you are my sunshine, my only sunshine".getBytes();
|
||||
@@ -351,10 +313,51 @@ public class AESInputStream extends FilterInputStream {
|
||||
}
|
||||
|
||||
log.info("Done testing 0 byte data");
|
||||
|
||||
for (int i = 0; i <= 32768; i++) {
|
||||
orig = new byte[i];
|
||||
ctx.random().nextBytes(orig);
|
||||
try {
|
||||
log.info("Testing " + orig.length);
|
||||
runTest(ctx, orig, key, iv);
|
||||
} catch (RuntimeException re) {
|
||||
log.error("Error testing " + orig.length);
|
||||
throw re;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
orig = new byte[615280];
|
||||
|
||||
RandomSource.getInstance().nextBytes(orig);
|
||||
for (int i = 0; i < 20; i++) {
|
||||
runTest(ctx, orig, key, iv);
|
||||
}
|
||||
|
||||
log.info("Done testing 615280 byte data");
|
||||
*/
|
||||
/*
|
||||
for (int i = 0; i < 100; i++) {
|
||||
orig = new byte[ctx.random().nextInt(1024*1024)];
|
||||
ctx.random().nextBytes(orig);
|
||||
try {
|
||||
runTest(ctx, orig, key, iv);
|
||||
} catch (RuntimeException re) {
|
||||
log.error("Error testing " + orig.length);
|
||||
throw re;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Done testing 100 random lengths");
|
||||
*/
|
||||
|
||||
orig = new byte[32];
|
||||
RandomSource.getInstance().nextBytes(orig);
|
||||
runOffsetTest(ctx, orig, key, iv);
|
||||
try {
|
||||
runOffsetTest(ctx, orig, key, iv);
|
||||
} catch (Exception e) {
|
||||
log.info("Error running offset test", e);
|
||||
}
|
||||
|
||||
log.info("Done testing offset test (it should have come back with a statement NOT EQUAL!)");
|
||||
|
||||
@@ -377,30 +380,33 @@ public class AESInputStream extends FilterInputStream {
|
||||
long endE = Clock.getInstance().now();
|
||||
|
||||
ByteArrayInputStream encryptedStream = new ByteArrayInputStream(encrypted);
|
||||
AESInputStream in = new AESInputStream(ctx, encryptedStream, key, iv);
|
||||
AESInputStream sin = new AESInputStream(ctx, encryptedStream, key, iv);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
|
||||
byte buf[] = new byte[1024 * 32];
|
||||
int read = DataHelper.read(in, buf);
|
||||
int read = DataHelper.read(sin, buf);
|
||||
if (read > 0) baos.write(buf, 0, read);
|
||||
in.close();
|
||||
sin.close();
|
||||
byte fin[] = baos.toByteArray();
|
||||
long end = Clock.getInstance().now();
|
||||
Hash origHash = SHA256Generator.getInstance().calculateHash(orig);
|
||||
|
||||
Hash newHash = SHA256Generator.getInstance().calculateHash(fin);
|
||||
boolean eq = origHash.equals(newHash);
|
||||
if (eq)
|
||||
log.info("Equal hashes. hash: " + origHash);
|
||||
else
|
||||
log.error("NOT EQUAL! \norig: \t" + Base64.encode(orig) + "\nnew : \t" + Base64.encode(fin));
|
||||
if (eq) {
|
||||
//log.info("Equal hashes. hash: " + origHash);
|
||||
} else {
|
||||
throw new RuntimeException("NOT EQUAL! len=" + orig.length + " read=" + read
|
||||
+ "\norig: \t" + Base64.encode(orig) + "\nnew : \t"
|
||||
+ Base64.encode(fin));
|
||||
}
|
||||
boolean ok = DataHelper.eq(orig, fin);
|
||||
log.debug("EQ data? " + ok + " origLen: " + orig.length + " fin.length: " + fin.length);
|
||||
log.debug("Time to D(E(" + orig.length + ")): " + (end - start) + "ms");
|
||||
log.debug("Time to E(" + orig.length + "): " + (endE - start) + "ms");
|
||||
log.debug("Time to D(" + orig.length + "): " + (end - endE) + "ms");
|
||||
|
||||
} catch (Throwable t) {
|
||||
log.error("ERROR transferring", t);
|
||||
} catch (IOException ioe) {
|
||||
log.error("ERROR transferring", ioe);
|
||||
}
|
||||
//try { Thread.sleep(5000); } catch (Throwable t) {}
|
||||
}
|
||||
@@ -422,15 +428,15 @@ public class AESInputStream extends FilterInputStream {
|
||||
System.arraycopy(encrypted, 0, encryptedSegment, 0, 40);
|
||||
|
||||
ByteArrayInputStream encryptedStream = new ByteArrayInputStream(encryptedSegment);
|
||||
AESInputStream in = new AESInputStream(ctx, encryptedStream, key, iv);
|
||||
AESInputStream sin = new AESInputStream(ctx, encryptedStream, key, iv);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
|
||||
byte buf[] = new byte[1024 * 32];
|
||||
int read = DataHelper.read(in, buf);
|
||||
int remaining = in.remainingBytes();
|
||||
int readyBytes = in.readyBytes();
|
||||
int read = DataHelper.read(sin, buf);
|
||||
int remaining = sin.remainingBytes();
|
||||
int readyBytes = sin.readyBytes();
|
||||
log.info("Read: " + read);
|
||||
if (read > 0) baos.write(buf, 0, read);
|
||||
in.close();
|
||||
sin.close();
|
||||
byte fin[] = baos.toByteArray();
|
||||
log.info("fin.length: " + fin.length + " remaining: " + remaining + " ready: " + readyBytes);
|
||||
long end = Clock.getInstance().now();
|
||||
@@ -441,15 +447,16 @@ public class AESInputStream extends FilterInputStream {
|
||||
if (eq)
|
||||
log.info("Equal hashes. hash: " + origHash);
|
||||
else
|
||||
log.error("NOT EQUAL! \norig: \t" + Base64.encode(orig) + "\nnew : \t" + Base64.encode(fin));
|
||||
throw new RuntimeException("NOT EQUAL! len=" + orig.length + "\norig: \t" + Base64.encode(orig) + "\nnew : \t" + Base64.encode(fin));
|
||||
boolean ok = DataHelper.eq(orig, fin);
|
||||
log.debug("EQ data? " + ok + " origLen: " + orig.length + " fin.length: " + fin.length);
|
||||
log.debug("Time to D(E(" + orig.length + ")): " + (end - start) + "ms");
|
||||
log.debug("Time to E(" + orig.length + "): " + (endE - start) + "ms");
|
||||
log.debug("Time to D(" + orig.length + "): " + (end - endE) + "ms");
|
||||
|
||||
} catch (Throwable t) {
|
||||
log.error("ERROR transferring", t);
|
||||
} catch (RuntimeException re) {
|
||||
throw re;
|
||||
} catch (IOException ioe) {
|
||||
log.error("ERROR transferring", ioe);
|
||||
}
|
||||
//try { Thread.sleep(5000); } catch (Throwable t) {}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user