Compare commits

..

64 Commits

Author SHA1 Message Date
jrandom
98b5252a2d 0.4.1.4 2004-11-07 03:00:56 +00:00
jrandom
8abf42023a removed fproxy2, added postman 2004-11-07 02:49:40 +00:00
mpc
b792238f3f added my ping everyone script 2004-11-07 02:45:40 +00:00
jrandom
2486e5e75f on some resends, drop our session tags for the peer and revert to ElGamal, since
they may have restarted or otherwise lost the tags delivered to them.
2004-11-07 02:36:42 +00:00
jrandom
5f113f1610 2004-11-06 jrandom
* Expose a drop down on the /configclients.jsp to enter the outbound
      tunnel depth.
    * Improved *hosts.txt loading
    * Explicitly override the JVM's timezone settings to use GMT so that
      any client applications which use timezones won't leak sensitive
      data (thanks gott!)
    * Bundle sam.jar in the update (thanks duck!)
2004-11-07 02:25:13 +00:00
jrandom
592e9dc3ff * limit the resend delay to a max of 60s between resends
* reduce the max # resends to 5
* if the session.sendMessage fails, we're fucked, so kill the socket
2004-11-06 08:02:02 +00:00
jrandom
314316cee0 2004-11-06 jrandom
* Fix for a long standing synchronization bug in the SDK that in rare
      instances can add a few seconds of lag.
2004-11-06 07:59:54 +00:00
jrandom
9cf663063e added orion and protokol 2004-11-06 02:36:12 +00:00
jrandom
9ea9210a4b 2004-11-05 jrandom
* Bugfixes and unit tests for the SAM bridge to handle quoted message
      parameters, verify proper operation after multiple session lifetimes,
      as well as some synchronization problems.
    * New properties method on the DataHelper class.
    * Address a race on fast disconnecting clients
2004-11-05 11:59:07 +00:00
jrandom
2bf1a94608 added blog.polecat.i2p 2004-11-05 11:22:56 +00:00
jrandom
7a0236ad29 2004-11-05 jrandom
* Bugfixes and unit tests for the SAM bridge to handle quoted message
      parameters, verify proper operation after multiple session lifetimes,
      as well as some synchronization problems.
    * New properties method on the DataHelper class.
    * Address a race on fast disconnecting clients
2004-11-05 10:53:40 +00:00
jrandom
4341a0c198 added ciaran.i2p 2004-11-05 10:35:58 +00:00
jrandom
8071612c12 added polecat.i2p 2004-11-05 09:35:49 +00:00
jrandom
ea9cc3da04 added susi.i2p 2004-11-05 08:39:07 +00:00
jrandom
0df588fffb blackdown workarounds 2004-11-04 11:57:26 +00:00
jrandom
a622311dbc make the updater clean things (thanks nickster\!) 2004-11-04 08:31:11 +00:00
duck
1c95ac2470 Dont need no authentication 2004-11-03 01:34:22 +00:00
jrandom
6ef22166f9 2004-11-02 jrandom
* Fix for a long standing synchronization bug in the JobQueue (and added
      some kooky flags to make sure it stays dead)
    * Update the ministreaming lib to force mode=guaranteed if the default
      lib is used, and mode=best_effort for all other libs.
2004-11-02 11:57:07 +00:00
jrandom
1107e50108 2004-11-02 jrandom
* Fixed up the configuration overrides for the streaming socket lib
      integration so that it properly honors env settings.
    * More memory usage streamlining (last major revamp for now, i promise)
2004-11-02 08:27:55 +00:00
jrandom
c19355a7b2 2004-11-01 jrandom
* Increase the tunnel test timeout rapidly if our tunnels are failing.
    * Honor message expirations for some tunnel jobs that were prematurely
      expired.
    * Streamline memory usage with temporary object caches and more efficient
      serialization for SHA256 calculation, logging, and both I2CP and I2NP
      message handling.
    * Fix some situations where we forward messages too eagerly.  For a
      request at the tunnel endpoint, if the tunnel is inbound and the target
      is remote, honor the message by tunnel routing the data rather than
      sending it directly to the requested location.
2004-11-01 13:31:29 +00:00
jrandom
65d415fade javadoc fixes 2004-10-30 23:58:50 +00:00
jrandom
b37313d3f9 a chunk of streaming lib updates (cwin calc & timed win, pings influencing rtt, etc) 2004-10-30 23:46:01 +00:00
jrandom
58fcbad20a (mmMMmm profiling)
2004-10-30  jrandom
    * Cache the temporary objects used in the AES encryption/decryption
      process so that AES doesn't require any memory allocation to process
      data.
    * Dramatically reduce memory usage within various crypto implementations
      by avoiding unnecessary (though simplifying) buffers.
    * If we specify some tags to be sent in an I2CP message explicitly, use
      only those, not those plus a new set (otherwise we aren't sure on ACK
      which set was delivered)
    * Allow configuration for the partial send timeout (how long before
      resending a message down a different tunnel in a lease).  This can be
      updated with the "router.clientPartialSendTimeout" router config prop.
    * Logging
2004-10-30 23:43:59 +00:00
duck
b571f331ec removed duplicate beyond.i2p, added edge.i2p 2004-10-30 18:56:07 +00:00
mpc
2547d4b3e7 *** empty log message *** 2004-10-30 12:41:18 +00:00
jrandom
892786bf0c 2004-10-29 jrandom
* Strip the Referer, Via, and From headers completely, rather than
      inserting a bogus value ("i2p").  This should help with the use of
      SnipSnap and Geeklog (thanks nickster and DrWoo!)
2004-10-30 02:40:52 +00:00
jrandom
0c51f2b583 2004-10-27 jrandom
* Fix a strange race condition on i2cp client disconnect.
    * win98 startup fixes (thanks tester-1 and ardvark!)
    * include build scripts for the new streaming lib (which is NOT ready
      for use yet, but you can hack around with it)
(enjoy, duck)
2004-10-28 02:11:52 +00:00
jrandom
d5607ca195 * updated output stream test to match new API
* new paired stream server and client helpers
2004-10-28 02:05:51 +00:00
jrandom
48cdf17a4f * revamped locking to block on flush and close until all of the
packets through that point have been ACKed, throwing an
  InterruptedIOException if there was a writeTimeout or an IOException
  if the con failed
* revamped the ack/nack field settings to ack as much as possible
* handle some strange timeout/resend errors on connection
* pass 1/2rtt as the packet 'optional delay' field, and use that to
  schedule the ack time (the 'last' messages in a window set the
  optional delay to 0, asking for immediate ack of all received)
* increase the optional delay to 2 bytes (#ms to delay)
* inject random failures and delays if configured to do so in
  PacketHandler.choke
* fix up the window size adjustment (increment on ack, /= 2 on resend)
* use the highest RTT in the new RTT calculation so that we fit more
  in (via SACK)
* fix up the SACK handling (duh)
* revise the resend time calculation
2004-10-28 02:03:38 +00:00
duck
669a8fae15 iterate through the dict values
(thanks dinoman)
2004-10-27 04:07:00 +00:00
jrandom
d592936873 * mark the input stream as closed after receiving the packet's data
* properly close the source file in StreamSinkSend
* always adjust the rtt on ack, not just for packets with 1 send
* handle dup SYN gracefully
* revamp the default connection options
* logging
2004-10-25 20:04:07 +00:00
jrandom
87898dd2f1 added shiftfox.i2p 2004-10-25 06:15:47 +00:00
jrandom
15c227f568 * sliding windows w/ additive increase / multiplicitive decrease
* immediately send an ack on receiving a duplicate payload message
  (unless we've sent one within the last RTT)
* only adjust the RTT when there have been no resends
* added some (disabled) throttles - randomly injecting delays on
  received packets, as well as randomly dropping them
* logging
2004-10-25 03:22:29 +00:00
jrandom
8de41acfe1 * if we send a blank ACK message (that will not in turn be ACKed) and it
has session tags within it, send an additional ping to the peer,
  bundling those tags a second time, ACKing those tags on the pong.
* handle packets transferred during a race after the receiver ACKs the
  connection but before the establisher receives the ACK.
* notify the messageInputStream reader on close (duh)
* new stream sink test, shoving lots and lots of data down a stream
  with the existing StreamSinkServer and StreamSinkClient apps
* logging
2004-10-24 23:23:35 +00:00
jrandom
9680effb9f 2004-10-24 jrandom
* Allow explicit inclusion of session tags in the SDK, enabling the
      resending of tags bundled with messages that would not otherwise
      be ACKed.
    * Don't force mode=guaranteed for end to end delivery - if mode=bestEffort
      no DeliveryStatusMessage will be bundled (and as such, client apps using
      it will need to do their own session tag ack/nack).
    * Handle client errors when notifying them of message availability.
    * New StreamSinkSend which sends a file to a destination and disconnects.
    * Update the I2PSocketManagerFactory to build the specific
      I2PSocketManager instance based on the "i2p.streaming.manager" property,
      containing the class name of the I2PSocketManager implementation to instantiate.
2004-10-24 23:00:44 +00:00
jrandom
40df846e3f logging 2004-10-24 04:59:42 +00:00
jrandom
eee94fbf84 * deal with writes > the packet size limit
* deal with window size > 1, especially before receiving the first ACK
* disable congestion control for the moment (aka unlimited window size)
2004-10-24 04:56:26 +00:00
jrandom
813679ba25 2004-10-23 jrandom
* Minor ministreaming lib refactoring to simplify integration of the full
      streaming lib.
    * Minor bugfixes to data structure serialization.
2004-10-24 01:42:34 +00:00
jrandom
2b9e16c9c9 very basic tests pass (ping, open then pause then close, open then echo back and forth a few times then close) 2004-10-24 00:59:29 +00:00
jrandom
f9bb7f7cff added underground.i2p 2004-10-22 05:05:28 +00:00
jrandom
41e9569094 renamed newsbytetest to newsbyte 2004-10-21 23:30:21 +00:00
jrandom
336ee07191 added newsbytetest.i2p 2004-10-20 03:35:52 +00:00
duck
81e0a145f1 foil evil typo plot 2004-10-18 23:37:49 +00:00
jrandom
a95a968fa8 * 2004-10-18 0.4.1.3 released
2004-10-18  jrandom
    * Allow sending messages with a section of a byte array.
    * Reduced stats published.
2004-10-18 19:07:59 +00:00
jrandom
6c08941d8b updated curiosity.i2p 2004-10-18 18:23:13 +00:00
jrandom
e13a5b3865 added blueheron.i2p 2004-10-17 21:39:06 +00:00
jrandom
9011d5604a 2004-10-17 jrandom
* Don't b0rk on whitespace in the router address.
2004-10-17 21:03:05 +00:00
jrandom
78aa4ca137 updated files.i2p 2004-10-17 08:45:16 +00:00
jrandom
93111842df clarify history - we reduce the capacity calc, not the 'isFailing'.
* More aggressively reduce the capacity of peers if their tunnels are
      failing so that we move off them quicker.
2004-10-17 04:00:21 +00:00
jrandom
88693f8adc 2004-10-16 jrandom
* More aggressively fail peers if their tunnels are failing so that we
      move off them quicker.
    * Simplify some data structure serialization for reuse in the streaming
      lib, as well as add support for signing and verifying partial byte
      arrays.
    * Logging updates
2004-10-17 03:58:08 +00:00
jrandom
f904b012e9 initial impl for the new streaming lib (saying this isn't done should be obvious, but the
packet spec is at a save point)
2004-10-17 03:47:03 +00:00
jrandom
cebe0a151f added utansans.i2p 2004-10-16 21:45:55 +00:00
jrandom
8fffad0891 2004-10-16 jrandom
* Increased the default minimum tunnel test time to 5 seconds, since we
      still see the occational message processing time spike to 2 seconds.
    * Update the SimpleTimer to allow rescheduling a task thats already
      queued (useful for the new streaming lib).
2004-10-16 18:00:47 +00:00
jrandom
fb1263dad7 2004-10-15 jrandom
* Replaced old minimum tunnel test timeout of 1s with a configurable
      value (router.config property "router.tunnelTestMinimum", with the
      default of 2s).
2004-10-15 17:39:18 +00:00
jrandom
28c5d6c10d 2004-10-14 jrandom
* Tunnel rejection is no longer a sign of an overwhelmingly loaded
      peer, so don't use it as a key point of the IsFailing calculator.
      We still use it as a key point of the Capacity calculator, however.
2004-10-15 01:20:12 +00:00
jrandom
e7a6f6836e added irc.orz.i2p 2004-10-14 22:35:11 +00:00
jrandom
f8ffe016d1 2004-10-14 jrandom
* Allow for a configurable tunnel "growth factor", rather than trying
      to achieve a steady state.  This will let us grow gradually when
      the router is needed more, rather than blindly accepting the request
      or arbitrarily choking it at an averaged value.  Configure this with
      "router.tunnelGrowthFactor" in the router.config (default "1.5").
    * Adjust the tunnel test timeouts dynamically - rather than the old
      flat 30s (!!!) timeout, we set the timeout to 2x the average tunnel
      test time (the deviation factor can be adjusted by setting
      "router.tunnelTestDeviation" to "3.0" or whatever).  This should help
      find the 'good' tunnels.
    * Added some crazy debugging to try and track down an intermittent hang.
2004-10-14 20:02:45 +00:00
jrandom
ec322f0966 added nano.i2p 2004-10-13 20:42:35 +00:00
jrandom
0674709fc6 added ragnarok.i2p 2004-10-13 20:33:19 +00:00
jrandom
d91ac7ef21 2004-10-13 jrandom
* Fix the probabalistic tunnel reject (we always accepted everything,
      since the docs on java.util.Random.nextDouble() are wrong..)
    * Fixed a race on startup (thanks Quadn!)
2004-10-13 19:40:47 +00:00
jrandom
2f0c3c7baf added marcos.i2p 2004-10-13 16:29:27 +00:00
jrandom
be68407707 duh (oops) 2004-10-12 21:50:17 +00:00
jrandom
f799a25aeb 2004-10-12 jrandom
* Disable the probabalistic drop by default (enable via the router config
      property "tcp.dropProbabalistically=true")
    * Disable the actual watchdog shutdown by default, but keep track of more
      variables and log a lot more when it occurs (enable via the router
      config property "watchdog.haltOnHang=true")
    * Implement some tunnel participation smoothing by refusing requests
      probabalistically as our participating tunnel count exceeds the previous
      hour's, or when the 10 minute average tunnel test time exceeds the 60
      minute average tunnel test time.  The probabilities in both cases are
      oldAverage / #current, so if you're suddenly flooded with 200 tunnels
      and you had previously only participated in 50, you'll have a 25% chance
      of accepting a subsequent request.
2004-10-12 21:29:41 +00:00
duck
8329d045f1 confirm removal 2004-10-11 00:23:26 +00:00
149 changed files with 8800 additions and 1699 deletions

View File

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

View File

@@ -298,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
}
}

View File

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

View File

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

View File

@@ -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,343 +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.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;
}
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)
@@ -448,117 +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);
}
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());
}
}
NoRouteToHostException, InterruptedIOException;
/**
* Create a new connected socket (block until the socket is created)
@@ -571,182 +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.warn(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.warn(getName() + ": I2PException:", ex);
return false;
}
}
public boolean ping(Destination peer, long timeoutMs);
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();
}
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(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?");
}
}
public void init(I2PAppContext context, I2PSession session, Properties opts, String name);
}

View File

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

View File

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

View File

@@ -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.
*

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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>

View File

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

View File

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

View File

@@ -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>

View File

@@ -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:

View 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

View File

@@ -35,6 +35,7 @@ extern "C" {
#endif
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/*

View File

@@ -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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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):

View File

@@ -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]

View 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>

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,7 @@
<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/" />
@@ -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" />
@@ -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/" />
@@ -219,11 +224,13 @@
<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/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/i2ptunnel.war" todir="pkg-temp/webapps/" />

View File

@@ -14,8 +14,8 @@ package net.i2p;
*
*/
public class CoreVersion {
public final static String ID = "$Revision: 1.22 $ $Date: 2004/10/01 12:23:00 $";
public final static String VERSION = "0.4.1.2";
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);

View File

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

View File

@@ -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.

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,6 +16,7 @@ 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;
@@ -61,12 +62,10 @@ public class HostsTxtNamingService extends NamingService {
for (int i = 0; i < filenames.size(); i++) {
String hostsfile = (String)filenames.get(i);
Properties hosts = new Properties();
FileInputStream fis = null;
try {
File f = new File(hostsfile);
if ( (f.exists()) && (f.canRead()) ) {
fis = new FileInputStream(f);
hosts.load(fis);
DataHelper.loadProps(hosts, f);
String key = hosts.getProperty(hostname);
if ( (key != null) && (key.trim().length() > 0) ) {
@@ -78,11 +77,6 @@ public class HostsTxtNamingService extends NamingService {
}
} catch (Exception ioe) {
_log.error("Error loading hosts file " + hostsfile, ioe);
} finally {
if (fis != null) try {
fis.close();
} catch (IOException ioe) { // nop
}
}
// not found, continue to the next file
}

View File

@@ -9,8 +9,6 @@ package net.i2p.crypto;
*
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import net.i2p.I2PAppContext;
@@ -52,25 +50,29 @@ public class AESEngine {
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(iv);
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;
}
byte orig[] = baos.toByteArray();
byte rv[] = new byte[orig.length];
encrypt(orig, 0, rv, 0, sessionKey, iv, rv.length);
return rv;
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[]) {
@@ -82,31 +84,32 @@ public class AESEngine {
_log.error("Error decrypting the data - payload " + payload.length + " decrypted to null");
return null;
}
ByteArrayInputStream bais = new ByteArrayInputStream(decr);
Hash h = _context.sha().calculateHash(iv);
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;
}

View File

@@ -9,7 +9,6 @@ package net.i2p.crypto;
*
*/
import java.io.ByteArrayOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;

View File

@@ -29,10 +29,12 @@ public class CryptixAESEngine extends AESEngine {
private final static CryptixRijndael_Algorithm _algo = new CryptixRijndael_Algorithm();
private final static boolean USE_FAKE_CRYPTO = false;
private final static byte FAKE_KEY = 0x2A;
private CryptixAESKeyCache _cache;
public CryptixAESEngine(I2PAppContext context) {
super(context);
_log = context.logManager().getLog(CryptixAESEngine.class);
_cache = new CryptixAESKeyCache();
}
public void encrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
@@ -65,8 +67,13 @@ public class CryptixAESEngine extends AESEngine {
public void decrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
if ((iv== null) || (payload == null) || (payload.length <= 0) || (sessionKey == null)
|| (iv.length != 16))
|| (iv.length != 16) )
throw new IllegalArgumentException("bad setup");
else if (out == null)
throw new IllegalArgumentException("out is null");
else if (out.length - outIndex < length)
throw new IllegalArgumentException("out is too small (out.length=" + out.length
+ " outIndex=" + outIndex + " length=" + length);
if (USE_FAKE_CRYPTO) {
_log.warn("AES Crypto disabled! Using trivial XOR");
@@ -74,23 +81,26 @@ public class CryptixAESEngine extends AESEngine {
return ;
}
int numblock = payload.length / 16;
if (payload.length % 16 != 0) numblock++;
int numblock = length / 16;
if (length % 16 != 0) numblock++;
decryptBlock(payload, 0, sessionKey, out, 0);
DataHelper.xor(out, 0, iv, 0, out, 0, 16);
decryptBlock(payload, payloadIndex, sessionKey, out, outIndex);
DataHelper.xor(out, outIndex, iv, 0, out, outIndex, 16);
for (int x = 1; x < numblock; x++) {
decryptBlock(payload, x * 16, sessionKey, out, x * 16);
DataHelper.xor(out, x * 16, payload, (x - 1) * 16, out, x * 16, 16);
decryptBlock(payload, payloadIndex + (x * 16), sessionKey, out, outIndex + (x * 16));
DataHelper.xor(out, outIndex + x * 16, payload, payloadIndex + (x - 1) * 16, out, outIndex + x * 16, 16);
}
}
final void encryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte out[], int outIndex) {
CryptixAESKeyCache.KeyCacheEntry keyData = _cache.acquireKey();
try {
Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16);
Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16, keyData);
CryptixRijndael_Algorithm.blockEncrypt(payload, out, inIndex, outIndex, key, 16);
} catch (InvalidKeyException ike) {
_log.error("Invalid key", ike);
} finally {
_cache.releaseKey(keyData);
}
}
@@ -100,11 +110,20 @@ public class CryptixAESEngine extends AESEngine {
* @return unencrypted data
*/
final void decryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte rv[], int outIndex) {
if ( (payload == null) || (rv == null) )
throw new IllegalArgumentException("null block args [payload=" + payload + " rv="+rv);
if (payload.length - inIndex > rv.length - outIndex)
throw new IllegalArgumentException("bad block args [payload.len=" + payload.length
+ " inIndex=" + inIndex + " rv.len=" + rv.length
+ " outIndex="+outIndex);
CryptixAESKeyCache.KeyCacheEntry keyData = _cache.acquireKey();
try {
Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16);
Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16, keyData);
CryptixRijndael_Algorithm.blockDecrypt(payload, rv, inIndex, outIndex, key, 16);
} catch (InvalidKeyException ike) {
_log.error("Invalid key", ike);
} finally {
_cache.releaseKey(keyData);
}
}
@@ -113,6 +132,8 @@ public class CryptixAESEngine extends AESEngine {
try {
testEDBlock(ctx);
testED(ctx);
testFake(ctx);
testNull(ctx);
} catch (Exception e) {
e.printStackTrace();
}
@@ -134,6 +155,42 @@ public class CryptixAESEngine extends AESEngine {
else
System.out.println("full D(E(orig)) == orig");
}
private static void testFake(I2PAppContext ctx) {
SessionKey key = ctx.keyGenerator().generateSessionKey();
SessionKey wrongKey = ctx.keyGenerator().generateSessionKey();
byte iv[] = new byte[16];
byte orig[] = new byte[128];
byte encrypted[] = new byte[128];
byte decrypted[] = new byte[128];
ctx.random().nextBytes(iv);
ctx.random().nextBytes(orig);
CryptixAESEngine aes = new CryptixAESEngine(ctx);
aes.encrypt(orig, 0, encrypted, 0, key, iv, orig.length);
aes.decrypt(encrypted, 0, decrypted, 0, wrongKey, iv, encrypted.length);
if (DataHelper.eq(decrypted,orig))
throw new RuntimeException("full D(E(orig)) == orig when we used the wrong key!");
else
System.out.println("full D(E(orig)) != orig when we used the wrong key");
}
private static void testNull(I2PAppContext ctx) {
SessionKey key = ctx.keyGenerator().generateSessionKey();
SessionKey wrongKey = ctx.keyGenerator().generateSessionKey();
byte iv[] = new byte[16];
byte orig[] = new byte[128];
byte encrypted[] = new byte[128];
byte decrypted[] = new byte[128];
ctx.random().nextBytes(iv);
ctx.random().nextBytes(orig);
CryptixAESEngine aes = new CryptixAESEngine(ctx);
aes.encrypt(orig, 0, encrypted, 0, key, iv, orig.length);
try {
aes.decrypt(null, 0, null, 0, wrongKey, iv, encrypted.length);
} catch (IllegalArgumentException iae) {
return;
}
throw new RuntimeException("full D(E(orig)) didn't fail when we used null!");
}
private static void testEDBlock(I2PAppContext ctx) {
SessionKey key = ctx.keyGenerator().generateSessionKey();
byte iv[] = new byte[16];

View File

@@ -0,0 +1,70 @@
package net.i2p.crypto;
import java.util.ArrayList;
import java.util.List;
/**
* Cache the objects used in CryptixRijndael_Algorithm.makeKey to reduce
* memory churn. The KeyCacheEntry should be held onto as long as the
* data referenced in it is needed (which often is only one or two lines
* of code)
*
*/
final class CryptixAESKeyCache {
private List _availableKeys;
private static final int KEYSIZE = 32; // 256bit AES
private static final int BLOCKSIZE = 16;
private static final int ROUNDS = CryptixRijndael_Algorithm.getRounds(KEYSIZE, BLOCKSIZE);
private static final int BC = BLOCKSIZE / 4;
private static final int KC = KEYSIZE / 4;
private static final int MAX_KEYS = 64;
public CryptixAESKeyCache() {
_availableKeys = new ArrayList(MAX_KEYS);
}
/**
* Get the next available structure, either from the cache or a brand new one
*
*/
public final KeyCacheEntry acquireKey() {
synchronized (_availableKeys) {
if (_availableKeys.size() > 0)
return (KeyCacheEntry)_availableKeys.remove(0);
}
return createNew();
}
/**
* Put this structure back onto the available cache for reuse
*
*/
public final void releaseKey(KeyCacheEntry key) {
synchronized (_availableKeys) {
if (_availableKeys.size() < MAX_KEYS)
_availableKeys.add(key);
}
}
private static final KeyCacheEntry createNew() {
KeyCacheEntry e = new KeyCacheEntry();
e.Ke = new int[ROUNDS + 1][BC]; // encryption round keys
e.Kd = new int[ROUNDS + 1][BC]; // decryption round keys
e.tk = new int[KC];
e.key = new Object[] { e.Ke, e.Kd };
return e;
}
/**
* all the data alloc'ed in a makeKey call
*/
public static final class KeyCacheEntry {
int[][] Ke;
int[][] Kd;
int[] tk;
Object[] key;
}
}

View File

@@ -453,6 +453,11 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
* @param sessionKey The session key to use for decryption.
*/
public static final void blockDecrypt(byte[] in, byte[] result, int inOffset, int outOffset, Object sessionKey) {
if (in.length - inOffset > result.length - outOffset)
throw new IllegalArgumentException("result too small: in.len=" + in.length + " in.offset=" + inOffset
+ " result.len=" + result.length + " result.offset=" + outOffset);
if (in.length - inOffset <= 15)
throw new IllegalArgumentException("data too small: " + in.length + " inOffset: " + inOffset);
if (_RDEBUG) trace(_IN, "blockDecrypt(" + in + ", " + inOffset + ", " + sessionKey + ")");
int[][] Kd = (int[][]) ((Object[]) sessionKey)[1]; // extract decryption round keys
int ROUNDS = Kd.length - 1;
@@ -534,18 +539,31 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
* @exception InvalidKeyException If the key is invalid.
*/
public static final/* synchronized */Object makeKey(byte[] k, int blockSize) throws InvalidKeyException {
return makeKey(k, blockSize, null);
}
public static final/* synchronized */Object makeKey(byte[] k, int blockSize, CryptixAESKeyCache.KeyCacheEntry keyData) throws InvalidKeyException {
if (_RDEBUG) trace(_IN, "makeKey(" + k + ", " + blockSize + ")");
if (k == null) throw new InvalidKeyException("Empty key");
if (!(k.length == 16 || k.length == 24 || k.length == 32))
throw new InvalidKeyException("Incorrect key length");
int ROUNDS = getRounds(k.length, blockSize);
int BC = blockSize / 4;
int[][] Ke = new int[ROUNDS + 1][BC]; // encryption round keys
int[][] Kd = new int[ROUNDS + 1][BC]; // decryption round keys
int[][] Ke = null; // new int[ROUNDS + 1][BC]; // encryption round keys
int[][] Kd = null; // new int[ROUNDS + 1][BC]; // decryption round keys
int ROUND_KEY_COUNT = (ROUNDS + 1) * BC;
int KC = k.length / 4;
int[] tk = new int[KC];
int[] tk = null; // new int[KC];
int i, j;
if (keyData == null) {
Ke = new int[ROUNDS + 1][BC];
Kd = new int[ROUNDS + 1][BC];
tk = new int[KC];
} else {
Ke = keyData.Ke;
Kd = keyData.Kd;
tk = keyData.tk;
}
// copy user material bytes into temporary ints
for (i = 0, j = 0; i < KC;)
@@ -604,7 +622,11 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
}
// assemble the encryption (Ke) and decryption (Kd) round keys into
// one sessionKey object
Object[] sessionKey = new Object[] { Ke, Kd};
Object[] sessionKey = null;
if (keyData == null)
sessionKey = new Object[] { Ke, Kd};
else
sessionKey = keyData.key;
if (_RDEBUG) trace(_OUT, "makeKey()");
return sessionKey;
}

View File

@@ -30,6 +30,7 @@ package net.i2p.crypto;
*/
import java.math.BigInteger;
import java.util.Arrays;
import net.i2p.I2PAppContext;
import net.i2p.data.Hash;
@@ -50,8 +51,10 @@ public class DSAEngine {
public static DSAEngine getInstance() {
return I2PAppContext.getGlobalContext().dsa();
}
public boolean verifySignature(Signature signature, byte signedData[], SigningPublicKey verifyingKey) {
return verifySignature(signature, signedData, 0, signedData.length, verifyingKey);
}
public boolean verifySignature(Signature signature, byte signedData[], int offset, int size, SigningPublicKey verifyingKey) {
long start = _context.clock().now();
byte[] sigbytes = signature.getData();
@@ -68,7 +71,7 @@ public class DSAEngine {
BigInteger r = new NativeBigInteger(1, rbytes);
BigInteger y = new NativeBigInteger(1, verifyingKey.getData());
BigInteger w = s.modInverse(CryptoConstants.dsaq);
byte data[] = calculateHash(signedData).getData();
byte data[] = calculateHash(signedData, offset, size).getData();
NativeBigInteger bi = new NativeBigInteger(1, data);
BigInteger u1 = bi.multiply(w).mod(CryptoConstants.dsaq);
BigInteger u2 = r.multiply(w).mod(CryptoConstants.dsaq);
@@ -88,6 +91,9 @@ public class DSAEngine {
}
public Signature sign(byte data[], SigningPrivateKey signingKey) {
return sign(data, 0, data.length, signingKey);
}
public Signature sign(byte data[], int offset, int length, SigningPrivateKey signingKey) {
if ((signingKey == null) || (data == null) || (data.length <= 0)) return null;
long start = _context.clock().now();
@@ -100,7 +106,7 @@ public class DSAEngine {
BigInteger r = CryptoConstants.dsag.modPow(k, CryptoConstants.dsap).mod(CryptoConstants.dsaq);
BigInteger kinv = k.modInverse(CryptoConstants.dsaq);
Hash h = calculateHash(data);
Hash h = calculateHash(data, offset, length);
if (h == null) return null;
@@ -150,42 +156,42 @@ public class DSAEngine {
private int[] H0 = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0};
private Hash calculateHash(byte[] source) {
long length = source.length * 8;
private Hash calculateHash(byte[] source, int offset, int len) {
long length = len * 8;
int k = 448 - (int) ((length + 1) % 512);
if (k < 0) {
k += 512;
}
int padbytes = k / 8;
int wordlength = source.length / 4 + padbytes / 4 + 3;
int wordlength = len / 4 + padbytes / 4 + 3;
int[] M0 = new int[wordlength];
int wordcount = 0;
int x = 0;
for (x = 0; x < (source.length / 4) * 4; x += 4) {
M0[wordcount] = source[x] << 24 >>> 24 << 24;
M0[wordcount] |= source[x + 1] << 24 >>> 24 << 16;
M0[wordcount] |= source[x + 2] << 24 >>> 24 << 8;
M0[wordcount] |= source[x + 3] << 24 >>> 24 << 0;
for (x = 0; x < (len / 4) * 4; x += 4) {
M0[wordcount] = source[offset + x] << 24 >>> 24 << 24;
M0[wordcount] |= source[offset + x + 1] << 24 >>> 24 << 16;
M0[wordcount] |= source[offset + x + 2] << 24 >>> 24 << 8;
M0[wordcount] |= source[offset + x + 3] << 24 >>> 24 << 0;
wordcount++;
}
switch (source.length - (wordcount + 1) * 4 + 4) {
switch (len - (wordcount + 1) * 4 + 4) {
case 0:
M0[wordcount] |= 0x80000000;
break;
case 1:
M0[wordcount] = source[x] << 24 >>> 24 << 24;
M0[wordcount] = source[offset + x] << 24 >>> 24 << 24;
M0[wordcount] |= 0x00800000;
break;
case 2:
M0[wordcount] = source[x] << 24 >>> 24 << 24;
M0[wordcount] |= source[x + 1] << 24 >>> 24 << 16;
M0[wordcount] = source[offset + x] << 24 >>> 24 << 24;
M0[wordcount] |= source[offset + x + 1] << 24 >>> 24 << 16;
M0[wordcount] |= 0x00008000;
break;
case 3:
M0[wordcount] = source[x] << 24 >>> 24 << 24;
M0[wordcount] |= source[x + 1] << 24 >>> 24 << 16;
M0[wordcount] |= source[x + 2] << 24 >>> 24 << 8;
M0[wordcount] = source[offset + x] << 24 >>> 24 << 24;
M0[wordcount] |= source[offset + x + 1] << 24 >>> 24 << 16;
M0[wordcount] |= source[offset + x + 2] << 24 >>> 24 << 8;
M0[wordcount] |= 0x00000080;
break;
}
@@ -196,6 +202,8 @@ public class DSAEngine {
H[x] = H0[x];
}
int blocks = M0.length / 16;
int[] W = new int[80];
for (int bl = 0; bl < blocks; bl++) {
int a = H[0];
int b = H[1];
@@ -203,8 +211,8 @@ public class DSAEngine {
int d = H[3];
int e = H[4];
int[] W = new int[80];
Arrays.fill(W, 0);
for (x = 0; x < 80; x++) {
if (x < 16) {
W[x] = M0[bl * 16 + x];

View File

@@ -10,7 +10,6 @@ package net.i2p.crypto;
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
@@ -67,11 +66,11 @@ public class ElGamalAESEngine {
*/
public byte[] decrypt(byte data[], PrivateKey targetPrivateKey) throws DataFormatException {
if (data == null) {
if (_log.shouldLog(Log.WARN)) _log.warn("Null data being decrypted?");
if (_log.shouldLog(Log.ERROR)) _log.error("Null data being decrypted?");
return null;
} else if (data.length < MIN_ENCRYPTED_SIZE) {
if (_log.shouldLog(Log.WARN))
_log.warn("Data is less than the minimum size (" + data.length + " < " + MIN_ENCRYPTED_SIZE + ")");
if (_log.shouldLog(Log.ERROR))
_log.error("Data is less than the minimum size (" + data.length + " < " + MIN_ENCRYPTED_SIZE + ")");
return null;
}
@@ -147,33 +146,29 @@ public class ElGamalAESEngine {
System.arraycopy(data, 0, elgEncr, 514 - data.length, data.length);
}
byte elgDecr[] = _context.elGamalEngine().decrypt(elgEncr, targetPrivateKey);
if (elgDecr == null) return null;
ByteArrayInputStream bais = new ByteArrayInputStream(elgDecr);
byte preIV[] = null;
try {
usedKey.readBytes(bais);
preIV = new byte[32];
int read = bais.read(preIV);
if (read != preIV.length) {
// hmm, this can't really happen...
throw new DataFormatException("Somehow ElGamal broke and 256 bytes is less than 32 bytes..."); }
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR)) _log.error("Error decrypting the new session", ioe);
if (elgDecr == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("decrypt returned null", new Exception("decrypt failed"));
return null;
}
// ignore the next 192 bytes
byte aesEncr[] = new byte[data.length - 514];
System.arraycopy(data, 514, aesEncr, 0, aesEncr.length);
byte preIV[] = null;
byte key[] = new byte[SessionKey.KEYSIZE_BYTES];
System.arraycopy(elgDecr, 0, key, 0, SessionKey.KEYSIZE_BYTES);
usedKey.setData(key);
preIV = new byte[32];
System.arraycopy(elgDecr, SessionKey.KEYSIZE_BYTES, preIV, 0, 32);
//_log.debug("Pre IV for decryptNewSession: " + DataHelper.toString(preIV, 32));
//_log.debug("SessionKey for decryptNewSession: " + DataHelper.toString(key.getData(), 32));
Hash ivHash = _context.sha().calculateHash(preIV);
SHA256EntryCache.CacheEntry cache = _context.sha().cache().acquire(preIV.length);
Hash ivHash = _context.sha().calculateHash(preIV, cache);
byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
_context.sha().cache().release(cache);
byte aesDecr[] = decryptAESBlock(aesEncr, usedKey, iv, null, foundTags, foundKey);
byte aesDecr[] = decryptAESBlock(data, 514, data.length-514, usedKey, iv, null, foundTags, foundKey);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Decrypt with a NEW session successfull: # tags read = " + foundTags.size(),
@@ -203,17 +198,17 @@ public class ElGamalAESEngine {
SessionKey usedKey, SessionKey foundKey) throws DataFormatException {
byte preIV[] = new byte[32];
System.arraycopy(data, 0, preIV, 0, preIV.length);
byte encr[] = new byte[data.length - 32];
System.arraycopy(data, 32, encr, 0, encr.length);
Hash ivHash = _context.sha().calculateHash(preIV);
SHA256EntryCache.CacheEntry cache = _context.sha().cache().acquire(32);
Hash ivHash = _context.sha().calculateHash(preIV, cache);
byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
_context.sha().cache().release(cache);
usedKey.setData(key.getData());
//_log.debug("Pre IV for decryptExistingSession: " + DataHelper.toString(preIV, 32));
//_log.debug("SessionKey for decryptNewSession: " + DataHelper.toString(key.getData(), 32));
byte decrypted[] = decryptAESBlock(encr, key, iv, preIV, foundTags, foundKey);
byte decrypted[] = decryptAESBlock(data, 32, data.length-32, key, iv, preIV, foundTags, foundKey);
if (decrypted == null) {
// it begins with a valid session tag, but thats just a coincidence.
if (_log.shouldLog(Log.DEBUG))
@@ -244,12 +239,16 @@ public class ElGamalAESEngine {
* @param foundTags set which is filled with any sessionTags found during decryption
* @param foundKey session key which may be filled with a new sessionKey found during decryption
*/
byte[] decryptAESBlock(byte encrypted[], SessionKey key, byte iv[], byte sentTag[], Set foundTags,
SessionKey foundKey) throws DataFormatException {
byte[] decryptAESBlock(byte encrypted[], SessionKey key, byte iv[],
byte sentTag[], Set foundTags, SessionKey foundKey) throws DataFormatException {
return decryptAESBlock(encrypted, 0, encrypted.length, key, iv, sentTag, foundTags, foundKey);
}
byte[] decryptAESBlock(byte encrypted[], int offset, int encryptedLen, SessionKey key, byte iv[],
byte sentTag[], Set foundTags, SessionKey foundKey) throws DataFormatException {
//_log.debug("iv for decryption: " + DataHelper.toString(iv, 16));
//_log.debug("decrypting AES block. encr.length = " + (encrypted == null? -1 : encrypted.length) + " sentTag: " + DataHelper.toString(sentTag, 32));
byte decrypted[] = new byte[encrypted.length];
_context.aes().decrypt(encrypted, 0, decrypted, 0, key, iv, encrypted.length);
byte decrypted[] = new byte[encryptedLen];
_context.aes().decrypt(encrypted, offset, decrypted, 0, key, iv, encryptedLen);
//Hash h = _context.sha().calculateHash(decrypted);
//_log.debug("Hash of entire aes block after decryption: \n" + DataHelper.toString(h.getData(), 32));
try {
@@ -257,39 +256,48 @@ public class ElGamalAESEngine {
Hash readHash = null;
List tags = new ArrayList();
ByteArrayInputStream bais = new ByteArrayInputStream(decrypted);
long numTags = DataHelper.readLong(bais, 2);
//ByteArrayInputStream bais = new ByteArrayInputStream(decrypted);
int cur = 0;
long numTags = DataHelper.fromLong(decrypted, cur, 2);
cur += 2;
//_log.debug("# tags: " + numTags);
if ((numTags < 0) || (numTags > 65535)) throw new Exception("Invalid number of session tags");
if (numTags * SessionTag.BYTE_LENGTH > decrypted.length - 2) {
throw new Exception("# tags: " + numTags + " is too many for " + (decrypted.length - 2));
}
for (int i = 0; i < numTags; i++) {
byte tag[] = new byte[32];
int read = bais.read(tag);
if (read != 32)
throw new Exception("Invalid session tag - # tags: " + numTags + " curTag #: " + i + " read: "
+ read);
byte tag[] = new byte[SessionTag.BYTE_LENGTH];
System.arraycopy(decrypted, cur, tag, 0, SessionTag.BYTE_LENGTH);
cur += SessionTag.BYTE_LENGTH;
tags.add(new SessionTag(tag));
}
long len = DataHelper.readLong(bais, 4);
long len = DataHelper.fromLong(decrypted, cur, 4);
cur += 4;
//_log.debug("len: " + len);
if ((len < 0) || (len > encrypted.length)) throw new Exception("Invalid size of payload");
byte hashval[] = new byte[32];
int read = bais.read(hashval);
if (read != hashval.length) throw new Exception("Invalid size of hash");
if ((len < 0) || (len > decrypted.length - cur - Hash.HASH_LENGTH - 1))
throw new Exception("Invalid size of payload (" + len + ", remaining " + (decrypted.length-cur) +")");
byte hashval[] = new byte[Hash.HASH_LENGTH];
System.arraycopy(decrypted, cur, hashval, 0, Hash.HASH_LENGTH);
cur += Hash.HASH_LENGTH;
readHash = new Hash();
readHash.setData(hashval);
byte flag = (byte) bais.read();
byte flag = decrypted[cur++];
if (flag == 0x01) {
byte rekeyVal[] = new byte[32];
read = bais.read(rekeyVal);
if (read != rekeyVal.length) throw new Exception("Invalid size of the rekeyed session key");
byte rekeyVal[] = new byte[SessionKey.KEYSIZE_BYTES];
System.arraycopy(decrypted, cur, rekeyVal, 0, SessionKey.KEYSIZE_BYTES);
cur += SessionKey.KEYSIZE_BYTES;
newKey = new SessionKey();
newKey.setData(rekeyVal);
}
byte unencrData[] = new byte[(int) len];
read = bais.read(unencrData);
if (read != unencrData.length) throw new Exception("Invalid size of the data read");
Hash calcHash = _context.sha().calculateHash(unencrData);
if (calcHash.equals(readHash)) {
System.arraycopy(decrypted, cur, unencrData, 0, (int)len);
cur += len;
SHA256EntryCache.CacheEntry cache = _context.sha().cache().acquire(unencrData.length);
Hash calcHash = _context.sha().calculateHash(unencrData, cache);
boolean eq = calcHash.equals(readHash);
_context.sha().cache().release(cache);
if (eq) {
// everything matches. w00t.
foundTags.addAll(tags);
if (newKey != null) foundKey.setData(newKey.getData());
@@ -371,54 +379,47 @@ public class ElGamalAESEngine {
byte[] encryptNewSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
SessionKey newKey, long paddedSize) {
//_log.debug("Encrypting to a NEW session");
try {
ByteArrayOutputStream elgSrc = new ByteArrayOutputStream(64);
key.writeBytes(elgSrc);
byte preIV[] = new byte[32];
_context.random().nextBytes(preIV);
elgSrc.write(preIV);
byte rnd[] = new byte[158];
_context.random().nextBytes(rnd);
elgSrc.write(rnd);
elgSrc.flush();
byte elgSrcData[] = new byte[SessionKey.KEYSIZE_BYTES+32+158];
System.arraycopy(key.getData(), 0, elgSrcData, 0, SessionKey.KEYSIZE_BYTES);
byte preIV[] = new byte[32];
_context.random().nextBytes(preIV);
System.arraycopy(preIV, 0, elgSrcData, SessionKey.KEYSIZE_BYTES, 32);
byte rnd[] = new byte[158];
_context.random().nextBytes(rnd);
System.arraycopy(rnd, 0, elgSrcData, SessionKey.KEYSIZE_BYTES+32, 158);
//_log.debug("Pre IV for encryptNewSession: " + DataHelper.toString(preIV, 32));
//_log.debug("SessionKey for encryptNewSession: " + DataHelper.toString(key.getData(), 32));
long before = _context.clock().now();
byte elgEncr[] = _context.elGamalEngine().encrypt(elgSrc.toByteArray(), target);
long after = _context.clock().now();
if (_log.shouldLog(Log.INFO))
_log.info("elgEngine.encrypt of the session key took " + (after - before) + "ms");
if (elgEncr.length < 514) {
byte elg[] = new byte[514];
int diff = elg.length - elgEncr.length;
if (_log.shouldLog(Log.DEBUG)) _log.debug("Difference in size: " + diff);
System.arraycopy(elgEncr, 0, elg, diff, elgEncr.length);
elgEncr = elg;
}
//_log.debug("ElGamal encrypted length: " + elgEncr.length + " elGamal source length: " + elgSrc.toByteArray().length);
Hash ivHash = _context.sha().calculateHash(preIV);
byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
byte aesEncr[] = encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize);
//_log.debug("AES encrypted length: " + aesEncr.length);
byte rv[] = new byte[elgEncr.length + aesEncr.length];
System.arraycopy(elgEncr, 0, rv, 0, elgEncr.length);
System.arraycopy(aesEncr, 0, rv, elgEncr.length, aesEncr.length);
//_log.debug("Return length: " + rv.length);
long finish = _context.clock().now();
if (_log.shouldLog(Log.DEBUG))
_log.debug("after the elgEngine.encrypt took a total of " + (finish - after) + "ms");
return rv;
} catch (IOException ioe) {
_log.error("Error encrypting the new session", ioe);
return null;
} catch (DataFormatException dfe) {
_log.error("Error writing out the bytes for the new session", dfe);
return null;
//_log.debug("Pre IV for encryptNewSession: " + DataHelper.toString(preIV, 32));
//_log.debug("SessionKey for encryptNewSession: " + DataHelper.toString(key.getData(), 32));
long before = _context.clock().now();
byte elgEncr[] = _context.elGamalEngine().encrypt(elgSrcData, target);
long after = _context.clock().now();
if (_log.shouldLog(Log.INFO))
_log.info("elgEngine.encrypt of the session key took " + (after - before) + "ms");
if (elgEncr.length < 514) {
byte elg[] = new byte[514];
int diff = elg.length - elgEncr.length;
if (_log.shouldLog(Log.DEBUG)) _log.debug("Difference in size: " + diff);
System.arraycopy(elgEncr, 0, elg, diff, elgEncr.length);
elgEncr = elg;
}
//_log.debug("ElGamal encrypted length: " + elgEncr.length + " elGamal source length: " + elgSrc.toByteArray().length);
SHA256EntryCache.CacheEntry cache = _context.sha().cache().acquire(preIV.length);
Hash ivHash = _context.sha().calculateHash(preIV, cache);
byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
_context.sha().cache().release(cache);
byte aesEncr[] = encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize);
//_log.debug("AES encrypted length: " + aesEncr.length);
byte rv[] = new byte[elgEncr.length + aesEncr.length];
System.arraycopy(elgEncr, 0, rv, 0, elgEncr.length);
System.arraycopy(aesEncr, 0, rv, elgEncr.length, aesEncr.length);
//_log.debug("Return length: " + rv.length);
long finish = _context.clock().now();
if (_log.shouldLog(Log.DEBUG))
_log.debug("after the elgEngine.encrypt took a total of " + (finish - after) + "ms");
return rv;
}
/**
@@ -441,15 +442,16 @@ public class ElGamalAESEngine {
//_log.debug("Pre IV for encryptExistingSession (aka tag): " + currentTag.toString());
//_log.debug("SessionKey for encryptNewSession: " + DataHelper.toString(key.getData(), 32));
Hash ivHash = _context.sha().calculateHash(rawTag);
SHA256EntryCache.CacheEntry cache = _context.sha().cache().acquire(rawTag.length);
Hash ivHash = _context.sha().calculateHash(rawTag, cache);
byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
_context.sha().cache().release(cache);
byte aesEncr[] = encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize);
byte rv[] = new byte[rawTag.length + aesEncr.length];
System.arraycopy(rawTag, 0, rv, 0, rawTag.length);
System.arraycopy(aesEncr, 0, rv, rawTag.length, aesEncr.length);
return rv;
byte aesEncr[] = encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize, SessionTag.BYTE_LENGTH);
// that prepended SessionTag.BYTE_LENGTH bytes at the beginning of the buffer
System.arraycopy(rawTag, 0, aesEncr, 0, rawTag.length);
return aesEncr;
}
private final static Set EMPTY_SET = new HashSet();
@@ -469,52 +471,66 @@ public class ElGamalAESEngine {
*/
final byte[] encryptAESBlock(byte data[], SessionKey key, byte[] iv, Set tagsForDelivery, SessionKey newKey,
long paddedSize) {
return encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize, 0);
}
final byte[] encryptAESBlock(byte data[], SessionKey key, byte[] iv, Set tagsForDelivery, SessionKey newKey,
long paddedSize, int prefixBytes) {
//_log.debug("iv for encryption: " + DataHelper.toString(iv, 16));
//_log.debug("Encrypting AES");
try {
ByteArrayOutputStream aesSrc = new ByteArrayOutputStream((int) paddedSize);
if (tagsForDelivery == null) tagsForDelivery = EMPTY_SET;
DataHelper.writeLong(aesSrc, 2, tagsForDelivery.size());
for (Iterator iter = tagsForDelivery.iterator(); iter.hasNext();) {
SessionTag tag = (SessionTag) iter.next();
aesSrc.write(tag.getData());
}
//_log.debug("# tags created, registered, and written: " + tags.size());
DataHelper.writeLong(aesSrc, 4, data.length);
//_log.debug("data length: " + data.length);
Hash hash = _context.sha().calculateHash(data);
hash.writeBytes(aesSrc);
//_log.debug("hash of data: " + DataHelper.toString(hash.getData(), 32));
if (newKey == null) {
byte flag = 0x00; // don't rekey
aesSrc.write(flag);
//_log.debug("flag written");
} else {
byte flag = 0x01; // rekey
aesSrc.write(flag);
aesSrc.write(newKey.getData());
}
aesSrc.write(data);
int len = aesSrc.toByteArray().length;
//_log.debug("raw data written: " + len);
byte padding[] = getPadding(_context, len, paddedSize);
//_log.debug("padding length: " + padding.length);
aesSrc.write(padding);
if (tagsForDelivery == null) tagsForDelivery = EMPTY_SET;
int size = 2 // sizeof(tags)
+ tagsForDelivery.size()
+ SessionTag.BYTE_LENGTH*tagsForDelivery.size()
+ 4 // payload length
+ Hash.HASH_LENGTH
+ (newKey == null ? 1 : 1 + SessionKey.KEYSIZE_BYTES)
+ data.length;
int totalSize = size + getPaddingSize(size, paddedSize);
byte aesUnencr[] = aesSrc.toByteArray();
//Hash h = _context.sha().calculateHash(aesUnencr);
//_log.debug("Hash of entire aes block before encryption: (len=" + aesUnencr.length + ")\n" + DataHelper.toString(h.getData(), 32));
byte aesEncr[] = new byte[aesUnencr.length];
_context.aes().encrypt(aesUnencr, 0, aesEncr, 0, key, iv, aesEncr.length);
//_log.debug("Encrypted length: " + aesEncr.length);
return aesEncr;
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR)) _log.error("Error encrypting AES chunk", ioe);
return null;
} catch (DataFormatException dfe) {
if (_log.shouldLog(Log.ERROR)) _log.error("Error formatting the bytes to write the AES chunk", dfe);
return null;
byte aesData[] = new byte[totalSize + prefixBytes];
int cur = prefixBytes;
DataHelper.toLong(aesData, cur, 2, tagsForDelivery.size());
cur += 2;
for (Iterator iter = tagsForDelivery.iterator(); iter.hasNext();) {
SessionTag tag = (SessionTag) iter.next();
System.arraycopy(tag.getData(), 0, aesData, cur, SessionTag.BYTE_LENGTH);
cur += SessionTag.BYTE_LENGTH;
}
//_log.debug("# tags created, registered, and written: " + tags.size());
DataHelper.toLong(aesData, cur, 4, data.length);
cur += 4;
//_log.debug("data length: " + data.length);
SHA256EntryCache.CacheEntry cache = _context.sha().cache().acquire(data.length);
Hash hash = _context.sha().calculateHash(data, cache);
System.arraycopy(hash.getData(), 0, aesData, cur, Hash.HASH_LENGTH);
cur += Hash.HASH_LENGTH;
_context.sha().cache().release(cache);
//_log.debug("hash of data: " + DataHelper.toString(hash.getData(), 32));
if (newKey == null) {
aesData[cur++] = 0x00; // don't rekey
//_log.debug("flag written");
} else {
aesData[cur++] = 0x01; // rekey
System.arraycopy(newKey.getData(), 0, aesData, cur, SessionKey.KEYSIZE_BYTES);
cur += SessionKey.KEYSIZE_BYTES;
}
System.arraycopy(data, 0, aesData, cur, data.length);
cur += data.length;
//_log.debug("raw data written: " + len);
byte padding[] = getPadding(_context, size, paddedSize);
//_log.debug("padding length: " + padding.length);
System.arraycopy(padding, 0, aesData, cur, padding.length);
cur += padding.length;
//Hash h = _context.sha().calculateHash(aesUnencr);
//_log.debug("Hash of entire aes block before encryption: (len=" + aesUnencr.length + ")\n" + DataHelper.toString(h.getData(), 32));
_context.aes().encrypt(aesData, prefixBytes, aesData, prefixBytes, key, iv, aesData.length - prefixBytes);
//_log.debug("Encrypted length: " + aesEncr.length);
//return aesEncr;
return aesData;
}
/**
@@ -523,6 +539,12 @@ public class ElGamalAESEngine {
*
*/
final static byte[] getPadding(I2PAppContext context, int curSize, long minPaddedSize) {
int size = getPaddingSize(curSize, minPaddedSize);
byte rv[] = new byte[size];
context.random().nextBytes(rv);
return rv;
}
final static int getPaddingSize(int curSize, long minPaddedSize) {
int diff = 0;
if (curSize < minPaddedSize) {
diff = (int) minPaddedSize - curSize;
@@ -530,9 +552,36 @@ public class ElGamalAESEngine {
int numPadding = diff;
if (((curSize + diff) % 16) != 0) numPadding += (16 - ((curSize + diff) % 16));
byte rv[] = new byte[numPadding];
context.random().nextBytes(rv);
return rv;
return numPadding;
}
public static void main(String args[]) {
I2PAppContext ctx = new I2PAppContext();
ElGamalAESEngine e = new ElGamalAESEngine(ctx);
Object kp[] = ctx.keyGenerator().generatePKIKeypair();
PublicKey pubKey = (PublicKey)kp[0];
PrivateKey privKey = (PrivateKey)kp[1];
SessionKey sessionKey = ctx.keyGenerator().generateSessionKey();
for (int i = 0; i < 10; i++) {
try {
Set tags = new HashSet(5);
if (i == 0) {
for (int j = 0; j < 5; j++)
tags.add(new SessionTag(true));
}
byte encrypted[] = e.encrypt("blah".getBytes(), pubKey, sessionKey, tags, 1024);
byte decrypted[] = e.decrypt(encrypted, privKey);
if ("blah".equals(new String(decrypted))) {
System.out.println("equal on " + i);
} else {
System.out.println("NOT equal on " + i + ": " + new String(decrypted));
break;
}
ctx.sessionKeyManager().tagsDelivered(pubKey, sessionKey, tags);
} catch (Exception ee) {
ee.printStackTrace();
break;
}
}
}
}

View File

@@ -30,7 +30,6 @@ package net.i2p.crypto;
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import net.i2p.I2PAppContext;
@@ -98,19 +97,14 @@ public class ElGamalEngine {
long start = _context.clock().now();
ByteArrayOutputStream baos = new ByteArrayOutputStream(256);
try {
baos.write(0xFF);
Hash hash = _context.sha().calculateHash(data);
hash.writeBytes(baos);
baos.write(data);
baos.flush();
} catch (Exception e) {
if (_log.shouldLog(Log.ERROR)) _log.error("Internal error writing to buffer", e);
return null;
}
byte d2[] = baos.toByteArray();
byte d2[] = new byte[1+Hash.HASH_LENGTH+data.length];
d2[0] = (byte)0xFF;
SHA256EntryCache.CacheEntry cache = _context.sha().cache().acquire(data.length);
Hash hash = _context.sha().calculateHash(data, cache);
System.arraycopy(hash.getData(), 0, d2, 1, Hash.HASH_LENGTH);
_context.sha().cache().release(cache);
System.arraycopy(data, 0, d2, 1+Hash.HASH_LENGTH, data.length);
long t0 = _context.clock().now();
BigInteger m = new NativeBigInteger(1, d2);
long t1 = _context.clock().now();
@@ -188,21 +182,23 @@ public class ElGamalEngine {
for (i = 0; i < val.length; i++)
if (val[i] != (byte) 0x00) break;
ByteArrayInputStream bais = new ByteArrayInputStream(val, i, val.length - i);
Hash hash = new Hash();
byte rv[] = null;
try {
bais.read(); // skip first byte
hash.readBytes(bais);
rv = new byte[val.length - i - 1 - 32];
bais.read(rv);
} catch (Exception e) {
if (_log.shouldLog(Log.ERROR)) _log.error("Internal error reading value", e);
//ByteArrayInputStream bais = new ByteArrayInputStream(val, i, val.length - i);
byte hashData[] = new byte[Hash.HASH_LENGTH];
System.arraycopy(val, i + 1, hashData, 0, Hash.HASH_LENGTH);
Hash hash = new Hash(hashData);
int payloadLen = val.length - i - 1 - Hash.HASH_LENGTH;
if (payloadLen < 0) {
if (_log.shouldLog(Log.ERROR))
_log.error("Decrypted data is too small (" + (val.length - i)+ ")");
return null;
}
byte rv[] = new byte[payloadLen];
System.arraycopy(val, i + 1 + Hash.HASH_LENGTH, rv, 0, rv.length);
Hash calcHash = _context.sha().calculateHash(rv);
SHA256EntryCache.CacheEntry cache = _context.sha().cache().acquire(payloadLen);
Hash calcHash = _context.sha().calculateHash(rv, cache);
boolean ok = calcHash.equals(hash);
_context.sha().cache().release(cache);
long end = _context.clock().now();
@@ -219,7 +215,7 @@ public class ElGamalEngine {
return rv;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Doesn't match hash [calc=" + calcHash + " sent hash=" + hash + "]\ndata = "
_log.debug("Doesn't match hash [sent hash=" + hash + "]\ndata = "
+ Base64.encode(rv), new Exception("Doesn't match"));
return null;
}

View File

@@ -0,0 +1,238 @@
package net.i2p.crypto;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.data.Hash;
/**
* Cache the objects used in SHA256Generator's calculate method to reduce
* memory churn. The CacheEntry should be held onto as long as the
* data referenced in it is needed (which often is only one or two lines
* of code)
*
*/
public final class SHA256EntryCache {
private static final int ONE_KB = 0;
private static final int FOUR_KB = 1;
private static final int EIGHT_KB = 2;
private static final int SIXTEEN_KB = 3;
private static final int THIRTYTWO_KB = 4;
private static final int FOURTYEIGHT_KB = 5;
private static final int LARGER = 6;
/**
* Array of Lists of free CacheEntry objects, indexed
* by the payload size they are capable of handling
*/
private List _available[] = new List[6];
/** count up how often we use the cache for each size */
private long _used[] = new long[7];
private int _sizes[] = new int[] { 1024,4*1024,8*1024,16*1024,32*1024,48*1024 };
/** no more than 32 at each size level */
private static final int MAX_CACHED = 64;
public SHA256EntryCache() {
for (int i = 0; i < _available.length; i++) {
_available[i] = new ArrayList(MAX_CACHED);
//for (int j = 0; j < MAX_CACHED; j++)
// _available[i].add(new CacheEntry(_sizes[i]));
}
}
/**
* Get the next available structure, either from the cache or a brand new one
*
*/
public final CacheEntry acquire(int payload) {
int entrySize = getBucket(payload);
switch (entrySize) {
case 1024:
_used[ONE_KB]++;
synchronized (_available[ONE_KB]) {
if (_available[ONE_KB].size() > 0) {
return (CacheEntry)_available[ONE_KB].remove(0);
}
}
break;
case 4*1024:
_used[FOUR_KB]++;
synchronized (_available[FOUR_KB]) {
if (_available[FOUR_KB].size() > 0) {
return (CacheEntry)_available[FOUR_KB].remove(0);
}
}
break;
case 8*1024:
_used[EIGHT_KB]++;
synchronized (_available[EIGHT_KB]) {
if (_available[EIGHT_KB].size() > 0) {
return (CacheEntry)_available[EIGHT_KB].remove(0);
}
}
break;
case 16*1024:
_used[SIXTEEN_KB]++;
synchronized (_available[SIXTEEN_KB]) {
if (_available[SIXTEEN_KB].size() > 0) {
return (CacheEntry)_available[SIXTEEN_KB].remove(0);
}
}
break;
case 32*1024:
_used[THIRTYTWO_KB]++;
synchronized (_available[THIRTYTWO_KB]) {
if (_available[THIRTYTWO_KB].size() > 0) {
return (CacheEntry)_available[THIRTYTWO_KB].remove(0);
}
}
break;
case 48*1024:
_used[FOURTYEIGHT_KB]++;
synchronized (_available[FOURTYEIGHT_KB]) {
if (_available[FOURTYEIGHT_KB].size() > 0) {
return (CacheEntry)_available[FOURTYEIGHT_KB].remove(0);
}
}
break;
default:
_used[LARGER]++;
// not for the bucket, so make it exact
return new CacheEntry(payload);
}
return new CacheEntry(entrySize);
}
/**
* Put this structure back onto the available cache for reuse
*
*/
public final void release(CacheEntry entry) {
entry.reset();
if (false) return;
switch (entry.bucket) {
case 1024:
synchronized (_available[ONE_KB]) {
if (_available[ONE_KB].size() < MAX_CACHED) {
_available[ONE_KB].add(entry);
}
}
return;
case 4*1024:
synchronized (_available[FOUR_KB]) {
if (_available[FOUR_KB].size() < MAX_CACHED) {
_available[FOUR_KB].add(entry);
}
}
return;
case 8*1024:
synchronized (_available[EIGHT_KB]) {
if (_available[EIGHT_KB].size() < MAX_CACHED) {
_available[EIGHT_KB].add(entry);
}
}
return;
case 16*1024:
synchronized (_available[SIXTEEN_KB]) {
if (_available[SIXTEEN_KB].size() < MAX_CACHED) {
_available[SIXTEEN_KB].add(entry);
}
}
return;
case 32*1024:
synchronized (_available[THIRTYTWO_KB]) {
if (_available[THIRTYTWO_KB].size() < MAX_CACHED) {
_available[THIRTYTWO_KB].add(entry);
}
}
return;
case 48*1024:
synchronized (_available[FOURTYEIGHT_KB]) {
if (_available[FOURTYEIGHT_KB].size() < MAX_CACHED) {
_available[FOURTYEIGHT_KB].add(entry);
}
}
return;
}
}
/**
* all the data alloc'ed in a calculateHash call
*/
public static final class CacheEntry {
byte hashbytes[];
int W[];
int M0[];
int H[];
Hash hash;
int wordlength;
int bucket;
public CacheEntry(int payload) {
wordlength = SHA256Generator.getWordlength(payload);
bucket = payload;
hashbytes = new byte[32];
M0 = new int[wordlength];
W = new int[64];
H = new int[8];
hash = new Hash();
hash.setData(hashbytes);
}
public final void reset() {
Arrays.fill(hashbytes, (byte)0x0);
Arrays.fill(M0, (byte)0x0);
Arrays.fill(W, (byte)0x0);
Arrays.fill(H, (byte)0x0);
}
}
private static final int getBucket(int payload) {
if (payload <= 1024)
return 1024;
else if (payload <= 4*1024)
return 4*1024;
else if (payload <= 8*1024)
return 8*1024;
else if (payload <= 16*1024)
return 16*1024;
else if (payload <= 32*1024)
return 32*1024;
else if (payload <= 48*1024)
return 48*1024;
else
return payload;
}
public static void main(String args[]) {
I2PAppContext ctx = new I2PAppContext();
for (int i = 1; i < 20000; i+=2) {
test(ctx, i);
}
}
private static void test(I2PAppContext ctx, int size) {
System.out.print("Size = " + size);
for (int i = 0; i < 2; i++) {
byte orig[] = new byte[size];
ctx.random().nextBytes(orig);
CacheEntry cache = ctx.sha().cache().acquire(orig.length);
try {
Hash h = ctx.sha().calculateHash(orig, cache);
Hash h2 = ctx.sha().calculateHash(orig);
boolean eq = h.equals(h2);
ctx.sha().cache().release(cache);
if (eq) {
System.out.print(".");
} else {
System.out.print("ERROR " + i);
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println();
}
}

View File

@@ -29,6 +29,7 @@ package net.i2p.crypto;
* POSSIBILITY OF SUCH DAMAGE.
*/
import java.util.Arrays;
import net.i2p.I2PAppContext;
import net.i2p.data.Hash;
@@ -38,15 +39,18 @@ import net.i2p.data.Hash;
*
* @author thecrypto,jrandom
*/
public class SHA256Generator {
public final class SHA256Generator {
private final SHA256EntryCache _cache = new SHA256EntryCache();
public SHA256Generator(I2PAppContext context) { // nop
}
public static SHA256Generator getInstance() {
public static final SHA256Generator getInstance() {
return I2PAppContext.getGlobalContext().sha();
}
public final SHA256EntryCache cache() { return _cache; }
static int[] K = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
static final int[] K = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
@@ -55,16 +59,49 @@ public class SHA256Generator {
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
static int[] H0 = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
static final int[] H0 = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
public static final int getWordlength(int sourceLength) {
long length = sourceLength * 8;
int k = 448 - (int) ((length + 1) % 512);
if (k < 0) {
k += 512;
}
int padbytes = k / 8;
int rv = sourceLength / 4 + padbytes / 4 + 3;
return rv;
}
private final SHA256EntryCache.CacheEntry getNewEntry(int payloadSize) {
return new SHA256EntryCache.CacheEntry(payloadSize);
}
/** Calculate the SHA-256 has of the source
* @param source what to hash
* @return hash of the source
*/
public Hash calculateHash(byte[] source) {
return calculateHash(source, 0, source.length);
public final Hash calculateHash(byte[] source) {
byte rv[] = new byte[Hash.HASH_LENGTH];
SHA256EntryCache.CacheEntry cache = _cache.acquire(source.length);
Hash hash = calculateHash(source, 0, source.length, cache);
System.arraycopy(hash.getData(), 0, rv, 0, Hash.HASH_LENGTH);
_cache.release(cache);
return new Hash(rv);
}
public Hash calculateHash(byte[] source, int start, int len) {
public final Hash calculateHash(byte[] source, SHA256EntryCache.CacheEntry cache) {
return calculateHash(source, 0, source.length, cache);
}
public final Hash calculateHash(byte[] source, int start, int len) {
byte rv[] = new byte[Hash.HASH_LENGTH];
SHA256EntryCache.CacheEntry cache = _cache.acquire(len);
Hash hash = calculateHash(source, start, len, cache);
System.arraycopy(hash.getData(), 0, rv, 0, Hash.HASH_LENGTH);
_cache.release(cache);
return new Hash(rv);
}
public final Hash calculateHash(byte[] source, int start, int len, SHA256EntryCache.CacheEntry cache) {
if (cache == null)
return calculateHash(source, start, len);
long length = len * 8;
int k = 448 - (int) ((length + 1) % 512);
if (k < 0) {
@@ -72,7 +109,10 @@ public class SHA256Generator {
}
int padbytes = k / 8;
int wordlength = len / 4 + padbytes / 4 + 3;
int[] M0 = new int[wordlength];
if (wordlength != getWordlength(len))
throw new RuntimeException("len = " + len + " wordlength = " + wordlength
+ " getWordlength = " + getWordlength(len));
int[] M0 = cache.M0;
int wordcount = 0;
int x = 0;
for (x = 0; x < (len / 4) * 4; x += 4) {
@@ -104,11 +144,12 @@ public class SHA256Generator {
}
M0[wordlength - 2] = (int) (length >>> 32);
M0[wordlength - 1] = (int) (length);
int[] H = new int[8];
int[] H = cache.H;
for (x = 0; x < 8; x++) {
H[x] = H0[x];
}
int blocks = M0.length / 16;
int blocks = wordlength / 16;
int[] W = cache.W;
for (int bl = 0; bl < blocks; bl++) {
int a = H[0];
int b = H[1];
@@ -118,7 +159,7 @@ public class SHA256Generator {
int f = H[5];
int g = H[6];
int h = H[7];
int[] W = new int[64];
Arrays.fill(W, 0);
for (x = 0; x < 64; x++) {
if (x < 16) {
W[x] = M0[bl * 16 + x];
@@ -147,51 +188,49 @@ public class SHA256Generator {
H[6] = add(g, H[6]);
H[7] = add(h, H[7]);
}
byte[] hashbytes = new byte[32];
byte[] hashbytes = cache.hashbytes;
for (x = 0; x < 8; x++) {
hashbytes[x * 4] = (byte) (H[x] << 0 >>> 24);
hashbytes[x * 4 + 1] = (byte) (H[x] << 8 >>> 24);
hashbytes[x * 4 + 2] = (byte) (H[x] << 16 >>> 24);
hashbytes[x * 4 + 3] = (byte) (H[x] << 24 >>> 24);
}
Hash hash = new Hash();
hash.setData(hashbytes);
return hash;
return cache.hash;
}
private static int Ch(int x, int y, int z) {
private static final int Ch(int x, int y, int z) {
return (x & y) ^ (~x & z);
}
private static int Maj(int x, int y, int z) {
private static final int Maj(int x, int y, int z) {
return (x & y) ^ (x & z) ^ (y & z);
}
private static int ROTR(int x, int n) {
private static final int ROTR(int x, int n) {
return (x >>> n) | (x << 32 - n);
}
private static int e0(int x) {
private static final int e0(int x) {
return ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22);
}
private static int e1(int x) {
private static final int e1(int x) {
return ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25);
}
private static int SHR(int x, int n) {
private static final int SHR(int x, int n) {
return x >>> n;
}
private static int o0(int x) {
private static final int o0(int x) {
return ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3);
}
private static int o1(int x) {
private static final int o1(int x) {
return ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10);
}
private static int add(int x, int y) {
private static final int add(int x, int y) {
return x + y;
}
}

View File

@@ -207,6 +207,8 @@ class TransientSessionKeyManager extends SessionKeyManager {
sess.addTags(set);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Tags delivered to set " + set + " on session " + sess);
if (sessionTags.size() > 0)
_log.debug("Tags delivered: " + sessionTags.size() + " total = " + sess.availableTags());
}
/**

View File

@@ -15,9 +15,8 @@ import java.io.Serializable;
* Wrap up an array of bytes so that they can be compared and placed in hashes,
* maps, and the like.
*
* @author jrandom
*/
public class ByteArray implements Serializable {
public class ByteArray implements Serializable, Comparable {
private byte[] _data;
public ByteArray() {
@@ -28,7 +27,7 @@ public class ByteArray implements Serializable {
_data = data;
}
public byte[] getData() {
public final byte[] getData() {
return _data;
}
@@ -36,7 +35,7 @@ public class ByteArray implements Serializable {
_data = data;
}
public boolean equals(Object o) {
public final boolean equals(Object o) {
if (o == null) return false;
if (o instanceof ByteArray) {
return compare(getData(), ((ByteArray) o).getData());
@@ -50,15 +49,20 @@ public class ByteArray implements Serializable {
}
}
private boolean compare(byte[] lhs, byte[] rhs) {
private static final boolean compare(byte[] lhs, byte[] rhs) {
return DataHelper.eq(lhs, rhs);
}
public final int compareTo(Object obj) {
if (obj.getClass() != getClass()) throw new ClassCastException("invalid object: " + obj);
return DataHelper.compareTo(_data, ((ByteArray)obj).getData());
}
public int hashCode() {
public final int hashCode() {
return DataHelper.hashCode(getData());
}
public String toString() {
public final String toString() {
return DataHelper.toString(getData(), 32);
}
}

View File

@@ -86,6 +86,49 @@ public class Certificate extends DataStructureImpl {
DataHelper.writeLong(out, 2, 0L);
}
}
public int writeBytes(byte target[], int offset) {
int cur = offset;
DataHelper.toLong(target, cur, 1, _type);
cur++;
if (_payload != null) {
DataHelper.toLong(target, cur, 2, _payload.length);
cur += 2;
System.arraycopy(_payload, 0, target, cur, _payload.length);
cur += _payload.length;
} else {
DataHelper.toLong(target, cur, 2, 0);
cur += 2;
}
return cur - offset;
}
public int readBytes(byte source[], int offset) throws DataFormatException {
if (source == null) throw new DataFormatException("Cert is null");
if (source.length <= offset + 3)
throw new DataFormatException("Cert is too small [" + source.length + " off=" + offset + "]");
int cur = offset;
_type = (int)DataHelper.fromLong(source, cur, 1);
cur++;
int length = (int)DataHelper.fromLong(source, cur, 2);
cur += 2;
if (length > 0) {
if (length + cur > source.length)
throw new DataFormatException("Payload on the certificate is insufficient (len="
+ source.length + " off=" + offset + " cur=" + cur
+ " payloadLen=" + length);
_payload = new byte[length];
System.arraycopy(source, cur, _payload, 0, length);
cur += length;
}
return cur - offset;
}
public int size() {
return 1 + 2 + (_payload != null ? _payload.length : 0);
}
public boolean equals(Object object) {
if ((object == null) || !(object instanceof Certificate)) return false;

View File

@@ -10,14 +10,18 @@ package net.i2p.data;
*/
import java.io.BufferedReader;
import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
@@ -160,7 +164,22 @@ public class DataHelper {
if (in != null) try { in.close(); } catch (IOException ioe) {}
}
}
public static void storeProps(Properties props, File file) throws IOException {
PrintWriter out = null;
try {
out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
String name = (String)iter.next();
String val = props.getProperty(name);
out.println(name + "=" + val);
}
out.flush();
out.close();
} finally {
if (out != null) out.close();
}
}
/**
* Pretty print the collection
@@ -255,20 +274,25 @@ public class DataHelper {
public static void writeLong(OutputStream rawStream, int numBytes, long value)
throws DataFormatException, IOException {
try {
UnsignedInteger i = new UnsignedInteger(value);
rawStream.write(i.getBytes(numBytes));
UnsignedInteger.writeBytes(rawStream, numBytes, value);
//UnsignedInteger i = new UnsignedInteger(value);
//rawStream.write(i.getBytes(numBytes));
} catch (IllegalArgumentException iae) {
throw new DataFormatException("Invalid value (must be positive)", iae);
}
}
public static byte[] toLong(int numBytes, long value) throws IllegalArgumentException {
byte val[] = new byte[numBytes];
toLong(val, 0, numBytes, value);
return val;
}
public static void toLong(byte target[], int offset, int numBytes, long value) throws IllegalArgumentException {
if (numBytes <= 0) throw new IllegalArgumentException("Invalid number of bytes");
if (value < 0) throw new IllegalArgumentException("Negative value not allowed");
byte val[] = new byte[numBytes];
for (int i = 0; i < numBytes; i++)
val[numBytes-i-1] = (byte)(value >>> (i*8));
return val;
target[offset+numBytes-i-1] = (byte)(value >>> (i*8));
}
public static long fromLong(byte src[], int offset, int numBytes) {
@@ -336,7 +360,7 @@ public class DataHelper {
return new Date(date);
}
/** Write out a date to the stream as specified by the I2P data structure spec.
* @param out stream to write to
* @param date date to write (can be null)
@@ -356,12 +380,18 @@ public class DataHelper {
else
return toLong(DATE_LENGTH, date.getTime());
}
public static Date fromDate(byte src[], int offset) throws IllegalArgumentException {
long when = fromLong(src, offset, DATE_LENGTH);
if (when <= 0)
return null;
else
return new Date(when);
public static Date fromDate(byte src[], int offset) throws DataFormatException {
if ( (src == null) || (offset + DATE_LENGTH > src.length) )
throw new DataFormatException("Not enough data to read a date");
try {
long when = fromLong(src, offset, DATE_LENGTH);
if (when <= 0)
return null;
else
return new Date(when);
} catch (IllegalArgumentException iae) {
throw new DataFormatException(iae.getMessage());
}
}
public static final int DATE_LENGTH = 8;
@@ -678,11 +708,14 @@ public class DataHelper {
/** compress the data and return a new GZIP compressed array */
public static byte[] compress(byte orig[]) {
return compress(orig, 0, orig.length);
}
public static byte[] compress(byte orig[], int offset, int size) {
if ((orig == null) || (orig.length <= 0)) return orig;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream(orig.length);
GZIPOutputStream out = new GZIPOutputStream(baos, orig.length);
out.write(orig);
ByteArrayOutputStream baos = new ByteArrayOutputStream(size);
GZIPOutputStream out = new GZIPOutputStream(baos, size);
out.write(orig, offset, size);
out.finish();
out.flush();
byte rv[] = baos.toByteArray();

View File

@@ -79,7 +79,45 @@ public class Destination extends DataStructureImpl {
_signingKey.writeBytes(out);
_certificate.writeBytes(out);
}
public int writeBytes(byte target[], int offset) {
int cur = offset;
System.arraycopy(_publicKey.getData(), 0, target, cur, PublicKey.KEYSIZE_BYTES);
cur += PublicKey.KEYSIZE_BYTES;
System.arraycopy(_signingKey.getData(), 0, target, cur, SigningPublicKey.KEYSIZE_BYTES);
cur += SigningPublicKey.KEYSIZE_BYTES;
cur += _certificate.writeBytes(target, cur);
return cur - offset;
}
public int readBytes(byte source[], int offset) throws DataFormatException {
if (source == null) throw new DataFormatException("Null source");
if (source.length <= offset + PublicKey.KEYSIZE_BYTES + SigningPublicKey.KEYSIZE_BYTES)
throw new DataFormatException("Not enough data (len=" + source.length + " off=" + offset + ")");
int cur = offset;
_publicKey = new PublicKey();
byte buf[] = new byte[PublicKey.KEYSIZE_BYTES];
System.arraycopy(source, cur, buf, 0, PublicKey.KEYSIZE_BYTES);
_publicKey.setData(buf);
cur += PublicKey.KEYSIZE_BYTES;
_signingKey = new SigningPublicKey();
buf = new byte[SigningPublicKey.KEYSIZE_BYTES];
System.arraycopy(source, cur, buf, 0, SigningPublicKey.KEYSIZE_BYTES);
_signingKey.setData(buf);
cur += SigningPublicKey.KEYSIZE_BYTES;
_certificate = new Certificate();
cur += _certificate.readBytes(source, cur);
return cur - offset;
}
public int size() {
return PublicKey.KEYSIZE_BYTES + SigningPublicKey.KEYSIZE_BYTES + _certificate.size();
}
public boolean equals(Object object) {
if ((object == null) || !(object instanceof Destination)) return false;
Destination dst = (Destination) object;

View File

@@ -81,6 +81,13 @@ public class Payload extends DataStructureImpl {
out.write(_encryptedData);
_log.debug("wrote payload: " + _encryptedData.length);
}
public int writeBytes(byte target[], int offset) {
if (_encryptedData == null) throw new IllegalStateException("Not yet encrypted. Please set the encrypted data");
DataHelper.toLong(target, offset, 4, _encryptedData.length);
offset += 4;
System.arraycopy(_encryptedData, 0, target, offset, _encryptedData.length);
return 4 + _encryptedData.length;
}
public boolean equals(Object object) {
if ((object == null) || !(object instanceof Payload)) return false;
@@ -94,6 +101,7 @@ public class Payload extends DataStructureImpl {
}
public String toString() {
if (true) return "[Payload]";
StringBuffer buf = new StringBuffer(128);
buf.append("[Payload: ");
if (getUnencryptedData() != null)

View File

@@ -378,7 +378,11 @@ public class RouterInfo extends DataStructureImpl {
public synchronized void readBytes(InputStream in) throws DataFormatException, IOException {
_identity = new RouterIdentity();
_identity.readBytes(in);
_published = DataHelper.readDate(in).getTime();
Date when = DataHelper.readDate(in);
if (when == null)
_published = 0;
else
_published = when.getTime();
int numAddresses = (int) DataHelper.readLong(in, 1);
for (int i = 0; i < numAddresses; i++) {
RouterAddress address = new RouterAddress();
@@ -402,7 +406,7 @@ public class RouterInfo extends DataStructureImpl {
public synchronized void writeBytes(OutputStream out) throws DataFormatException, IOException {
if (_identity == null) throw new DataFormatException("Missing identity");
if (_published <= 0) throw new DataFormatException("Invalid published date: " + _published);
if (_published < 0) throw new DataFormatException("Invalid published date: " + _published);
if (_signature == null) throw new DataFormatException("Signature is null");
//if (!isValid())
// throw new DataFormatException("Data is not valid");

View File

@@ -71,6 +71,11 @@ public class TunnelId extends DataStructureImpl {
if (_tunnelId < 0) throw new DataFormatException("Invalid tunnel ID: " + _tunnelId);
DataHelper.writeLong(out, 4, _tunnelId);
}
public int writeBytes(byte target[], int offset) throws DataFormatException {
if (_tunnelId < 0) throw new DataFormatException("Invalid tunnel ID: " + _tunnelId);
DataHelper.toLong(target, offset, 4, _tunnelId);
return 4;
}
public boolean equals(Object obj) {
if ( (obj == null) || !(obj instanceof TunnelId))

View File

@@ -9,6 +9,8 @@ package net.i2p.data;
*
*/
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import net.i2p.util.Log;
@@ -189,6 +191,16 @@ public class UnsignedInteger {
System.arraycopy(_data, 0, data, numBytes - _data.length, _data.length);
return data;
}
public static void writeBytes(OutputStream rawStream, int numBytes, long value)
throws DataFormatException, IOException {
if (value < 0) throw new DataFormatException("Invalid value (" + value + ")");
for (int i = numBytes - 1; i >= 0; i--) {
byte cur = (byte)( (value >>> (i*8) ) & 0xFF);
rawStream.write(cur);
}
}
public BigInteger getBigInteger() {
return new BigInteger(1, _data);
@@ -238,6 +250,7 @@ public class UnsignedInteger {
testNum(1024 * 1024 * 1024 * 4L + 1L);
_log.debug("Testing MaxLong");
testNum(Long.MAX_VALUE);
testWrite();
} catch (Throwable t) { t.printStackTrace(); }
try {
Thread.sleep(1000);
@@ -260,4 +273,18 @@ public class UnsignedInteger {
BigInteger tbi = new BigInteger(1, calculateBytes(num));
_log.debug(num + " As a shifted : 0x" + tbi.toString(16));
}
private static void testWrite() throws Exception {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(8);
UnsignedInteger i = new UnsignedInteger(12345);
baos.write(i.getBytes(8));
byte v1[] = baos.toByteArray();
baos.reset();
UnsignedInteger.writeBytes(baos, 8, 12345);
byte v2[] = baos.toByteArray();
System.out.println("v1 len: " + v1.length + " v2 len: " + v2.length);
System.out.println("v1: " + DataHelper.toHexString(v1));
System.out.println("v2: " + DataHelper.toHexString(v2));
}
}

View File

@@ -12,6 +12,7 @@ package net.i2p.data.i2cp;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
@@ -75,6 +76,15 @@ public class MessagePayloadMessage extends I2CPMessageImpl {
}
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
throw new RuntimeException("go away, we dont want any");
}
/**
* Write out the full message to the stream, including the 4 byte size and 1
* byte type header.
*
*/
public void writeMessage(OutputStream out) throws I2CPMessageException, IOException {
if (_sessionId == null)
throw new I2CPMessageException("Unable to write out the message, as the session ID has not been defined");
if (_messageId == null)
@@ -82,15 +92,17 @@ public class MessagePayloadMessage extends I2CPMessageImpl {
if (_payload == null)
throw new I2CPMessageException("Unable to write out the message, as the payload has not been defined");
ByteArrayOutputStream os = new ByteArrayOutputStream(512);
int size = 2 + 4 + 4 + _payload.getSize();
try {
_sessionId.writeBytes(os);
_messageId.writeBytes(os);
_payload.writeBytes(os);
DataHelper.writeLong(out, 4, size);
DataHelper.writeLong(out, 1, getType());
DataHelper.writeLong(out, 2, _sessionId.getSessionId());
DataHelper.writeLong(out, 4, _messageId.getMessageId());
DataHelper.writeLong(out, 4, _payload.getSize());
out.write(_payload.getEncryptedData());
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing out the message data", dfe);
throw new I2CPMessageException("Unable to write the message length or type", dfe);
}
return os.toByteArray();
}
public int getType() {

View File

@@ -12,11 +12,13 @@ package net.i2p.data.i2cp;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Payload;
import net.i2p.data.i2cp.I2CPMessageException;
import net.i2p.util.Log;
/**
@@ -73,6 +75,19 @@ public class SendMessageMessage extends I2CPMessageImpl {
}
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException {
if (true) throw new IllegalStateException("wtf, do not run me");
}
/**
* Read the body into the data structures
*
*/
public void readMessage(InputStream in, int length, int type) throws I2CPMessageException, IOException {
if (type != getType())
throw new I2CPMessageException("Invalid message type (found: " + type + " supported: " + getType()
+ " class: " + getClass().getName() + ")");
if (length < 0) throw new IOException("Negative payload size");
try {
_sessionId = new SessionId();
_sessionId.readBytes(in);
@@ -87,20 +102,31 @@ public class SendMessageMessage extends I2CPMessageImpl {
}
protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
if ((_sessionId == null) || (_destination == null) || (_payload == null) || (_nonce <= 0))
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
ByteArrayOutputStream os = new ByteArrayOutputStream(512);
try {
_sessionId.writeBytes(os);
_destination.writeBytes(os);
_payload.writeBytes(os);
DataHelper.writeLong(os, 4, _nonce);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing out the message data", dfe);
}
return os.toByteArray();
throw new RuntimeException("wtf, dont run me");
}
/**
* Write out the full message to the stream, including the 4 byte size and 1
* byte type header. Override the parent so we can be more mem efficient
*
*/
public void writeMessage(OutputStream out) throws I2CPMessageException, IOException {
if ((_sessionId == null) || (_destination == null) || (_payload == null) || (_nonce <= 0))
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
int len = 2 + _destination.size() + _payload.getSize() + 4 + 4;
try {
DataHelper.writeLong(out, 4, len);
DataHelper.writeLong(out, 1, getType());
_sessionId.writeBytes(out);
_destination.writeBytes(out);
_payload.writeBytes(out);
DataHelper.writeLong(out, 4, _nonce);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing the msg", dfe);
}
}
public int getType() {
return MESSAGE_TYPE;
}

View File

@@ -26,10 +26,10 @@ public class FrequencyStat {
_frequencies[i].eventOccurred();
}
/** coallesce all the stats */
public void coallesceStats() {
/** coalesce all the stats */
public void coalesceStats() {
//for (int i = 0; i < _frequencies.length; i++)
// _frequencies[i].coallesceStats();
// _frequencies[i].coalesceStats();
}
public String getName() {
@@ -61,4 +61,4 @@ public class FrequencyStat {
public int hashCode() {
return _statName.hashCode();
}
}
}

View File

@@ -27,11 +27,11 @@ public class Rate {
private volatile long _lifetimeEventCount;
private volatile long _lifetimeTotalEventTime;
private volatile long _lastCoallesceDate;
private volatile long _lastCoalesceDate;
private long _creationDate;
private long _period;
/** locked during coallesce and addData */
/** locked during coalesce and addData */
private Object _lock = new Object();
/** in the current (partial) period, what is the total value acrued through all events? */
@@ -94,9 +94,9 @@ public class Rate {
return _lifetimeTotalEventTime;
}
/** when was the rate last coallesced? */
public long getLastCoallesceDate() {
return _lastCoallesceDate;
/** when was the rate last coalesced? */
public long getLastCoalesceDate() {
return _lastCoalesceDate;
}
/** when was this rate created? */
@@ -130,7 +130,7 @@ public class Rate {
_lifetimeTotalEventTime = 0;
_creationDate = now();
_lastCoallesceDate = _creationDate;
_lastCoalesceDate = _creationDate;
_period = period;
}
@@ -175,23 +175,23 @@ public class Rate {
}
}
public void coallesce() {
public void coalesce() {
synchronized (_lock) {
long now = now();
long measuredPeriod = now - _lastCoallesceDate;
long measuredPeriod = now - _lastCoalesceDate;
if (measuredPeriod < _period) {
// no need to coallesce
// no need to coalesce
return;
}
// ok ok, lets coallesce
// ok ok, lets coalesce
// how much were we off by? (so that we can sample down the measured values)
double periodFactor = measuredPeriod / _period;
_lastTotalValue = (_currentTotalValue == 0 ? 0.0D : _currentTotalValue / periodFactor);
_lastEventCount = (_currentEventCount == 0 ? 0L : (long) (_currentEventCount / periodFactor));
_lastTotalEventTime = (_currentTotalEventTime == 0 ? 0L : (long) (_currentTotalEventTime / periodFactor));
_lastCoallesceDate = now;
_lastCoalesceDate = now;
if (_lastTotalValue > _extremeTotalValue) {
_extremeTotalValue = _lastTotalValue;
@@ -346,25 +346,25 @@ public class Rate {
PersistenceHelper.add(buf, prefix, ".period", "Number of milliseconds in the period", _period);
PersistenceHelper.add(buf, prefix, ".creationDate",
"When was this rate created? (milliseconds since the epoch, GMT)", _creationDate);
PersistenceHelper.add(buf, prefix, ".lastCoallesceDate",
"When did we last coallesce this rate? (milliseconds since the epoch, GMT)",
_lastCoallesceDate);
PersistenceHelper.add(buf, prefix, ".lastCoalesceDate",
"When did we last coalesce this rate? (milliseconds since the epoch, GMT)",
_lastCoalesceDate);
PersistenceHelper.add(buf, prefix, ".currentDate",
"When did this data get written? (milliseconds since the epoch, GMT)", now());
PersistenceHelper.add(buf, prefix, ".currentTotalValue",
"Total value of data points in the current (uncoallesced) period", _currentTotalValue);
"Total value of data points in the current (uncoalesced) period", _currentTotalValue);
PersistenceHelper
.add(buf, prefix, ".currentEventCount",
"How many events have occurred in the current (uncoallesced) period?", _currentEventCount);
"How many events have occurred in the current (uncoalesced) period?", _currentEventCount);
PersistenceHelper.add(buf, prefix, ".currentTotalEventTime",
"How many milliseconds have the events in the current (uncoallesced) period consumed?",
"How many milliseconds have the events in the current (uncoalesced) period consumed?",
_currentTotalEventTime);
PersistenceHelper.add(buf, prefix, ".lastTotalValue",
"Total value of data points in the most recent (coallesced) period", _lastTotalValue);
"Total value of data points in the most recent (coalesced) period", _lastTotalValue);
PersistenceHelper.add(buf, prefix, ".lastEventCount",
"How many events have occurred in the most recent (coallesced) period?", _lastEventCount);
"How many events have occurred in the most recent (coalesced) period?", _lastEventCount);
PersistenceHelper.add(buf, prefix, ".lastTotalEventTime",
"How many milliseconds have the events in the most recent (coallesced) period consumed?",
"How many milliseconds have the events in the most recent (coalesced) period consumed?",
_lastTotalEventTime);
PersistenceHelper.add(buf, prefix, ".extremeTotalValue",
"Total value of data points in the most extreme period", _extremeTotalValue);
@@ -395,7 +395,7 @@ public class Rate {
public void load(Properties props, String prefix, boolean treatAsCurrent) throws IllegalArgumentException {
_period = PersistenceHelper.getLong(props, prefix, ".period");
_creationDate = PersistenceHelper.getLong(props, prefix, ".creationDate");
_lastCoallesceDate = PersistenceHelper.getLong(props, prefix, ".lastCoallesceDate");
_lastCoalesceDate = PersistenceHelper.getLong(props, prefix, ".lastCoalesceDate");
_currentTotalValue = PersistenceHelper.getDouble(props, prefix, ".currentTotalValue");
_currentEventCount = PersistenceHelper.getLong(props, prefix, ".currentEventCount");
_currentTotalEventTime = PersistenceHelper.getLong(props, prefix, ".currentTotalEventTime");
@@ -409,17 +409,17 @@ public class Rate {
_lifetimeEventCount = PersistenceHelper.getLong(props, prefix, ".lifetimeEventCount");
_lifetimeTotalEventTime = PersistenceHelper.getLong(props, prefix, ".lifetimeTotalEventTime");
if (treatAsCurrent) _lastCoallesceDate = now();
if (treatAsCurrent) _lastCoalesceDate = now();
if (_period <= 0) throw new IllegalArgumentException("Period for " + prefix + " is invalid");
coallesce();
coalesce();
}
public boolean equals(Object obj) {
if ((obj == null) || (obj.getClass() != Rate.class)) return false;
Rate r = (Rate) obj;
return _period == r.getPeriod() && _creationDate == r.getCreationDate() &&
//_lastCoallesceDate == r.getLastCoallesceDate() &&
//_lastCoalesceDate == r.getLastCoalesceDate() &&
_currentTotalValue == r.getCurrentTotalValue() && _currentEventCount == r.getCurrentEventCount()
&& _currentTotalEventTime == r.getCurrentTotalEventTime() && _lastTotalValue == r.getLastTotalValue()
&& _lastEventCount == r.getLastEventCount() && _lastTotalEventTime == r.getLastTotalEventTime()
@@ -466,7 +466,7 @@ public class Rate {
}
rate.addData(i * 100, 20);
}
rate.coallesce();
rate.coalesce();
StringBuffer buf = new StringBuffer(1024);
try {
rate.store("rate.test", buf);
@@ -489,4 +489,4 @@ public class Rate {
} catch (InterruptedException ie) { // nop
}
}
}
}

View File

@@ -41,10 +41,10 @@ public class RateStat {
_rates[i].addData(value, eventDuration);
}
/** coallesce all the stats */
public void coallesceStats() {
/** coalesce all the stats */
public void coalesceStats() {
for (int i = 0; i < _rates.length; i++)
_rates[i].coallesce();
_rates[i].coalesce();
}
public String getName() {
@@ -166,7 +166,7 @@ public class RateStat {
}
rs.addData(i * 100, 20);
}
rs.coallesceStats();
rs.coalesceStats();
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(2048);
try {
rs.store(baos, "rateStat.test");
@@ -192,4 +192,4 @@ public class RateStat {
} catch (InterruptedException ie) { // nop
}
}
}
}

View File

@@ -94,12 +94,12 @@ public class StatManager {
if (stat != null) stat.addData(data, eventDuration);
}
public void coallesceStats() {
public void coalesceStats() {
synchronized (_frequencyStats) {
for (Iterator iter = _frequencyStats.values().iterator(); iter.hasNext();) {
FrequencyStat stat = (FrequencyStat)iter.next();
if (stat != null) {
stat.coallesceStats();
stat.coalesceStats();
}
}
}
@@ -107,7 +107,7 @@ public class StatManager {
for (Iterator iter = _rateStats.values().iterator(); iter.hasNext();) {
RateStat stat = (RateStat)iter.next();
if (stat != null) {
stat.coallesceStats();
stat.coalesceStats();
}
}
}
@@ -156,4 +156,4 @@ public class StatManager {
}
return groups;
}
}
}

View File

@@ -25,6 +25,7 @@ import net.i2p.data.DataHelper;
*/
public class Log {
private Class _class;
private String _className;
private String _name;
private int _minPriority;
private LogScope _scope;
@@ -90,6 +91,7 @@ public class Log {
Log(LogManager manager, Class cls, String name) {
_manager = manager;
_class = cls;
_className = cls != null ? cls.getName() : null;
_name = name;
_minPriority = DEBUG;
_scope = new LogScope(name, cls);
@@ -161,33 +163,37 @@ public class Log {
}
public String getName() {
if (_class != null) return _class.getName();
if (_className != null) return _className;
return _name;
}
public Object getScope() { return _scope; }
static String getScope(String name, Class cls) {
if ( (name == null) && (cls == null) ) return "f00";
if (cls == null) return name;
if (name == null) return cls.getName();
return name + "" + cls.getName();
}
private static final class LogScope {
private String _scopeName;
private Class _scopeClass;
private String _scopeCache;
public LogScope(String name, Class cls) {
_scopeName = name;
_scopeClass = cls;
_scopeCache = getScope(name, cls);
}
public int hashCode() {
if (_scopeClass != null)
return _scopeClass.hashCode();
else if (_scopeName != null)
return _scopeName.hashCode();
else
return 42;
return _scopeCache.hashCode();
}
public boolean equals(Object obj) {
if (obj == null) throw new NullPointerException("Null object scope?");
if (obj instanceof LogScope) {
LogScope s = (LogScope)obj;
return DataHelper.eq(s._scopeName, _scopeName) &&
DataHelper.eq(s._scopeClass, _scopeClass);
return s._scopeCache.equals(_scopeCache);
} else if (obj instanceof String) {
return obj.equals(_scopeCache);
}
return false;

Some files were not shown because too many files have changed in this diff Show More