Compare commits

..

118 Commits

Author SHA1 Message Date
61c97ab940 0.3.1.2 (backwards compatible, etc) 2004-05-13 23:49:08 +00:00
b0a1b3b5ca added some harvest options
dont use javaw, since its a bitch to kill multiple jvms (yeah, this leaves a dos box.  we'll deal until we've got the shutdown admin control)
2004-05-13 23:32:57 +00:00
4c7af01edc allow dynamic update to the reliability threshold factor (e.g. rather than the top 2/3rds being considered "reliable", allow that to be the top 1/3, or 1/2, etc)
the router.config var "profileOrganizer.reliabilityThresholdFactor=0.75" (or environment property -DprofileOrganizer.reliabilityThresholdFactor=0.5) etc
2004-05-13 23:24:09 +00:00
0d431213cd include the previous period in the measurements (since they're discrete, not rolling)
also include the other elements as necessary by default
2004-05-13 07:14:54 +00:00
ad9dd9a2e2 Lots of updates. I'm not calling this 0.3.1.2, still need to
"burn it it" some more, but its looking good.
* test all tunnels we manage every period or two. later we'll want to include some randomization to help fight traffic analysis, but that falls into the i2p 3.0 tunnel chaff / mixing / etc)
* test inbound tunnels correctly (use an outbound tunnel, not direct)
* only give the tunnels 30 seconds to succeed
* mark the tunnel as tested for both the inbound and outbound side and adjust the profiles for all participants accordingly
* keep track of the 'last test time' on a tunnel
* new tunnel test response time profile stat, as well as overall router stat (published in the netDb as "tunnel.testSuccessTime")
* rewrite of the speed calculator - the value it generates now is essentially "how many round trip messages can this router pass in a minute".
  it also allows a few runtime configurable options:
  = speedCalculator.eventThreshold:
    we use the smallest measurement period that has at least this many events in it (10m, 60m, 24h, lifetime)
  = speedCalculator.useInstantaneousRates:
    when we use the estimated round trip time, do we use instantaneous or period averages?
  = speedCalculator.useTunnelTestOnly:
    do we only use the tunnel test time (no db response or tunnel create time, or even estimated round trip times)?
* fix the reliability calculator to use the 10 minute tunnel create successes, not the (almost always 0) 1 minute rate.
* persist the tunnel create response time and send success time correctly (duh)
* add a new main() to PeerProfile - PeerProfile [filename]* will calculate the values of the peer profiles specified.  useful for tweaking the calculator, and/or the configurable options.  ala:
     java -DspeedCalculator.useInstantaneousRates peerProfiles/profile-*.dat
2004-05-13 04:32:26 +00:00
c7895ed905 oh, you mean we're supposed to be at least a /little/ resiliant? 2004-05-13 03:54:33 +00:00
57d7979d51 removed obsolete code
minor reorganization to help track down whats intermittently b0rking my kaffe instance
logging
2004-05-12 07:55:25 +00:00
61f6871cd1 logging and a catastrophic try/catch (no situations have called for this yet, but its worth testing for) 2004-05-12 07:47:22 +00:00
406048f7b9 ugly tests to see if the minimal RAW side of SAM works (it does, w00t) 2004-05-11 03:00:53 +00:00
6dd5b0fe45 basic datagram tests (that work now :) 2004-05-11 02:44:16 +00:00
fd4bc5e3cf keystream fixes 2004-05-11 02:43:52 +00:00
3bab2d8957 only append the client's config properties to the SESSION commands (since some of the rest get confused with unknown tags...) 2004-05-11 02:07:27 +00:00
af2f5cd2e1 kaffe shits a brick if you want the socket's address after .close() (grumble) 2004-05-11 01:55:22 +00:00
d4bb32da82 fix per http://twiki.ntp.org/bin/view/Support/JavaSntpClientDev (thanks duck!)
this may have caused some clock skew problems on the deployed 0.3.1.1, so we'll get this deployed asap
2004-05-11 01:45:18 +00:00
08aca6ca61 while (true) { m00; } 2004-05-09 07:23:43 +00:00
697b3c6772 SAM .net lib work in progress - dm and firerabbit 2004-05-09 07:16:04 +00:00
878525ced8 handle corrupt files more gracefully 2004-05-09 04:14:30 +00:00
418531736b imports
Did you miss me?
(shendaras)
2004-05-09 01:31:12 +00:00
6c175440c6 added mush.zeit.i2p 2004-05-08 06:41:01 +00:00
723a2f2008 include timestamper in installer 2004-05-07 19:01:47 +00:00
ea9b9fbf17 0.3.1.1 (tastes like chicken)
backwards compatible but some config changes necessary
2004-05-07 17:52:49 +00:00
303e257841 synchronization reduction and keep track of the 'last' job for each runner (to help debug something i see once a week on kaffe) 2004-05-07 17:51:28 +00:00
07b6a8ba92 if we lose our I2CP connection to the router, die hard and fast.
(only relevent for people whose socket manager / i2ptunnel / etc are located remote from the router)
2004-05-07 07:01:26 +00:00
e216e18368 update the clock more liberally (since we've got the new ntp code) 2004-05-07 04:34:03 +00:00
e57c5b4bc2 log to CRIT for jbigi load failure 2004-05-07 04:23:30 +00:00
f772d6ddeb /me reboots brain, understands, and thanks mihi 2004-05-07 04:19:43 +00:00
cd37c301d9 if (log.shouldLog())
yeah, aspect oriented programming sure would be nice.
2004-05-07 04:07:14 +00:00
f5fa26639e minor html cleanup 2004-05-07 03:45:48 +00:00
45ec73c115 include a unique request id in the client runner thread name
minor cleanup
2004-05-07 03:33:23 +00:00
13952ebd8b include the expiration and messageId in the toString (shown on msg drop due to "unknown tunnel") 2004-05-07 03:31:33 +00:00
2df0007a10 logging 2004-05-07 03:29:06 +00:00
89bc5db3e1 increase the bundle probability to yet another arbitrary value
add the jobId to log messages to simplify tracing individual parallel sends
logging cleanup
2004-05-07 03:28:22 +00:00
4021deec7f poke jrandom's eyes into the semantic of an "else" clause
(you may remove both comments when you understood it)

[mihi]
2004-05-07 03:10:57 +00:00
a3977f37f7 javadoc, no functional changes 2004-05-07 03:06:41 +00:00
766c12242e logging, javadoc 2004-05-07 01:45:12 +00:00
a82b951aff made private things that don't need to be public
remove semantic inconsistency wrt getRemoteId(false) - it shouldn't ever timeout, since it always returns immediately
javadoc (though i wish i understood the close/close2/sendClose more clearly so i could javadoc that process)
2004-05-07 01:32:48 +00:00
997a94eecc removed PHTTP lines since they were only used for time sync
added more info wrt NTP entry
added filename for the SAM bridge
2004-05-06 07:48:45 +00:00
635535aac2 implement keyfile persistence (storing name=privKeyDataBase64\n for each name)
filename specified on the command line: SAMBridge [keyfile [listenHost] listenPortNum [ name=val]*]
2004-05-06 07:35:44 +00:00
25314fd91a make sure we mark the send as *failed* if we need to reconnect 2004-05-06 04:18:28 +00:00
9a06a5758d check shouldBundle only when its ready to be checked (duh) 2004-05-06 01:02:50 +00:00
e5a2a9644f *cough* [d'oh] 2004-05-05 23:01:36 +00:00
e0e7211852 lets default the read timeout to 5 minutes for clients (that hanging irc disconnect / not disconnected thing) 2004-05-05 22:57:43 +00:00
cdaeb4d176 track and publish two new stats:
* netDb.failedPeers (how many peers didn't reply to a lookup in time)
* netDb.searchCount (how many searches we send out in a 3 hour period)
probabalistically include the leaseSet of the sender in the garlic sent
to a peer if the client requests it to be included (aka if they want
replies).  By default, this is enabled (disable by setting the I2CP
prop "shouldBundleReplyInfo=false").  Also, by default the probability is
30% (w00 h00, arbitrary values!), which can be overridden via the I2CP
prop "bundleReplyInfoProbability=80" (or =10, or =100, etc).  The tradeoff
here is quicker replies in exchange for bandwidth (the dbStore).
Yeah, it'd be nice if there were something keeping track of which leaseSet
each client sent to each peer so that it could explicitly include it only
if it were necessary, but for now that's probably overkill.
2004-05-05 22:46:10 +00:00
07aa2e280d strip the Connection, Keep-Alive, and Proxy-Connection headers, and always inject Connection: close
(this is the cause of the intermittent "view $page through squid, try to view eepsite, end up requesting through squid" bug)
2004-05-05 07:29:48 +00:00
6c4bc67ff3 simplistic streaming test (w00t, the streams worked - no mods necessary. go human, its your birthday, go human, its your birthday) 2004-05-05 04:43:05 +00:00
d9f0cc27ef formatting 2004-05-05 03:37:26 +00:00
59aec9d289 expose the read timeout for the client and put the default read timeout back to -1 for the server (override via command line, etc) 2004-05-05 03:36:18 +00:00
451f4c503d fixed typo on timestamper, keep NetMonitor off by default 2004-05-05 01:32:08 +00:00
cd82089d4d upgrade deprecated argument
fix ze german
(duck)
2004-05-04 17:17:10 +00:00
3db8b63cde by default, set the readTimeout to 3 minutes, NOT infinity. Overridable as before (setting the timeout to -1)
add a unique id to the server thread
2004-05-04 08:16:41 +00:00
6edf5d1e4f add a unique id to the thread names 2004-05-04 08:15:18 +00:00
a23fa6fadd allow multiple concurrent connections to be created
added a unique ID to more threads
2004-05-04 08:14:19 +00:00
51eb77e409 logging 2004-05-04 08:13:01 +00:00
691326cea8 make sure we kill the threads that failed to ACK, rather than leave them sitting there, waiting forever
logging
2004-05-04 08:09:28 +00:00
3cac1238ed handle reclose, logging, more clear notification 2004-05-04 05:53:11 +00:00
b04512a4f6 add unique IDs to the threads for easier tracing 2004-05-04 04:46:04 +00:00
3a4d0549aa add accept timeouts (default is that if the server doesnt .accept() in 5s, refuse the con)
add unique IDs to the various threads for logging / tracing purposes
2004-05-04 04:44:05 +00:00
d7467f5dc3 disconnect isn't an error 2004-05-04 01:58:37 +00:00
141902b86d parseParams throws exception on bad formatting, and its perfectly valid to have params with 0 values (e.g. DEST GENERATE\n) 2004-05-04 01:35:09 +00:00
5aa680fc93 simple test of whether DEST GENERATE works 2004-05-04 01:32:39 +00:00
a790117f5a test the naming commnads (fetching ME, a known host, and an unknown host) 2004-05-04 01:11:44 +00:00
2a5a52c810 added xilog.i2p 2004-05-04 00:43:09 +00:00
2156f4c2f3 * more verbose errors (include MESSAGE data on the I2P_ERROR reply, not just in the log)
* don't create excess I2PAppContexts (if any old context will do, use the global)
keep track of keys per spec (when DESTINATION=blah, create (or reuse) the destination private
keys).  we still need to persist this data though.
* the DESTINATION in the SESSION STATUS is now the same as the one sent in the
SESSION CREATE, /not/ the base64 of the private key, per spec
* enum is a reserved word in 1.5, so s/enum/names/ for future compatability
* logging
2004-05-03 11:34:38 +00:00
2585460286 initial tests for HELLO and create session (style=stream). covers the basics, but doesn't cover a single normal scenario yet 2004-05-03 11:16:59 +00:00
1b4af66986 flag as closed /after/ we send the disconnect message *cough* 2004-05-03 11:13:44 +00:00
0324bac044 added lucky.i2p, removed lp.i2p 2004-05-03 07:04:12 +00:00
2bfbe1ca27 logging (toss a unique ID onto the handler / inactivity threads) 2004-05-03 03:36:38 +00:00
60584228d9 refactored packet handling into type specific methods
removed nested synchronization (which had been causing undetected deadlocks)
made sync blocks smaller, though this may have opened holes related to
resent ACK/SYN/CLOSE packets that are delivered in a race.  I'm not as
fluent in the ministreaming lib code as i should be (yet), but duck's thread
dumps were showing hundreds of threads waiting on a lock that'll never get
released (since the only way to release it would be to receive another
packet, and no more packets can be received until the lock is released, etc)
also, I2PSession is threadsafe - i can see no reason to synchronize on it
(and it was being synchronized on only part of the time?)
also, refactored the charset encoding stuff and minor log tweaking
i've been testing this for the last hour or so, on eepsites and squid (large
and small files), as well as irc, and there haven't been any glitches.  but
it needs more testing before it can be released, obviously.
2004-05-03 03:34:25 +00:00
44e34f7b11 trim the request line (StringTokenizer w/ " " as a delim doesn't include \n as a delim, etc) 2004-05-02 07:50:20 +00:00
7912050647 allow overriding the I2CP port/host 2004-05-02 07:49:22 +00:00
2231abd407 default the DIRECTION to BOTH for streams 2004-05-02 07:07:25 +00:00
8d17ba4d66 added sungo.i2p 2004-05-02 06:59:38 +00:00
8244bdb440 include the timestamper (and fire the client on startup, using a pair of arbitrary NTP hosts - to be configured later) 2004-05-02 05:11:06 +00:00
bc3b7ffd86 start the admin listener ASAP (right after reading the config)
fire the LoadClientAppsJob right after the admin listener is booted, which now includes support for the onBoot property (which causes the client to run immediately, instead of waiting 2+ minutes)
(yeah, it'd suck if all routers started up, tried to connect to people, got shitlisted, then 2 minutes later got the right NTP time, 'eh?)
2004-05-02 05:02:10 +00:00
e22cb62493 handle /setTime?blah&now=yyyyMMdd_HH:mm:ss.SSSS (updating the router's clock)
yes, we'll want to filter the access to the admin manager ;)
2004-05-02 04:46:52 +00:00
e923aa1f72 add the timestamper 2004-05-02 04:29:18 +00:00
68a21f1fbb NTP client, GPLed 2004-05-02 04:18:53 +00:00
74209e2607 0.3.1 (backwards compatible, still testnet, release later this evening) 2004-04-30 23:04:13 +00:00
1a38271104 made the event name more consistent 2004-04-30 23:01:35 +00:00
e5ab5d6a5a new stat: client.sendAckTime containing the average time to get an ack for a client message send
this stat is published in the netDb, but the quantity fields (how many acks the stat is averaged over) is h4x0red
(it always reads "666", since otherwise it'd be fairly easy to identify what routers run servers, and i can live without knowing the quantity)
2004-04-30 07:56:05 +00:00
2745ff727f don't include a peer by default 2004-04-30 07:16:30 +00:00
24ea383937 don't harvest so much data 2004-04-30 07:15:29 +00:00
9cb11d4d5f allow some overrides that the ExploreJob needs
logging
2004-04-30 07:14:26 +00:00
7202ea3340 dont dieFatal() if the first leaseSet lookup fails (this was the cause of those "fast fail" errors [bugId=69])
try up to 6 times to search (or until the expiration, etc)
logging
2004-04-30 07:11:41 +00:00
d234ea01d0 logging 2004-04-30 07:09:05 +00:00
e246cd37dd simple exponential decay on the exploration
(min/max frequency=1m/30m, doubling every time we don't find something new)
reduce the bredth and duration of explorations
republish less often (every 60s send out one random one [or however many explicit ones])
logging
2004-04-30 07:08:25 +00:00
0a4ddedac9 be more forgiving of slow connections (and fix my math in the comments) 2004-04-30 07:03:13 +00:00
0e4b80b002 make sure we only try to request from people we know about (*cough*) 2004-04-30 07:02:16 +00:00
a460a0dc44 logging, and be more forgiving if the guaranteed/failed comes back before the ack does 2004-04-30 07:00:13 +00:00
f7212112b8 mark the ping datapoint before actually sending it, for those instances where a reply can be sent back faster than the ACK of the send 2004-04-30 06:58:42 +00:00
86d55b32a6 include the (redundant) rtt in the output file to let tools that dont know how to combine columns do so
*cough*
2004-04-30 06:56:25 +00:00
4b0d1aac15 only read from or write to disk if necessary 2004-04-27 08:47:48 +00:00
fb7c06aa01 throw in a 10 second pause between starting up each router to try and avoid too much synchronicity 2004-04-27 08:47:00 +00:00
5c41be3470 only read from or write to disk if there is new data to transfer
(and set the file modification date to the 'published on' date in the netDb object)
2004-04-27 08:44:23 +00:00
a78df1a152 logging (reduce gc churn) 2004-04-27 08:42:40 +00:00
34e8db0fe3 logging & formatting to reduce gc churn 2004-04-27 08:41:38 +00:00
70faecb8b5 handle the UnsignedInteger's new "IllegalArgumentException on negative" policy 2004-04-27 08:33:15 +00:00
237f278479 we only need to reread it if its been updated, dimwit. 2004-04-27 08:31:56 +00:00
e766a00a12 dont be such a reuse zealot and just fscking optimize the use case
(aka shift instead of creating heavyweight BigInteger objects)
plus some good ol' caching and gc churn reduction
2004-04-27 08:30:55 +00:00
ea03637ec1 cache the hashCode and getBytes data, significantly reducing the contention on these objects 2004-04-27 08:28:16 +00:00
d0f6d47b14 logging (reduced temporary object creation by _log.shouldLog) 2004-04-27 08:26:23 +00:00
5bf1658d9a dont go into an infinite loop if we shut down before the log manager was fully created
(since the appContext creates a log manager if one doesn't exist, and that create will fail
if we're shutting down, and it'll create a log manager to log the fact that its failing, etc)
2004-04-26 03:32:59 +00:00
4ce9fb5b5a new MultiRouter (allows you to fire up mutliple routers in the same JVM)
new VMCommSystem (useful for running large multirouter instances)
new MultiRouterBuilder (helper app for setting up a MultiRouter simulator)
updates to the router to handle multiple routers in the same VM, as well as
deal with the multiple OOM listener stuff
see the javadocs for info on the MultiRouter and MutliRouterBuilder
(yeah, its not ready for prime time, and its really just for the simulator,
so I'm not sure if anyone else is going to use it anyway ;)
2004-04-26 03:30:20 +00:00
f80f02da73 use the context to find a location for the sessionKeys.dat file (router.sessionKeys.location=filename in router.config) 2004-04-26 01:09:10 +00:00
52ece833a7 logging 2004-04-26 01:00:22 +00:00
1ad6dde146 allow a whole set of OOM listeners on threads, not just one
cache the hash's base64 value too
use the context's logging more
logging updates
2004-04-26 00:57:10 +00:00
64bcfd23bd expose the way to specify context env properties
remove unused lazy load code (we actively load components in the RouterContext)
2004-04-24 22:32:10 +00:00
d659447879 allow overriding the env props 2004-04-24 22:29:01 +00:00
e73eb55d75 don't create two contexts just for this I2PTunnel (one implicit from the static new Log(I2PTunnel.class) and one explicit) 2004-04-24 12:51:15 +00:00
a52cea29f4 Class.getConstructor pulls the public ones... 2004-04-24 12:49:13 +00:00
3d91e59386 la la la 2004-04-24 12:38:21 +00:00
393b1d7674 big ol' update to strip out the singletons, replacing them with
a rooted app context.  The core itself has its own I2PAppContext
(see its javadoc for, uh, docs), and the router extends that to
expose the router's singletons.  The main point of this is to
make it so that we can run multiple routers in the same JVM, even
to allow different apps in the same JVM to switch singleton
implementations (e.g. run some routers with one set of profile
calculators, and other routers with a different one).
There is still some work to be done regarding the actual boot up
of multiple routers in a JVM, as well as their configuration,
though the plan is to have the RouterContext override the
I2PAppContext's getProperty/getPropertyNames methods to read from
a config file (seperate ones per context) instead of using the
System.getProperty that the base I2PAppContext uses.
Once the multi-router is working, i'll shim in a VMCommSystem
that doesn't depend upon sockets or threads to read/write (and
that uses configurable message send delays / disconnects / etc,
perhaps using data from the routerContext.getProperty to drive it).
I could hold off until the sim is all working, but there's a
truckload of changes in here and I hate dealing with conflicts ;)
Everything works - I've been running 'er for a while and kicked
the tires a bit, but if you see something amiss, please let me
know.
2004-04-24 11:54:35 +00:00
c29a6b95ae Increased logging priority for connection timeouts
(human)
2004-04-23 14:18:48 +00:00
567a4e8361 so this is why we're not harvesting the dropped jobs/messages (d'oh)
if you update your harvester.config and rerun the router, it'll start paying attention to it
(no, unfortunately the NetMonitor doesn't periodically read this file)
2004-04-22 03:17:41 +00:00
4d3e4c1a15 d'oh 2004-04-22 03:11:12 +00:00
afeecdf4af logging, formatting 2004-04-22 03:09:03 +00:00
4fe7105e2f Implemented timeout handling to I2PTunnelServer 2004-04-21 17:58:26 +00:00
d7c3a53f2d Initial implementation of read() timeout on I2PSocket. Let's see whether it
could solve duck's problems with dangling threads...
(human)
2004-04-21 17:56:16 +00:00
283 changed files with 22433 additions and 17034 deletions

View File

@ -4,7 +4,6 @@ import net.i2p.data.Destination;
import net.i2p.util.Clock;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import net.i2p.util.Clock;
/**
* Responsible for actually conducting the tests, coordinating the storing of the
@ -84,8 +83,8 @@ class ClientEngine {
/** fire off a new ping */
private void doSend() {
long now = Clock.getInstance().now();
_heartbeat.sendPing(_data.getConfig().getPeer(), _id, now, _data.getConfig().getSendSize());
_data.addPing(now);
_heartbeat.sendPing(_data.getConfig().getPeer(), _id, now, _data.getConfig().getSendSize());
}
/** our actual heartbeat pumper - this drives the test */

View File

@ -59,7 +59,7 @@ public class PeerDataWriter {
String header = getHeader(data);
out.write(header.getBytes());
out.write("#action\tstatus\tdate and time sent \tsendMs\treplyMs\n".getBytes());
out.write("#action\tstatus\tdate and time sent \tsendMs\treplyMs\troundTrip\n".getBytes());
for (Iterator iter = data.getDataPoints().iterator(); iter.hasNext();) {
PeerData.EventDataPoint point = (PeerData.EventDataPoint) iter.next();
String line = getEvent(point);
@ -84,12 +84,15 @@ public class PeerDataWriter {
buf.append("lifetimeSent \t").append(data.getLifetimeSent()).append('\n');
buf.append("lifetimeRecv \t").append(data.getLifetimeReceived()).append('\n');
int periods[] = data.getAveragePeriods();
buf.append("#averages\tminutes\tsendMs\trecvMs\tnumLost\n");
buf.append("#averages\tminutes\tsendMs\trecvMs\tnumLost\troundTrip\n");
for (int i = 0; i < periods.length; i++) {
buf.append("periodAverage\t").append(periods[i]).append('\t');
buf.append(getNum(data.getAverageSendTime(periods[i]))).append('\t');
buf.append(getNum(data.getAverageReceiveTime(periods[i]))).append('\t');
buf.append(getNum(data.getLostMessages(periods[i]))).append('\n');
buf.append(getNum(data.getLostMessages(periods[i]))).append('\t');
double rtt = data.getAverageSendTime(periods[i])
+ data.getAverageReceiveTime(periods[i]);
buf.append(getNum(rtt)).append('\n');
}
return buf.toString();
}
@ -105,6 +108,7 @@ public class PeerDataWriter {
if (point.getWasPonged()) {
buf.append(point.getPongSent() - point.getPingSent()).append('\t');
buf.append(point.getPongReceived() - point.getPongSent()).append('\t');
buf.append(point.getPongReceived() - point.getPingSent()).append('\t');
}
buf.append('\n');
return buf.toString();

View File

@ -10,7 +10,6 @@ import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
class HeartbeatMonitorGUI extends JFrame {
private HeartbeatMonitor _monitor;

View File

@ -1,6 +1,5 @@
package net.i2p.heartbeat.gui;
import java.awt.Color;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

View File

@ -1,26 +1,23 @@
package net.i2p.heartbeat.gui;
import java.awt.Color;
import java.awt.Font;
import java.util.List;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.XYItemRenderer;
import org.jfree.chart.renderer.XYLineAndShapeRenderer;
import org.jfree.data.XYSeries;
import org.jfree.data.XYSeriesCollection;
import net.i2p.heartbeat.PeerData;
import net.i2p.util.Log;
import org.jfree.data.XYSeries;
import org.jfree.data.XYSeriesCollection;
import org.jfree.data.MovingAverage;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.renderer.XYLineAndShapeRenderer;
import org.jfree.chart.renderer.XYItemRenderer;
import org.jfree.chart.renderer.XYDotRenderer;
import java.util.List;
import javax.swing.JPanel;
import java.awt.Font;
import java.awt.Color;
class JFreeChartAdapter {
private final static Log _log = new Log(JFreeChartAdapter.class);
private final static Color WHITE = new Color(255, 255, 255);

View File

@ -1,21 +1,14 @@
package net.i2p.heartbeat.gui;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import javax.swing.JLabel;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import net.i2p.heartbeat.PeerDataWriter;
import net.i2p.util.Log;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import org.jfree.chart.ChartPanel;
import net.i2p.util.Log;
/**
* Render the graph and legend
*

View File

@ -53,7 +53,7 @@ public class HTTPListener extends Thread {
private boolean proxyUsed = false;
/**
* Query whether this is the first use of the proxy or not . . .
* Query whether this is the first use of the proxy or not
* @return Whether this is the first proxy use, no doubt.
*/
public boolean firstProxyUse() {

View File

@ -19,6 +19,7 @@ import net.i2p.httptunnel.SocketManagerProducer;
import net.i2p.httptunnel.filter.Filter;
import net.i2p.httptunnel.filter.NullFilter;
import net.i2p.util.Log;
import net.i2p.I2PAppContext;
/**
* Handler for browsing Eepsites.
@ -26,6 +27,7 @@ import net.i2p.util.Log;
public class EepHandler {
private static final Log _log = new Log(EepHandler.class);
private static I2PAppContext _context = new I2PAppContext();
protected ErrorHandler errorHandler;
@ -44,7 +46,7 @@ public class EepHandler {
public void handle(Request req, HTTPListener httpl, OutputStream out,
/* boolean fromProxy, */String destination) throws IOException {
SocketManagerProducer smp = httpl.getSMP();
Destination dest = NamingService.getInstance().lookup(destination);
Destination dest = _context.namingService().lookup(destination);
if (dest == null) {
errorHandler.handle(req, httpl, out, "Could not lookup host: " + destination);
return;
@ -66,8 +68,8 @@ public class EepHandler {
* @return boolean, true if something was written, false otherwise.
* @throws IOException
*/
public boolean handle(Request req, Filter f, OutputStream out, Destination dest, I2PSocketManager sm)
throws IOException {
public boolean handle(Request req, Filter f, OutputStream out, Destination dest,
I2PSocketManager sm) throws IOException {
I2PSocket s = null;
boolean written = false;
try {

View File

@ -12,6 +12,7 @@ import net.i2p.httptunnel.SocketManagerProducer;
import net.i2p.httptunnel.filter.Filter;
import net.i2p.httptunnel.filter.NullFilter;
import net.i2p.util.Log;
import net.i2p.I2PAppContext;
/**
* Handler for proxying "normal" HTTP requests.
@ -19,6 +20,7 @@ import net.i2p.util.Log;
public class ProxyHandler extends EepHandler {
private static final Log _log = new Log(ErrorHandler.class);
private static I2PAppContext _context = new I2PAppContext();
/* package private */ProxyHandler(ErrorHandler eh) {
super(eh);
@ -31,7 +33,7 @@ public class ProxyHandler extends EepHandler {
* @throws IOException
*/
public void handle(Request req, HTTPListener httpl, OutputStream out
/*, boolean fromProxy */) throws IOException {
/*, boolean fromProxy */) throws IOException {
SocketManagerProducer smp = httpl.getSMP();
Destination dest = findProxy();
if (dest == null) {
@ -48,6 +50,6 @@ public class ProxyHandler extends EepHandler {
private Destination findProxy() {
//FIXME!
return NamingService.getInstance().lookup("squid.i2p");
return _context.namingService().lookup("squid.i2p");
}
}

View File

@ -29,7 +29,7 @@ public class RootHandler {
private static RootHandler instance;
/**
* Singleton stuff . . .
* Singleton stuff
* @return the one and only instance, yay!
*/
public static synchronized RootHandler getInstance() {

View File

@ -49,6 +49,7 @@ import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PClientFactory;
@ -63,8 +64,9 @@ import net.i2p.util.EventDispatcherImpl;
import net.i2p.util.Log;
public class I2PTunnel implements Logging, EventDispatcher {
private final static Log _log = new Log(I2PTunnel.class);
private final EventDispatcherImpl _event = new EventDispatcherImpl();
private Log _log;
private EventDispatcherImpl _event;
private I2PAppContext _context;
public static final int PACKET_DELAY = 100;
@ -74,6 +76,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
public static String host = System.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
public static String listenHost = host;
public static long readTimeout = -1;
private static final String nocli_args[] = { "-nocli", "-die"};
private List tasks = new ArrayList();
@ -94,6 +98,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
}
public I2PTunnel(String[] args, ConnectionEventListener lsnr) {
_context = new I2PAppContext();
_log = _context.logManager().getLog(I2PTunnel.class);
_event = new EventDispatcherImpl();
addConnectionEventListener(lsnr);
boolean gui = true;
boolean checkRunByE = true;
@ -201,6 +208,8 @@ public class I2PTunnel implements Logging, EventDispatcher {
runConfig(args, l);
} else if ("listen_on".equals(cmdname)) {
runListenOn(args, l);
} else if ("read_timeout".equals(cmdname)) {
runReadTimeout(args, l);
} else if ("genkeys".equals(cmdname)) {
runGenKeys(args, l);
} else if ("gentextkeys".equals(cmdname)) {
@ -235,6 +244,7 @@ public class I2PTunnel implements Logging, EventDispatcher {
l.log("Command list:");
l.log("config <i2phost> <i2pport>");
l.log("listen_on <ip>");
l.log("read_timeout <msecs>");
l.log("owndest yes|no");
l.log("ping <args>");
l.log("server <host> <port> <privkeyfile>");
@ -291,10 +301,11 @@ public class I2PTunnel implements Logging, EventDispatcher {
notifyEvent("serverTaskId", new Integer(-1));
return;
}
I2PTunnelTask task;
task = new I2PTunnelServer(serverHost, portNum, privKeyFile, args[2], l, (EventDispatcher) this);
addtask(task);
notifyEvent("serverTaskId", new Integer(task.getId()));
I2PTunnelServer serv = new I2PTunnelServer(serverHost, portNum, privKeyFile, args[2], l, (EventDispatcher) this);
serv.setReadTimeout(readTimeout);
serv.startRunning();
addtask(serv);
notifyEvent("serverTaskId", new Integer(serv.getId()));
return;
} else {
l.log("server <host> <port> <privkeyfile>");
@ -336,10 +347,11 @@ public class I2PTunnel implements Logging, EventDispatcher {
return;
}
I2PTunnelTask task;
task = new I2PTunnelServer(serverHost, portNum, args[2], l, (EventDispatcher) this);
addtask(task);
notifyEvent("serverTaskId", new Integer(task.getId()));
I2PTunnelServer serv = new I2PTunnelServer(serverHost, portNum, args[2], l, (EventDispatcher) this);
serv.setReadTimeout(readTimeout);
serv.startRunning();
addtask(serv);
notifyEvent("serverTaskId", new Integer(serv.getId()));
} else {
l.log("textserver <host> <port> <privkey>");
l.log(" creates a server that sends all incoming data\n" + " of its destination to host:port.");
@ -510,7 +522,31 @@ public class I2PTunnel implements Logging, EventDispatcher {
} else {
l.log("listen_on <ip>");
l.log(" sets the interface to listen for the I2PClient.");
notifyEvent("listen_onResult", "ok");
notifyEvent("listen_onResult", "error");
}
}
/**
* Specify the read timeout going to be used for newly-created I2PSockets
*
* Sets the event "read_timeoutResult" = "ok" or "error" after the interface has been specified
*
* @param args {hostname}
* @param l logger to receive events and output
*/
public void runReadTimeout(String args[], Logging l) {
if (args.length == 1) {
try {
readTimeout = Long.parseLong(args[0]);
} catch (NumberFormatException e) {
notifyEvent("read_timeoutResult", "error");
}
notifyEvent("read_timeoutResult", "ok");
} else {
l.log("read_timeout <msecs>");
l.log(" sets the read timeout (in milliseconds) for I2P connections\n"
+" Negative values will make the connections wait forever");
notifyEvent("read_timeoutResult", "error");
}
}
@ -888,6 +924,9 @@ public class I2PTunnel implements Logging, EventDispatcher {
if ((name == null) || (name.trim().length() <= 0)) throw new DataFormatException("Empty destination provided");
I2PAppContext ctx = I2PAppContext.getGlobalContext();
Log log = ctx.logManager().getLog(I2PTunnel.class);
if (name.startsWith("file:")) {
Destination result = new Destination();
byte content[] = null;
@ -911,19 +950,21 @@ public class I2PTunnel implements Logging, EventDispatcher {
result.fromByteArray(content);
return result;
} catch (Exception ex) {
if (_log.shouldLog(Log.INFO)) _log.info("File is not a binary destination - trying base64");
if (log.shouldLog(Log.INFO))
log.info("File is not a binary destination - trying base64");
try {
byte decoded[] = Base64.decode(new String(content));
result.fromByteArray(decoded);
return result;
} catch (DataFormatException dfe) {
if (_log.shouldLog(Log.WARN)) _log.warn("File is not a base64 destination either - failing!");
if (log.shouldLog(Log.WARN))
log.warn("File is not a base64 destination either - failing!");
return null;
}
}
} else {
// ask naming service
NamingService inst = NamingService.getInstance();
NamingService inst = ctx.namingService();
return inst.lookup(name);
}
}
@ -1000,4 +1041,4 @@ public class I2PTunnel implements Logging, EventDispatcher {
public Object waitEventValue(String n) {
return _event.waitEventValue(n);
}
}
}

View File

@ -17,6 +17,8 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
private static final Log _log = new Log(I2PTunnelClient.class);
protected Destination dest;
private static final long DEFAULT_READ_TIMEOUT = 5*60*1000; // -1
protected long readTimeout = DEFAULT_READ_TIMEOUT;
public I2PTunnelClient(int localPort, String destination, Logging l, boolean ownDest, EventDispatcher notifyThis) {
super(localPort, ownDest, l, notifyThis, "SynSender");
@ -45,9 +47,13 @@ public class I2PTunnelClient extends I2PTunnelClientBase {
notifyEvent("openClientResult", "ok");
}
public void setReadTimeout(long ms) { readTimeout = ms; }
public long getReadTimeout() { return readTimeout; }
protected void clientConnectionRun(Socket s) {
try {
I2PSocket i2ps = createI2PSocket(dest);
i2ps.setReadTimeout(readTimeout);
new I2PTunnelRunner(s, i2ps, sockLock, null);
} catch (Exception ex) {
_log.info("Error connecting", ex);

View File

@ -33,6 +33,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
private static final long DEFAULT_CONNECT_TIMEOUT = 60 * 1000;
private static volatile long __clientId = 0;
private long _clientId;
protected Object sockLock = new Object(); // Guards sockMgr and mySockets
private I2PSocketManager sockMgr;
private List mySockets = new ArrayList();
@ -60,9 +62,10 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
public I2PTunnelClientBase(int localPort, boolean ownDest, Logging l, EventDispatcher notifyThis, String handlerName) {
super(localPort + " (uninitialized)", notifyThis);
_clientId = ++__clientId;
this.localPort = localPort;
this.l = l;
this.handlerName = handlerName;
this.handlerName = handlerName + _clientId;
synchronized (sockLock) {
if (ownDest) {
@ -75,7 +78,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
l.log("I2P session created");
Thread t = new I2PThread(this);
t.setName("Client");
t.setName("Client " + _clientId);
listenerReady = false;
t.start();
open = true;
@ -179,8 +182,8 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
public I2PSocket createI2PSocket(Destination dest, I2PSocketOptions opt) throws I2PException, ConnectException, NoRouteToHostException, InterruptedIOException {
I2PSocket i2ps;
i2ps = sockMgr.connect(dest, opt);
synchronized (sockLock) {
i2ps = sockMgr.connect(dest, opt);
mySockets.add(i2ps);
}
@ -272,12 +275,14 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
}
}
private static volatile long __runnerId = 0;
public class ClientConnectionRunner extends I2PThread {
private Socket s;
public ClientConnectionRunner(Socket s, String name) {
this.s = s;
setName(name);
setName(name + '.' + (++__runnerId));
start();
}

View File

@ -10,6 +10,9 @@ import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.StringTokenizer;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
@ -20,6 +23,25 @@ import net.i2p.util.EventDispatcher;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
/**
* Act as a mini HTTP proxy, handling various different types of requests,
* forwarding them through I2P appropriately, and displaying the reply. Supported
* request formats are: <pre>
* $method http://$site[$port]/$path $protocolVersion
* or
* $method $path $protocolVersion\nHost: $site
* or
* $method http://i2p/$site/$path $protocolVersion
* or
* $method /$site/$path $protocolVersion
* </pre>
*
* If the $site resolves with the I2P naming service, then it is directed towards
* that eepsite, otherwise it is directed towards this client's outproxy (typically
* "squid.i2p"). Only HTTP is supported (no HTTPS, ftp, mailto, etc). Both GET
* and POST have been tested, though other $methods should work.
*
*/
public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable {
private static final Log _log = new Log(I2PTunnelHTTPClient.class);
@ -43,7 +65,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
"That Desitination was not found. Perhaps you pasted in the wrong "+
"BASE64 I2P Destination or the link you are following is bad. "+
"The host (or the WWW proxy, if you're using one) could also be "+
"temporarily offline. "+
"temporarily offline. You may want to <b>retry</b>. "+
"Could not find the following Destination:<BR><BR>")
.getBytes();
@ -53,19 +75,17 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
"Cache-control: no-cache\r\n\r\n"+
"<html><body><H1>I2P ERROR: TIMEOUT</H1>"+
"That Desitination was reachable, but timed out getting a "+
"response. This may be a temporary error, so you should simply "+
"response. This is likely a temporary error, so you should simply "+
"try to refresh, though if the problem persists, the remote "+
"destination may have issues. Could not get a response from "+
"the following Destination:<BR><BR>")
.getBytes();
// public I2PTunnelHTTPClient(int localPort, Logging l,
// boolean ownDest, String wwwProxy) {
// this(localPort, l, ownDest, wwwProxy, (EventDispatcher)null);
// }
/** used to assign unique IDs to the threads / clients. no logic or functionality */
private static volatile long __clientId = 0;
public I2PTunnelHTTPClient(int localPort, Logging l, boolean ownDest, String wwwProxy, EventDispatcher notifyThis) {
super(localPort, ownDest, l, notifyThis, "HTTPHandler");
super(localPort, ownDest, l, notifyThis, "HTTPHandler " + (++__clientId));
if (waitEventValue("openBaseClientResult").equals("error")) {
notifyEvent("openHTTPClientResult", "error");
@ -92,7 +112,18 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
String line, method = null, protocol = null, host = null, destination = null;
StringBuffer newRequest = new StringBuffer();
while ((line = br.readLine()) != null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Line=[" + line + "]");
if (line.startsWith("Connection: ") ||
line.startsWith("Keep-Alive: ") ||
line.startsWith("Proxy-Connection: "))
continue;
if (method == null) { // first line (GET /base64/realaddr)
if (_log.shouldLog(Log.DEBUG))
_log.debug("Method is null for [" + line + "]");
int pos = line.indexOf(" ");
if (pos == -1) break;
method = line.substring(0, pos);
@ -126,6 +157,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
// The request must be forwarded to a WWW proxy
destination = wwwProxy;
usingWWWProxy = true;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Host doesnt end with .i2p and it contains a period [" + host + "]: wwwProxy!");
} else {
request = request.substring(pos + 1);
pos = request.indexOf("/");
@ -150,14 +183,25 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
_log.debug("HOST :" + host + ":");
_log.debug("DEST :" + destination + ":");
}
} else if (line.startsWith("Host: ") && !usingWWWProxy) {
line = "Host: " + host;
if (_log.shouldLog(Log.INFO)) _log.info("Setting host = " + host);
} else {
if (line.startsWith("Host: ") && !usingWWWProxy) {
line = "Host: " + host;
if (_log.shouldLog(Log.INFO))
_log.info("Setting host = " + host);
}
}
if (line.length() == 0) {
newRequest.append("Connection: close\r\n\r\n");
break;
} else {
newRequest.append(line).append("\r\n"); // HTTP spec
}
newRequest.append(line).append("\r\n"); // HTTP spec
if (line.length() == 0) break;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("NewRequest header: [" + newRequest.toString() + "]");
while (br.ready()) { // empty the buffer (POST requests)
int i = br.read();
if (i != -1) {
@ -176,6 +220,10 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
s.close();
return;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Destination: " + destination);
Destination dest = I2PTunnel.destFromName(destination);
if (dest == null) {
l.log("Could not resolve " + destination + ".");
@ -211,6 +259,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
}
private static final long INACTIVITY_TIMEOUT = 120 * 1000;
private static volatile long __timeoutId = 0;
private class InactivityTimeoutThread extends I2PThread {
@ -230,7 +279,8 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
_targetRequest = targetRequest;
_useWWWProxy = useWWWProxy;
_disabled = false;
setName("InactivityThread");
long timeoutId = ++__timeoutId;
setName("InactivityThread " + timeoutId);
}
public void disable() {

View File

@ -6,6 +6,7 @@ package net.i2p.i2ptunnel;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
@ -20,6 +21,8 @@ import net.i2p.util.Log;
public class I2PTunnelRunner extends I2PThread {
private final static Log _log = new Log(I2PTunnelRunner.class);
private static volatile long __runnerId;
private long _runnerId;
/**
* max bytes streamed in a packet - smaller ones might be filled
* up to this size. Larger ones are not split (at least not on
@ -50,7 +53,8 @@ public class I2PTunnelRunner extends I2PThread {
lastActivityOn = -1;
startedOn = -1;
_log.info("I2PTunnelRunner started");
setName("I2PTunnelRunner");
_runnerId = ++__runnerId;
setName("I2PTunnelRunner " + _runnerId);
start();
}
@ -116,7 +120,7 @@ public class I2PTunnelRunner extends I2PThread {
_log.error("Interrupted", ex);
} catch (IOException ex) {
ex.printStackTrace();
_log.error("Error forwarding", ex);
_log.debug("Error forwarding", ex);
} finally {
try {
if (s != null) s.close();
@ -128,6 +132,8 @@ public class I2PTunnelRunner extends I2PThread {
}
}
private volatile long __forwarderId = 0;
private class StreamForwarder extends I2PThread {
InputStream in;
@ -136,7 +142,7 @@ public class I2PTunnelRunner extends I2PThread {
private StreamForwarder(InputStream in, OutputStream out) {
this.in = in;
this.out = out;
setName("StreamForwarder");
setName("StreamForwarder " + _runnerId + "." + (++__forwarderId));
start();
}
@ -163,11 +169,14 @@ public class I2PTunnelRunner extends I2PThread {
} catch (SocketException ex) {
// this *will* occur when the other threads closes the socket
synchronized (finishLock) {
if (!finished)
_log.error("Error reading and writing", ex);
else
_log.warn("You may ignore this", ex);
if (!finished) {
_log.debug("Socket closed - error reading and writing",
ex);
}
}
} catch (InterruptedIOException ex) {
_log.warn("Closing connection due to timeout (error: \""
+ ex.getMessage() + "\")");
} catch (IOException ex) {
if (!finished)
_log.error("Error forwarding", ex);
@ -188,4 +197,4 @@ public class I2PTunnelRunner extends I2PThread {
}
}
}
}
}

View File

@ -40,6 +40,10 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
private Logging l;
private static final long DEFAULT_READ_TIMEOUT = -1; // 3*60*1000;
/** default timeout to 3 minutes - override if desired */
private long readTimeout = DEFAULT_READ_TIMEOUT;
public I2PTunnelServer(InetAddress host, int port, String privData, Logging l, EventDispatcher notifyThis) {
super(host + ":" + port + " <- " + privData, notifyThis);
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(privData));
@ -57,8 +61,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
}
}
public I2PTunnelServer(InetAddress host, int port, InputStream privData, String privkeyname, Logging l,
EventDispatcher notifyThis) {
public I2PTunnelServer(InetAddress host, int port, InputStream privData, String privkeyname, Logging l, EventDispatcher notifyThis) {
super(host + ":" + port + " <- " + privkeyname, notifyThis);
init(host, port, privData, privkeyname, l);
}
@ -78,11 +81,40 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
l.log("Ready!");
notifyEvent("openServerResult", "ok");
open = true;
}
private static volatile long __serverId = 0;
/**
* Start running the I2PTunnelServer.
*
*/
public void startRunning() {
Thread t = new I2PThread(this);
t.setName("Server");
t.setName("Server " + (++__serverId));
t.start();
}
/**
* Set the read idle timeout for newly-created connections (in
* milliseconds). After this time expires without data being reached from
* the I2P network, the connection itself will be closed.
*/
public void setReadTimeout(long ms) {
readTimeout = ms;
}
/**
* Get the read idle timeout for newly-created connections (in
* milliseconds).
*
* @return The read timeout used for connections
*/
public long getReadTimeout() {
return readTimeout;
}
public boolean close(boolean forced) {
if (!open) return true;
synchronized (lock) {
@ -115,6 +147,7 @@ public class I2PTunnelServer extends I2PTunnelTask implements Runnable {
//local is fast, so synchronously. Does not need that many
//threads.
try {
i2ps.setReadTimeout(readTimeout);
Socket s = new Socket(remoteHost, remotePort);
new I2PTunnelRunner(s, i2ps, slock, null);
} catch (SocketException ex) {

View File

@ -4,71 +4,148 @@ import java.net.ConnectException;
import net.i2p.I2PException;
import net.i2p.util.Log;
import net.i2p.util.Clock;
import net.i2p.I2PAppContext;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
/**
* Initial stub implementation for the server socket
* Server socket implementation, allowing multiple threads to accept I2PSockets
* and pull from a queue populated by various threads (each of whom have their own
* timeout)
*
*/
class I2PServerSocketImpl implements I2PServerSocket {
private final static Log _log = new Log(I2PServerSocketImpl.class);
private I2PSocketManager mgr;
private I2PSocket cached = null; // buffer one socket here
private boolean closing = false; // Are we being closed?
private Object acceptLock = new Object();
/** list of sockets waiting for the client to accept them */
private List pendingSockets = Collections.synchronizedList(new ArrayList(4));
/** have we been closed */
private volatile boolean closing = false;
/** lock on this when accepting a pending socket, and wait on it for notification of acceptance */
private Object socketAcceptedLock = new Object();
/** lock on this when adding a new socket to the pending list, and wait on it accordingly */
private Object socketAddedLock = new Object();
public I2PServerSocketImpl(I2PSocketManager mgr) {
this.mgr = mgr;
}
public synchronized I2PSocket accept() throws I2PException, ConnectException {
I2PSocket ret;
synchronized (acceptLock) {
while ((cached == null) && !closing) {
myWait();
}
if (closing) {
throw new ConnectException("I2PServerSocket closed");
}
ret = cached;
cached = null;
acceptLock.notifyAll();
}
_log.debug("TIMING: handed out accept result " + ret.hashCode());
/**
* Waits for the next socket connecting. If a remote user tried to make a
* connection and the local application wasn't .accept()ing new connections,
* they should get refused (if .accept() doesnt occur in some small period -
* currently 5 seconds)
*
* @return a connected I2PSocket
*
* @throws I2PException if there is a problem with reading a new socket
* from the data available (aka the I2PSession closed, etc)
* @throws ConnectException if the I2PServerSocket is closed
*/
public I2PSocket accept() throws I2PException, ConnectException {
if (_log.shouldLog(Log.DEBUG))
_log.debug("accept() called, pending: " + pendingSockets.size());
I2PSocket ret = null;
while ( (ret == null) && (!closing) ){
while (pendingSockets.size() <= 0) {
if (closing) throw new ConnectException("I2PServerSocket closed");
try {
synchronized(socketAddedLock) {
socketAddedLock.wait();
}
} catch (InterruptedException ie) {}
}
synchronized (pendingSockets) {
if (pendingSockets.size() > 0) {
ret = (I2PSocket)pendingSockets.remove(0);
}
}
if (ret != null) {
synchronized (socketAcceptedLock) {
socketAcceptedLock.notifyAll();
}
}
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("TIMING: handed out accept result " + ret.hashCode());
return ret;
}
public boolean getNewSocket(I2PSocket s) {
synchronized (acceptLock) {
while (cached != null) {
myWait();
}
cached = s;
acceptLock.notifyAll();
}
/**
* Make the socket available and wait until the client app accepts it, or until
* the given timeout elapses. This doesn't have any limits on the queue size -
* perhaps it should add some choking (e.g. after 5 waiting for accept, refuse)
*
* @param timeoutMs how long to wait until accept
* @return true if the socket was accepted, false if the timeout expired
* or the socket was closed
*/
public boolean addWaitForAccept(I2PSocket s, long timeoutMs) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("addWaitForAccept [new socket arrived, pending: " + pendingSockets.size());
if (closing) {
if (_log.shouldLog(Log.WARN))
_log.warn("Already closing the socket");
return false;
}
Clock clock = I2PAppContext.getGlobalContext().clock();
long start = clock.now();
long end = start + timeoutMs;
pendingSockets.add(s);
synchronized (socketAddedLock) {
socketAddedLock.notifyAll();
}
// keep looping until the socket has been grabbed by the accept()
// (or the expiration passes, or the socket is closed)
while (pendingSockets.contains(s)) {
long now = clock.now();
if (now >= end) {
if (_log.shouldLog(Log.INFO))
_log.info("Expired while waiting for accept (time elapsed =" + (now - start) + "ms");
pendingSockets.remove(s);
return false;
}
if (closing) {
if (_log.shouldLog(Log.WARN))
_log.warn("Server socket closed while waiting for accept");
pendingSockets.remove(s);
return false;
}
long remaining = end - now;
try {
synchronized (socketAcceptedLock) {
socketAcceptedLock.wait(remaining);
}
} catch (InterruptedException ie) {}
}
long now = clock.now();
if (_log.shouldLog(Log.DEBUG))
_log.info("Socket accepted after " + (now-start) + "ms");
return true;
}
public void close() throws I2PException {
synchronized (acceptLock) {
closing = true;
acceptLock.notifyAll();
}
}
public I2PSocketManager getManager() {
return mgr;
}
private void myWait() {
try {
acceptLock.wait();
} catch (InterruptedException ex) {}
public void close() {
closing = true;
// let anyone .accept()ing know to fsck off
synchronized (socketAddedLock) {
socketAddedLock.notifyAll();
}
// let anyone addWaitForAccept()ing know to fsck off
synchronized (socketAcceptedLock) {
socketAcceptedLock.notifyAll();
}
}
public I2PSocketManager getManager() { return mgr; }
}

View File

@ -32,8 +32,22 @@ public interface I2PSocket {
*/
public OutputStream getOutputStream() throws IOException;
/**
* How long we will wait blocked on a read() operation.
*
* @return milliseconds to wait, or -1 if we will wait indefinitely
*/
public long getReadTimeout();
/**
* Define how long we will wait blocked on a read() operation (-1 will make
* the socket wait forever).
*
*/
public void setReadTimeout(long ms);
/**
* Closes the socket if not closed yet
*/
public void close() throws IOException;
}
}

View File

@ -31,8 +31,32 @@ class I2PSocketImpl implements I2PSocket {
private I2POutputStream out;
private boolean outgoing;
private Object flagLock = new Object();
private boolean closed = false, sendClose = true, closed2 = false;
/**
* Whether the I2P socket has already been closed.
*/
private boolean closed = false;
/**
* Whether to send out a close packet when the socket is
* closed. (If the socket is closed because of an incoming close
* packet, we need not send one.)
*/
private boolean sendClose = true;
/**
* Whether the I2P socket has already been closed and all data
* (from I2P to the app, dunno whether to call this incoming or
* outgoing) has been processed.
*/
private boolean closed2 = false;
/**
* @param peer who this socket is (or should be) connected to
* @param mgr how we talk to the network
* @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) {
this.outgoing = outgoing;
manager = mgr;
@ -45,10 +69,17 @@ class I2PSocketImpl implements I2PSocket {
this.localID = localID;
}
/**
* Our half of the socket's unique ID
*
*/
public String getLocalID() {
return localID;
}
/**
* We've received the other side's half of the socket's unique ID
*/
public void setRemoteID(String id) {
synchronized (remoteIDWaiter) {
remoteID = id;
@ -56,36 +87,71 @@ class I2PSocketImpl implements I2PSocket {
}
}
public String getRemoteID(boolean wait) throws InterruptedIOException {
return getRemoteID(wait, -1);
/**
* Retrieve the other side's half of the socket's unique ID, or null if it
* isn't known yet
*
* @param wait if true, we should wait until we receive it from the peer, otherwise
* return what we know immediately (which may be null)
*/
public String getRemoteID(boolean wait) {
try {
return getRemoteID(wait, -1);
} catch (InterruptedIOException iie) {
_log.error("wtf, we said we didn't want it to time out! you smell", iie);
return null;
}
}
/**
* Retrieve the other side's half of the socket's unique ID, or null if it isn't
* known yet and we were instructed not to wait
*
* @param wait should we wait for the peer to send us their half of the ID, or
* just return immediately?
* @param maxWait if we're going to wait, after how long should we timeout and fail?
* (if this value is < 0, we wait indefinitely)
* @throws InterruptedIOException when the max waiting period has been exceeded
*/
public String getRemoteID(boolean wait, long maxWait) throws InterruptedIOException {
long dieAfter = System.currentTimeMillis() + maxWait;
synchronized (remoteIDWaiter) {
if (wait) {
try {
if (maxWait > 0)
if (maxWait >= 0)
remoteIDWaiter.wait(maxWait);
else
remoteIDWaiter.wait();
} catch (InterruptedException ex) {
}
if ((maxWait > 0) && (System.currentTimeMillis() > dieAfter))
if ((maxWait >= 0) && (System.currentTimeMillis() >= dieAfter))
throw new InterruptedIOException("Timed out waiting for remote ID");
_log.debug("TIMING: RemoteID set to " + I2PSocketManager.getReadableForm(remoteID) + " for "
+ this.hashCode());
if (_log.shouldLog(Log.DEBUG))
_log.debug("TIMING: RemoteID set to "
+ I2PSocketManager.getReadableForm(remoteID) + " for "
+ this.hashCode());
}
return remoteID;
}
}
public String getRemoteID() throws InterruptedIOException {
/**
* Retrieve the other side's half of the socket's unique ID, or null if it
* isn't known yet. This does not wait
*
*/
public String getRemoteID() {
return getRemoteID(false);
}
/**
* The other side has given us some data, so inject it into our socket's
* inputStream
*
* @param data the data to inject into our local inputStream
*/
public void queueData(byte[] data) {
in.queueData(data);
}
@ -121,7 +187,8 @@ class I2PSocketImpl implements I2PSocket {
}
/**
* Closes the socket if not closed yet
* Closes the socket if not closed yet (from the Application
* side).
*/
public void close() throws IOException {
synchronized (flagLock) {
@ -132,7 +199,10 @@ class I2PSocketImpl implements I2PSocket {
in.notifyClosed();
}
public void internalClose() {
/**
* Close the socket from the I2P side, e. g. by a close packet.
*/
protected void internalClose() {
synchronized (flagLock) {
closed = true;
closed2 = true;
@ -143,14 +213,40 @@ class I2PSocketImpl implements I2PSocket {
}
private byte getMask(int add) {
return (byte) ((outgoing ? (byte) 0xA0 : (byte) 0x50) + (byte) add);
if (outgoing)
return (byte)(I2PSocketManager.DATA_IN + (byte)add);
else
return (byte)(I2PSocketManager.DATA_OUT + (byte)add);
}
/**
* What is the longest we'll block on the input stream while waiting
* for more data? If this value is exceeded, the read() throws
* InterruptedIOException
*/
public long getReadTimeout() {
return in.getReadTimeout();
}
public void setReadTimeout(long ms) {
in.setReadTimeout(ms);
}
//--------------------------------------------------
public class I2PInputStream extends InputStream {
private class I2PInputStream extends InputStream {
private ByteCollector bc = new ByteCollector();
private long readTimeout = -1;
public long getReadTimeout() {
return readTimeout;
}
public void setReadTimeout(long ms) {
readTimeout = ms;
}
public int read() throws IOException {
byte[] b = new byte[1];
int res = read(b);
@ -162,30 +258,43 @@ class I2PSocketImpl implements I2PSocket {
public synchronized int read(byte[] b, int off, int len) throws IOException {
_log.debug("Read called: " + this.hashCode());
if (len == 0) return 0;
long dieAfter = System.currentTimeMillis() + readTimeout;
byte[] read = bc.startToByteArray(len);
boolean timedOut = false;
while (read.length == 0) {
synchronized (flagLock) {
if (closed) {
_log.debug("Closed is set, so closing stream: " + this.hashCode());
_log.debug("Closed is set, so closing stream: " + hashCode());
return -1;
}
}
try {
wait();
} catch (InterruptedException ex) {
if (readTimeout >= 0) {
wait(readTimeout);
} else {
wait();
}
} catch (InterruptedException ex) {}
if ((readTimeout >= 0)
&& (System.currentTimeMillis() >= dieAfter)) {
throw new InterruptedIOException("Timeout reading from I2PSocket (" + readTimeout + " msecs)");
}
read = bc.startToByteArray(len);
}
if (read.length > len) throw new RuntimeException("BUG");
System.arraycopy(read, 0, b, off, read.length);
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("Read from I2PInputStream " + this.hashCode() + " returned " + read.length + " bytes");
_log.debug("Read from I2PInputStream " + hashCode() + " returned "
+ read.length + " bytes");
}
//if (_log.shouldLog(Log.DEBUG)) {
// _log.debug("Read from I2PInputStream " + this.hashCode()
// + " returned "+read.length+" bytes:\n"
// + HexDump.dump(read));
// + " returned "+read.length+" bytes:\n"
// + HexDump.dump(read));
//}
return read.length;
}
@ -199,18 +308,24 @@ class I2PSocketImpl implements I2PSocket {
}
public synchronized void queueData(byte[] data, int off, int len) {
_log.debug("Insert " + len + " bytes into queue: " + this.hashCode());
if (_log.shouldLog(Log.DEBUG))
_log.debug("Insert " + len + " bytes into queue: " + hashCode());
bc.append(data, off, len);
notifyAll();
}
public synchronized void notifyClosed() {
notifyAll();
I2PInputStream.this.notifyAll();
}
public void close() throws IOException {
super.close();
notifyClosed();
}
}
public class I2POutputStream extends OutputStream {
private class I2POutputStream extends OutputStream {
public I2PInputStream sendTo;
@ -231,79 +346,96 @@ class I2PSocketImpl implements I2PSocket {
}
}
public class I2PSocketRunner extends I2PThread {
private static volatile long __runnerId = 0;
private class I2PSocketRunner extends I2PThread {
public InputStream in;
public I2PSocketRunner(InputStream in) {
_log.debug("Runner's input stream is: " + in.hashCode());
if (_log.shouldLog(Log.DEBUG))
_log.debug("Runner's input stream is: " + in.hashCode());
this.in = in;
setName("SocketRunner from " + I2PSocketImpl.this.remote.calculateHash().toBase64().substring(0, 4));
String peer = I2PSocketImpl.this.remote.calculateHash().toBase64();
setName("SocketRunner " + (++__runnerId) + " " + peer.substring(0, 4));
start();
}
/**
* Pump some more data
*
* @return true if we should keep on handling, false otherwise
*/
private boolean handleNextPacket(ByteCollector bc, byte buffer[])
throws IOException, I2PSessionException {
int len = in.read(buffer);
int bcsize = bc.getCurrentSize();
if (len != -1) {
bc.append(buffer, len);
} else if (bcsize == 0) {
// nothing left in the buffer, and read(..) got EOF (-1).
// the bart the
return false;
}
if ((bcsize < MAX_PACKET_SIZE) && (in.available() == 0)) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Runner Point d: " + hashCode());
try {
Thread.sleep(PACKET_DELAY);
} catch (InterruptedException e) {
_log.warn("wtf", e);
}
}
if ((bcsize >= MAX_PACKET_SIZE) || (in.available() == 0)) {
byte[] data = bc.startToByteArray(MAX_PACKET_SIZE);
if (data.length > 0) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Message size is: " + data.length);
boolean sent = sendBlock(data);
if (!sent) {
_log.error("Error sending message to peer. Killing socket runner");
return false;
}
}
}
return true;
}
public void run() {
byte[] buffer = new byte[MAX_PACKET_SIZE];
ByteCollector bc = new ByteCollector();
boolean sent = true;
boolean keepHandling = true;
int packetsHandled = 0;
try {
int len, bcsize;
// try {
while (true) {
len = in.read(buffer);
bcsize = bc.getCurrentSize();
if (len != -1) {
bc.append(buffer, len);
} else if (bcsize == 0) {
break;
}
if ((bcsize < MAX_PACKET_SIZE) && (in.available() == 0)) {
_log.debug("Runner Point d: " + this.hashCode());
try {
Thread.sleep(PACKET_DELAY);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if ((bcsize >= MAX_PACKET_SIZE) || (in.available() == 0)) {
byte[] data = bc.startToByteArray(MAX_PACKET_SIZE);
if (data.length > 0) {
_log.debug("Message size is: " + data.length);
sent = sendBlock(data);
if (!sent) {
_log.error("Error sending message to peer. Killing socket runner");
break;
}
}
}
// try {
while (keepHandling) {
keepHandling = handleNextPacket(bc, buffer);
packetsHandled++;
}
if ((bc.getCurrentSize() > 0) && sent) {
_log.error("A SCARY MONSTER HAS EATEN SOME DATA! " + "(input stream: " + in.hashCode() + "; "
if ((bc.getCurrentSize() > 0) && (packetsHandled > 1)) {
_log.error("A SCARY MONSTER HAS EATEN SOME DATA! " + "(input stream: "
+ in.hashCode() + "; "
+ "queue size: " + bc.getCurrentSize() + ")");
}
synchronized (flagLock) {
closed2 = true;
}
// } catch (IOException ex) {
// if (_log.shouldLog(Log.INFO))
// _log.info("Error reading and writing", ex);
// }
boolean sc;
synchronized (flagLock) {
sc = sendClose;
} // FIXME: Race here?
if (sc) {
_log.info("Sending close packet: " + outgoing);
byte[] packet = I2PSocketManager.makePacket((byte) (getMask(0x02)), remoteID, new byte[0]);
synchronized (manager.getSession()) {
sent = manager.getSession().sendMessage(remote, packet);
}
if (_log.shouldLog(Log.INFO))
_log.info("Sending close packet: " + outgoing);
byte[] packet = I2PSocketManager.makePacket(getMask(0x02), remoteID, new byte[0]);
boolean sent = manager.getSession().sendMessage(remote, packet);
if (!sent) {
_log.error("Error sending close packet to peer");
}
}
manager.removeSocket(I2PSocketImpl.this);
} catch (InterruptedIOException ex) {
_log.error("BUG! read() operations should not timeout!", ex);
} catch (IOException ex) {
// WHOEVER removes this event on inconsistent
// state before fixing the inconsistent state (a
@ -316,7 +448,8 @@ class I2PSocketImpl implements I2PSocket {
}
private boolean sendBlock(byte data[]) throws I2PSessionException {
_log.debug("TIMING: Block to send for " + I2PSocketImpl.this.hashCode());
if (_log.shouldLog(Log.DEBUG))
_log.debug("TIMING: Block to send for " + I2PSocketImpl.this.hashCode());
if (remoteID == null) {
_log.error("NULL REMOTEID");
return false;
@ -326,9 +459,7 @@ class I2PSocketImpl implements I2PSocket {
synchronized (flagLock) {
if (closed2) return false;
}
synchronized (manager.getSession()) {
sent = manager.getSession().sendMessage(remote, packet);
}
sent = manager.getSession().sendMessage(remote, packet);
return sent;
}
}

View File

@ -22,6 +22,7 @@ import net.i2p.client.I2PSessionException;
import net.i2p.client.I2PSessionListener;
import net.i2p.data.Base64;
import net.i2p.data.Destination;
import net.i2p.data.DataFormatException;
import net.i2p.util.Log;
/**
@ -40,11 +41,27 @@ public class I2PSocketManager implements I2PSessionListener {
private HashMap _outSockets;
private HashMap _inSockets;
private I2PSocketOptions _defaultOptions;
private long _acceptTimeout;
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() {
_session = null;
_inSockets = new HashMap(16);
_outSockets = new HashMap(16);
_acceptTimeout = ACCEPT_TIMEOUT_DEFAULT;
}
public I2PSession getSession() {
@ -55,15 +72,25 @@ public class I2PSocketManager implements I2PSessionListener {
_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.error("Disconnected from the session");
_log.info("Disconnected from the session");
destroySocketManager();
}
public void errorOccurred(I2PSession session, String message, Throwable error) {
_log.error("Error occurred: [" + message + "]", error);
}
public void messageAvailable(I2PSession session, int msgId, long size) {
try {
I2PSocketImpl s;
@ -77,157 +104,276 @@ public class I2PSocketManager implements I2PSessionListener {
return;
}
int type = msg[0] & 0xff;
String id = new String(new byte[] { msg[1], msg[2], msg[3]}, "ISO-8859-1");
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);
_log.debug("Message read: type = [" + Integer.toHexString(type) + "] id = [" + getReadableForm(id)
+ "] payload length: " + payload.length + "]");
synchronized (lock) {
switch (type) {
case 0x51:
// ACK outgoing
s = (I2PSocketImpl) _outSockets.get(id);
if (s == null) {
_log.warn("No socket responsible for ACK packet");
return;
}
if (payload.length == 3 && s.getRemoteID(false) == null) {
String newID = new String(payload, "ISO-8859-1");
s.setRemoteID(newID);
return;
} else {
if (payload.length != 3)
_log.warn("Ack packet had " + payload.length + " bytes");
else
_log.warn("Remote ID already exists? " + s.getRemoteID());
return;
}
case 0x52:
// disconnect outgoing
_log.debug("*Disconnect outgoing!");
try {
s = (I2PSocketImpl) _outSockets.get(id);
if (s != null) {
if (payload.length > 0) {
_log.debug("Disconnect packet had "
+ payload.length + " bytes");
}
if (s.getRemoteID(false) == null) {
s.setRemoteID(null); // Just to wake up socket
return;
}
s.internalClose();
_outSockets.remove(id);
}
return;
} catch (Exception t) {
_log.error("Ignoring error on disconnect", t);
}
case 0x50:
// packet send outgoing
_log.debug("*Packet send outgoing [" + payload.length + "]");
s = (I2PSocketImpl) _outSockets.get(id);
if (s != null) {
s.queueData(payload);
return;
} else {
_log.error("Null socket with data available");
throw new IllegalStateException("Null socket with data available");
}
case 0xA1:
// SYN incoming
_log.debug("*Syn!");
String newLocalID = makeID(_inSockets);
Destination d = new Destination();
d.readBytes(new ByteArrayInputStream(payload));
if (_serverSocket == null) {
// The app did not instantiate an I2PServerSocket
byte[] packet = makePacket((byte) 0x52, id, newLocalID.getBytes("ISO-8859-1"));
boolean replySentOk = false;
synchronized (_session) {
replySentOk = _session.sendMessage(d, packet);
}
if (!replySentOk) {
_log.error("Error sending close to " + d.calculateHash().toBase64()
+ " in response to a new con message", new Exception("Failed creation"));
}
return;
}
s = new I2PSocketImpl(d, this, false, newLocalID);
s.setRemoteID(id);
if (_serverSocket.getNewSocket(s)) {
_inSockets.put(newLocalID, s);
byte[] packet = makePacket((byte) 0x51, id, newLocalID.getBytes("ISO-8859-1"));
boolean replySentOk = false;
synchronized (_session) {
replySentOk = _session.sendMessage(d, packet);
}
if (!replySentOk) {
_log.error("Error sending reply to " + d.calculateHash().toBase64()
+ " in response to a new con message", new Exception("Failed creation"));
s.internalClose();
}
} else {
byte[] packet = (" " + id).getBytes("ISO-8859-1");
packet[0] = 0x52;
boolean nackSent = session.sendMessage(d, packet);
if (!nackSent) {
_log.error("Error sending NACK for session creation");
}
s.internalClose();
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Message read: type = [" + Integer.toHexString(type)
+ "] id = [" + getReadableForm(id)
+ "] payload length: [" + payload.length + "]");
switch (type) {
case ACK:
ackAvailable(id, payload);
return;
case 0xA2:
// disconnect incoming
_log.debug("*Disconnect incoming!");
try {
s = (I2PSocketImpl) _inSockets.get(id);
if (payload.length == 0 && s != null) {
s.internalClose();
_inSockets.remove(id);
return;
} else {
if (payload.length > 0) _log.warn("Disconnect packet had " + payload.length + " bytes");
return;
}
} catch (Exception t) {
_log.error("Ignoring error on disconnect", t);
return;
}
case 0xA0:
// packet send incoming
_log.debug("*Packet send incoming [" + payload.length + "]");
s = (I2PSocketImpl) _inSockets.get(id);
if (s != null) {
s.queueData(payload);
return;
} else {
_log.error("Null socket with data available");
throw new IllegalStateException("Null socket with data available");
}
case 0xFF:
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;
}
_log.error("\n\n=============== Unknown packet! " + "============" + "\nType: " + (int) type
+ "\nID: " + getReadableForm(id) + "\nBase64'ed Data: " + Base64.encode(payload)
+ "\n\n\n");
if (id != null) {
_inSockets.remove(id);
_outSockets.remove(id);
}
default:
handleUnknown(type, id, payload);
return;
}
} catch (I2PException ise) {
_log.error("Error processing", ise);
} catch (IOException ioe) {
_log.error("Error processing", ioe);
} catch (IllegalStateException ise) {
_log.debug("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[]) {
I2PSocketImpl s = null;
synchronized (lock) {
s = (I2PSocketImpl) _outSockets.get(id);
}
if (s == null) {
_log.warn("No socket responsible for ACK packet");
return;
}
String remoteId = null;
remoteId = s.getRemoteID(false);
if ( (payload.length == 3) && (remoteId == null) ) {
String newID = toString(payload);
s.setRemoteID(newID);
return;
} else {
// (payload.length != 3 || getRemoteId != null)
if (_log.shouldLog(Log.WARN)) {
if (payload.length != 3)
_log.warn("Ack packet had " + payload.length + " bytes");
else
_log.warn("Remote ID already exists? " + remoteId);
}
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("*Disconnect outgoing!");
try {
if (s != null) {
if (payload.length > 0) {
_log.debug("Disconnect packet had "
+ payload.length + " bytes");
}
if (s.getRemoteID(false) == null) {
s.setRemoteID(null); // Just to wake up socket
return;
}
s.internalClose();
synchronized (lock) {
_outSockets.remove(id);
}
}
return;
} catch (Exception t) {
_log.error("Ignoring error on disconnect", 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("*Packet send outgoing [" + payload.length + "]");
if (s != null) {
s.queueData(payload);
return;
} else {
_log.error("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 {
_log.debug("*Syn!");
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);
}
}
if (!acceptConnections) {
// The app did not instantiate an I2PServerSocket
byte[] packet = makePacket((byte) CLOSE_OUT, id, toBytes(newLocalID));
boolean replySentOk = false;
synchronized (_session) {
replySentOk = _session.sendMessage(d, packet);
}
if (!replySentOk) {
_log.error("Error sending close to " + d.calculateHash().toBase64()
+ " in response to a new con message",
new Exception("Failed creation"));
}
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("Error sending reply to " + d.calculateHash().toBase64()
+ " in response to a new con message",
new Exception("Failed creation"));
s.internalClose();
}
} else {
// timed out or serverSocket closed
byte[] packet = toBytes(" " + id);
packet[0] = CLOSE_OUT;
boolean nackSent = session.sendMessage(d, packet);
if (!nackSent) {
_log.warn("Error sending NACK for session creation");
}
s.internalClose();
}
return;
}
/**
* We've received a disconnect for a socket we didn't initiate, so kill
* the socket.
*
*/
private void disconnectIncoming(String id, byte payload[]) {
_log.debug("*Disconnect incoming!");
I2PSocketImpl s = null;
synchronized (lock) {
s = (I2PSocketImpl) _inSockets.get(id);
if (payload.length == 0 && s != null) {
_inSockets.remove(id);
}
}
try {
if (payload.length == 0 && s != null) {
s.internalClose();
return;
} else {
if ( (payload.length > 0) && (_log.shouldLog(Log.ERROR)) )
_log.error("Disconnect packet had " + payload.length + " bytes");
if (s != null)
s.internalClose();
return;
}
} catch (Exception t) {
_log.error("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[]) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("*Packet send incoming [" + payload.length + "]");
I2PSocketImpl s = null;
synchronized (lock) {
s = (I2PSocketImpl) _inSockets.get(id);
}
if (s != null) {
s.queueData(payload);
return;
} else {
_log.info("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("\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("Abuse reported [" + severity + "]");
}
@ -258,25 +404,24 @@ public class I2PSocketManager implements I2PSessionListener {
* @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 {
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(s.getLocalID(), s);
_outSockets.put(localID, s);
}
try {
ByteArrayOutputStream pubkey = new ByteArrayOutputStream();
_session.getMyDestination().writeBytes(pubkey);
String remoteID;
byte[] packet = makePacket((byte) 0xA1, localID, pubkey.toByteArray());
byte[] packet = makePacket((byte) SYN, localID, pubkey.toByteArray());
boolean sent = false;
synchronized (_session) {
sent = _session.sendMessage(peer, packet);
}
sent = _session.sendMessage(peer, packet);
if (!sent) {
_log.info("Unable to send & receive ack for SYN packet");
synchronized (lock) {
@ -285,32 +430,51 @@ public class I2PSocketManager implements I2PSessionListener {
throw new I2PException("Error sending through I2P network");
}
remoteID = s.getRemoteID(true, options.getConnectTimeout());
if (remoteID == null) { throw new ConnectException("Connection refused by peer"); }
if ("".equals(remoteID)) { throw new NoRouteToHostException("Unable to reach peer"); }
_log.debug("TIMING: s given out for remoteID " + getReadableForm(remoteID));
if (remoteID == null)
throw new ConnectException("Connection refused by peer");
if ("".equals(remoteID))
throw new NoRouteToHostException("Unable to reach peer");
if (_log.shouldLog(Log.DEBUG))
_log.debug("TIMING: s given out for remoteID "
+ getReadableForm(remoteID));
return s;
} catch (InterruptedIOException ioe) {
_log.error("Timeout waiting for ack from syn for id " + getReadableForm(lcID), ioe);
if (_log.shouldLog(Log.ERROR))
_log.error("Timeout waiting for ack from syn for id "
+ getReadableForm(lcID), ioe);
synchronized (lock) {
_outSockets.remove(s.getLocalID());
}
s.internalClose();
throw new InterruptedIOException("Timeout waiting for ack");
} catch (ConnectException ex) {
s.internalClose();
throw ex;
} catch (NoRouteToHostException ex) {
s.internalClose();
throw ex;
} catch (IOException ex) {
_log.error("Error sending syn on id " + getReadableForm(lcID), ex);
if (_log.shouldLog(Log.ERROR))
_log.error("Error sending syn on id " + getReadableForm(lcID), ex);
synchronized (lock) {
_outSockets.remove(s.getLocalID());
}
s.internalClose();
throw new I2PException("Unhandled IOException occurred");
} catch (I2PException ex) {
_log.info("Error sending syn on id " + getReadableForm(lcID), ex);
if (_log.shouldLog(Log.INFO))
_log.info("Error sending syn on id " + getReadableForm(lcID), ex);
synchronized (lock) {
_outSockets.remove(s.getLocalID());
}
s.internalClose();
throw ex;
} catch (Exception e) {
s.internalClose();
_log.error("Unhandled error connecting", e);
throw new ConnectException("Unhandled error connecting: " + e.getMessage());
}
}
@ -324,7 +488,8 @@ public class I2PSocketManager implements I2PSessionListener {
* @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 {
public I2PSocket connect(Destination peer) throws I2PException, ConnectException,
NoRouteToHostException, InterruptedIOException {
return connect(peer, null);
}
@ -334,14 +499,9 @@ public class I2PSocketManager implements I2PSessionListener {
*
*/
public void destroySocketManager() {
try {
if (_serverSocket != null) {
_serverSocket.close();
_serverSocket = null;
}
} catch (I2PException ex) {
_log.error("Error closing I2PServerSocket", ex);
if (_serverSocket != null) {
_serverSocket.close();
_serverSocket = null;
}
synchronized (lock) {
@ -353,8 +513,9 @@ public class I2PSocketManager implements I2PSessionListener {
while (iter.hasNext()) {
id = (String)iter.next();
sock = (I2PSocketImpl)_inSockets.get(id);
_log.debug("Closing inSocket \""
+ getReadableForm(sock.getLocalID()) + "\"");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Closing inSocket \""
+ getReadableForm(sock.getLocalID()) + "\"");
sock.internalClose();
}
@ -362,8 +523,9 @@ public class I2PSocketManager implements I2PSessionListener {
while (iter.hasNext()) {
id = (String)iter.next();
sock = (I2PSocketImpl)_outSockets.get(id);
_log.debug("Closing outSocket \""
+ getReadableForm(sock.getLocalID()) + "\"");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Closing outSocket \""
+ getReadableForm(sock.getLocalID()) + "\"");
sock.internalClose();
}
}
@ -406,7 +568,7 @@ public class I2PSocketManager implements I2PSessionListener {
*/
public boolean ping(Destination peer, long timeoutMs) {
try {
return _session.sendMessage(peer, new byte[] { (byte) 0xFF});
return _session.sendMessage(peer, new byte[] { (byte) CHAFF});
} catch (I2PException ex) {
_log.error("I2PException:", ex);
return false;
@ -415,8 +577,8 @@ public class I2PSocketManager implements I2PSessionListener {
public void removeSocket(I2PSocketImpl sock) {
synchronized (lock) {
_log.debug("Removing socket \""
+ getReadableForm(sock.getLocalID()) + "\"");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Removing socket \"" + getReadableForm(sock.getLocalID()) + "\"");
_inSockets.remove(sock.getLocalID());
_outSockets.remove(sock.getLocalID());
lock.notify();
@ -424,14 +586,9 @@ public class I2PSocketManager implements I2PSessionListener {
}
public static String getReadableForm(String id) {
try {
if (id == null) return "(null)";
if (id.length() != 3) return "Bogus";
return Base64.encode(id.getBytes("ISO-8859-1"));
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
return null;
}
if (id == null) return "(null)";
if (id.length() != 3) return "Bogus";
return Base64.encode(toBytes(id));
}
/**
@ -439,22 +596,17 @@ public class I2PSocketManager implements I2PSessionListener {
*
* @param uniqueIn map of already known local IDs so we don't collide. WARNING - NOT THREADSAFE!
*/
public static String makeID(HashMap uniqueIn) {
private static String makeID(HashMap uniqueIn) {
String newID;
try {
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 = new String(nid, "ISO-8859-1");
} while (uniqueIn.get(newID) != null);
return newID;
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
return null;
}
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;
}
/**
@ -462,17 +614,28 @@ public class I2PSocketManager implements I2PSessionListener {
* 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 {
byte[] packet = new byte[payload.length + 4];
packet[0] = type;
byte[] temp = id.getBytes("ISO-8859-1");
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;
} catch (UnsupportedEncodingException ex) {
if (_log.shouldLog(Log.ERROR)) _log.error("Error building the packet", ex);
return new byte[0];
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

@ -2,7 +2,6 @@ package net.i2p.client.streaming;
/**
* Define the configuration for streaming and verifying data on the socket.
* No options available...
*
*/
public class I2PSocketOptions {
@ -21,7 +20,11 @@ public class I2PSocketOptions {
return _connectTimeout;
}
/**
* Define how long we will wait for the ACK from a SYN, in milliseconds.
*
*/
public void setConnectTimeout(long ms) {
_connectTimeout = ms;
}
}
}

View File

@ -33,57 +33,61 @@ class PeerSummaryReader {
PeerSummary summary = null;
String curDescription = null;
List curArgs = null;
while ((line = reader.readLine()) != null) {
if (line.startsWith("peer\t")) {
String name = line.substring("peer\t".length()).trim();
summary = monitor.getSummary(name);
if (summary == null)
summary = new PeerSummary(name);
} else if (line.startsWith("## ")) {
curDescription = line.substring("## ".length()).trim();
curArgs = new ArrayList(4);
} else if (line.startsWith("# param ")) {
int start = line.indexOf(':');
String arg = line.substring(start+1).trim();
curArgs.add(arg);
} else {
StringTokenizer tok = new StringTokenizer(line);
String name = tok.nextToken();
try {
long when = getTime(tok.nextToken());
boolean isDouble = false;
List argVals = new ArrayList(curArgs.size());
while (tok.hasMoreTokens()) {
String val = (String)tok.nextToken();
if (val.indexOf('.') >= 0) {
argVals.add(new Double(val));
isDouble = true;
} else {
argVals.add(new Long(val));
try {
while ((line = reader.readLine()) != null) {
if (line.startsWith("peer\t")) {
String name = line.substring("peer\t".length()).trim();
summary = monitor.getSummary(name);
if (summary == null)
summary = new PeerSummary(name);
} else if (line.startsWith("## ")) {
curDescription = line.substring("## ".length()).trim();
curArgs = new ArrayList(4);
} else if (line.startsWith("# param ")) {
int start = line.indexOf(':');
String arg = line.substring(start+1).trim();
curArgs.add(arg);
} else {
StringTokenizer tok = new StringTokenizer(line);
String name = tok.nextToken();
try {
long when = getTime(tok.nextToken());
boolean isDouble = false;
List argVals = new ArrayList(curArgs.size());
while (tok.hasMoreTokens()) {
String val = (String)tok.nextToken();
if (val.indexOf('.') >= 0) {
argVals.add(new Double(val));
isDouble = true;
} else {
argVals.add(new Long(val));
}
}
String valDescriptions[] = new String[curArgs.size()];
for (int i = 0; i < curArgs.size(); i++)
valDescriptions[i] = (String)curArgs.get(i);
if (isDouble) {
double values[] = new double[argVals.size()];
for (int i = 0; i < argVals.size(); i++)
values[i] = ((Double)argVals.get(i)).doubleValue();
summary.addData(name, curDescription, valDescriptions, when, values);
} else {
long values[] = new long[argVals.size()];
for (int i = 0; i < argVals.size(); i++)
values[i] = ((Long)argVals.get(i)).longValue();
summary.addData(name, curDescription, valDescriptions, when, values);
}
} catch (ParseException pe) {
_log.error("Error parsing the data line [" + line + "]", pe);
} catch (NumberFormatException nfe) {
_log.error("Error parsing the data line [" + line + "]", nfe);
}
String valDescriptions[] = new String[curArgs.size()];
for (int i = 0; i < curArgs.size(); i++)
valDescriptions[i] = (String)curArgs.get(i);
if (isDouble) {
double values[] = new double[argVals.size()];
for (int i = 0; i < argVals.size(); i++)
values[i] = ((Double)argVals.get(i)).doubleValue();
summary.addData(name, curDescription, valDescriptions, when, values);
} else {
long values[] = new long[argVals.size()];
for (int i = 0; i < argVals.size(); i++)
values[i] = ((Long)argVals.get(i)).longValue();
summary.addData(name, curDescription, valDescriptions, when, values);
}
} catch (ParseException pe) {
_log.error("Error parsing the data line [" + line + "]", pe);
} catch (NumberFormatException nfe) {
_log.error("Error parsing the data line [" + line + "]", nfe);
}
}
}
} catch (Exception e) {
_log.error("Error handling the data", e);
throw new IOException("Error parsing the data");
}
if (summary == null)
return;
summary.coallesceData(monitor.getSummaryDurationHours() * 60*60*1000);

View File

@ -0,0 +1,58 @@
using System.Reflection;
using System.Runtime.CompilerServices;
//
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
//
[assembly: AssemblyTitle("")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
//
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.*")]
//
// In order to sign your assembly you must specify a key to use. Refer to the
// Microsoft .NET Framework documentation for more information on assembly signing.
//
// Use the attributes below to control which key is used for signing.
//
// Notes:
// (*) If no key is specified, the assembly is not signed.
// (*) KeyName refers to a key that has been installed in the Crypto Service
// Provider (CSP) on your machine. KeyFile refers to a file which contains
// a key.
// (*) If the KeyFile and the KeyName values are both specified, the
// following processing occurs:
// (1) If the KeyName can be found in the CSP, that key is used.
// (2) If the KeyName does not exist and the KeyFile does exist, the key
// in the KeyFile is installed into the CSP and used.
// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility.
// When specifying the KeyFile, the location of the KeyFile should be
// relative to the project output directory which is
// %Project Directory%\obj\<configuration>. For example, if your KeyFile is
// located in the project directory, you would specify the AssemblyKeyFile
// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")]
// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework
// documentation for more information on this.
//
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("")]
[assembly: AssemblyKeyName("")]

View File

@ -0,0 +1,50 @@
using System;
using System.Net;
using System.Threading;
using System.Text;
using System.Collections;
namespace SAM.NET
{
class SAMTester
{
[STAThread]
static void Main(string[] args)
{
new SAMTester();
}
public SAMTester ()
{
SAMConnection connection1 = new SAMConnection(IPAddress.Parse("127.0.0.1"),7656);
SAMSession session1 = new SAMSession(connection1,SAM.NET.SamSocketType.Stream,"alice");
SAMConnection connection2 = new SAMConnection(IPAddress.Parse("127.0.0.1"),7656);
SAMSession session2 = new SAMSession(connection2,SAM.NET.SamSocketType.Stream,"bob");
SAMStream stream1 = new SAMStream(connection1,session1,233);
stream1.Connect(session2.getKey());
//Wait till we are connected to destination
while (!stream1.isConnected)
Thread.Sleep(1000);
//Send some bytes
stream1.Write(Encoding.ASCII.GetBytes(DateTime.Now.ToLongTimeString() + "Hi!!!!!!"));
//Wait till a stream magically appears on the other side
while (session2.getStreams().Count == 0) Thread.Sleep(1000);
Thread.Sleep(1000);
foreach (SAMStream stream in session2.getStreams().Values)
{
Console.WriteLine("Text received on " + stream.getID() + " at " + DateTime.Now.ToLongTimeString());
Console.WriteLine(Encoding.ASCII.GetString(stream.ReadToEnd()));
stream.Close();
}
stream1.Close();
connection1.Close();
connection2.Close();
}
}
}

View File

@ -0,0 +1,58 @@
using System.Reflection;
using System.Runtime.CompilerServices;
//
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
//
[assembly: AssemblyTitle("")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
//
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.*")]
//
// In order to sign your assembly you must specify a key to use. Refer to the
// Microsoft .NET Framework documentation for more information on assembly signing.
//
// Use the attributes below to control which key is used for signing.
//
// Notes:
// (*) If no key is specified, the assembly is not signed.
// (*) KeyName refers to a key that has been installed in the Crypto Service
// Provider (CSP) on your machine. KeyFile refers to a file which contains
// a key.
// (*) If the KeyFile and the KeyName values are both specified, the
// following processing occurs:
// (1) If the KeyName can be found in the CSP, that key is used.
// (2) If the KeyName does not exist and the KeyFile does exist, the key
// in the KeyFile is installed into the CSP and used.
// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility.
// When specifying the KeyFile, the location of the KeyFile should be
// relative to the project output directory which is
// %Project Directory%\obj\<configuration>. For example, if your KeyFile is
// located in the project directory, you would specify the AssemblyKeyFile
// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")]
// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework
// documentation for more information on this.
//
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("")]
[assembly: AssemblyKeyName("")]

View File

@ -0,0 +1,271 @@
using System;
using System.Net.Sockets;
using System.Text;
using System.Net;
using System.IO;
using System.Collections;
using System.Threading;
namespace SAM.NET
{
public enum SamSocketType
{
Stream,
Datagram,
Raw
}
public class SAMConnection
{
private const string propertyMinVersion = "1.0";
private const string propertyMaxVersion = "1.0";
private Socket _sock;
private NetworkStream _sockStream;
private StreamReader _sockStreamIn;
private StreamWriter _sockStreamOut;
public SAMConnection(IPAddress routerIP, int port)
{
_sock = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
IPEndPoint rEP = new IPEndPoint(routerIP,port);
_sock.Connect(rEP);
_sockStream = new NetworkStream(_sock);
_sockStreamIn = new StreamReader(_sockStream);
_sockStreamOut = new StreamWriter(_sockStream);
try
{
sendVersion(propertyMinVersion,propertyMinVersion);
}
catch (Exception e)
{
_sock.Close();
throw (new Exception("No SAM for you :("));
}
}
private void sendVersion(string min, string max)
{
_sockStreamOut.WriteLine("HELLO VERSION MIN=" + propertyMinVersion + " MAX=" + propertyMaxVersion);
_sockStreamOut.Flush();
Hashtable response = SAMUtil.parseKeyValues(_sockStreamIn.ReadLine(),2);
if (response["RESULT"].ToString() != "OK") throw (new Exception("Version mismatch"));
}
public StreamWriter getOutputStream()
{
return _sockStreamOut;
}
public StreamReader getInputStream()
{
return _sockStreamIn;
}
public NetworkStream getStream()
{
return _sockStream;
}
public void Close()
{
_sock.Close();
}
}
/*
* Creating a SAMSession object will automatically:
* 1) create a sesion on SAM
* 1) query for the base64key
* 2) start a listening thread to catch all stream commands
*/
public class SAMSession
{
private Hashtable _streams;
private string _sessionKey;
public SAMSession (SAMConnection connection, SamSocketType type, string destination)
{
_streams = new Hashtable();
StreamWriter writer = connection.getOutputStream();
StreamReader reader = connection.getInputStream();
writer.WriteLine("SESSION CREATE STYLE=STREAM DESTINATION=" + destination);
writer.Flush();
Hashtable response = SAMUtil.parseKeyValues(reader.ReadLine(),2);
if (response["RESULT"].ToString() != "OK")
{
throw (new Exception(response["MESSAGE"].ToString()));
}
else
{
writer.WriteLine("NAMING LOOKUP NAME=ME");
writer.Flush();
response = SAMUtil.parseKeyValues(reader.ReadLine(),2);
_sessionKey = response["VALUE"].ToString();
SAMSessionListener listener = new SAMSessionListener(connection,this,_streams);
new Thread(new ThreadStart(listener.startListening)).Start();
}
}
public void addStream(SAMStream stream)
{
_streams.Add(stream.getID(),stream);
}
public string getKey()
{
return _sessionKey;
}
public Hashtable getStreams()
{
return _streams;
}
}
public class SAMSessionListener
{
private Hashtable _streams;
private SAMConnection _connection;
private SAMSession _session;
private bool stayAlive = true;
public SAMSessionListener(SAMConnection connection,SAMSession session, Hashtable streams)
{
_streams = streams;
_connection = connection;
_session = session;
}
public void startListening()
{
StreamReader reader = _connection.getInputStream();
while (stayAlive)
{
string response = reader.ReadLine();
if (response.StartsWith("STREAM STATUS"))
{
Hashtable values = SAMUtil.parseKeyValues(response,2);
SAMStream theStream = (SAMStream)_streams[int.Parse(values["ID"].ToString())];
if (theStream != null) theStream.ReceivedStatus(values);
}
if (response.StartsWith("STREAM CONNECTED"))
{
Hashtable values = SAMUtil.parseKeyValues(response,2);
SAMStream theStream = (SAMStream)_streams[int.Parse(values["ID"].ToString())];
if (theStream != null) theStream.isConnected = true;
}
if (response.StartsWith("STREAM RECEIVED"))
{
Hashtable values = SAMUtil.parseKeyValues(response,2);
int streamID = int.Parse(values["ID"].ToString());
SAMStream theStream = (SAMStream)_streams[streamID];
if (theStream == null) new SAMStream(_connection,_session,streamID);
theStream = (SAMStream)_streams[streamID];
theStream.ReceivedData(int.Parse(values["SIZE"].ToString()));
}
if (response.StartsWith("STREAM CLOSE"))
{
Hashtable values = SAMUtil.parseKeyValues(response,2);
SAMStream theStream = (SAMStream)_streams[int.Parse(values["ID"].ToString())];
if (theStream != null) theStream.isConnected = false;
}
}
}
}
public class SAMStream
{
private int _ID;
private byte[] _data;
private int _position=0;
private int _size=0;
private SAMSession _session;
private SAMConnection _connection;
public bool isConnected=false;
public SAMStream (SAMConnection connection,SAMSession session, int ID)
{
_data = new byte[100000]; //FIXME: change to non-static structure for storing stream data
_ID = ID;
_connection = connection;
_session = session;
_session.addStream(this);
}
public void Connect(string destination)
{
StreamWriter writer = _connection.getOutputStream();
writer.WriteLine("STREAM CONNECT ID=" + _ID.ToString() + " DESTINATION=" + destination);
writer.Flush();
}
public void ReceivedData(int size) //FIXME: WTF is going on when reading the payload here? All zeros and way too many of them.
{
NetworkStream stream = _connection.getStream();
int bytesRead = stream.Read(_data,_size,size);
_size = _size + bytes;
}
public void ReceivedStatus(Hashtable response)
{
if (response["RESULT"].ToString() != "OK")
{
throw (new Exception(response["RESULT"].ToString()));
}
else
{
isConnected = true;
}
}
public int getID() {return _ID;}
public bool DataAvailable()
{
return _position != _size;
}
public void Write(byte[] buf)
{
NetworkStream stream = _connection.getStream();
int sent = 0;
while (sent < buf.Length)
{
int toSend = Math.Min(buf.Length - sent,32768);
string header = "STREAM SEND ID=" + _ID.ToString() + " SIZE=" + toSend.ToString() + "\n";
byte[] headerbytes = Encoding.ASCII.GetBytes(header);
stream.Write(headerbytes,0,headerbytes.Length);
stream.Write(buf,sent,toSend);
sent = sent + toSend;
}
}
public byte[] ReadToEnd()
{
byte[] ret = new byte[_size - _position];
Array.Copy(_data,_position,ret,0,_size - _position);
_position = _size;
return ret;
}
public void Close()
{
StreamWriter writer = _connection.getOutputStream();
writer.WriteLine("STREAM CLOSE " + _ID.ToString());
writer.Flush();
}
}
public class SAMUtil
{
public static Hashtable parseKeyValues(string str, int startingWord)
{
Hashtable hash = new Hashtable();
string strTruncated = string.Join(" ",str.Split(' '),startingWord,str.Split(' ').Length - startingWord);
string[] sets = strTruncated.Split('=',' ');
for (int i=0; i<sets.Length; i=i+2)
{
hash.Add(sets[i],sets[i+1]);
}
return hash;
}
}
}

View File

@ -9,13 +9,28 @@ package net.i2p.sam;
*/
import java.io.IOException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.FileNotFoundException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Properties;
import java.util.Map;
import java.util.HashMap;
import java.util.Collections;
import java.util.Iterator;
import net.i2p.data.Destination;
import net.i2p.data.DataFormatException;
import net.i2p.data.PrivateKey;
import net.i2p.data.SigningPrivateKey;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import net.i2p.I2PAppContext;
/**
* SAM bridge implementation.
@ -23,15 +38,25 @@ import net.i2p.util.Log;
* @author human
*/
public class SAMBridge implements Runnable {
private final static Log _log = new Log(SAMBridge.class);
private ServerSocket serverSocket;
private Properties i2cpProps;
/**
* filename in which the name to private key mapping should
* be stored (and loaded from)
*/
private String persistFilename;
/**
* 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 boolean acceptConnections = true;
private final static int SAM_LISTENPORT = 7656;
private static final int SAM_LISTENPORT = 7656;
public static final String DEFAULT_SAM_KEYFILE = "sam.keys";
private SAMBridge() {}
/**
@ -40,8 +65,11 @@ public class SAMBridge implements Runnable {
* @param listenHost hostname to listen for SAM connections on ("0.0.0.0" for all)
* @param listenPort port number to listen for SAM connections on
* @param i2cpProps set of I2CP properties for finding and communicating with the router
* @param persistFile location to store/load named keys to/from
*/
public SAMBridge(String listenHost, int listenPort, Properties i2cpProps) {
public SAMBridge(String listenHost, int listenPort, Properties i2cpProps, String persistFile) {
persistFilename = persistFile;
loadKeys();
try {
if ( (listenHost != null) && !("0.0.0.0".equals(listenHost)) ) {
serverSocket = new ServerSocket(listenPort, 0, InetAddress.getByName(listenHost));
@ -63,6 +91,97 @@ public class SAMBridge implements Runnable {
this.i2cpProps = i2cpProps;
}
/**
* Retrieve the destination associated with the given name
*
* @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;
}
}
/**
* Retrieve the I2P private keystream for the given name, formatted
* as a base64 string (Destination+PrivateKey+SessionPrivateKey, as I2CP
* stores it).
*
* @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;
}
/**
* Specify that the given keystream should be used for the given name
*
*/
public void addKeystream(String name, String stream) {
nameToPrivKeys.put(name, stream);
storeKeys();
}
/**
* 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);
}
} 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');
}
} catch (IOException ioe) {
_log.error("Error writing out the SAM keys to " + persistFilename, ioe);
} finally {
if (out != null) try { out.close(); } catch (IOException ioe) {}
}
}
/**
* Usage:
* <pre>SAMBridge [[listenHost ]listenPort[ name=val]*]</pre>
@ -72,15 +191,17 @@ public class SAMBridge implements Runnable {
* depth, etc.
*/
public static void main(String args[]) {
String keyfile = DEFAULT_SAM_KEYFILE;
int port = SAM_LISTENPORT;
String host = "0.0.0.0";
Properties opts = null;
if (args.length > 0) {
int portIndex = 0;
keyfile = args[0];
int portIndex = 1;
try {
port = Integer.parseInt(args[portIndex]);
} catch (NumberFormatException nfe) {
host = args[0];
host = args[1];
portIndex++;
try {
port = Integer.parseInt(args[portIndex]);
@ -91,7 +212,7 @@ public class SAMBridge implements Runnable {
}
opts = parseOptions(args, portIndex+1);
}
SAMBridge bridge = new SAMBridge(host, port, opts);
SAMBridge bridge = new SAMBridge(host, port, opts, keyfile);
I2PThread t = new I2PThread(bridge, "SAMListener");
t.start();
}
@ -114,7 +235,8 @@ public class SAMBridge implements Runnable {
}
private static void usage() {
System.err.println("Usage: SAMBridge [listenHost listenPortNum[ name=val]*]");
System.err.println("Usage: SAMBridge [keyfile [listenHost] listenPortNum[ name=val]*]");
System.err.println(" keyfile: location to persist private keys (default sam.keys)");
System.err.println(" listenHost: interface to listen on (0.0.0.0 for all interfaces)");
System.err.println(" listenPort: port to listen for SAM connections on (default 7656)");
System.err.println(" name=val: options to pass when connecting via I2CP, such as ");
@ -140,10 +262,18 @@ public class SAMBridge implements Runnable {
} catch (IOException e) {}
continue;
}
handler.setBridge(this);
handler.startHandling();
} catch (SAMException e) {
if (_log.shouldLog(Log.ERROR))
_log.error("SAM error: " + e.getMessage(), e);
try {
String reply = "HELLO REPLY RESULT=I2P_ERROR MESSAGE=\"" + e.getMessage() + "\"\n";
s.getOutputStream().write(reply.getBytes("ISO-8859-1"));
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR))
_log.error("SAM Error sending error reply", ioe);
}
s.close();
}
}

View File

@ -30,6 +30,7 @@ import net.i2p.util.Log;
public class SAMDatagramSession extends SAMMessageSession {
private final static Log _log = new Log(SAMDatagramSession.class);
public static int DGRAM_SIZE_MAX = 31*1024;
private SAMDatagramReceiver recv = null;
@ -43,7 +44,8 @@ public class SAMDatagramSession extends SAMMessageSession {
* @param recv Object that will receive incoming data
*/
public SAMDatagramSession(String dest, Properties props,
SAMDatagramReceiver recv) throws IOException, DataFormatException, I2PSessionException {
SAMDatagramReceiver recv) throws IOException,
DataFormatException, I2PSessionException {
super(dest, props);
this.recv = recv;
@ -58,7 +60,8 @@ public class SAMDatagramSession extends SAMMessageSession {
* @param recv Object that will receive incoming data
*/
public SAMDatagramSession(InputStream destStream, Properties props,
SAMDatagramReceiver recv) throws IOException, DataFormatException, I2PSessionException {
SAMDatagramReceiver recv) throws IOException,
DataFormatException, I2PSessionException {
super(destStream, props);
this.recv = recv;
@ -73,6 +76,9 @@ public class SAMDatagramSession extends SAMMessageSession {
* @return True if the data was sent, false otherwise
*/
public boolean sendBytes(String dest, byte[] data) throws DataFormatException {
if (data.length > DGRAM_SIZE_MAX)
throw new DataFormatException("Datagram size exceeded (" + data.length + ")");
byte[] dgram = dgramMaker.makeI2PDatagram(data);
return sendBytesThroughMessageSession(dest, dgram);

View File

@ -30,6 +30,7 @@ public abstract class SAMHandler implements Runnable {
private final static Log _log = new Log(SAMHandler.class);
protected I2PThread thread = null;
protected SAMBridge bridge = null;
private Object socketWLock = new Object(); // Guards writings on socket
private Socket socket = null;
@ -71,6 +72,8 @@ public abstract class SAMHandler implements Runnable {
thread.start();
}
public void setBridge(SAMBridge bridge) { this.bridge = bridge; }
/**
* Actually handle the SAM protocol.
*
@ -124,7 +127,9 @@ public abstract class SAMHandler implements Runnable {
*
*/
protected final void closeClientSocket() throws IOException {
socket.close();
if (socket != null)
socket.close();
socket = null;
}
/**

View File

@ -32,8 +32,8 @@ public class SAMHandlerFactory {
*
* @param s Socket attached to SAM client
* @param i2cpProps config options for our i2cp connection
*
* @return A SAM protocol handler
* @throws SAMException if the connection handshake (HELLO message) was malformed
* @return A SAM protocol handler, or null if the client closed before the handshake
*/
public static SAMHandler createSAMHandler(Socket s, Properties i2cpProps) throws SAMException {
BufferedReader br;
@ -66,8 +66,8 @@ public class SAMHandlerFactory {
{
String opcode;
if (!(opcode = tok.nextToken()).equals("VERSION")) {
throw new SAMException("Unrecognized HELLO message opcode: \""
+ opcode + "\"");
throw new SAMException("Unrecognized HELLO message opcode: '"
+ opcode + "'");
}
}
@ -88,22 +88,8 @@ public class SAMHandlerFactory {
}
String ver = chooseBestVersion(minVer, maxVer);
if (ver == null) {
// Let's answer negatively
try {
OutputStream out = s.getOutputStream();
out.write("HELLO REPLY RESULT=NOVERSION\n".getBytes("ISO-8859-1"));
return null;
} catch (UnsupportedEncodingException e) {
_log.error("Caught UnsupportedEncodingException ("
+ e.getMessage() + ")");
throw new SAMException("Character encoding error: "
+ e.getMessage());
} catch (IOException e) {
throw new SAMException("Error reading from socket: "
+ e.getMessage());
}
}
if (ver == null)
throw new SAMException("No version specified");
// Let's answer positively
try {
@ -135,8 +121,8 @@ public class SAMHandlerFactory {
throw new SAMException("BUG! (in handler instantiation)");
}
} catch (IOException e) {
_log.error("IOException caught during SAM handler instantiation");
return null;
_log.error("Error creating the v1 handler", e);
throw new SAMException("IOException caught during SAM handler instantiation");
}
return handler;
}

View File

@ -26,6 +26,7 @@ import net.i2p.util.Log;
public class SAMRawSession extends SAMMessageSession {
private final static Log _log = new Log(SAMRawSession.class);
public static final int RAW_SIZE_MAX = 32*1024;
private SAMRawReceiver recv = null;
/**
@ -64,6 +65,8 @@ public class SAMRawSession extends SAMMessageSession {
* @return True if the data was sent, false otherwise
*/
public boolean sendBytes(String dest, byte[] data) throws DataFormatException {
if (data.length > RAW_SIZE_MAX)
throw new DataFormatException("Data size limit exceeded (" + data.length + ")");
return sendBytesThroughMessageSession(dest, data);
}

View File

@ -26,6 +26,7 @@ import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.client.streaming.I2PSocketOptions;
import net.i2p.client.I2PClient;
import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
@ -100,11 +101,23 @@ public class SAMStreamSession {
allprops.putAll(System.getProperties());
allprops.putAll(props);
// FIXME: we should setup I2CP host and port, too
String i2cpHost = allprops.getProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
int i2cpPort = 7654;
String port = allprops.getProperty(I2PClient.PROP_TCP_PORT, "7654");
try {
i2cpPort = Integer.parseInt(port);
} catch (NumberFormatException nfe) {
throw new SAMException("Invalid I2CP port specified [" + port + "]");
}
// streams MUST be mode=guaranteed (though i think the socket manager
// enforces this anyway...
allprops.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
_log.debug("Creating I2PSocketManager...");
socketMgr = I2PSocketManagerFactory.createManager(destStream,
"127.0.0.1",
7654, allprops);
i2cpHost,
i2cpPort,
allprops);
if (socketMgr == null) {
throw new SAMException("Error creating I2PSocketManager");
}

View File

@ -23,6 +23,7 @@ import net.i2p.data.Base64;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.Log;
import net.i2p.I2PAppContext;
/**
* Miscellaneous utility methods used by SAM protocol handlers.
@ -84,7 +85,7 @@ public class SAMUtils {
* @return the Destination for the specified hostname, or null if not found
*/
public static Destination lookupHost(String name, OutputStream pubKey) {
NamingService ns = NamingService.getInstance();
NamingService ns = I2PAppContext.getGlobalContext().namingService();
Destination dest = ns.lookup(name);
if ((pubKey != null) && (dest != null)) {
@ -107,9 +108,10 @@ public class SAMUtils {
*
* @param tok A StringTokenizer pointing to the SAM parameters
*
* @return Properties with the parsed SAM params, or null if none is found
* @throws SAMException if the data was formatted incorrectly
* @return Properties with the parsed SAM params
*/
public static Properties parseParams(StringTokenizer tok) {
public static Properties parseParams(StringTokenizer tok) throws SAMException {
int pos, nprops = 0, ntoks = tok.countTokens();
String token, param, value;
Properties props = new Properties();
@ -120,7 +122,7 @@ public class SAMUtils {
pos = token.indexOf("=");
if (pos == -1) {
_log.debug("Error in params format");
return null;
throw new SAMException("Bad formatting for param [" + token + "]");
}
param = token.substring(0, pos);
value = token.substring(pos + 1);
@ -133,22 +135,18 @@ public class SAMUtils {
_log.debug("Parsed properties: " + dumpProperties(props));
}
if (nprops != 0) {
return props;
} else {
return null;
}
return props;
}
/* Dump a Properties object in an human-readable form */
private static String dumpProperties(Properties props) {
Enumeration enum = props.propertyNames();
Enumeration names = props.propertyNames();
String msg = "";
String key, val;
boolean firstIter = true;
while (enum.hasMoreElements()) {
key = (String)enum.nextElement();
while (names.hasMoreElements()) {
key = (String)names.nextElement();
val = props.getProperty(key);
if (!firstIter) {

View File

@ -108,7 +108,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
break;
}
msg = buf.toString("ISO-8859-1");
msg = buf.toString("ISO-8859-1").trim();
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("New message received: " + msg);
}
@ -127,8 +127,6 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
+ "\"; opcode: \"" + opcode + "\")");
}
props = SAMUtils.parseParams(tok);
if (i2cpProps != null)
props.putAll(i2cpProps); // make sure we've got the i2cp settings
if (domain.equals("STREAM")) {
canContinue = execStreamMessage(opcode, props);
@ -137,6 +135,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
} else if (domain.equals("RAW")) {
canContinue = execRawMessage(opcode, props);
} else if (domain.equals("SESSION")) {
if (i2cpProps != null)
props.putAll(i2cpProps); // make sure we've got the i2cp settings
canContinue = execSessionMessage(opcode, props);
} else if (domain.equals("DEST")) {
canContinue = execDestMessage(opcode, props);
@ -154,10 +154,10 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
}
} catch (UnsupportedEncodingException e) {
_log.error("Caught UnsupportedEncodingException ("
+ e.getMessage() + ")");
+ e.getMessage() + ")", e);
} catch (IOException e) {
_log.debug("Caught IOException ("
+ e.getMessage() + ")");
+ e.getMessage() + ")", e);
} catch (Exception e) {
_log.error("Unexpected exception", e);
} finally {
@ -189,76 +189,91 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
if ((rawSession != null) || (datagramSession != null)
|| (streamSession != null)) {
_log.debug("Trying to create a session, but one still exists");
return false;
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Session already exists\"\n");
}
if (props == null) {
_log.debug("No parameters specified in SESSION CREATE message");
return false;
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No parameters for SESSION CREATE\"\n");
}
dest = props.getProperty("DESTINATION");
if (dest == null) {
_log.debug("SESSION DESTINATION parameter not specified");
return false;
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"DESTINATION not specified\"\n");
}
props.remove("DESTINATION");
String destKeystream = null;
if (dest.equals("TRANSIENT")) {
_log.debug("TRANSIENT destination requested");
ByteArrayOutputStream priv = new ByteArrayOutputStream();
ByteArrayOutputStream priv = new ByteArrayOutputStream(640);
SAMUtils.genRandomKey(priv, null);
dest = Base64.encode(priv.toByteArray());
destKeystream = Base64.encode(priv.toByteArray());
} else {
destKeystream = bridge.getKeystream(dest);
if (destKeystream == null) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Custom destination specified [" + dest + "] but it isnt know, creating a new one");
ByteArrayOutputStream baos = new ByteArrayOutputStream(640);
SAMUtils.genRandomKey(baos, null);
destKeystream = Base64.encode(baos.toByteArray());
bridge.addKeystream(dest, destKeystream);
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Custom destination specified [" + dest + "] and it is already known");
}
}
String style = props.getProperty("STYLE");
if (style == null) {
_log.debug("SESSION STYLE parameter not specified");
return false;
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"No SESSION STYLE specified\"\n");
}
props.remove("STYLE");
if (style.equals("RAW")) {
rawSession = new SAMRawSession(dest, props, this);
rawSession = new SAMRawSession(destKeystream, props, this);
} else if (style.equals("DATAGRAM")) {
datagramSession = new SAMDatagramSession(dest, props,this);
datagramSession = new SAMDatagramSession(destKeystream, props,this);
} else if (style.equals("STREAM")) {
String dir = props.getProperty("DIRECTION");
if (dir == null) {
_log.debug("No DIRECTION parameter in STREAM session");
return false;
_log.debug("No DIRECTION parameter in STREAM session, defaulting to BOTH");
dir = "BOTH";
}
if (!dir.equals("CREATE") && !dir.equals("RECEIVE")
&& !dir.equals("BOTH")) {
_log.debug("Unknow DIRECTION parameter value: " + dir);
return false;
_log.debug("Unknow DIRECTION parameter value: [" + dir + "]");
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unknown DIRECTION parameter\"\n");
}
props.remove("DIRECTION");
streamSession = new SAMStreamSession(dest, dir,props,this);
streamSession = new SAMStreamSession(destKeystream, dir,props,this);
} else {
_log.debug("Unrecognized SESSION STYLE: \"" + style +"\"");
return false;
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized SESSION STYLE\"\n");
}
return writeString("SESSION STATUS RESULT=OK DESTINATION="
+ dest + "\n");
} else {
_log.debug("Unrecognized SESSION message opcode: \""
+ opcode + "\"");
return false;
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized opcode\"\n");
}
} catch (DataFormatException e) {
_log.debug("Invalid destination specified");
return writeString("SESSION STATUS RESULT=INVALID_KEY DESTINATION=" + dest + "\n");
return writeString("SESSION STATUS RESULT=INVALID_KEY DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
} catch (I2PSessionException e) {
_log.debug("I2P error when instantiating session", e);
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + "\n");
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
} catch (SAMException e) {
_log.error("Unexpected SAM error", e);
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + "\n");
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
} catch (IOException e) {
_log.error("Unexpected IOException", e);
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + "\n");
return writeString("SESSION STATUS RESULT=I2P_ERROR DESTINATION=" + dest + " MESSAGE=\"" + e.getMessage() + "\"\n");
}
}
@ -266,7 +281,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
private boolean execDestMessage(String opcode, Properties props) {
if (opcode.equals("GENERATE")) {
if (props != null) {
if (props.size() > 0) {
_log.debug("Properties specified in DEST GENERATE message");
return false;
}
@ -483,159 +498,171 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
}
if (opcode.equals("SEND")) {
if (props == null) {
_log.debug("No parameters specified in STREAM SEND message");
return false;
}
int id;
{
String strid = props.getProperty("ID");
if (strid == null) {
_log.debug("ID not specified in STREAM SEND message");
return false;
}
try {
id = Integer.parseInt(strid);
} catch (NumberFormatException e) {
_log.debug("Invalid STREAM SEND ID specified: " + strid);
return false;
}
}
int size;
{
String strsize = props.getProperty("SIZE");
if (strsize == null) {
_log.debug("Size not specified in STREAM SEND message");
return false;
}
try {
size = Integer.parseInt(strsize);
} catch (NumberFormatException e) {
_log.debug("Invalid STREAM SEND size specified: "+strsize);
return false;
}
if (!checkSize(size)) {
_log.debug("Specified size (" + size
+ ") is out of protocol limits");
return false;
}
}
try {
DataInputStream in = new DataInputStream(getClientSocketInputStream());
byte[] data = new byte[size];
in.readFully(data);
if (!streamSession.sendBytes(id, data)) {
_log.error("STREAM SEND failed");
return false;
}
return true;
} catch (EOFException e) {
_log.debug("Too few bytes with RAW SEND message (expected: "
+ size);
return false;
} catch (IOException e) {
_log.debug("Caught IOException while parsing RAW SEND message",
e);
return false;
}
return execStreamSend(props);
} else if (opcode.equals("CONNECT")) {
if (props == null) {
_log.debug("No parameters specified in STREAM CONNECT message");
return false;
}
int id;
{
String strid = props.getProperty("ID");
if (strid == null) {
_log.debug("ID not specified in STREAM SEND message");
return false;
}
try {
id = Integer.parseInt(strid);
} catch (NumberFormatException e) {
_log.debug("Invalid STREAM CONNECT ID specified: " +strid);
return false;
}
if (id < 1) {
_log.debug("Invalid STREAM CONNECT ID specified: " +strid);
return false;
}
props.remove("ID");
}
String dest = props.getProperty("DESTINATION");
if (dest == null) {
_log.debug("Destination not specified in RAW SEND message");
return false;
}
props.remove("DESTINATION");
try {
if (!streamSession.connect(id, dest, props)) {
_log.debug("STREAM connection failed");
return false;
}
return writeString("STREAM STATUS RESULT=OK ID=" + id + "\n");
} catch (DataFormatException e) {
_log.debug("Invalid destination in STREAM CONNECT message");
return writeString("STREAM STATUS RESULT=INVALID_KEY ID="
+ id + "\n");
} catch (SAMInvalidDirectionException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=INVALID_DIRECTION ID="
+ id + "\n");
} catch (ConnectException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=CONNECTION_REFUSED ID="
+ id + "\n");
} catch (NoRouteToHostException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=CANT_REACH_PEER ID="
+ id + "\n");
} catch (InterruptedIOException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=TIMEOUT ID="
+ id + "\n");
} catch (I2PException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=I2P_ERROR ID="
+ id + "\n");
}
return execStreamConnect(props);
} else if (opcode.equals("CLOSE")) {
if (props == null) {
_log.debug("No parameters specified in STREAM CLOSE message");
return false;
}
int id;
{
String strid = props.getProperty("ID");
if (strid == null) {
_log.debug("ID not specified in STREAM CLOSE message");
return false;
}
try {
id = Integer.parseInt(strid);
} catch (NumberFormatException e) {
_log.debug("Invalid STREAM CLOSE ID specified: " +strid);
return false;
}
}
return streamSession.closeConnection(id);
return execStreamClose(props);
} else {
_log.debug("Unrecognized RAW message opcode: \""
+ opcode + "\"");
return false;
}
}
private boolean execStreamSend(Properties props) {
if (props == null) {
_log.debug("No parameters specified in STREAM SEND message");
return false;
}
int id;
{
String strid = props.getProperty("ID");
if (strid == null) {
_log.debug("ID not specified in STREAM SEND message");
return false;
}
try {
id = Integer.parseInt(strid);
} catch (NumberFormatException e) {
_log.debug("Invalid STREAM SEND ID specified: " + strid);
return false;
}
}
int size;
{
String strsize = props.getProperty("SIZE");
if (strsize == null) {
_log.debug("Size not specified in STREAM SEND message");
return false;
}
try {
size = Integer.parseInt(strsize);
} catch (NumberFormatException e) {
_log.debug("Invalid STREAM SEND size specified: "+strsize);
return false;
}
if (!checkSize(size)) {
_log.debug("Specified size (" + size
+ ") is out of protocol limits");
return false;
}
}
try {
DataInputStream in = new DataInputStream(getClientSocketInputStream());
byte[] data = new byte[size];
in.readFully(data);
if (!streamSession.sendBytes(id, data)) {
_log.error("STREAM SEND failed");
return false;
}
return true;
} catch (EOFException e) {
_log.debug("Too few bytes with RAW SEND message (expected: "
+ size);
return false;
} catch (IOException e) {
_log.debug("Caught IOException while parsing RAW SEND message",
e);
return false;
}
}
private boolean execStreamConnect(Properties props) {
if (props == null) {
_log.debug("No parameters specified in STREAM CONNECT message");
return false;
}
int id;
{
String strid = props.getProperty("ID");
if (strid == null) {
_log.debug("ID not specified in STREAM SEND message");
return false;
}
try {
id = Integer.parseInt(strid);
} catch (NumberFormatException e) {
_log.debug("Invalid STREAM CONNECT ID specified: " +strid);
return false;
}
if (id < 1) {
_log.debug("Invalid STREAM CONNECT ID specified: " +strid);
return false;
}
props.remove("ID");
}
String dest = props.getProperty("DESTINATION");
if (dest == null) {
_log.debug("Destination not specified in RAW SEND message");
return false;
}
props.remove("DESTINATION");
try {
if (!streamSession.connect(id, dest, props)) {
_log.debug("STREAM connection failed");
return false;
}
return writeString("STREAM STATUS RESULT=OK ID=" + id + "\n");
} catch (DataFormatException e) {
_log.debug("Invalid destination in STREAM CONNECT message");
return writeString("STREAM STATUS RESULT=INVALID_KEY ID="
+ id + "\n");
} catch (SAMInvalidDirectionException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=INVALID_DIRECTION ID="
+ id + "\n");
} catch (ConnectException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=CONNECTION_REFUSED ID="
+ id + "\n");
} catch (NoRouteToHostException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=CANT_REACH_PEER ID="
+ id + "\n");
} catch (InterruptedIOException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=TIMEOUT ID="
+ id + "\n");
} catch (I2PException e) {
_log.debug("STREAM CONNECT failed: " + e.getMessage());
return writeString("STREAM STATUS RESULT=I2P_ERROR ID="
+ id + "\n");
}
}
private boolean execStreamClose(Properties props) {
if (props == null) {
_log.debug("No parameters specified in STREAM CLOSE message");
return false;
}
int id;
{
String strid = props.getProperty("ID");
if (strid == null) {
_log.debug("ID not specified in STREAM CLOSE message");
return false;
}
try {
id = Integer.parseInt(strid);
} catch (NumberFormatException e) {
_log.debug("Invalid STREAM CLOSE ID specified: " +strid);
return false;
}
}
return streamSession.closeConnection(id);
}
/* Check whether a size is inside the limits allowed by this protocol */
private boolean checkSize(int size) {

View File

@ -0,0 +1,78 @@
package net.i2p.sam;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import net.i2p.util.Log;
public class TestCreateSessionDatagram {
private static Log _log = new Log(TestCreateSessionDatagram.class);
private static void runTest(String samHost, int samPort, String conOptions) {
testTransient(samHost, samPort, conOptions);
testNewDest(samHost, samPort, conOptions);
testOldDest(samHost, samPort, conOptions);
}
private static void testTransient(String host, int port, String conOptions) {
testDest(host, port, conOptions, "TRANSIENT");
_log.debug("\n\nTest of transient complete\n\n\n");
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
private static void testNewDest(String host, int port, String conOptions) {
String destName = "Alice" + Math.random();
testDest(host, port, conOptions, destName);
}
private static void testOldDest(String host, int port, String conOptions) {
String destName = "Alice" + Math.random();
testDest(host, port, conOptions, destName);
_log.debug("\n\nTest of initial contact for " + destName + " complete, waiting 90 seconds");
try { Thread.sleep(90*1000); } catch (InterruptedException ie) {}
_log.debug("now testing subsequent contact\n\n\n");
testDest(host, port, conOptions, destName);
_log.debug("\n\nTest of subsequent contact complete\n\n");
}
private static void testDest(String host, int port, String conOptions, String destName) {
_log.info("\n\nTesting creating a new destination (should come back with 'SESSION STATUS RESULT=OK DESTINATION=someName)\n\n\n");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
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=DATAGRAM DESTINATION=" + destName + " " + conOptions + "\n";
out.write(req.getBytes());
line = reader.readLine();
_log.info("Response to creating the session with destination " + destName + ": " + line);
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
s.close();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
public static void main(String args[]) {
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765";
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
String conOptions = "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0"; // "i2cp.tcp.host=dev.i2p.net i2cp.tcp.port=7002 tunnels.inboundDepth=0";
if (args.length > 0) {
conOptions = "";
for (int i = 0; i < args.length; i++)
conOptions = conOptions + " " + args[i];
}
try {
TestUtil.startupBridge(6000);
runTest("localhost", 6000, conOptions);
} catch (Throwable t) {
_log.error("Error running test", t);
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
System.exit(0);
}
}

View File

@ -0,0 +1,78 @@
package net.i2p.sam;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import net.i2p.util.Log;
public class TestCreateSessionRaw {
private static Log _log = new Log(TestCreateSessionRaw.class);
private static void runTest(String samHost, int samPort, String conOptions) {
testTransient(samHost, samPort, conOptions);
testNewDest(samHost, samPort, conOptions);
testOldDest(samHost, samPort, conOptions);
}
private static void testTransient(String host, int port, String conOptions) {
testDest(host, port, conOptions, "TRANSIENT");
_log.debug("\n\nTest of transient complete\n\n\n");
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
private static void testNewDest(String host, int port, String conOptions) {
String destName = "Alice" + Math.random();
testDest(host, port, conOptions, destName);
}
private static void testOldDest(String host, int port, String conOptions) {
String destName = "Alice" + Math.random();
testDest(host, port, conOptions, destName);
_log.debug("\n\nTest of initial contact for " + destName + " complete, waiting 90 seconds");
try { Thread.sleep(90*1000); } catch (InterruptedException ie) {}
_log.debug("now testing subsequent contact\n\n\n");
testDest(host, port, conOptions, destName);
_log.debug("\n\nTest of subsequent contact complete\n\n");
}
private static void testDest(String host, int port, String conOptions, String destName) {
_log.info("\n\nTesting creating a new destination (should come back with 'SESSION STATUS RESULT=OK DESTINATION=someName)\n\n\n");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
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=RAW DESTINATION=" + destName + " " + conOptions + "\n";
out.write(req.getBytes());
line = reader.readLine();
_log.info("Response to creating the session with destination " + destName + ": " + line);
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
s.close();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
public static void main(String args[]) {
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765";
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
String conOptions = "i2cp.tcp.host=dev.i2p.net i2cp.tcp.port=7002 tunnels.inboundDepth=0";
if (args.length > 0) {
conOptions = "";
for (int i = 0; i < args.length; i++)
conOptions = conOptions + " " + args[i];
}
try {
TestUtil.startupBridge(6000);
runTest("localhost", 6000, conOptions);
} catch (Throwable t) {
_log.error("Error running test", t);
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
System.exit(0);
}
}

View File

@ -0,0 +1,78 @@
package net.i2p.sam;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import net.i2p.util.Log;
public class TestCreateSessionStream {
private static Log _log = new Log(TestCreateSessionStream.class);
private static void runTest(String samHost, int samPort, String conOptions) {
testTransient(samHost, samPort, conOptions);
testNewDest(samHost, samPort, conOptions);
testOldDest(samHost, samPort, conOptions);
}
private static void testTransient(String host, int port, String conOptions) {
testDest(host, port, conOptions, "TRANSIENT");
_log.debug("\n\nTest of transient complete\n\n\n");
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
private static void testNewDest(String host, int port, String conOptions) {
String destName = "Alice" + Math.random();
testDest(host, port, conOptions, destName);
}
private static void testOldDest(String host, int port, String conOptions) {
String destName = "Alice" + Math.random();
testDest(host, port, conOptions, destName);
_log.debug("\n\nTest of initial contact for " + destName + " complete, waiting 90 seconds");
try { Thread.sleep(90*1000); } catch (InterruptedException ie) {}
_log.debug("now testing subsequent contact\n\n\n");
testDest(host, port, conOptions, destName);
_log.debug("\n\nTest of subsequent contact complete\n\n");
}
private static void testDest(String host, int port, String conOptions, String destName) {
_log.info("\n\nTesting creating a new destination (should come back with 'SESSION STATUS RESULT=OK DESTINATION=someName)\n\n\n");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
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=" + destName + " " + conOptions + "\n";
out.write(req.getBytes());
line = reader.readLine();
_log.info("Response to creating the session with destination " + destName + ": " + line);
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
s.close();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
public static void main(String args[]) {
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765";
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
String conOptions = "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
if (args.length > 0) {
conOptions = "";
for (int i = 0; i < args.length; i++)
conOptions = conOptions + " " + args[i];
}
try {
TestUtil.startupBridge(6000);
runTest("localhost", 6000, conOptions);
} catch (Throwable t) {
_log.error("Error running test", t);
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
System.exit(0);
}
}

View File

@ -0,0 +1,106 @@
package net.i2p.sam;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.StringTokenizer;
import java.util.Properties;
import net.i2p.util.Log;
import net.i2p.sam.SAMUtils;
public class TestDatagramTransfer {
private static Log _log = new Log(TestCreateSessionDatagram.class);
private static void runTest(String samHost, int samPort, String conOptions) {
testTransfer(samHost, samPort, conOptions);
}
private static void testTransfer(String host, int port, String conOptions) {
String destName = "TRANSIENT";
_log.info("\n\nTesting creating a new destination (should come back with 'SESSION STATUS RESULT=OK DESTINATION=someName)\n\n\n");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
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=DATAGRAM DESTINATION=" + destName + " " + conOptions + "\n";
out.write(req.getBytes());
line = reader.readLine();
_log.info("Response to creating the session with destination " + destName + ": " + line);
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
String lookup = "NAMING LOOKUP NAME=ME\n";
out.write(lookup.getBytes());
line = reader.readLine();
_log.info("Response from the lookup for ME: " + line);
_log.debug("The above should be a NAMING REPLY");
StringTokenizer tok = new StringTokenizer(line);
String maj = tok.nextToken();
String min = tok.nextToken();
Properties props = SAMUtils.parseParams(tok);
String value = props.getProperty("VALUE");
if (value == null) {
_log.error("No value for ME found! [" + line + "]");
return;
} else {
_log.info("Alice is located at " + value);
}
String send = "DATAGRAM SEND DESTINATION=" + value + " SIZE=3\nYo!";
out.write(send.getBytes());
line = reader.readLine();
tok = new StringTokenizer(line);
maj = tok.nextToken();
min = tok.nextToken();
props = SAMUtils.parseParams(tok);
String size = props.getProperty("SIZE");
String from = props.getProperty("DESTINATION");
if ( (value == null) || (size == null) ||
(!from.equals(value)) || (!size.equals("3")) ) {
_log.error("Reply of the datagram is incorrect: [" + line + "]");
return;
}
char buf[] = new char[3];
int read = reader.read(buf);
if (read != 3) {
_log.error("Unable to read the full datagram");
return;
}
if (new String(buf).equals("Yo!")) {
_log.info("Received payload successfully");
} else {
_log.error("Payload is incorrect! [" + new String(buf) + "]");
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
s.close();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
public static void main(String args[]) {
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765";
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
String conOptions = "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0"; // "i2cp.tcp.host=dev.i2p.net i2cp.tcp.port=7002 tunnels.inboundDepth=0";
if (args.length > 0) {
conOptions = "";
for (int i = 0; i < args.length; i++)
conOptions = conOptions + " " + args[i];
}
try {
TestUtil.startupBridge(6000);
runTest("localhost", 6000, conOptions);
} catch (Throwable t) {
_log.error("Error running test", t);
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
System.exit(0);
}
}

View File

@ -0,0 +1,62 @@
package net.i2p.sam;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import net.i2p.util.Log;
public class TestDest {
private static Log _log = new Log(TestDest.class);
private static void runTest(String samHost, int samPort, String conOptions) {
test(samHost, samPort, conOptions);
}
private static void test(String host, int port, String conOptions) {
_log.info("\n\nTesting a DEST generate (should come back with 'DEST REPLY PUB=val PRIV=val')\n\n\n");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
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=testNaming " + conOptions + "\n";
out.write(req.getBytes());
line = reader.readLine();
_log.debug("Response to creating the session with destination testNaming: " + line);
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
String lookup = "DEST GENERATE\n";
out.write(lookup.getBytes());
line = reader.readLine();
_log.info("Response from the dest generate: " + line);
_log.debug("The abouve should be a DEST REPLY");
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
s.close();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
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"; // "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
if (args.length > 0) {
conOptions = "";
for (int i = 0; i < args.length; i++)
conOptions = conOptions + " " + args[i];
}
try {
TestUtil.startupBridge(6000);
runTest("localhost", 6000, conOptions);
} catch (Throwable t) {
_log.error("Error running test", t);
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
System.exit(0);
}
}

View File

@ -0,0 +1,76 @@
package net.i2p.sam;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import net.i2p.util.Log;
public class TestHello {
private static Log _log = new Log(TestHello.class);
private static void runTest(String samHost, int samPort) {
testValidVersion(samHost, samPort);
testInvalidVersion(samHost, samPort);
testCorruptLine(samHost, samPort);
}
private static void testValidVersion(String host, int port) {
_log.info("\n\nTesting valid version (should come back with an OK)\n\n\n");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = reader.readLine();
_log.info("line read for valid version: " + line);
s.close();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
private static void testInvalidVersion(String host, int port) {
_log.info("\n\nTesting invalid version (should come back with an error)\n\n\n");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write("HELLO VERSION MIN=9.0 MAX=8.3\n".getBytes());
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = reader.readLine();
_log.info("line read for invalid version: " + line);
s.close();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
private static void testCorruptLine(String host, int port) {
_log.info("\n\nTesting corrupt line (should come back with an error)\n\n\n");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write("HELLO h0 h0 h0\n".getBytes());
BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = reader.readLine();
_log.info("line read for valid version: " + line);
s.close();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
public static void main(String args[]) {
try {
TestUtil.startupBridge(6000);
runTest("localhost", 6000);
} catch (Throwable t) {
_log.error("Error running test", t);
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
System.exit(0);
}
}

View File

@ -0,0 +1,82 @@
package net.i2p.sam;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import net.i2p.util.Log;
public class TestNaming {
private static Log _log = new Log(TestNaming.class);
private static void runTest(String samHost, int samPort, String conOptions) {
testMe(samHost, samPort, conOptions);
testDuck(samHost, samPort, conOptions);
testUnknown(samHost, samPort, conOptions);
}
private static void testMe(String host, int port, String conOptions) {
testName(host, port, conOptions, "ME");
_log.debug("\n\nTest of ME complete\n\n\n");
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
private static void testDuck(String host, int port, String conOptions) {
testName(host, port, conOptions, "duck.i2p");
_log.debug("\n\nTest of duck complete\n\n\n");
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
private static void testUnknown(String host, int port, String conOptions) {
testName(host, port, conOptions, "www.odci.gov");
_log.debug("\n\nTest of unknown host complete\n\n\n");
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
}
private static void testName(String host, int port, String conOptions, String name) {
_log.info("\n\nTesting a name lookup (should come back with 'NAMING REPLY RESULT=OK VALUE=someName)\n\n\n");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
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=testNaming " + conOptions + "\n";
out.write(req.getBytes());
line = reader.readLine();
_log.debug("Response to creating the session with destination testNaming: " + line);
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
String lookup = "NAMING LOOKUP NAME=" + name + "\n";
out.write(lookup.getBytes());
line = reader.readLine();
_log.info("Response from the lookup for [" + name +"]: " + line);
_log.debug("The abouve should be a NAMING REPLY");
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
s.close();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
public static void main(String args[]) {
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765";
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
String conOptions = "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
if (args.length > 0) {
conOptions = "";
for (int i = 0; i < args.length; i++)
conOptions = conOptions + " " + args[i];
}
try {
TestUtil.startupBridge(6000);
runTest("localhost", 6000, conOptions);
} catch (Throwable t) {
_log.error("Error running test", t);
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
System.exit(0);
}
}

View File

@ -0,0 +1,109 @@
package net.i2p.sam;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.StringTokenizer;
import java.util.Properties;
import net.i2p.util.Log;
import net.i2p.sam.SAMUtils;
public class TestRawTransfer {
private static Log _log = new Log(TestCreateSessionDatagram.class);
private static void runTest(String samHost, int samPort, String conOptions) {
testTransfer(samHost, samPort, conOptions);
}
private static void testTransfer(String host, int port, String conOptions) {
String destName = "TRANSIENT";
_log.info("\n\nTesting creating a new destination (should come back with 'SESSION STATUS RESULT=OK DESTINATION=someName)\n\n\n");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
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=RAW DESTINATION=" + destName + " " + conOptions + "\n";
out.write(req.getBytes());
line = reader.readLine();
_log.info("Response to creating the session with destination " + destName + ": " + line);
_log.debug("The above should contain SESSION STATUS RESULT=OK\n\n\n");
String lookup = "NAMING LOOKUP NAME=ME\n";
out.write(lookup.getBytes());
line = reader.readLine();
_log.info("Response from the lookup for ME: " + line);
_log.debug("The above should be a NAMING REPLY");
StringTokenizer tok = new StringTokenizer(line);
String maj = tok.nextToken();
String min = tok.nextToken();
Properties props = SAMUtils.parseParams(tok);
String value = props.getProperty("VALUE");
if (value == null) {
_log.error("No value for ME found! [" + line + "]");
return;
} else {
_log.info("Alice is located at " + value);
}
String send = "RAW SEND DESTINATION=" + value + " SIZE=3\nYo!";
out.write(send.getBytes());
line = reader.readLine();
try {
tok = new StringTokenizer(line);
maj = tok.nextToken();
min = tok.nextToken();
props = SAMUtils.parseParams(tok);
} catch (Exception e) {
_log.error("Error parsing response line: [" + line + "]", e);
return;
}
String size = props.getProperty("SIZE");
if ( (size == null) || (!size.equals("3")) ) {
_log.error("Reply of the datagram is incorrect: [" + line + "]");
return;
}
char buf[] = new char[3];
int read = reader.read(buf);
if (read != 3) {
_log.error("Unable to read the full datagram");
return;
}
if (new String(buf).equals("Yo!")) {
_log.info("Rec8eived payload successfully");
} else {
_log.error("Payload is incorrect! [" + new String(buf) + "]");
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
s.close();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
public static void main(String args[]) {
// "i2cp.tcp.host=www.i2p.net i2cp.tcp.port=7765";
// "i2cp.tcp.host=localhost i2cp.tcp.port=7654 tunnels.inboundDepth=0";
String conOptions = "i2cp.tcp.host=dev.i2p.net i2cp.tcp.port=7002 tunnels.inboundDepth=0";
if (args.length > 0) {
conOptions = "";
for (int i = 0; i < args.length; i++)
conOptions = conOptions + " " + args[i];
}
try {
TestUtil.startupBridge(6000);
runTest("localhost", 6000, conOptions);
} catch (Throwable t) {
_log.error("Error running test", t);
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
System.exit(0);
}
}

View File

@ -0,0 +1,223 @@
package net.i2p.sam;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Map;
import java.util.Collections;
import java.util.HashMap;
import java.util.Properties;
import java.util.StringTokenizer;
import net.i2p.util.Log;
import net.i2p.util.I2PThread;
/**
* <ol>
* <li>start up SAM</li>
* <li>Alice connects as 'Alice', gets her destination, stashes it away, and
* listens for any streams, echoing back whatever she receives.</li>
* <li>Bob connects as 'Bob', establishes a stream to the destination Alice
* stashed away, sends a few bundles of data, and closes the stream.</li>
* <li>Alice and Bob disconnect from SAM</li>
* <li>SAM bridge taken down</li>
* </ol>
*/
public class TestStreamTransfer {
private static Log _log = new Log(TestStreamTransfer.class);
private static String _alice = null;
private static boolean _dead = false;
private static void runTest(String samHost, int samPort, String conOptions) {
startAlice(samHost, samPort, conOptions);
testBob(samHost, samPort, conOptions);
}
private static void startAlice(String host, int port, String conOptions) {
_log.info("\n\nStarting up Alice");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
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=Alice " + conOptions + "\n";
out.write(req.getBytes());
line = reader.readLine();
_log.info("Response to creating the session with destination Alice: " + line);
req = "NAMING LOOKUP NAME=ME\n";
out.write(req.getBytes());
line = reader.readLine();
StringTokenizer tok = new StringTokenizer(line);
String maj = tok.nextToken();
String min = tok.nextToken();
Properties props = SAMUtils.parseParams(tok);
String value = props.getProperty("VALUE");
if (value == null) {
_log.error("No value for ME found! [" + line + "]");
return;
} else {
_log.info("Alice is located at " + value);
}
_alice = value;
I2PThread aliceThread = new I2PThread(new AliceRunner(reader, out, s));
aliceThread.setName("Alice");
aliceThread.start();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
private static class AliceRunner implements Runnable {
private BufferedReader _reader;
private OutputStream _out;
private Socket _s;
/** ID (string) to base64 destination */
private Map _streams;
public AliceRunner(BufferedReader reader, OutputStream out, Socket s) {
_reader = reader;
_out = out;
_s = s;
_streams = Collections.synchronizedMap(new HashMap(4));
}
public void run() {
while (!_dead) {
try {
doRun();
} catch (Exception e) {
_log.error("Error running alice", e);
try { _reader.close(); } catch (IOException ioe) {}
try { _out.close(); } catch (IOException ioe) {}
try { _s.close(); } catch (IOException ioe) {}
_streams.clear();
}
}
}
private void doRun() throws IOException, SAMException {
String line = _reader.readLine();
StringTokenizer tok = new StringTokenizer(line);
String maj = tok.nextToken();
String min = tok.nextToken();
Properties props = SAMUtils.parseParams(tok);
if ( ("STREAM".equals(maj)) && ("CONNECTED".equals(min)) ) {
String dest = props.getProperty("DESTINATION");
String id = props.getProperty("ID");
if ( (dest == null) || (id == null) ) {
_log.error("Invalid STREAM CONNECTED line: [" + line + "]");
return;
}
dest = dest.trim();
id = id.trim();
_streams.put(id, dest);
} else if ( ("STREAM".equals(maj)) && ("CLOSED".equals(min)) ) {
String id = props.getProperty("ID");
if (id == null) {
_log.error("Invalid STREAM CLOSED line: [" + line + "]");
return;
}
_streams.remove(id);
} else if ( ("STREAM".equals(maj)) && ("RECEIVED".equals(min)) ) {
String id = props.getProperty("ID");
String size = props.getProperty("SIZE");
if ( (id == null) || (size == null) ) {
_log.error("Invalid STREAM RECEIVED line: [" + line + "]");
return;
}
id = id.trim();
size = size.trim();
int payloadSize = -1;
try {
payloadSize = Integer.parseInt(size);
} catch (NumberFormatException nfe) {
_log.error("Invalid SIZE in message [" + size + "]");
return;
}
// i know, its bytes, but this test uses chars
char payload[] = new char[payloadSize];
int read = _reader.read(payload);
if (read != payloadSize) {
_log.error("Incorrect size read - expected " + payloadSize + " got " + read);
return;
}
_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;
_out.write(reply.getBytes());
_out.flush();
} else {
_log.error("Received unsupported type [" + maj + "/"+ min + "]");
return;
}
}
}
private static void testBob(String host, int port, String conOptions) {
_log.info("\n\nTesting Bob\n\n\n");
try {
Socket s = new Socket(host, port);
OutputStream out = s.getOutputStream();
out.write("HELLO VERSION MIN=1.0 MAX=1.0\n".getBytes());
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";
out.write(req.getBytes());
line = reader.readLine();
_log.info("Response to creating the session with destination Bob: " + 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);
StringTokenizer tok = new StringTokenizer(line);
String maj = tok.nextToken();
String min = tok.nextToken();
Properties props = SAMUtils.parseParams(tok);
String result = props.getProperty("RESULT");
if (!("OK".equals(result))) {
_log.error("Unable to connect!");
_dead = true;
return;
}
try { Thread.sleep(5*1000) ; } catch (InterruptedException ie) {}
req = "STREAM SEND ID=42 SIZE=10\nBlahBlah!!";
out.write(req.getBytes());
try { Thread.sleep(20*1000); } catch (InterruptedException ie) {}
req = "STREAM CLOSE ID=42\n";
out.write(req.getBytes());
try { Thread.sleep(3*1000); } catch (InterruptedException ie) {}
_dead = true;
s.close();
} catch (Exception e) {
_log.error("Error testing for valid version", e);
}
}
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";
if (args.length > 0) {
conOptions = "";
for (int i = 0; i < args.length; i++)
conOptions = conOptions + " " + args[i];
}
try {
TestUtil.startupBridge(6000);
runTest("localhost", 6000, conOptions);
} catch (Throwable t) {
_log.error("Error running test", t);
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
System.exit(0);
}
}

View File

@ -0,0 +1,9 @@
package net.i2p.sam;
public class TestUtil {
public static void startupBridge(int listenPort) {
// Usage: SAMBridge [listenHost listenPortNum[ name=val]*]
SAMBridge.main(new String[] { "0.0.0.0", listenPort+"" });
}
}

41
apps/time/java/build.xml Normal file
View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="time">
<target name="all" depends="clean, build" />
<target name="build" depends="builddep, jar" />
<target name="builddep">
<ant dir="../../../core/java/" target="build" />
</target>
<target name="compile">
<mkdir dir="./build" />
<mkdir dir="./build/obj" />
<javac srcdir="./src" debug="true" destdir="./build/obj" includes="**/*.java" classpath="../../../core/java/build/i2p.jar" />
</target>
<target name="jar" depends="compile">
<jar destfile="./build/timestamper.jar" basedir="./build/obj" includes="**/*.class">
<manifest>
<attribute name="Main-Class" value="net.i2p.time.Timestamper" />
<attribute name="Class-Path" value="i2p.jar timestamper.jar" />
</manifest>
</jar>
</target>
<target name="javadoc">
<mkdir dir="./build" />
<mkdir dir="./build/javadoc" />
<javadoc
sourcepath="./src:../../../core/java/src:../../../core/java/test" destdir="./build/javadoc"
packagenames="*"
use="true"
access="package"
splitindex="true"
windowtitle="I2P timestamper" />
</target>
<target name="clean">
<delete dir="./build" />
</target>
<target name="cleandep" depends="clean">
<ant dir="../../../core/java/" target="cleandep" />
</target>
<target name="distclean" depends="clean">
<ant dir="../../../core/java/" target="distclean" />
</target>
</project>

View File

@ -0,0 +1,145 @@
package net.i2p.time;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.text.DecimalFormat;
/**
* NtpClient - an NTP client for Java. This program connects to an NTP server
* and prints the response to the console.
*
* The local clock offset calculation is implemented according to the SNTP
* algorithm specified in RFC 2030.
*
* Note that on windows platforms, the curent time-of-day timestamp is limited
* to an resolution of 10ms and adversely affects the accuracy of the results.
*
*
* This code is copyright (c) Adam Buckley 2004
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version. A HTML version of the GNU General Public License can be
* seen at http://www.gnu.org/licenses/gpl.html
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* @author Adam Buckley
* (minor refactoring by jrandom)
*/
public class NtpClient {
/** difference between the unix epoch and jan 1 1900 (NTP uses that) */
private final static double SECONDS_1900_TO_EPOCH = 2208988800.0;
private final static int NTP_PORT = 123;
/**
* Query the ntp servers, returning the current time from first one we find
*
* @return milliseconds since january 1, 1970 (UTC)
* @throws IllegalArgumentException if none of the servers are reachable
*/
public static long currentTime(String serverNames[]) {
if (serverNames == null)
throw new IllegalArgumentException("No NTP servers specified");
for (int i = 0; i < serverNames.length; i++) {
long now = currentTime(serverNames[i]);
if (now > 0)
return now;
}
throw new IllegalArgumentException("No reachable NTP servers specified");
}
/**
* Query the given NTP server, returning the current internet time
*
* @return milliseconds since january 1, 1970 (UTC), or -1 on error
*/
public static long currentTime(String serverName) {
try {
// Send request
DatagramSocket socket = new DatagramSocket();
InetAddress address = InetAddress.getByName(serverName);
byte[] buf = new NtpMessage().toByteArray();
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, NTP_PORT);
// Set the transmit timestamp *just* before sending the packet
// ToDo: Does this actually improve performance or not?
NtpMessage.encodeTimestamp(packet.getData(), 40,
(System.currentTimeMillis()/1000.0)
+ SECONDS_1900_TO_EPOCH);
socket.send(packet);
// Get response
packet = new DatagramPacket(buf, buf.length);
socket.setSoTimeout(10*1000);
try {
socket.receive(packet);
} catch (InterruptedIOException iie) {
socket.close();
return -1;
}
// Immediately record the incoming timestamp
double destinationTimestamp = (System.currentTimeMillis()/1000.0) + SECONDS_1900_TO_EPOCH;
// Process response
NtpMessage msg = new NtpMessage(packet.getData());
double roundTripDelay = (destinationTimestamp-msg.originateTimestamp) -
(msg.receiveTimestamp-msg.transmitTimestamp);
double localClockOffset = ((msg.receiveTimestamp - msg.originateTimestamp) +
(msg.transmitTimestamp - destinationTimestamp)) / 2;
socket.close();
//System.out.println("host: " + serverName + " rtt: " + roundTripDelay + " offset: " + localClockOffset + " seconds");
return (long)(System.currentTimeMillis() + localClockOffset*1000);
} catch (IOException ioe) {
ioe.printStackTrace();
return -1;
}
}
public static void main(String[] args) throws IOException {
// Process command-line args
if(args.length <= 0) {
printUsage();
return;
// args = new String[] { "ntp1.sth.netnod.se", "ntp2.sth.netnod.se" };
}
long now = currentTime(args);
System.out.println("Current time: " + new java.util.Date(now));
}
/**
* Prints usage
*/
static void printUsage() {
System.out.println(
"NtpClient - an NTP client for Java.\n" +
"\n" +
"This program connects to an NTP server and prints the current time to the console.\n" +
"\n" +
"\n" +
"Usage: java NtpClient server[ server]*\n" +
"\n" +
"\n" +
"This program is copyright (c) Adam Buckley 2004 and distributed under the terms\n" +
"of the GNU General Public License. This program is distributed in the hope\n" +
"that it will be useful, but WITHOUT ANY WARRANTY; without even the implied\n" +
"warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" +
"General Public License available at http://www.gnu.org/licenses/gpl.html for\n" +
"more details.");
}
}

View File

@ -0,0 +1,451 @@
package net.i2p.time;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* This class represents a NTP message, as specified in RFC 2030. The message
* format is compatible with all versions of NTP and SNTP.
*
* This class does not support the optional authentication protocol, and
* ignores the key ID and message digest fields.
*
* For convenience, this class exposes message values as native Java types, not
* the NTP-specified data formats. For example, timestamps are
* stored as doubles (as opposed to the NTP unsigned 64-bit fixed point
* format).
*
* However, the contructor NtpMessage(byte[]) and the method toByteArray()
* allow the import and export of the raw NTP message format.
*
*
* Usage example
*
* // Send message
* DatagramSocket socket = new DatagramSocket();
* InetAddress address = InetAddress.getByName("ntp.cais.rnp.br");
* byte[] buf = new NtpMessage().toByteArray();
* DatagramPacket packet = new DatagramPacket(buf, buf.length, address, 123);
* socket.send(packet);
*
* // Get response
* socket.receive(packet);
* System.out.println(msg.toString());
*
*
* This code is copyright (c) Adam Buckley 2004
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version. A HTML version of the GNU General Public License can be
* seen at http://www.gnu.org/licenses/gpl.html
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*
* Comments for member variables are taken from RFC2030 by David Mills,
* University of Delaware.
*
* Number format conversion code in NtpMessage(byte[] array) and toByteArray()
* inspired by http://www.pps.jussieu.fr/~jch/enseignement/reseaux/
* NTPMessage.java which is copyright (c) 2003 by Juliusz Chroboczek
*
* @author Adam Buckley
*/
public class NtpMessage {
/**
* This is a two-bit code warning of an impending leap second to be
* inserted/deleted in the last minute of the current day. It's values
* may be as follows:
*
* Value Meaning
* ----- -------
* 0 no warning
* 1 last minute has 61 seconds
* 2 last minute has 59 seconds)
* 3 alarm condition (clock not synchronized)
*/
public byte leapIndicator = 0;
/**
* This value indicates the NTP/SNTP version number. The version number
* is 3 for Version 3 (IPv4 only) and 4 for Version 4 (IPv4, IPv6 and OSI).
* If necessary to distinguish between IPv4, IPv6 and OSI, the
* encapsulating context must be inspected.
*/
public byte version = 3;
/**
* This value indicates the mode, with values defined as follows:
*
* Mode Meaning
* ---- -------
* 0 reserved
* 1 symmetric active
* 2 symmetric passive
* 3 client
* 4 server
* 5 broadcast
* 6 reserved for NTP control message
* 7 reserved for private use
*
* In unicast and anycast modes, the client sets this field to 3 (client)
* in the request and the server sets it to 4 (server) in the reply. In
* multicast mode, the server sets this field to 5 (broadcast).
*/
public byte mode = 0;
/**
* This value indicates the stratum level of the local clock, with values
* defined as follows:
*
* Stratum Meaning
* ----------------------------------------------
* 0 unspecified or unavailable
* 1 primary reference (e.g., radio clock)
* 2-15 secondary reference (via NTP or SNTP)
* 16-255 reserved
*/
public short stratum = 0;
/**
* This value indicates the maximum interval between successive messages,
* in seconds to the nearest power of two. The values that can appear in
* this field presently range from 4 (16 s) to 14 (16284 s); however, most
* applications use only the sub-range 6 (64 s) to 10 (1024 s).
*/
public byte pollInterval = 0;
/**
* This value indicates the precision of the local clock, in seconds to
* the nearest power of two. The values that normally appear in this field
* range from -6 for mains-frequency clocks to -20 for microsecond clocks
* found in some workstations.
*/
public byte precision = 0;
/**
* This value indicates the total roundtrip delay to the primary reference
* source, in seconds. Note that this variable can take on both positive
* and negative values, depending on the relative time and frequency
* offsets. The values that normally appear in this field range from
* negative values of a few milliseconds to positive values of several
* hundred milliseconds.
*/
public double rootDelay = 0;
/**
* This value indicates the nominal error relative to the primary reference
* source, in seconds. The values that normally appear in this field
* range from 0 to several hundred milliseconds.
*/
public double rootDispersion = 0;
/**
* This is a 4-byte array identifying the particular reference source.
* In the case of NTP Version 3 or Version 4 stratum-0 (unspecified) or
* stratum-1 (primary) servers, this is a four-character ASCII string, left
* justified and zero padded to 32 bits. In NTP Version 3 secondary
* servers, this is the 32-bit IPv4 address of the reference source. In NTP
* Version 4 secondary servers, this is the low order 32 bits of the latest
* transmit timestamp of the reference source. NTP primary (stratum 1)
* servers should set this field to a code identifying the external
* reference source according to the following list. If the external
* reference is one of those listed, the associated code should be used.
* Codes for sources not listed can be contrived as appropriate.
*
* Code External Reference Source
* ---- -------------------------
* LOCL uncalibrated local clock used as a primary reference for
* a subnet without external means of synchronization
* PPS atomic clock or other pulse-per-second source
* individually calibrated to national standards
* ACTS NIST dialup modem service
* USNO USNO modem service
* PTB PTB (Germany) modem service
* TDF Allouis (France) Radio 164 kHz
* DCF Mainflingen (Germany) Radio 77.5 kHz
* MSF Rugby (UK) Radio 60 kHz
* WWV Ft. Collins (US) Radio 2.5, 5, 10, 15, 20 MHz
* WWVB Boulder (US) Radio 60 kHz
* WWVH Kaui Hawaii (US) Radio 2.5, 5, 10, 15 MHz
* CHU Ottawa (Canada) Radio 3330, 7335, 14670 kHz
* LORC LORAN-C radionavigation system
* OMEG OMEGA radionavigation system
* GPS Global Positioning Service
* GOES Geostationary Orbit Environment Satellite
*/
public byte[] referenceIdentifier = {0, 0, 0, 0};
/**
* This is the time at which the local clock was last set or corrected, in
* seconds since 00:00 1-Jan-1900.
*/
public double referenceTimestamp = 0;
/**
* This is the time at which the request departed the client for the
* server, in seconds since 00:00 1-Jan-1900.
*/
public double originateTimestamp = 0;
/**
* This is the time at which the request arrived at the server, in seconds
* since 00:00 1-Jan-1900.
*/
public double receiveTimestamp = 0;
/**
* This is the time at which the reply departed the server for the client,
* in seconds since 00:00 1-Jan-1900.
*/
public double transmitTimestamp = 0;
/**
* Constructs a new NtpMessage from an array of bytes.
*/
public NtpMessage(byte[] array) {
// See the packet format diagram in RFC 2030 for details
leapIndicator = (byte) ((array[0] >> 6) & 0x3);
version = (byte) ((array[0] >> 3) & 0x7);
mode = (byte) (array[0] & 0x7);
stratum = unsignedByteToShort(array[1]);
pollInterval = array[2];
precision = array[3];
rootDelay = (array[4] * 256.0) +
unsignedByteToShort(array[5]) +
(unsignedByteToShort(array[6]) / 256.0) +
(unsignedByteToShort(array[7]) / 65536.0);
rootDispersion = (unsignedByteToShort(array[8]) * 256.0) +
unsignedByteToShort(array[9]) +
(unsignedByteToShort(array[10]) / 256.0) +
(unsignedByteToShort(array[11]) / 65536.0);
referenceIdentifier[0] = array[12];
referenceIdentifier[1] = array[13];
referenceIdentifier[2] = array[14];
referenceIdentifier[3] = array[15];
referenceTimestamp = decodeTimestamp(array, 16);
originateTimestamp = decodeTimestamp(array, 24);
receiveTimestamp = decodeTimestamp(array, 32);
transmitTimestamp = decodeTimestamp(array, 40);
}
/**
* Constructs a new NtpMessage in client -> server mode, and sets the
* transmit timestamp to the current time.
*/
public NtpMessage() {
// Note that all the other member variables are already set with
// appropriate default values.
this.mode = 3;
this.transmitTimestamp = (System.currentTimeMillis()/1000.0) + 2208988800.0;
}
/**
* This method constructs the data bytes of a raw NTP packet.
*/
public byte[] toByteArray() {
// All bytes are automatically set to 0
byte[] p = new byte[48];
p[0] = (byte) (leapIndicator << 6 | version << 3 | mode);
p[1] = (byte) stratum;
p[2] = (byte) pollInterval;
p[3] = (byte) precision;
// root delay is a signed 16.16-bit FP, in Java an int is 32-bits
int l = (int) (rootDelay * 65536.0);
p[4] = (byte) ((l >> 24) & 0xFF);
p[5] = (byte) ((l >> 16) & 0xFF);
p[6] = (byte) ((l >> 8) & 0xFF);
p[7] = (byte) (l & 0xFF);
// root dispersion is an unsigned 16.16-bit FP, in Java there are no
// unsigned primitive types, so we use a long which is 64-bits
long ul = (long) (rootDispersion * 65536.0);
p[8] = (byte) ((ul >> 24) & 0xFF);
p[9] = (byte) ((ul >> 16) & 0xFF);
p[10] = (byte) ((ul >> 8) & 0xFF);
p[11] = (byte) (ul & 0xFF);
p[12] = referenceIdentifier[0];
p[13] = referenceIdentifier[1];
p[14] = referenceIdentifier[2];
p[15] = referenceIdentifier[3];
encodeTimestamp(p, 16, referenceTimestamp);
encodeTimestamp(p, 24, originateTimestamp);
encodeTimestamp(p, 32, receiveTimestamp);
encodeTimestamp(p, 40, transmitTimestamp);
return p;
}
/**
* Returns a string representation of a NtpMessage
*/
public String toString() {
String precisionStr = new DecimalFormat("0.#E0").format(Math.pow(2, precision));
return "Leap indicator: " + leapIndicator + "\n" +
"Version: " + version + "\n" +
"Mode: " + mode + "\n" +
"Stratum: " + stratum + "\n" +
"Poll: " + pollInterval + "\n" +
"Precision: " + precision + " (" + precisionStr + " seconds)\n" +
"Root delay: " + new DecimalFormat("0.00").format(rootDelay*1000) + " ms\n" +
"Root dispersion: " + new DecimalFormat("0.00").format(rootDispersion*1000) + " ms\n" +
"Reference identifier: " + referenceIdentifierToString(referenceIdentifier, stratum, version) + "\n" +
"Reference timestamp: " + timestampToString(referenceTimestamp) + "\n" +
"Originate timestamp: " + timestampToString(originateTimestamp) + "\n" +
"Receive timestamp: " + timestampToString(receiveTimestamp) + "\n" +
"Transmit timestamp: " + timestampToString(transmitTimestamp);
}
/**
* Converts an unsigned byte to a short. By default, Java assumes that
* a byte is signed.
*/
public static short unsignedByteToShort(byte b) {
if((b & 0x80)==0x80)
return (short) (128 + (b & 0x7f));
else
return (short) b;
}
/**
* Will read 8 bytes of a message beginning at <code>pointer</code>
* and return it as a double, according to the NTP 64-bit timestamp
* format.
*/
public static double decodeTimestamp(byte[] array, int pointer) {
double r = 0.0;
for(int i=0; i<8; i++) {
r += unsignedByteToShort(array[pointer+i]) * Math.pow(2, (3-i)*8);
}
return r;
}
/**
* Encodes a timestamp in the specified position in the message
*/
public static void encodeTimestamp(byte[] array, int pointer, double timestamp) {
// Converts a double into a 64-bit fixed point
for(int i=0; i<8; i++) {
// 2^24, 2^16, 2^8, .. 2^-32
double base = Math.pow(2, (3-i)*8);
// Capture byte value
array[pointer+i] = (byte) (timestamp / base);
// Subtract captured value from remaining total
timestamp = timestamp - (double) (unsignedByteToShort(array[pointer+i]) * base);
}
// From RFC 2030: It is advisable to fill the non-significant
// low order bits of the timestamp with a random, unbiased
// bitstring, both to avoid systematic roundoff errors and as
// a means of loop detection and replay detection.
array[7+pointer] = (byte) (Math.random()*255.0);
}
/**
* Returns a timestamp (number of seconds since 00:00 1-Jan-1900) as a
* formatted date/time string.
*/
public static String timestampToString(double timestamp) {
if(timestamp==0) return "0";
// timestamp is relative to 1900, utc is used by Java and is relative
// to 1970
double utc = timestamp - (2208988800.0);
// milliseconds
long ms = (long) (utc * 1000.0);
// date/time
String date = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss").format(new Date(ms));
// fraction
double fraction = timestamp - ((long) timestamp);
String fractionSting = new DecimalFormat(".000000").format(fraction);
return date + fractionSting;
}
/**
* Returns a string representation of a reference identifier according
* to the rules set out in RFC 2030.
*/
public static String referenceIdentifierToString(byte[] ref, short stratum, byte version) {
// From the RFC 2030:
// In the case of NTP Version 3 or Version 4 stratum-0 (unspecified)
// or stratum-1 (primary) servers, this is a four-character ASCII
// string, left justified and zero padded to 32 bits.
if(stratum==0 || stratum==1) {
return new String(ref);
}
// In NTP Version 3 secondary servers, this is the 32-bit IPv4
// address of the reference source.
else if(version==3) {
return unsignedByteToShort(ref[0]) + "." +
unsignedByteToShort(ref[1]) + "." +
unsignedByteToShort(ref[2]) + "." +
unsignedByteToShort(ref[3]);
}
// In NTP Version 4 secondary servers, this is the low order 32 bits
// of the latest transmit timestamp of the reference source.
else if(version==4) {
return "" + ((unsignedByteToShort(ref[0]) / 256.0) +
(unsignedByteToShort(ref[1]) / 65536.0) +
(unsignedByteToShort(ref[2]) / 16777216.0) +
(unsignedByteToShort(ref[3]) / 4294967296.0));
}
return "";
}
}

View File

@ -0,0 +1,110 @@
package net.i2p.time;
import net.i2p.util.Log;
import net.i2p.util.I2PThread;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.net.URL;
import java.net.MalformedURLException;
import java.io.IOException;
/**
* Periodically query a series of NTP servers and post the offset
* to a given URL. It tries the NTP servers in order, contacting them
* using UDP port 123, and sends the current date to the URL specified
* (specifically, URL+"&now=" + yyyyMMdd_HH:mm:ss.SSS in the UK locale).
* It does this every 5 minutes, forever.
*
* Usage: <pre>
* Timestamper URL ntpServer1[ ntpServer2]*
* </pre>
*/
public class Timestamper implements Runnable {
private static Log _log = new Log(Timestamper.class);
private String _targetURL;
private String _serverList[];
private int DELAY_MS = 5*60*1000;
public Timestamper(String url, String serverNames[]) {
if (_log.shouldLog(Log.INFO))
_log.info("Creating new timestamper pointing at " + url);
_targetURL = url;
_serverList = serverNames;
}
public void startTimestamper() {
if (_log.shouldLog(Log.INFO))
_log.info("Starting timestamper pointing at " + _targetURL);
I2PThread t = new I2PThread(this, "Timestamper");
t.setPriority(I2PThread.MIN_PRIORITY);
t.start();
}
public void run() {
if (_log.shouldLog(Log.INFO))
_log.info("Starting up timestamper");
try {
while (true) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Querying servers " + _serverList);
long now = NtpClient.currentTime(_serverList);
if (now < 0) {
_log.error("Unable to contact any of the NTP servers - network disconnect?");
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Stamp time");
stampTime(now);
}
try { Thread.sleep(DELAY_MS); } catch (InterruptedException ie) {}
}
} catch (Throwable t) {
_log.log(Log.CRIT, "Timestamper died!", t);
}
}
/**
* Send an HTTP request to a given URL specifying the current time
*/
private void stampTime(long now) {
try {
String toRequest = _targetURL + "&now=" + getNow(now);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Stamping [" + toRequest + "]");
URL url = new URL(toRequest);
Object o = url.getContent();
// ignore the content
} catch (MalformedURLException mue) {
_log.error("Invalid URL", mue);
} catch (IOException ioe) {
_log.error("Error stamping the time", ioe);
}
}
private SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd_HH:mm:ss.SSS", Locale.UK);
private String getNow(long now) {
synchronized (_fmt) {
return _fmt.format(new Date(now));
}
}
public static void main(String args[]) {
if ( (args == null) || (args.length < 2) ) {
usage();
return;
//args = new String[] { "http://dev.i2p.net:80/somePath?pass=password", "ntp1.sth.netnod.se", "ntp2.sth.netnod.se" };
}
String servers[] = new String[args.length-1];
System.arraycopy(args, 1, servers, 0, servers.length);
Timestamper ts = new Timestamper(args[0], servers);
ts.startTimestamper();
}
private static void usage() {
System.err.println("Usage: Timestamper URL ntpServer[ ntpServer]*");
_log.error("Usage: Timestamper URL ntpServer[ ntpServer]*");
}
}

View File

@ -23,6 +23,7 @@
<ant dir="apps/sam/java/" target="jar" />
<ant dir="apps/heartbeat/java/" target="jar" />
<ant dir="apps/netmonitor/java/" target="jar" />
<ant dir="apps/time/java/" target="jar" />
<ant dir="installer/java/" target="jar" />
</target>
<target name="compile" />
@ -36,6 +37,7 @@
<copy file="apps/sam/java/build/sam.jar" todir="build/" />
<copy file="apps/heartbeat/java/build/heartbeat.jar" todir="build/" />
<copy file="apps/netmonitor/java/build/netmonitor.jar" todir="build/" />
<copy file="apps/time/java/build/timestamper.jar" todir="build/" />
<copy file="installer/java/build/install.jar" todir="build/" />
<copy file="installer/java/build/guiinstall.jar" todir="build/" />
<copy file="installer/java/build/fetchseeds.jar" todir="build/" />
@ -64,6 +66,7 @@
<ant dir="apps/sam/java/" target="distclean" />
<ant dir="apps/heartbeat/java/" target="distclean" />
<ant dir="apps/netmonitor/java/" target="distclean" />
<ant dir="apps/time/java/" target="distclean" />
<ant dir="installer/java/" target="distclean" />
<delete>
<fileset dir="." includes="**/*.class" />

View File

@ -14,8 +14,8 @@ package net.i2p;
*
*/
public class CoreVersion {
public final static String ID = "$Revision: 1.2 $ $Date: 2004/04/10 06:40:05 $";
public final static String VERSION = "0.3.0.4";
public final static String ID = "$Revision: 1.5 $ $Date: 2004/05/07 12:52:49 $";
public final static String VERSION = "0.3.1.2";
public static void main(String args[]) {
System.out.println("I2P Core version: " + VERSION);

View File

@ -0,0 +1,449 @@
package net.i2p;
import net.i2p.stat.StatManager;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.crypto.PersistentSessionKeyManager;
import net.i2p.crypto.ElGamalAESEngine;
import net.i2p.crypto.ElGamalEngine;
import net.i2p.crypto.DummyElGamalEngine;
import net.i2p.crypto.SHA256Generator;
import net.i2p.crypto.HMACSHA256Generator;
import net.i2p.crypto.AESEngine;
import net.i2p.crypto.CryptixAESEngine;
import net.i2p.crypto.DSAEngine;
import net.i2p.client.naming.NamingService;
import net.i2p.util.LogManager;
import net.i2p.util.Clock;
import net.i2p.util.RandomSource;
import net.i2p.data.RoutingKeyGenerator;
import net.i2p.crypto.KeyGenerator;
import java.util.Properties;
import java.util.HashSet;
import java.util.Set;
/**
* <p>Provide a base scope for accessing singletons that I2P exposes. Rather than
* using the traditional singleton, where any component can access the component
* in question directly, all of those I2P related singletons are exposed through
* a particular I2PAppContext. This helps not only with understanding their use
* and the components I2P exposes, but it also allows multiple isolated
* environments to operate concurrently within the same JVM - particularly useful
* for stubbing out implementations of the rooted components and simulating the
* software's interaction between multiple instances.</p>
*
* As a simplification, there is also a global context - if some component needs
* access to one of the singletons but doesn't have its own context from which
* to root itself, it binds to the I2PAppContext's globalAppContext(), which is
* the first context that was created within the JVM, or a new one if no context
* existed already. This functionality is often used within the I2P core for
* logging - e.g. <pre>
* private static final Log _log = new Log(someClass.class);
* </pre>
* It is for this reason that applications that care about working with multiple
* contexts should build their own context as soon as possible (within the main(..))
* so that any referenced components will latch on to that context instead of
* instantiating a new one. However, there are situations in which both can be
* relevent.
*
*/
public class I2PAppContext {
/** the context that components without explicit root are bound */
protected static I2PAppContext _globalAppContext;
/**
* Determine if the app context been initialized. If this is false
* and something asks for the globalAppContext, a new one is created,
* otherwise the existing one is used.
*
*/
protected static volatile boolean _globalAppContextInitialized;
private Properties _overrideProps;
private StatManager _statManager;
private SessionKeyManager _sessionKeyManager;
private NamingService _namingService;
private ElGamalEngine _elGamalEngine;
private ElGamalAESEngine _elGamalAESEngine;
private AESEngine _AESEngine;
private LogManager _logManager;
private HMACSHA256Generator _hmac;
private SHA256Generator _sha;
private Clock _clock;
private DSAEngine _dsa;
private RoutingKeyGenerator _routingKeyGenerator;
private RandomSource _random;
private KeyGenerator _keyGenerator;
private volatile boolean _statManagerInitialized;
private volatile boolean _sessionKeyManagerInitialized;
private volatile boolean _namingServiceInitialized;
private volatile boolean _elGamalEngineInitialized;
private volatile boolean _elGamalAESEngineInitialized;
private volatile boolean _AESEngineInitialized;
private volatile boolean _logManagerInitialized;
private volatile boolean _hmacInitialized;
private volatile boolean _shaInitialized;
private volatile boolean _clockInitialized;
private volatile boolean _dsaInitialized;
private volatile boolean _routingKeyGeneratorInitialized;
private volatile boolean _randomInitialized;
private volatile boolean _keyGeneratorInitialized;
/**
* Pull the default context, creating a new one if necessary, else using
* the first one created.
*
*/
public static I2PAppContext getGlobalContext() {
if (!_globalAppContextInitialized) {
synchronized (I2PAppContext.class) {
System.err.println("*** Building seperate global context!");
if (_globalAppContext == null)
_globalAppContext = new I2PAppContext(false, null);
_globalAppContextInitialized = true;
}
}
return _globalAppContext;
}
/**
* Lets root a brand new context
*
*/
public I2PAppContext() {
this(true, null);
}
/**
* Lets root a brand new context
*
*/
public I2PAppContext(Properties envProps) {
this(true, envProps);
}
/**
* @param doInit should this context be used as the global one (if necessary)?
*/
private I2PAppContext(boolean doInit, Properties envProps) {
//System.out.println("App context created: " + this);
if (doInit) {
if (!_globalAppContextInitialized) {
synchronized (I2PAppContext.class) {
if (_globalAppContext == null) {
_globalAppContext = this;
_globalAppContextInitialized = true;
}
}
}
}
_overrideProps = envProps;
_statManager = null;
_sessionKeyManager = null;
_namingService = null;
_elGamalEngine = null;
_elGamalAESEngine = null;
_logManager = null;
_statManagerInitialized = false;
_sessionKeyManagerInitialized = false;
_namingServiceInitialized = false;
_elGamalEngineInitialized = false;
_elGamalAESEngineInitialized = false;
_logManagerInitialized = false;
}
/**
* Access the configuration attributes of this context, using properties
* provided during the context construction, or falling back on
* System.getProperty if no properties were provided during construction
* (or the specified prop wasn't included).
*
*/
public String getProperty(String propName) {
if (_overrideProps != null) {
if (_overrideProps.containsKey(propName))
return _overrideProps.getProperty(propName);
}
return System.getProperty(propName);
}
/**
* Access the configuration attributes of this context, using properties
* provided during the context construction, or falling back on
* System.getProperty if no properties were provided during construction
* (or the specified prop wasn't included).
*
*/
public String getProperty(String propName, String defaultValue) {
if (_overrideProps != null) {
if (_overrideProps.containsKey(propName))
return _overrideProps.getProperty(propName, defaultValue);
}
return System.getProperty(propName, defaultValue);
}
/**
* Access the configuration attributes of this context, listing the properties
* provided during the context construction, as well as the ones included in
* System.getProperties.
*
* @return set of Strings containing the names of defined system properties
*/
public Set getPropertyNames() {
Set names = new HashSet(System.getProperties().keySet());
if (_overrideProps != null)
names.addAll(_overrideProps.keySet());
return names;
}
/**
* The statistics component with which we can track various events
* over time.
*/
public StatManager statManager() {
if (!_statManagerInitialized) initializeStatManager();
return _statManager;
}
private void initializeStatManager() {
synchronized (this) {
if (_statManager == null)
_statManager = new StatManager(this);
_statManagerInitialized = true;
}
}
/**
* The session key manager which coordinates the sessionKey / sessionTag
* data. This component allows transparent operation of the
* ElGamal/AES+SessionTag algorithm, and contains all of the session tags
* for one particular application. If you want to seperate multiple apps
* to have their own sessionTags and sessionKeys, they should use different
* I2PAppContexts, and hence, different sessionKeyManagers.
*
*/
public SessionKeyManager sessionKeyManager() {
if (!_sessionKeyManagerInitialized) initializeSessionKeyManager();
return _sessionKeyManager;
}
private void initializeSessionKeyManager() {
synchronized (this) {
if (_sessionKeyManager == null)
_sessionKeyManager = new PersistentSessionKeyManager(this);
_sessionKeyManagerInitialized = true;
}
}
/**
* Pull up the naming service used in this context. The naming service itself
* works by querying the context's properties, so those props should be
* specified to customize the naming service exposed.
*/
public NamingService namingService() {
if (!_namingServiceInitialized) initializeNamingService();
return _namingService;
}
private void initializeNamingService() {
synchronized (this) {
if (_namingService == null) {
_namingService = NamingService.createInstance(this);
}
_namingServiceInitialized = true;
}
}
/**
* This is the ElGamal engine used within this context. While it doesn't
* really have anything substantial that is context specific (the algorithm
* just does the algorithm), it does transparently use the context for logging
* its performance and activity. In addition, the engine can be swapped with
* the context's properties (though only someone really crazy should mess with
* it ;)
*/
public ElGamalEngine elGamalEngine() {
if (!_elGamalEngineInitialized) initializeElGamalEngine();
return _elGamalEngine;
}
private void initializeElGamalEngine() {
synchronized (this) {
if (_elGamalEngine == null) {
if ("off".equals(getProperty("i2p.encryption", "on")))
_elGamalEngine = new DummyElGamalEngine(this);
else
_elGamalEngine = new ElGamalEngine(this);
}
_elGamalEngineInitialized = true;
}
}
/**
* Access the ElGamal/AES+SessionTag engine for this context. The algorithm
* makes use of the context's sessionKeyManager to coordinate transparent
* access to the sessionKeys and sessionTags, as well as the context's elGamal
* engine (which in turn keeps stats, etc).
*
*/
public ElGamalAESEngine elGamalAESEngine() {
if (!_elGamalAESEngineInitialized) initializeElGamalAESEngine();
return _elGamalAESEngine;
}
private void initializeElGamalAESEngine() {
synchronized (this) {
if (_elGamalAESEngine == null)
_elGamalAESEngine = new ElGamalAESEngine(this);
_elGamalAESEngineInitialized = true;
}
}
/**
* Ok, I'll admit it. there is no good reason for having a context specific
* AES engine. We dont really keep stats on it, since its just too fast to
* matter. Though for the crazy people out there, we do expose a way to
* disable it.
*/
public AESEngine AESEngine() {
if (!_AESEngineInitialized) initializeAESEngine();
return _AESEngine;
}
private void initializeAESEngine() {
synchronized (this) {
if (_AESEngine == null) {
if ("off".equals(getProperty("i2p.encryption", "on")))
_AESEngine = new AESEngine(this);
else
_AESEngine = new CryptixAESEngine(this);
}
_AESEngineInitialized = true;
}
}
/**
* Query the log manager for this context, which may in turn have its own
* set of configuration settings (loaded from the context's properties).
* Each context's logManager keeps its own isolated set of Log instances with
* their own log levels, output locations, and rotation configuration.
*/
public LogManager logManager() {
if (!_logManagerInitialized) initializeLogManager();
return _logManager;
}
private void initializeLogManager() {
synchronized (this) {
if (_logManager == null)
_logManager = new LogManager(this);
_logManagerInitialized = true;
}
}
/**
* There is absolutely no good reason to make this context specific,
* other than for consistency, and perhaps later we'll want to
* include some stats.
*/
public HMACSHA256Generator hmac() {
if (!_hmacInitialized) initializeHMAC();
return _hmac;
}
private void initializeHMAC() {
synchronized (this) {
if (_hmac == null)
_hmac= new HMACSHA256Generator(this);
_hmacInitialized = true;
}
}
/**
* Our SHA256 instance (see the hmac discussion for why its context specific)
*
*/
public SHA256Generator sha() {
if (!_shaInitialized) initializeSHA();
return _sha;
}
private void initializeSHA() {
synchronized (this) {
if (_sha == null)
_sha= new SHA256Generator(this);
_shaInitialized = true;
}
}
/**
* Our DSA engine (see HMAC and SHA above)
*
*/
public DSAEngine dsa() {
if (!_dsaInitialized) initializeDSA();
return _dsa;
}
private void initializeDSA() {
synchronized (this) {
if (_dsa == null)
_dsa = new DSAEngine(this);
_dsaInitialized = true;
}
}
/**
* Component to generate ElGamal, DSA, and Session keys. For why it is in
* the appContext, see the DSA, HMAC, and SHA comments above.
*/
public KeyGenerator keyGenerator() {
if (!_keyGeneratorInitialized) initializeKeyGenerator();
return _keyGenerator;
}
private void initializeKeyGenerator() {
synchronized (this) {
if (_keyGenerator == null)
_keyGenerator = new KeyGenerator(this);
_keyGeneratorInitialized = true;
}
}
/**
* The context's synchronized clock, which is kept context specific only to
* enable simulators to play with clock skew among different instances.
*
*/
public Clock clock() {
if (!_clockInitialized) initializeClock();
return _clock;
}
private void initializeClock() {
synchronized (this) {
if (_clock == null)
_clock = new Clock(this);
_clockInitialized = true;
}
}
/**
* Determine how much do we want to mess with the keys to turn them
* into something we can route. This is context specific because we
* may want to test out how things react when peers don't agree on
* how to skew.
*
*/
public RoutingKeyGenerator routingKeyGenerator() {
if (!_routingKeyGeneratorInitialized) initializeRoutingKeyGenerator();
return _routingKeyGenerator;
}
private void initializeRoutingKeyGenerator() {
synchronized (this) {
if (_routingKeyGenerator == null)
_routingKeyGenerator = new RoutingKeyGenerator(this);
_routingKeyGeneratorInitialized = true;
}
}
/**
* [insert snarky comment here]
*
*/
public RandomSource random() {
if (!_randomInitialized) initializeRandom();
return _random;
}
private void initializeRandom() {
synchronized (this) {
if (_random == null)
_random = new RandomSource(this);
_randomInitialized = true;
}
}
}

View File

@ -31,6 +31,7 @@ import net.i2p.util.Clock;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import net.i2p.util.LogManager;
import net.i2p.I2PAppContext;
/**
* ATalk - anonymous talk, demonstrating a trivial I2P usage scenario.
@ -290,6 +291,7 @@ public class ATalk implements I2PSessionListener, Runnable {
/** driver */
public static void main(String args[]) {
I2PAppContext context = new I2PAppContext();
if (args.length == 2) {
String myKeyFile = args[0];
String myDestinationFile = args[1];
@ -309,9 +311,9 @@ public class ATalk implements I2PSessionListener, Runnable {
String peerDestFile = args[1];
String shouldLog = args[2];
if (Boolean.TRUE.toString().equalsIgnoreCase(shouldLog))
LogManager.getInstance().setDisplayOnScreen(true);
context.logManager().setDisplayOnScreen(true);
else
LogManager.getInstance().setDisplayOnScreen(false);
context.logManager().setDisplayOnScreen(false);
String logFile = args[2];
Thread talkThread = new I2PThread(new ATalk(myKeyfile, peerDestFile));
talkThread.start();

View File

@ -11,6 +11,7 @@ package net.i2p.client;
import net.i2p.data.i2cp.DisconnectMessage;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.I2PAppContext;
/**
* Handle I2CP disconnect messages from the router
@ -18,8 +19,8 @@ import net.i2p.data.i2cp.I2CPMessage;
* @author jrandom
*/
class DisconnectMessageHandler extends HandlerImpl {
public DisconnectMessageHandler() {
super(DisconnectMessage.MESSAGE_TYPE);
public DisconnectMessageHandler(I2PAppContext context) {
super(context, DisconnectMessage.MESSAGE_TYPE);
}
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {

View File

@ -10,6 +10,7 @@ package net.i2p.client;
*/
import net.i2p.util.Log;
import net.i2p.I2PAppContext;
/**
* Base class for handling I2CP messages
@ -19,8 +20,10 @@ import net.i2p.util.Log;
abstract class HandlerImpl implements I2CPMessageHandler {
protected Log _log;
private int _type;
protected I2PAppContext _context;
public HandlerImpl(int type) {
public HandlerImpl(I2PAppContext context, int type) {
_context = context;
_type = type;
_log = new Log(getClass());
}

View File

@ -32,6 +32,7 @@ import net.i2p.data.i2cp.SendMessageMessage;
import net.i2p.data.i2cp.SessionConfig;
import net.i2p.util.Log;
import net.i2p.util.RandomSource;
import net.i2p.I2PAppContext;
/**
* Produce the various messages the session needs to send to the router.
@ -41,7 +42,12 @@ import net.i2p.util.RandomSource;
class I2CPMessageProducer {
private final static Log _log = new Log(I2CPMessageProducer.class);
private final static RandomSource _rand = RandomSource.getInstance();
private I2PAppContext _context;
public I2CPMessageProducer(I2PAppContext context) {
_context = context;
}
/**
* Send all the messages that a client needs to send to a router to establish
* a new session.
@ -102,7 +108,7 @@ class I2CPMessageProducer {
Payload data = new Payload();
// randomize padding
int size = payload.length + RandomSource.getInstance().nextInt(1024);
byte encr[] = ElGamalAESEngine.encrypt(payload, dest.getPublicKey(), key, tags, tag, newKey, size);
byte encr[] = _context.elGamalAESEngine().encrypt(payload, dest.getPublicKey(), key, tags, tag, newKey, size);
// yes, in an intelligent component, newTags would be queued for confirmation along with key, and
// generateNewTags would only generate tags if necessary

View File

@ -22,6 +22,7 @@ import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.data.SigningPrivateKey;
import net.i2p.data.SigningPublicKey;
import net.i2p.I2PAppContext;
/**
* Base client implementation
@ -70,7 +71,13 @@ class I2PClientImpl implements I2PClient {
*
*/
public I2PSession createSession(InputStream destKeyStream, Properties options) throws I2PSessionException {
//return new I2PSessionImpl(destKeyStream, options); // not thread safe
return new I2PSessionImpl2(destKeyStream, options); // thread safe
return createSession(I2PAppContext.getGlobalContext(), destKeyStream, options);
}
/**
* Create a new session (though do not connect it yet)
*
*/
public I2PSession createSession(I2PAppContext context, InputStream destKeyStream, Properties options) throws I2PSessionException {
return new I2PSessionImpl2(context, destKeyStream, options); // thread safe
}
}

View File

@ -19,6 +19,7 @@ import net.i2p.data.i2cp.RequestLeaseSetMessage;
import net.i2p.data.i2cp.SessionStatusMessage;
import net.i2p.data.i2cp.SetDateMessage;
import net.i2p.util.Log;
import net.i2p.I2PAppContext;
/**
* Contains a map of message handlers that a session will want to use
@ -28,19 +29,19 @@ import net.i2p.util.Log;
class I2PClientMessageHandlerMap {
private final static Log _log = new Log(I2PClientMessageHandlerMap.class);
/** map of message type id --> I2CPMessageHandler */
private static Map _handlers;
private Map _handlers;
static {
public I2PClientMessageHandlerMap(I2PAppContext context) {
_handlers = new HashMap();
_handlers.put(new Integer(DisconnectMessage.MESSAGE_TYPE), new DisconnectMessageHandler());
_handlers.put(new Integer(SessionStatusMessage.MESSAGE_TYPE), new SessionStatusMessageHandler());
_handlers.put(new Integer(RequestLeaseSetMessage.MESSAGE_TYPE), new RequestLeaseSetMessageHandler());
_handlers.put(new Integer(MessagePayloadMessage.MESSAGE_TYPE), new MessagePayloadMessageHandler());
_handlers.put(new Integer(MessageStatusMessage.MESSAGE_TYPE), new MessageStatusMessageHandler());
_handlers.put(new Integer(SetDateMessage.MESSAGE_TYPE), new SetDateMessageHandler());
_handlers.put(new Integer(DisconnectMessage.MESSAGE_TYPE), new DisconnectMessageHandler(context));
_handlers.put(new Integer(SessionStatusMessage.MESSAGE_TYPE), new SessionStatusMessageHandler(context));
_handlers.put(new Integer(RequestLeaseSetMessage.MESSAGE_TYPE), new RequestLeaseSetMessageHandler(context));
_handlers.put(new Integer(MessagePayloadMessage.MESSAGE_TYPE), new MessagePayloadMessageHandler(context));
_handlers.put(new Integer(MessageStatusMessage.MESSAGE_TYPE), new MessageStatusMessageHandler(context));
_handlers.put(new Integer(SetDateMessage.MESSAGE_TYPE), new SetDateMessageHandler(context));
}
public static I2CPMessageHandler getHandler(int messageTypeId) {
public I2CPMessageHandler getHandler(int messageTypeId) {
I2CPMessageHandler handler = (I2CPMessageHandler) _handlers.get(new Integer(messageTypeId));
return handler;
}

View File

@ -39,6 +39,7 @@ import net.i2p.data.i2cp.SessionId;
import net.i2p.util.Clock;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import net.i2p.I2PAppContext;
/**
* Implementation of an I2P session running over TCP. This class is NOT thread safe -
@ -47,7 +48,7 @@ import net.i2p.util.Log;
* @author jrandom
*/
abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessageEventListener {
private final static Log _log = new Log(I2PSessionImpl.class);
private Log _log;
/** who we are */
private Destination _myDestination;
/** private key for decryption */
@ -79,6 +80,11 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
protected I2CPMessageProducer _producer;
/** map of integer --> MessagePayloadMessage */
Map _availableMessages;
protected I2PClientMessageHandlerMap _handlerMap;
/** used to seperate things out so we can get rid of singletons */
protected I2PAppContext _context;
/** MessageStatusMessage status from the most recent send that hasn't been consumed */
private List _receivedStatus;
@ -108,9 +114,12 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
*
* @throws I2PSessionException if there is a problem loading the private keys or
*/
public I2PSessionImpl(InputStream destKeyStream, Properties options) throws I2PSessionException {
public I2PSessionImpl(I2PAppContext context, InputStream destKeyStream, Properties options) throws I2PSessionException {
_context = context;
_log = context.logManager().getLog(I2PSessionImpl.class);
_handlerMap = new I2PClientMessageHandlerMap(context);
_closed = true;
_producer = new I2CPMessageProducer();
_producer = new I2CPMessageProducer(context);
_availableMessages = new HashMap();
try {
readDestination(destKeyStream);
@ -139,13 +148,13 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
_portNum = Integer.parseInt(portNum);
} catch (NumberFormatException nfe) {
if (_log.shouldLog(Log.WARN))
_log.warn("Invalid port number specified, defaulting to "
+ TestServer.LISTEN_PORT, nfe);
_log.warn("Invalid port number specified, defaulting to "
+ TestServer.LISTEN_PORT, nfe);
_portNum = TestServer.LISTEN_PORT;
}
}
private static Properties filter(Properties options) {
private Properties filter(Properties options) {
Properties rv = new Properties();
for (Iterator iter = options.keySet().iterator(); iter.hasNext();) {
String key = (String) iter.next();
@ -212,7 +221,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
*/
public void connect() throws I2PSessionException {
_closed = false;
long startConnect = Clock.getInstance().now();
long startConnect = _context.clock().now();
try {
if (_log.shouldLog(Log.DEBUG)) _log.debug("connect begin to " + _hostname + ":" + _portNum);
_socket = new Socket(_hostname, _portNum);
@ -251,7 +260,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
}
}
}
long connected = Clock.getInstance().now();
long connected = _context.clock().now();
if (_log.shouldLog(Log.INFO))
_log.info("Lease set created with inbound tunnels after "
+ (connected - startConnect)
@ -309,6 +318,8 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
return tags;
}
private static volatile long __notifierId = 0;
/**
* Recieve a payload message and let the app know its available
*/
@ -328,9 +339,12 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
_sessionListener.messageAvailable(I2PSessionImpl.this, id, size);
}
});
notifier.setName("Notifier [" + _sessionId + "/" + id + "]");
long nid = ++__notifierId;
notifier.setName("Notifier " + nid);
notifier.setDaemon(true);
notifier.start();
if (_log.shouldLog(Log.INFO))
_log.info("Notifier " + nid + " is for session " + _sessionId + ", message " + id + "]");
}
}
@ -339,7 +353,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
*
*/
public void messageReceived(I2CPMessageReader reader, I2CPMessage message) {
I2CPMessageHandler handler = I2PClientMessageHandlerMap.getHandler(message.getType());
I2CPMessageHandler handler = _handlerMap.getHandler(message.getType());
if (handler == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Unknown message or unhandleable message received: type = "
@ -438,7 +452,6 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
if (_closed) return;
if (_log.shouldLog(Log.DEBUG)) _log.debug("Destroy the session", new Exception("DestroySession()"));
_closed = true;
if (sendDisconnect) {
try {
_producer.disconnect(this);
@ -446,6 +459,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
propogateError("Error destroying the session", ipe);
}
}
_closed = true;
closeSocket();
if (_sessionListener != null) _sessionListener.disconnected(this);
}

View File

@ -26,6 +26,7 @@ import net.i2p.data.i2cp.MessageStatusMessage;
import net.i2p.util.Clock;
import net.i2p.util.Log;
import net.i2p.util.RandomSource;
import net.i2p.I2PAppContext;
/**
* Thread safe implementation of an I2P session running over TCP.
@ -33,7 +34,7 @@ import net.i2p.util.RandomSource;
* @author jrandom
*/
class I2PSessionImpl2 extends I2PSessionImpl {
private final static Log _log = new Log(I2PSessionImpl2.class);
private Log _log;
/** set of MessageState objects, representing all of the messages in the process of being sent */
private Set _sendingStates;
@ -48,8 +49,9 @@ class I2PSessionImpl2 extends I2PSessionImpl {
*
* @throws I2PSessionException if there is a problem loading the private keys or
*/
public I2PSessionImpl2(InputStream destKeyStream, Properties options) throws I2PSessionException {
super(destKeyStream, options);
public I2PSessionImpl2(I2PAppContext ctx, InputStream destKeyStream, Properties options) throws I2PSessionException {
super(ctx, destKeyStream, options);
_log = ctx.logManager().getLog(I2PSessionImpl2.class);
_sendingStates = new HashSet(32);
}
@ -63,6 +65,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
}
public boolean sendMessage(Destination dest, byte[] payload) throws I2PSessionException {
return sendMessage(dest, payload, new SessionKey(), new HashSet(64));
}
@ -95,22 +98,22 @@ class I2PSessionImpl2 extends I2PSessionImpl {
private boolean sendBestEffort(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent)
throws I2PSessionException {
SessionKey key = SessionKeyManager.getInstance().getCurrentKey(dest.getPublicKey());
if (key == null) key = SessionKeyManager.getInstance().createSession(dest.getPublicKey());
SessionTag tag = SessionKeyManager.getInstance().consumeNextAvailableTag(dest.getPublicKey(), key);
SessionKey key = _context.sessionKeyManager().getCurrentKey(dest.getPublicKey());
if (key == null) key = _context.sessionKeyManager().createSession(dest.getPublicKey());
SessionTag tag = _context.sessionKeyManager().consumeNextAvailableTag(dest.getPublicKey(), key);
Set sentTags = null;
if (SessionKeyManager.getInstance().getAvailableTags(dest.getPublicKey(), key) < 10) {
if (_context.sessionKeyManager().getAvailableTags(dest.getPublicKey(), key) < 10) {
sentTags = createNewTags(50);
} else if (SessionKeyManager.getInstance().getAvailableTimeLeft(dest.getPublicKey(), key) < 30 * 1000) {
} 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("Tags are almost expired, adding 50 new ones");
}
SessionKey newKey = null;
if (false) // rekey
newKey = KeyGenerator.getInstance().generateSessionKey();
newKey = _context.keyGenerator().generateSessionKey();
long nonce = (long) RandomSource.getInstance().nextInt(Integer.MAX_VALUE);
long nonce = (long)_context.random().nextInt(Integer.MAX_VALUE);
MessageState state = new MessageState(nonce);
state.setKey(key);
state.setTags(sentTags);
@ -137,7 +140,8 @@ class I2PSessionImpl2 extends I2PSessionImpl {
_log.debug("Adding sending state " + state.getMessageId() + " / "
+ state.getNonce());
_producer.sendMessage(this, dest, nonce, payload, tag, key, sentTags, newKey);
state.waitFor(MessageStatusMessage.STATUS_SEND_ACCEPTED, Clock.getInstance().now() + getTimeout());
state.waitFor(MessageStatusMessage.STATUS_SEND_ACCEPTED,
_context.clock().now() + getTimeout());
synchronized (_sendingStates) {
_sendingStates.remove(state);
}
@ -163,22 +167,22 @@ class I2PSessionImpl2 extends I2PSessionImpl {
private boolean sendGuaranteed(Destination dest, byte payload[], SessionKey keyUsed, Set tagsSent)
throws I2PSessionException {
SessionKey key = SessionKeyManager.getInstance().getCurrentKey(dest.getPublicKey());
if (key == null) key = SessionKeyManager.getInstance().createSession(dest.getPublicKey());
SessionTag tag = SessionKeyManager.getInstance().consumeNextAvailableTag(dest.getPublicKey(), key);
SessionKey key = _context.sessionKeyManager().getCurrentKey(dest.getPublicKey());
if (key == null) key = _context.sessionKeyManager().createSession(dest.getPublicKey());
SessionTag tag = _context.sessionKeyManager().consumeNextAvailableTag(dest.getPublicKey(), key);
Set sentTags = null;
if (SessionKeyManager.getInstance().getAvailableTags(dest.getPublicKey(), key) < 10) {
if (_context.sessionKeyManager().getAvailableTags(dest.getPublicKey(), key) < 10) {
sentTags = createNewTags(50);
} else if (SessionKeyManager.getInstance().getAvailableTimeLeft(dest.getPublicKey(), key) < 30 * 1000) {
} 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("Tags are almost expired, adding 50 new ones");
}
SessionKey newKey = null;
if (false) // rekey
newKey = KeyGenerator.getInstance().generateSessionKey();
newKey = _context.keyGenerator().generateSessionKey();
long nonce = (long) RandomSource.getInstance().nextInt(Integer.MAX_VALUE);
long nonce = (long)_context.random().nextInt(Integer.MAX_VALUE);
MessageState state = new MessageState(nonce);
state.setKey(key);
state.setTags(sentTags);
@ -206,9 +210,11 @@ class I2PSessionImpl2 extends I2PSessionImpl {
+ state.getNonce());
_producer.sendMessage(this, dest, nonce, payload, tag, key, sentTags, newKey);
if (isGuaranteed())
state.waitFor(MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS, Clock.getInstance().now() + SEND_TIMEOUT);
state.waitFor(MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS,
_context.clock().now() + SEND_TIMEOUT);
else
state.waitFor(MessageStatusMessage.STATUS_SEND_ACCEPTED, Clock.getInstance().now() + SEND_TIMEOUT);
state.waitFor(MessageStatusMessage.STATUS_SEND_ACCEPTED,
_context.clock().now() + SEND_TIMEOUT);
synchronized (_sendingStates) {
_sendingStates.remove(state);
}
@ -218,11 +224,13 @@ class I2PSessionImpl2 extends I2PSessionImpl {
if ((!accepted) || (state.getMessageId() == null)) {
if (_log.shouldLog(Log.ERROR))
_log.error("State with nonce " + state.getNonce()
+ " was not accepted? (no messageId!!)");
+ " was not accepted? (no messageId!! found=" + found
+ " msgId=" + state.getMessageId() + ")",
new Exception("Race on accept/success status messages, or reconnected?"));
nackTags(state);
if (_log.shouldLog(Log.CRIT))
_log.log(Log.CRIT, "Disconnecting/reconnecting because we never were accepted!");
disconnect();
//if (_log.shouldLog(Log.CRIT))
// _log.log(Log.CRIT, "Disconnecting/reconnecting because we never were accepted!");
//disconnect();
return false;
}
@ -250,9 +258,9 @@ class I2PSessionImpl2 extends I2PSessionImpl {
+ state.getTags());
if ((state.getTags() != null) && (state.getTags().size() > 0)) {
if (state.getNewKey() == null)
SessionKeyManager.getInstance().tagsDelivered(state.getTo().getPublicKey(), state.getKey(), state.getTags());
_context.sessionKeyManager().tagsDelivered(state.getTo().getPublicKey(), state.getKey(), state.getTags());
else
SessionKeyManager.getInstance().tagsDelivered(state.getTo().getPublicKey(), state.getNewKey(), state.getTags());
_context.sessionKeyManager().tagsDelivered(state.getTo().getPublicKey(), state.getNewKey(), state.getTags());
}
}
@ -260,7 +268,7 @@ class I2PSessionImpl2 extends I2PSessionImpl {
if (_log.shouldLog(Log.INFO))
_log.info("nack tags for msgId " + state.getMessageId() + " / " + state.getNonce()
+ " key = " + state.getKey());
SessionKeyManager.getInstance().failTags(state.getTo().getPublicKey());
_context.sessionKeyManager().failTags(state.getTo().getPublicKey());
}
public void receiveStatus(int msgId, long nonce, int status) {

View File

@ -16,6 +16,7 @@ import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.MessageId;
import net.i2p.data.i2cp.MessagePayloadMessage;
import net.i2p.data.i2cp.ReceiveMessageEndMessage;
import net.i2p.I2PAppContext;
/**
* Handle I2CP MessagePayloadMessages from the router delivering the contents
@ -25,8 +26,8 @@ import net.i2p.data.i2cp.ReceiveMessageEndMessage;
* @author jrandom
*/
class MessagePayloadMessageHandler extends HandlerImpl {
public MessagePayloadMessageHandler() {
super(MessagePayloadMessage.MESSAGE_TYPE);
public MessagePayloadMessageHandler(I2PAppContext context) {
super(context, MessagePayloadMessage.MESSAGE_TYPE);
}
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
@ -53,7 +54,7 @@ class MessagePayloadMessageHandler extends HandlerImpl {
*/
private Payload decryptPayload(MessagePayloadMessage msg, I2PSessionImpl session) throws DataFormatException {
Payload payload = msg.getPayload();
byte[] data = ElGamalAESEngine.decrypt(payload.getEncryptedData(), session.getDecryptionKey());
byte[] data = _context.elGamalAESEngine().decrypt(payload.getEncryptedData(), session.getDecryptionKey());
if (data == null) {
_log
.error("Error decrypting the payload to public key "

View File

@ -12,6 +12,7 @@ package net.i2p.client;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.MessageStatusMessage;
import net.i2p.data.i2cp.ReceiveMessageBeginMessage;
import net.i2p.I2PAppContext;
/**
* Handle I2CP MessageStatusMessages from the router. This currently only takes
@ -21,8 +22,8 @@ import net.i2p.data.i2cp.ReceiveMessageBeginMessage;
* @author jrandom
*/
class MessageStatusMessageHandler extends HandlerImpl {
public MessageStatusMessageHandler() {
super(MessageStatusMessage.MESSAGE_TYPE);
public MessageStatusMessageHandler(I2PAppContext context) {
super(context, MessageStatusMessage.MESSAGE_TYPE);
}
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {

View File

@ -25,6 +25,7 @@ import net.i2p.data.SigningPublicKey;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.RequestLeaseSetMessage;
import net.i2p.util.Log;
import net.i2p.I2PAppContext;
/**
* Handle I2CP RequestLeaseSetMessage from the router by granting all leases
@ -35,8 +36,8 @@ class RequestLeaseSetMessageHandler extends HandlerImpl {
private final static Log _log = new Log(RequestLeaseSetMessageHandler.class);
private Map _existingLeaseSets;
public RequestLeaseSetMessageHandler() {
super(RequestLeaseSetMessage.MESSAGE_TYPE);
public RequestLeaseSetMessageHandler(I2PAppContext context) {
super(context, RequestLeaseSetMessage.MESSAGE_TYPE);
_existingLeaseSets = new HashMap(32);
}

View File

@ -11,6 +11,7 @@ package net.i2p.client;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.SessionStatusMessage;
import net.i2p.I2PAppContext;
/**
* Handle I2CP SessionStatusMessagese from the router, updating the session as
@ -19,8 +20,8 @@ import net.i2p.data.i2cp.SessionStatusMessage;
* @author jrandom
*/
class SessionStatusMessageHandler extends HandlerImpl {
public SessionStatusMessageHandler() {
super(SessionStatusMessage.MESSAGE_TYPE);
public SessionStatusMessageHandler(I2PAppContext context) {
super(context, SessionStatusMessage.MESSAGE_TYPE);
}
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {

View File

@ -12,6 +12,7 @@ package net.i2p.client;
import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.SetDateMessage;
import net.i2p.util.Clock;
import net.i2p.I2PAppContext;
/**
* Handle I2CP time messages from the router
@ -19,8 +20,8 @@ import net.i2p.util.Clock;
* @author jrandom
*/
class SetDateMessageHandler extends HandlerImpl {
public SetDateMessageHandler() {
super(SetDateMessage.MESSAGE_TYPE);
public SetDateMessageHandler(I2PAppContext ctx) {
super(ctx, SetDateMessage.MESSAGE_TYPE);
}
public void handleMessage(I2CPMessage message, I2PSessionImpl session) {

View File

@ -8,11 +8,21 @@
package net.i2p.client.naming;
import net.i2p.data.Destination;
import net.i2p.I2PAppContext;
/**
* A Dummy naming service that can only handle base64 destinations.
*/
class DummyNamingService extends NamingService {
/**
* The naming service should only be constructed and accessed through the
* application context. This constructor should only be used by the
* appropriate application context itself.
*
*/
protected DummyNamingService(I2PAppContext context) { super(context); }
private DummyNamingService() { super(null); }
public Destination lookup(String hostname) {
return lookupBase64(hostname);
}

View File

@ -14,12 +14,22 @@ import java.util.Properties;
import net.i2p.data.Destination;
import net.i2p.util.Log;
import net.i2p.I2PAppContext;
/**
* A naming service based on the "hosts.txt" file.
*/
public class HostsTxtNamingService extends NamingService {
/**
* The naming service should only be constructed and accessed through the
* application context. This constructor should only be used by the
* appropriate application context itself.
*
*/
public HostsTxtNamingService(I2PAppContext context) { super(context); }
private HostsTxtNamingService() { super(null); }
/**
* If this system property is specified, the tunnel will read the
* given file for hostname=destKey values when resolving names
@ -35,7 +45,7 @@ public class HostsTxtNamingService extends NamingService {
// Try to look it up in hosts.txt
// Reload file each time to catch changes.
// (and it's easier :P
String hostsfile = System.getProperty(PROP_HOSTS_FILE, DEFAULT_HOSTS_FILE);
String hostsfile = _context.getProperty(PROP_HOSTS_FILE, DEFAULT_HOSTS_FILE);
Properties hosts = new Properties();
FileInputStream fis = null;
try {

View File

@ -10,6 +10,9 @@ package net.i2p.client.naming;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.Log;
import net.i2p.I2PAppContext;
import java.lang.reflect.Constructor;
/**
* Naming services create a subclass of this class.
@ -17,10 +20,23 @@ import net.i2p.util.Log;
public abstract class NamingService {
private final static Log _log = new Log(NamingService.class);
protected I2PAppContext _context;
private static final String PROP_IMPL = "i2p.naming.impl";
private static final String DEFAULT_IMPL = "net.i2p.client.naming.HostsTxtNamingService";
/**
* The naming service should only be constructed and accessed through the
* application context. This constructor should only be used by the
* appropriate application context itself.
*
*/
protected NamingService(I2PAppContext context) {
_context = context;
}
private NamingService() {}
/**
* Look up a host name.
* @return the Destination for this host name, or
@ -52,23 +68,22 @@ public abstract class NamingService {
}
}
private static NamingService instance = null;
/**
* Get a naming service instance. This method ensures that there
* will be only one naming service instance (singleton) as well as
* choose the implementation from the "i2p.naming.impl" system
* property.
*/
public static synchronized NamingService getInstance() {
if (instance == null) {
String impl = System.getProperty(PROP_IMPL, DEFAULT_IMPL);
try {
instance = (NamingService) Class.forName(impl).newInstance();
} catch (Exception ex) {
_log.error("Cannot loadNaming service implementation", ex);
instance = new DummyNamingService(); // fallback
}
public static final synchronized NamingService createInstance(I2PAppContext context) {
NamingService instance = null;
String impl = context.getProperty(PROP_IMPL, DEFAULT_IMPL);
try {
Class cls = Class.forName(impl);
Constructor con = cls.getConstructor(new Class[] { I2PAppContext.class });
instance = (NamingService)con.newInstance(new Object[] { context });
} catch (Exception ex) {
_log.error("Cannot loadNaming service implementation", ex);
instance = new DummyNamingService(context); // fallback
}
return instance;
}

View File

@ -19,26 +19,22 @@ import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
import net.i2p.util.Log;
import net.i2p.util.RandomSource;
import net.i2p.I2PAppContext;
/**
* Wrapper singleton for AES cypher operation.
* Dummy wrapper for AES cipher operation.
*
* @author jrandom
*/
public class AESEngine {
private final static Log _log = new Log(AESEngine.class);
private static AESEngine _engine;
static {
if ("off".equals(System.getProperty("i2p.encryption", "on")))
_engine = new AESEngine();
else
_engine = new CryptixAESEngine();
private Log _log;
private I2PAppContext _context;
public AESEngine(I2PAppContext ctx) {
_context = ctx;
_log = _context.logManager().getLog(AESEngine.class);
if (getClass() == AESEngine.class)
_log.warn("Warning: AES is disabled");
}
public static AESEngine getInstance() {
return _engine;
}
/** Encrypt the payload with the session key
* @param payload data to be encrypted
* @param sessionKey private esession key to encrypt to
@ -50,7 +46,6 @@ public class AESEngine {
|| (initializationVector.length != 16)) return null;
byte cyphertext[] = new byte[payload.length + (16 - (payload.length % 16))];
_log.warn("Warning: AES is disabled");
System.arraycopy(payload, 0, cyphertext, 0, payload.length);
return cyphertext;
}
@ -59,13 +54,13 @@ public class AESEngine {
if ((iv == null) || (payload == null) || (sessionKey == null) || (iv.length != 16)) return null;
ByteArrayOutputStream baos = new ByteArrayOutputStream(paddedSize + 64);
Hash h = SHA256Generator.getInstance().calculateHash(sessionKey.getData());
Hash h = _context.sha().calculateHash(sessionKey.getData());
try {
h.writeBytes(baos);
DataHelper.writeLong(baos, 4, payload.length);
baos.write(payload);
byte tv[] = baos.toByteArray();
baos.write(ElGamalAESEngine.getPadding(tv.length, paddedSize));
baos.write(ElGamalAESEngine.getPadding(_context, tv.length, paddedSize));
} catch (IOException ioe) {
_log.error("Error writing data", ioe);
return null;
@ -85,7 +80,7 @@ public class AESEngine {
return null;
}
ByteArrayInputStream bais = new ByteArrayInputStream(decr);
Hash h = SHA256Generator.getInstance().calculateHash(sessionKey.getData());
Hash h = _context.sha().calculateHash(sessionKey.getData());
try {
Hash rh = new Hash();
rh.readBytes(bais);
@ -127,20 +122,21 @@ public class AESEngine {
}
public static void main(String args[]) {
SessionKey key = KeyGenerator.getInstance().generateSessionKey();
I2PAppContext ctx = new I2PAppContext();
SessionKey key = ctx.keyGenerator().generateSessionKey();
byte iv[] = new byte[16];
RandomSource.getInstance().nextBytes(iv);
byte sbuf[] = new byte[16];
RandomSource.getInstance().nextBytes(sbuf);
byte se[] = AESEngine.getInstance().encrypt(sbuf, key, iv);
byte sd[] = AESEngine.getInstance().decrypt(se, key, iv);
_log.debug("Short test: " + DataHelper.eq(sd, sbuf));
byte se[] = ctx.AESEngine().encrypt(sbuf, key, iv);
byte sd[] = ctx.AESEngine().decrypt(se, key, iv);
ctx.logManager().getLog(AESEngine.class).debug("Short test: " + DataHelper.eq(sd, sbuf));
byte lbuf[] = new byte[1024];
RandomSource.getInstance().nextBytes(sbuf);
byte le[] = AESEngine.getInstance().safeEncrypt(lbuf, key, iv, 2048);
byte ld[] = AESEngine.getInstance().safeDecrypt(le, key, iv);
_log.debug("Long test: " + DataHelper.eq(ld, lbuf));
byte le[] = ctx.AESEngine().safeEncrypt(lbuf, key, iv, 2048);
byte ld[] = ctx.AESEngine().safeDecrypt(le, key, iv);
ctx.logManager().getLog(AESEngine.class).debug("Long test: " + DataHelper.eq(ld, lbuf));
}
}

View File

@ -24,6 +24,7 @@ import net.i2p.data.SessionKey;
import net.i2p.util.Clock;
import net.i2p.util.Log;
import net.i2p.util.RandomSource;
import net.i2p.I2PAppContext;
/**
* This reads an underlying stream as written by AESOutputStream - AES256 encrypted
@ -36,8 +37,8 @@ import net.i2p.util.RandomSource;
*
*/
public class AESInputStream extends FilterInputStream {
private final static Log _log = new Log(AESInputStream.class);
private final static CryptixAESEngine _engine = new CryptixAESEngine();
private Log _log;
private I2PAppContext _context;
private SessionKey _key;
private byte[] _lastBlock;
private boolean _eofFound;
@ -52,8 +53,10 @@ public class AESInputStream extends FilterInputStream {
private final static int READ_SIZE = BLOCK_SIZE;
private final static int DECRYPT_SIZE = BLOCK_SIZE - 1;
public AESInputStream(InputStream source, SessionKey key, byte iv[]) {
public AESInputStream(I2PAppContext context, InputStream source, SessionKey key, byte iv[]) {
super(source);
_context = context;
_log = context.logManager().getLog(AESInputStream.class);
_key = key;
_lastBlock = new byte[BLOCK_SIZE];
System.arraycopy(iv, 0, _lastBlock, 0, BLOCK_SIZE);
@ -223,8 +226,8 @@ public class AESInputStream extends FilterInputStream {
byte block[] = new byte[BLOCK_SIZE];
for (int i = 0; i < numBlocks; i++) {
System.arraycopy(encrypted, i * BLOCK_SIZE, block, 0, BLOCK_SIZE);
byte decrypted[] = _engine.decrypt(block, _key, _lastBlock);
byte data[] = CryptixAESEngine.xor(decrypted, _lastBlock);
byte decrypted[] = _context.AESEngine().decrypt(block, _key, _lastBlock);
byte data[] = DataHelper.xor(decrypted, _lastBlock);
int cleaned[] = stripPadding(data);
for (int j = 0; j < cleaned.length; j++) {
if (((int) cleaned[j]) <= 0) {
@ -297,6 +300,8 @@ public class AESInputStream extends FilterInputStream {
* Test AESOutputStream/AESInputStream
*/
public static void main(String args[]) {
I2PAppContext ctx = new I2PAppContext();
Log log = ctx.logManager().getLog(AESInputStream.class);
byte orig[] = new byte[1024 * 32];
RandomSource.getInstance().nextBytes(orig);
//byte orig[] = "you are my sunshine, my only sunshine".getBytes();
@ -304,40 +309,40 @@ public class AESInputStream extends FilterInputStream {
byte iv[] = "there once was a".getBytes();
for (int i = 0; i < 20; i++) {
runTest(orig, key, iv);
runTest(ctx, orig, key, iv);
}
_log.info("Done testing 32KB data");
log.info("Done testing 32KB data");
orig = new byte[20];
RandomSource.getInstance().nextBytes(orig);
for (int i = 0; i < 20; i++) {
runTest(orig, key, iv);
runTest(ctx, orig, key, iv);
}
_log.info("Done testing 20 byte data");
log.info("Done testing 20 byte data");
orig = new byte[3];
RandomSource.getInstance().nextBytes(orig);
for (int i = 0; i < 20; i++) {
runTest(orig, key, iv);
runTest(ctx, orig, key, iv);
}
_log.info("Done testing 3 byte data");
log.info("Done testing 3 byte data");
orig = new byte[0];
RandomSource.getInstance().nextBytes(orig);
for (int i = 0; i < 20; i++) {
runTest(orig, key, iv);
runTest(ctx, orig, key, iv);
}
_log.info("Done testing 0 byte data");
log.info("Done testing 0 byte data");
orig = new byte[32];
RandomSource.getInstance().nextBytes(orig);
runOffsetTest(orig, key, iv);
runOffsetTest(ctx, orig, key, iv);
_log.info("Done testing offset test (it should have come back with a statement NOT EQUAL!)");
log.info("Done testing offset test (it should have come back with a statement NOT EQUAL!)");
try {
Thread.sleep(30 * 1000);
@ -345,11 +350,12 @@ public class AESInputStream extends FilterInputStream {
}
}
private static void runTest(byte orig[], SessionKey key, byte[] iv) {
private static void runTest(I2PAppContext ctx, byte orig[], SessionKey key, byte[] iv) {
Log log = ctx.logManager().getLog(AESInputStream.class);
try {
long start = Clock.getInstance().now();
ByteArrayOutputStream origStream = new ByteArrayOutputStream(512);
AESOutputStream out = new AESOutputStream(origStream, key, iv);
AESOutputStream out = new AESOutputStream(ctx, origStream, key, iv);
out.write(orig);
out.close();
@ -357,7 +363,7 @@ public class AESInputStream extends FilterInputStream {
long endE = Clock.getInstance().now();
ByteArrayInputStream encryptedStream = new ByteArrayInputStream(encrypted);
AESInputStream in = new AESInputStream(encryptedStream, key, iv);
AESInputStream in = new AESInputStream(ctx, encryptedStream, key, iv);
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
byte buf[] = new byte[1024 * 32];
int read = DataHelper.read(in, buf);
@ -370,65 +376,66 @@ public class AESInputStream extends FilterInputStream {
Hash newHash = SHA256Generator.getInstance().calculateHash(fin);
boolean eq = origHash.equals(newHash);
if (eq)
_log.info("Equal hashes. hash: " + origHash);
log.info("Equal hashes. hash: " + origHash);
else
_log.error("NOT EQUAL! \norig: \t" + Base64.encode(orig) + "\nnew : \t" + Base64.encode(fin));
log.error("NOT EQUAL! \norig: \t" + Base64.encode(orig) + "\nnew : \t" + Base64.encode(fin));
boolean ok = DataHelper.eq(orig, fin);
_log.debug("EQ data? " + ok + " origLen: " + orig.length + " fin.length: " + fin.length);
_log.debug("Time to D(E(" + orig.length + ")): " + (end - start) + "ms");
_log.debug("Time to E(" + orig.length + "): " + (endE - start) + "ms");
_log.debug("Time to D(" + orig.length + "): " + (end - endE) + "ms");
log.debug("EQ data? " + ok + " origLen: " + orig.length + " fin.length: " + fin.length);
log.debug("Time to D(E(" + orig.length + ")): " + (end - start) + "ms");
log.debug("Time to E(" + orig.length + "): " + (endE - start) + "ms");
log.debug("Time to D(" + orig.length + "): " + (end - endE) + "ms");
} catch (Throwable t) {
_log.error("ERROR transferring", t);
log.error("ERROR transferring", t);
}
//try { Thread.sleep(5000); } catch (Throwable t) {}
}
private static void runOffsetTest(byte orig[], SessionKey key, byte[] iv) {
private static void runOffsetTest(I2PAppContext ctx, byte orig[], SessionKey key, byte[] iv) {
Log log = ctx.logManager().getLog(AESInputStream.class);
try {
long start = Clock.getInstance().now();
ByteArrayOutputStream origStream = new ByteArrayOutputStream(512);
AESOutputStream out = new AESOutputStream(origStream, key, iv);
AESOutputStream out = new AESOutputStream(ctx, origStream, key, iv);
out.write(orig);
out.close();
byte encrypted[] = origStream.toByteArray();
long endE = Clock.getInstance().now();
_log.info("Encrypted segment length: " + encrypted.length);
log.info("Encrypted segment length: " + encrypted.length);
byte encryptedSegment[] = new byte[40];
System.arraycopy(encrypted, 0, encryptedSegment, 0, 40);
ByteArrayInputStream encryptedStream = new ByteArrayInputStream(encryptedSegment);
AESInputStream in = new AESInputStream(encryptedStream, key, iv);
AESInputStream in = new AESInputStream(ctx, encryptedStream, key, iv);
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
byte buf[] = new byte[1024 * 32];
int read = DataHelper.read(in, buf);
int remaining = in.remainingBytes();
int readyBytes = in.readyBytes();
_log.info("Read: " + read);
log.info("Read: " + read);
if (read > 0) baos.write(buf, 0, read);
in.close();
byte fin[] = baos.toByteArray();
_log.info("fin.length: " + fin.length + " remaining: " + remaining + " ready: " + readyBytes);
log.info("fin.length: " + fin.length + " remaining: " + remaining + " ready: " + readyBytes);
long end = Clock.getInstance().now();
Hash origHash = SHA256Generator.getInstance().calculateHash(orig);
Hash newHash = SHA256Generator.getInstance().calculateHash(fin);
boolean eq = origHash.equals(newHash);
if (eq)
_log.info("Equal hashes. hash: " + origHash);
log.info("Equal hashes. hash: " + origHash);
else
_log.error("NOT EQUAL! \norig: \t" + Base64.encode(orig) + "\nnew : \t" + Base64.encode(fin));
log.error("NOT EQUAL! \norig: \t" + Base64.encode(orig) + "\nnew : \t" + Base64.encode(fin));
boolean ok = DataHelper.eq(orig, fin);
_log.debug("EQ data? " + ok + " origLen: " + orig.length + " fin.length: " + fin.length);
_log.debug("Time to D(E(" + orig.length + ")): " + (end - start) + "ms");
_log.debug("Time to E(" + orig.length + "): " + (endE - start) + "ms");
_log.debug("Time to D(" + orig.length + "): " + (end - endE) + "ms");
log.debug("EQ data? " + ok + " origLen: " + orig.length + " fin.length: " + fin.length);
log.debug("Time to D(E(" + orig.length + ")): " + (end - start) + "ms");
log.debug("Time to E(" + orig.length + "): " + (endE - start) + "ms");
log.debug("Time to D(" + orig.length + "): " + (end - endE) + "ms");
} catch (Throwable t) {
_log.error("ERROR transferring", t);
log.error("ERROR transferring", t);
}
//try { Thread.sleep(5000); } catch (Throwable t) {}
}

View File

@ -16,7 +16,9 @@ import java.io.OutputStream;
import java.util.Arrays;
import net.i2p.data.SessionKey;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
import net.i2p.I2PAppContext;
/**
* This writes everything as CBC with PKCS#5 padding, but each block is padded
@ -28,8 +30,8 @@ import net.i2p.util.Log;
*
*/
public class AESOutputStream extends FilterOutputStream {
private final static CryptixAESEngine _engine = new CryptixAESEngine();
private final static Log _log = new Log(AESOutputStream.class);
private Log _log;
private I2PAppContext _context;
private SessionKey _key;
private byte[] _lastBlock;
private ByteArrayOutputStream _inBuf;
@ -42,8 +44,10 @@ public class AESOutputStream extends FilterOutputStream {
private final static int BLOCK_SIZE = CryptixRijndael_Algorithm._BLOCK_SIZE;
private final static int MAX_BUF = 256;
public AESOutputStream(OutputStream source, SessionKey key, byte[] iv) {
public AESOutputStream(I2PAppContext context, OutputStream source, SessionKey key, byte[] iv) {
super(source);
_context = context;
_log = context.logManager().getLog(AESOutputStream.class);
_key = key;
_lastBlock = new byte[BLOCK_SIZE];
System.arraycopy(iv, 0, _lastBlock, 0, BLOCK_SIZE);
@ -104,8 +108,8 @@ public class AESOutputStream extends FilterOutputStream {
block[BLOCK_SIZE - 1] = 0x01; // the padding byte for "full" blocks
for (int i = 0; i < numBlocks; i++) {
System.arraycopy(src, i * 15, block, 0, 15);
byte data[] = _engine.xor(block, _lastBlock);
byte encrypted[] = _engine.encrypt(data, _key, _lastBlock);
byte data[] = DataHelper.xor(block, _lastBlock);
byte encrypted[] = _context.AESEngine().encrypt(data, _key, _lastBlock);
_cumulativeWritten += encrypted.length;
out.write(encrypted);
System.arraycopy(encrypted, encrypted.length - BLOCK_SIZE, _lastBlock, 0, BLOCK_SIZE);
@ -118,8 +122,8 @@ public class AESOutputStream extends FilterOutputStream {
int paddingBytes = BLOCK_SIZE - remainingBytes;
System.arraycopy(src, numBlocks * 15, block, 0, remainingBytes);
Arrays.fill(block, remainingBytes, BLOCK_SIZE, (byte) paddingBytes);
byte data[] = _engine.xor(block, _lastBlock);
byte encrypted[] = _engine.encrypt(data, _key, _lastBlock);
byte data[] = DataHelper.xor(block, _lastBlock);
byte encrypted[] = _context.AESEngine().encrypt(data, _key, _lastBlock);
out.write(encrypted);
System.arraycopy(encrypted, encrypted.length - BLOCK_SIZE, _lastBlock, 0, BLOCK_SIZE);
_cumulativePadding += paddingBytes;

View File

@ -13,6 +13,7 @@ import java.security.InvalidKeyException;
import net.i2p.data.SessionKey;
import net.i2p.util.Log;
import net.i2p.I2PAppContext;
/**
* Wrapper for AES cypher operation using Cryptix's Rijndael implementation. Implements
@ -23,10 +24,15 @@ import net.i2p.util.Log;
* @author jrandom, thecrypto
*/
public class CryptixAESEngine extends AESEngine {
private final static Log _log = new Log(CryptixAESEngine.class);
private Log _log;
private final static CryptixRijndael_Algorithm _algo = new CryptixRijndael_Algorithm();
private final static boolean USE_FAKE_CRYPTO = false;
private final static byte FAKE_KEY = 0x2A;
public CryptixAESEngine(I2PAppContext context) {
super(context);
_log = context.logManager().getLog(CryptixAESEngine.class);
}
public byte[] encrypt(byte payload[], SessionKey sessionKey, byte initializationVector[]) {
if ((initializationVector == null) || (payload == null) || (payload.length <= 0) || (sessionKey == null)
@ -116,7 +122,7 @@ public class CryptixAESEngine extends AESEngine {
* @param sessionKey private esession key to encrypt to
* @return encrypted data
*/
final static byte[] encrypt(byte payload[], SessionKey sessionKey) {
final byte[] encrypt(byte payload[], SessionKey sessionKey) {
try {
Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16);
byte rv[] = new byte[payload.length];
@ -133,7 +139,7 @@ public class CryptixAESEngine extends AESEngine {
* @param sessionKey private session key
* @return unencrypted data
*/
final static byte[] decrypt(byte payload[], SessionKey sessionKey) {
final byte[] decrypt(byte payload[], SessionKey sessionKey) {
try {
Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16);
byte rv[] = new byte[payload.length];

View File

@ -20,6 +20,7 @@ import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import net.i2p.util.NativeBigInteger;
import net.i2p.util.RandomSource;
import net.i2p.I2PAppContext;
/**
* Generate a new session key through a diffie hellman exchange. This uses the
@ -62,22 +63,23 @@ public class DHSessionKeyBuilder {
public final static String DEFAULT_DH_PRECALC_DELAY = "1000";
static {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
try {
int val = Integer.parseInt(System.getProperty(PROP_DH_PRECALC_MIN, DEFAULT_DH_PRECALC_MIN));
int val = Integer.parseInt(ctx.getProperty(PROP_DH_PRECALC_MIN, DEFAULT_DH_PRECALC_MIN));
MIN_NUM_BUILDERS = val;
} catch (Throwable t) {
int val = Integer.parseInt(DEFAULT_DH_PRECALC_MIN);
MIN_NUM_BUILDERS = val;
}
try {
int val = Integer.parseInt(System.getProperty(PROP_DH_PRECALC_MAX, DEFAULT_DH_PRECALC_MAX));
int val = Integer.parseInt(ctx.getProperty(PROP_DH_PRECALC_MAX, DEFAULT_DH_PRECALC_MAX));
MAX_NUM_BUILDERS = val;
} catch (Throwable t) {
int val = Integer.parseInt(DEFAULT_DH_PRECALC_MAX);
MAX_NUM_BUILDERS = val;
}
try {
int val = Integer.parseInt(System.getProperty(PROP_DH_PRECALC_DELAY, DEFAULT_DH_PRECALC_DELAY));
int val = Integer.parseInt(ctx.getProperty(PROP_DH_PRECALC_DELAY, DEFAULT_DH_PRECALC_DELAY));
CALC_DELAY = val;
} catch (Throwable t) {
int val = Integer.parseInt(DEFAULT_DH_PRECALC_DELAY);
@ -266,6 +268,7 @@ public class DHSessionKeyBuilder {
Thread.sleep(20 * 1000);
} catch (InterruptedException ie) {
}
I2PAppContext ctx = new I2PAppContext();
_log.debug("\n\n\n\nBegin test\n");
long negTime = 0;
for (int i = 0; i < 5; i++) {
@ -289,8 +292,8 @@ public class DHSessionKeyBuilder {
byte iv[] = new byte[16];
RandomSource.getInstance().nextBytes(iv);
String origVal = "1234567890123456"; // 16 bytes max using AESEngine
byte enc[] = AESEngine.getInstance().encrypt(origVal.getBytes(), key1, iv);
byte dec[] = AESEngine.getInstance().decrypt(enc, key2, iv);
byte enc[] = ctx.AESEngine().encrypt(origVal.getBytes(), key1, iv);
byte dec[] = ctx.AESEngine().decrypt(enc, key2, iv);
String tranVal = new String(dec);
if (origVal.equals(tranVal))
_log.debug("**Success: D(E(val)) == val");

View File

@ -39,17 +39,22 @@ import net.i2p.util.Clock;
import net.i2p.util.Log;
import net.i2p.util.NativeBigInteger;
import net.i2p.util.RandomSource;
import net.i2p.I2PAppContext;
public class DSAEngine {
private final static Log _log = new Log(DSAEngine.class);
private static DSAEngine _instance = new DSAEngine();
private Log _log;
private I2PAppContext _context;
public static DSAEngine getInstance() {
return _instance;
public DSAEngine(I2PAppContext context) {
_log = context.logManager().getLog(DSAEngine.class);
_context = context;
}
public static DSAEngine getInstance() {
return I2PAppContext.getGlobalContext().dsa();
}
public boolean verifySignature(Signature signature, byte signedData[], SigningPublicKey verifyingKey) {
long start = Clock.getInstance().now();
long start = _context.clock().now();
byte[] sigbytes = signature.getData();
byte rbytes[] = new byte[20];
@ -65,22 +70,20 @@ public class DSAEngine {
BigInteger r = new NativeBigInteger(1, rbytes);
BigInteger y = new NativeBigInteger(1, verifyingKey.getData());
BigInteger w = s.modInverse(CryptoConstants.dsaq);
BigInteger u1 = ((new NativeBigInteger(1, calculateHash(signedData).getData())).multiply(w))
.mod(CryptoConstants.dsaq);
byte data[] = calculateHash(signedData).getData();
NativeBigInteger bi = new NativeBigInteger(1, data);
BigInteger u1 = bi.multiply(w).mod(CryptoConstants.dsaq);
BigInteger u2 = r.multiply(w).mod(CryptoConstants.dsaq);
BigInteger v = ((CryptoConstants.dsag.modPow(u1, CryptoConstants.dsap))
.multiply(y.modPow(u2,
CryptoConstants.dsap)))
.mod(
CryptoConstants.dsap)
.mod(
CryptoConstants.dsaq);
BigInteger modval = CryptoConstants.dsag.modPow(u1, CryptoConstants.dsap);
BigInteger modmulval = modval.multiply(y.modPow(u2,CryptoConstants.dsap));
BigInteger v = (modmulval).mod(CryptoConstants.dsap).mod(CryptoConstants.dsaq);
boolean ok = v.compareTo(r) == 0;
long diff = Clock.getInstance().now() - start;
long diff = _context.clock().now() - start;
if (diff > 1000) {
if (_log.shouldLog(Log.WARN)) _log.warn("Took too long to verify the signature (" + diff + "ms)");
if (_log.shouldLog(Log.WARN))
_log.warn("Took too long to verify the signature (" + diff + "ms)");
}
return ok;
@ -88,13 +91,13 @@ public class DSAEngine {
public Signature sign(byte data[], SigningPrivateKey signingKey) {
if ((signingKey == null) || (data == null) || (data.length <= 0)) return null;
long start = Clock.getInstance().now();
long start = _context.clock().now();
Signature sig = new Signature();
BigInteger k;
do {
k = new BigInteger(160, RandomSource.getInstance());
k = new BigInteger(160, _context.random());
} while (k.compareTo(CryptoConstants.dsaq) != 1);
BigInteger r = CryptoConstants.dsag.modPow(k, CryptoConstants.dsap).mod(CryptoConstants.dsaq);
@ -139,7 +142,7 @@ public class DSAEngine {
}
sig.setData(out);
long diff = Clock.getInstance().now() - start;
long diff = _context.clock().now() - start;
if (diff > 1000) {
if (_log.shouldLog(Log.WARN)) _log.warn("Took too long to sign (" + diff + "ms)");
}

View File

@ -17,6 +17,7 @@ import net.i2p.data.Hash;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
import net.i2p.util.Log;
import net.i2p.I2PAppContext;
/**
* Fake ElG E and D, useful for when performance isn't being tested
@ -24,13 +25,22 @@ import net.i2p.util.Log;
* @author jrandom
*/
public class DummyElGamalEngine extends ElGamalEngine {
private final static Log _log = new Log(DummyElGamalEngine.class);
private Log _log;
public DummyElGamalEngine() {
/**
* The ElGamal engine should only be constructed and accessed through the
* application context. This constructor should only be used by the
* appropriate application context itself.
*
*/
public DummyElGamalEngine(I2PAppContext context) {
super(context);
_log = context.logManager().getLog(DummyElGamalEngine.class);
_log.log(Log.CRIT, "Dummy ElGamal engine in use! NO DATA SECURITY. Danger Will Robinson, Danger!",
new Exception("I really hope you know what you're doing"));
}
private DummyElGamalEngine() { super(null); }
/** encrypt the data to the public key
* @return encrypted data
* @param publicKey public key encrypt to
@ -84,11 +94,13 @@ public class DummyElGamalEngine extends ElGamalEngine {
}
Hash calcHash = SHA256Generator.getInstance().calculateHash(rv);
if (calcHash.equals(hash)) {
_log.debug("Hash matches: " + DataHelper.toString(hash.getData(), hash.getData().length));
if (_log.shouldLog(Log.DEBUG))
_log.debug("Hash matches: " + DataHelper.toString(hash.getData(), hash.getData().length));
return rv;
} else {
_log.debug("Doesn't match hash [calc=" + calcHash + " sent hash=" + hash + "]\ndata = " + new String(rv),
new Exception("Doesn't match"));
if (_log.shouldLog(Log.DEBUG))
_log.debug("Doesn't match hash [calc=" + calcHash + " sent hash=" + hash + "]\ndata = " + new String(rv),
new Exception("Doesn't match"));
return null;
}
}

View File

@ -29,6 +29,7 @@ import net.i2p.stat.StatManager;
import net.i2p.util.Clock;
import net.i2p.util.Log;
import net.i2p.util.RandomSource;
import net.i2p.I2PAppContext;
/**
* Handles the actual ElGamal+AES encryption and decryption scenarios using the
@ -37,28 +38,27 @@ import net.i2p.util.RandomSource;
public class ElGamalAESEngine {
private final static Log _log = new Log(ElGamalAESEngine.class);
private final static int MIN_ENCRYPTED_SIZE = 80; // smallest possible resulting size
private I2PAppContext _context;
static {
StatManager.getInstance()
.createFrequencyStat("crypto.elGamalAES.encryptNewSession",
"how frequently we encrypt to a new ElGamal/AES+SessionTag session?",
"Encryption", new long[] { 60 * 1000l, 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
StatManager.getInstance()
.createFrequencyStat("crypto.elGamalAES.encryptExistingSession",
"how frequently we encrypt to an existing ElGamal/AES+SessionTag session?",
"Encryption", new long[] { 60 * 1000l, 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
StatManager.getInstance()
.createFrequencyStat("crypto.elGamalAES.decryptNewSession",
"how frequently we decrypt with a new ElGamal/AES+SessionTag session?",
"Encryption", new long[] { 60 * 1000l, 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
StatManager.getInstance()
.createFrequencyStat("crypto.elGamalAES.decryptExistingSession",
"how frequently we decrypt with an existing ElGamal/AES+SessionTag session?",
"Encryption", new long[] { 60 * 1000l, 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
StatManager.getInstance()
.createFrequencyStat("crypto.elGamalAES.decryptFail",
"how frequently we fail to decrypt with ElGamal/AES+SessionTag?", "Encryption",
new long[] { 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
private ElGamalAESEngine() {}
public ElGamalAESEngine(I2PAppContext ctx) {
_context = ctx;
_context.statManager().createFrequencyStat("crypto.elGamalAES.encryptNewSession",
"how frequently we encrypt to a new ElGamal/AES+SessionTag session?",
"Encryption", new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l});
_context.statManager().createFrequencyStat("crypto.elGamalAES.encryptExistingSession",
"how frequently we encrypt to an existing ElGamal/AES+SessionTag session?",
"Encryption", new long[] { 60 * 1000l, 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
_context.statManager().createFrequencyStat("crypto.elGamalAES.decryptNewSession",
"how frequently we decrypt with a new ElGamal/AES+SessionTag session?",
"Encryption", new long[] { 60 * 1000l, 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
_context.statManager().createFrequencyStat("crypto.elGamalAES.decryptExistingSession",
"how frequently we decrypt with an existing ElGamal/AES+SessionTag session?",
"Encryption", new long[] { 60 * 1000l, 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
_context.statManager().createFrequencyStat("crypto.elGamalAES.decryptFail",
"how frequently we fail to decrypt with ElGamal/AES+SessionTag?", "Encryption",
new long[] { 60 * 60 * 1000l, 24 * 60 * 60 * 1000l});
}
/**
@ -66,7 +66,7 @@ public class ElGamalAESEngine {
* ElGamal+AES algorithm in the data structure spec.
*
*/
public static byte[] decrypt(byte data[], PrivateKey targetPrivateKey) throws DataFormatException {
public byte[] decrypt(byte data[], PrivateKey targetPrivateKey) throws DataFormatException {
if (data == null) {
if (_log.shouldLog(Log.WARN)) _log.warn("Null data being decrypted?");
return null;
@ -79,7 +79,7 @@ public class ElGamalAESEngine {
byte tag[] = new byte[32];
System.arraycopy(data, 0, tag, 0, tag.length);
SessionTag st = new SessionTag(tag);
SessionKey key = SessionKeyManager.getInstance().consumeTag(st);
SessionKey key = _context.sessionKeyManager().consumeTag(st);
SessionKey foundKey = new SessionKey();
foundKey.setData(null);
SessionKey usedKey = new SessionKey();
@ -90,16 +90,16 @@ public class ElGamalAESEngine {
usedKey.setData(key.getData());
decrypted = decryptExistingSession(data, key, targetPrivateKey, foundTags, usedKey, foundKey);
if (decrypted != null)
StatManager.getInstance().updateFrequency("crypto.elGamalAES.decryptExistingSession");
_context.statManager().updateFrequency("crypto.elGamalAES.decryptExistingSession");
else
StatManager.getInstance().updateFrequency("crypto.elGamalAES.decryptFailed");
_context.statManager().updateFrequency("crypto.elGamalAES.decryptFailed");
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Key is NOT known for tag " + st);
decrypted = decryptNewSession(data, targetPrivateKey, foundTags, usedKey, foundKey);
if (decrypted != null)
StatManager.getInstance().updateFrequency("crypto.elGamalAES.decryptNewSession");
_context.statManager().updateFrequency("crypto.elGamalAES.decryptNewSession");
else
StatManager.getInstance().updateFrequency("crypto.elGamalAES.decryptFailed");
_context.statManager().updateFrequency("crypto.elGamalAES.decryptFailed");
}
if ((key == null) && (decrypted == null)) {
@ -109,10 +109,10 @@ public class ElGamalAESEngine {
if (foundTags.size() > 0) {
if (foundKey.getData() != null) {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Found key: " + foundKey);
SessionKeyManager.getInstance().tagsReceived(foundKey, foundTags);
_context.sessionKeyManager().tagsReceived(foundKey, foundTags);
} else {
if (_log.shouldLog(Log.DEBUG)) _log.debug("Used key: " + usedKey);
SessionKeyManager.getInstance().tagsReceived(usedKey, foundTags);
_context.sessionKeyManager().tagsReceived(usedKey, foundTags);
}
}
return decrypted;
@ -132,7 +132,7 @@ public class ElGamalAESEngine {
*
* @return null if decryption fails
*/
static byte[] decryptNewSession(byte data[], PrivateKey targetPrivateKey, Set foundTags, SessionKey usedKey,
byte[] decryptNewSession(byte data[], PrivateKey targetPrivateKey, Set foundTags, SessionKey usedKey,
SessionKey foundKey) throws DataFormatException {
if (data == null) {
if (_log.shouldLog(Log.WARN)) _log.warn("Data is null, unable to decrypt new session");
@ -147,7 +147,7 @@ public class ElGamalAESEngine {
} else {
System.arraycopy(data, 0, elgEncr, 514 - data.length, data.length);
}
byte elgDecr[] = ElGamalEngine.getInstance().decrypt(elgEncr, targetPrivateKey);
byte elgDecr[] = _context.elGamalEngine().decrypt(elgEncr, targetPrivateKey);
if (elgDecr == null) return null;
ByteArrayInputStream bais = new ByteArrayInputStream(elgDecr);
@ -170,7 +170,7 @@ public class ElGamalAESEngine {
//_log.debug("Pre IV for decryptNewSession: " + DataHelper.toString(preIV, 32));
//_log.debug("SessionKey for decryptNewSession: " + DataHelper.toString(key.getData(), 32));
Hash ivHash = SHA256Generator.getInstance().calculateHash(preIV);
Hash ivHash = _context.sha().calculateHash(preIV);
byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
@ -200,13 +200,13 @@ public class ElGamalAESEngine {
* @param foundKey session key which may be filled with a new sessionKey found during decryption
*
*/
static byte[] decryptExistingSession(byte data[], SessionKey key, PrivateKey targetPrivateKey, Set foundTags,
byte[] decryptExistingSession(byte data[], SessionKey key, PrivateKey targetPrivateKey, Set foundTags,
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 = SHA256Generator.getInstance().calculateHash(preIV);
Hash ivHash = _context.sha().calculateHash(preIV);
byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
@ -246,12 +246,12 @@ 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
*/
static byte[] decryptAESBlock(byte encrypted[], SessionKey key, byte iv[], byte sentTag[], Set foundTags,
byte[] decryptAESBlock(byte encrypted[], 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[] = AESEngine.getInstance().decrypt(encrypted, key, iv);
Hash h = SHA256Generator.getInstance().calculateHash(decrypted);
byte decrypted[] = _context.AESEngine().decrypt(encrypted, key, iv);
Hash h = _context.sha().calculateHash(decrypted);
//_log.debug("Hash of entire aes block after decryption: \n" + DataHelper.toString(h.getData(), 32));
try {
SessionKey newKey = null;
@ -289,7 +289,7 @@ public class ElGamalAESEngine {
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 = SHA256Generator.getInstance().calculateHash(unencrData);
Hash calcHash = _context.sha().calculateHash(unencrData);
if (calcHash.equals(readHash)) {
// everything matches. w00t.
foundTags.addAll(tags);
@ -317,17 +317,17 @@ public class ElGamalAESEngine {
* @param paddedSize minimum size in bytes of the body after padding it (if less than the
* body's real size, no bytes are appended but the body is not truncated)
*/
public static byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
public byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
SessionTag currentTag, SessionKey newKey, long paddedSize) {
if (currentTag == null) {
if (_log.shouldLog(Log.INFO))
_log.info("Current tag is null, encrypting as new session", new Exception("encrypt new"));
StatManager.getInstance().updateFrequency("crypto.elGamalAES.encryptNewSession");
_context.statManager().updateFrequency("crypto.elGamalAES.encryptNewSession");
return encryptNewSession(data, target, key, tagsForDelivery, newKey, paddedSize);
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Current tag is NOT null, encrypting as existing session", new Exception("encrypt existing"));
StatManager.getInstance().updateFrequency("crypto.elGamalAES.encryptExistingSession");
_context.statManager().updateFrequency("crypto.elGamalAES.encryptExistingSession");
return encryptExistingSession(data, target, key, tagsForDelivery, currentTag, newKey, paddedSize);
}
}
@ -335,7 +335,7 @@ public class ElGamalAESEngine {
/**
* Encrypt the data to the target using the given key and deliver the specified tags
*/
public static byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
public byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
SessionTag currentTag, long paddedSize) {
return encrypt(data, target, key, tagsForDelivery, currentTag, null, paddedSize);
}
@ -343,14 +343,14 @@ public class ElGamalAESEngine {
/**
* Encrypt the data to the target using the given key and deliver the specified tags
*/
public static byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, long paddedSize) {
public byte[] encrypt(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, long paddedSize) {
return encrypt(data, target, key, tagsForDelivery, null, null, paddedSize);
}
/**
* Encrypt the data to the target using the given key delivering no tags
*/
public static byte[] encrypt(byte data[], PublicKey target, SessionKey key, long paddedSize) {
public byte[] encrypt(byte data[], PublicKey target, SessionKey key, long paddedSize) {
return encrypt(data, target, key, null, null, null, paddedSize);
}
@ -370,25 +370,25 @@ public class ElGamalAESEngine {
* - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0
*
*/
static byte[] encryptNewSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
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];
RandomSource.getInstance().nextBytes(preIV);
_context.random().nextBytes(preIV);
elgSrc.write(preIV);
byte rnd[] = new byte[158];
RandomSource.getInstance().nextBytes(rnd);
_context.random().nextBytes(rnd);
elgSrc.write(rnd);
elgSrc.flush();
//_log.debug("Pre IV for encryptNewSession: " + DataHelper.toString(preIV, 32));
//_log.debug("SessionKey for encryptNewSession: " + DataHelper.toString(key.getData(), 32));
long before = Clock.getInstance().now();
byte elgEncr[] = ElGamalEngine.getInstance().encrypt(elgSrc.toByteArray(), target);
long after = Clock.getInstance().now();
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) {
@ -400,7 +400,7 @@ public class ElGamalAESEngine {
}
//_log.debug("ElGamal encrypted length: " + elgEncr.length + " elGamal source length: " + elgSrc.toByteArray().length);
Hash ivHash = SHA256Generator.getInstance().calculateHash(preIV);
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);
@ -410,7 +410,7 @@ public class ElGamalAESEngine {
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 = Clock.getInstance().now();
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;
@ -436,14 +436,14 @@ public class ElGamalAESEngine {
* - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0
*
*/
static byte[] encryptExistingSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
byte[] encryptExistingSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
SessionTag currentTag, SessionKey newKey, long paddedSize) {
//_log.debug("Encrypting to an EXISTING session");
byte rawTag[] = currentTag.getData();
//_log.debug("Pre IV for encryptExistingSession (aka tag): " + currentTag.toString());
//_log.debug("SessionKey for encryptNewSession: " + DataHelper.toString(key.getData(), 32));
Hash ivHash = SHA256Generator.getInstance().calculateHash(rawTag);
Hash ivHash = _context.sha().calculateHash(rawTag);
byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
@ -469,7 +469,7 @@ public class ElGamalAESEngine {
* - random bytes, padding the total size to greater than paddedSize with a mod 16 = 0
*
*/
final static byte[] encryptAESBlock(byte data[], SessionKey key, byte[] iv, Set tagsForDelivery, SessionKey newKey,
final byte[] encryptAESBlock(byte data[], SessionKey key, byte[] iv, Set tagsForDelivery, SessionKey newKey,
long paddedSize) {
//_log.debug("iv for encryption: " + DataHelper.toString(iv, 16));
//_log.debug("Encrypting AES");
@ -484,7 +484,7 @@ public class ElGamalAESEngine {
//_log.debug("# tags created, registered, and written: " + tags.size());
DataHelper.writeLong(aesSrc, 4, data.length);
//_log.debug("data length: " + data.length);
Hash hash = SHA256Generator.getInstance().calculateHash(data);
Hash hash = _context.sha().calculateHash(data);
hash.writeBytes(aesSrc);
//_log.debug("hash of data: " + DataHelper.toString(hash.getData(), 32));
if (newKey == null) {
@ -499,14 +499,14 @@ public class ElGamalAESEngine {
aesSrc.write(data);
int len = aesSrc.toByteArray().length;
//_log.debug("raw data written: " + len);
byte padding[] = getPadding(len, paddedSize);
byte padding[] = getPadding(_context, len, paddedSize);
//_log.debug("padding length: " + padding.length);
aesSrc.write(padding);
byte aesUnencr[] = aesSrc.toByteArray();
Hash h = SHA256Generator.getInstance().calculateHash(aesUnencr);
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[] = AESEngine.getInstance().encrypt(aesUnencr, key, iv);
byte aesEncr[] = _context.AESEngine().encrypt(aesUnencr, key, iv);
//_log.debug("Encrypted length: " + aesEncr.length);
return aesEncr;
} catch (IOException ioe) {
@ -523,7 +523,7 @@ public class ElGamalAESEngine {
* at least minPaddedSize
*
*/
final static byte[] getPadding(int curSize, long minPaddedSize) {
final static byte[] getPadding(I2PAppContext context, int curSize, long minPaddedSize) {
int diff = 0;
if (curSize < minPaddedSize) {
diff = (int) minPaddedSize - curSize;
@ -532,7 +532,7 @@ public class ElGamalAESEngine {
int numPadding = diff;
if (((curSize + diff) % 16) != 0) numPadding += (16 - ((curSize + diff) % 16));
byte rv[] = new byte[numPadding];
RandomSource.getInstance().nextBytes(rv);
context.random().nextBytes(rv);
return rv;
}

View File

@ -43,6 +43,7 @@ import net.i2p.util.Clock;
import net.i2p.util.Log;
import net.i2p.util.NativeBigInteger;
import net.i2p.util.RandomSource;
import net.i2p.I2PAppContext;
/**
* Wrapper for ElGamal encryption/signature schemes.
@ -56,25 +57,28 @@ import net.i2p.util.RandomSource;
*/
public class ElGamalEngine {
private final static Log _log = new Log(ElGamalEngine.class);
private static ElGamalEngine _engine;
static {
if ("off".equals(System.getProperty("i2p.encryption", "on")))
_engine = new DummyElGamalEngine();
else
_engine = new ElGamalEngine();
StatManager.getInstance().createRateStat("crypto.elGamal.encrypt",
"how long does it take to do a full ElGamal encryption", "Encryption",
new long[] { 60 * 1000, 60 * 60 * 1000, 24 * 60 * 60 * 1000});
StatManager.getInstance().createRateStat("crypto.elGamal.decrypt",
"how long does it take to do a full ElGamal decryption", "Encryption",
new long[] { 60 * 1000, 60 * 60 * 1000, 24 * 60 * 60 * 1000});
private Log _log;
private I2PAppContext _context;
/**
* The ElGamal engine should only be constructed and accessed through the
* application context. This constructor should only be used by the
* appropriate application context itself.
*
*/
public ElGamalEngine(I2PAppContext context) {
context.statManager().createRateStat("crypto.elGamal.encrypt",
"how long does it take to do a full ElGamal encryption", "Encryption",
new long[] { 60 * 1000, 60 * 60 * 1000, 24 * 60 * 60 * 1000});
context.statManager().createRateStat("crypto.elGamal.decrypt",
"how long does it take to do a full ElGamal decryption", "Encryption",
new long[] { 60 * 1000, 60 * 60 * 1000, 24 * 60 * 60 * 1000});
_context = context;
_log = context.logManager().getLog(ElGamalEngine.class);
}
private ElGamalEngine() {}
public static ElGamalEngine getInstance() {
return _engine;
}
private final static BigInteger _two = new NativeBigInteger(1, new byte[] { 0x02});
private BigInteger[] getNextYK() {
@ -91,12 +95,12 @@ public class ElGamalEngine {
throw new IllegalArgumentException("Data to encrypt must be < 223 bytes at the moment");
if (publicKey == null) throw new IllegalArgumentException("Null public key specified");
long start = Clock.getInstance().now();
long start = _context.clock().now();
ByteArrayOutputStream baos = new ByteArrayOutputStream(256);
try {
baos.write(0xFF);
Hash hash = SHA256Generator.getInstance().calculateHash(data);
Hash hash = _context.sha().calculateHash(data);
hash.writeBytes(baos);
baos.write(data);
baos.flush();
@ -106,25 +110,25 @@ public class ElGamalEngine {
}
byte d2[] = baos.toByteArray();
long t0 = Clock.getInstance().now();
long t0 = _context.clock().now();
BigInteger m = new NativeBigInteger(1, d2);
long t1 = Clock.getInstance().now();
long t1 = _context.clock().now();
if (m.compareTo(CryptoConstants.elgp) >= 0)
throw new IllegalArgumentException("ARGH. Data cannot be larger than the ElGamal prime. FIXME");
long t2 = Clock.getInstance().now();
long t2 = _context.clock().now();
BigInteger aalpha = new NativeBigInteger(1, publicKey.getData());
long t3 = Clock.getInstance().now();
long t3 = _context.clock().now();
BigInteger yk[] = getNextYK();
BigInteger k = yk[1];
BigInteger y = yk[0];
long t7 = Clock.getInstance().now();
long t7 = _context.clock().now();
BigInteger d = aalpha.modPow(k, CryptoConstants.elgp);
long t8 = Clock.getInstance().now();
long t8 = _context.clock().now();
d = d.multiply(m);
long t9 = Clock.getInstance().now();
long t9 = _context.clock().now();
d = d.mod(CryptoConstants.elgp);
long t10 = Clock.getInstance().now();
long t10 = _context.clock().now();
byte[] ybytes = y.toByteArray();
byte[] dbytes = d.toByteArray();
@ -146,14 +150,14 @@ public class ElGamalEngine {
buf.append("8-9: ").append(t9 - t8).append('\n');
buf.append("9-10: ").append(t10 - t9).append('\n');
//_log.debug(buf.toString());
long end = Clock.getInstance().now();
long end = _context.clock().now();
long diff = end - start;
if (diff > 1000) {
if (_log.shouldLog(Log.WARN)) _log.warn("Took too long to encrypt ElGamal block (" + diff + "ms)");
}
StatManager.getInstance().addRateData("crypto.elGamal.encrypt", diff, diff);
_context.statManager().addRateData("crypto.elGamal.encrypt", diff, diff);
return out;
}
@ -165,7 +169,7 @@ public class ElGamalEngine {
public byte[] decrypt(byte encrypted[], PrivateKey privateKey) {
if ((encrypted == null) || (encrypted.length > 514))
throw new IllegalArgumentException("Data to decrypt must be <= 514 bytes at the moment");
long start = Clock.getInstance().now();
long start = _context.clock().now();
byte[] ybytes = new byte[257];
byte[] dbytes = new byte[257];
@ -196,10 +200,10 @@ public class ElGamalEngine {
return null;
}
Hash calcHash = SHA256Generator.getInstance().calculateHash(rv);
Hash calcHash = _context.sha().calculateHash(rv);
boolean ok = calcHash.equals(hash);
long end = Clock.getInstance().now();
long end = _context.clock().now();
long diff = end - start;
if (diff > 1000) {
@ -207,7 +211,7 @@ public class ElGamalEngine {
_log.warn("Took too long to decrypt and verify ElGamal block (" + diff + "ms)");
}
StatManager.getInstance().addRateData("crypto.elGamal.decrypt", diff, diff);
_context.statManager().addRateData("crypto.elGamal.decrypt", diff, diff);
if (ok) {
//_log.debug("Hash matches: " + DataHelper.toString(hash.getData(), hash.getData().length));
@ -236,6 +240,7 @@ public class ElGamalEngine {
}
RandomSource.getInstance().nextBoolean();
I2PAppContext context = new I2PAppContext();
System.out.println("Running " + numRuns + " times");
@ -249,9 +254,9 @@ public class ElGamalEngine {
byte buf[] = new byte[128];
RandomSource.getInstance().nextBytes(buf);
long startE = Clock.getInstance().now();
byte encr[] = ElGamalEngine.getInstance().encrypt(buf, pubkey);
byte encr[] = context.elGamalEngine().encrypt(buf, pubkey);
long endE = Clock.getInstance().now();
byte decr[] = ElGamalEngine.getInstance().decrypt(encr, privkey);
byte decr[] = context.elGamalEngine().decrypt(encr, privkey);
long endD = Clock.getInstance().now();
eTime += endE - startE;
dTime += endD - endE;
@ -259,8 +264,7 @@ public class ElGamalEngine {
if (!DataHelper.eq(decr, buf)) {
System.out.println("PublicKey : " + DataHelper.toString(pubkey.getData(), pubkey.getData().length));
System.out.println("PrivateKey : "
+ DataHelper.toString(privkey.getData(), privkey.getData().length));
System.out.println("PrivateKey : " + DataHelper.toString(privkey.getData(), privkey.getData().length));
System.out.println("orig : " + DataHelper.toString(buf, buf.length));
System.out.println("d(e(orig) : " + DataHelper.toString(decr, decr.length));
System.out.println("orig.len : " + buf.length);

View File

@ -3,28 +3,23 @@ package net.i2p.crypto;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
import net.i2p.I2PAppContext;
/**
* Calculate the HMAC-SHA256 of a key+message. Currently FAKE - returns a stupid
* kludgy hash: H(H(key) XOR H(data)). Fix me!
*
*/
public abstract class HMACSHA256Generator {
private static HMACSHA256Generator _generator = new DummyHMACSHA256Generator();
public class HMACSHA256Generator {
public HMACSHA256Generator(I2PAppContext context) {};
public static HMACSHA256Generator getInstance() {
return _generator;
return I2PAppContext.getGlobalContext().hmac();
}
public abstract Hash calculate(SessionKey key, byte data[]);
}
/**
* jrandom smells.
*
*/
class DummyHMACSHA256Generator extends HMACSHA256Generator {
/**
* This should calculate the HMAC/SHA256, but it DOESNT. Its just a kludge.
* Fix me.
*/
public Hash calculate(SessionKey key, byte data[]) {
if ((key == null) || (key.getData() == null) || (data == null))
throw new NullPointerException("Null arguments for HMAC");

View File

@ -22,18 +22,24 @@ import net.i2p.util.Clock;
import net.i2p.util.Log;
import net.i2p.util.NativeBigInteger;
import net.i2p.util.RandomSource;
import net.i2p.I2PAppContext;
/** Define a way of generating asymetrical key pairs as well as symetrical keys
* @author jrandom
*/
public class KeyGenerator {
private final static Log _log = new Log(KeyGenerator.class);
private static final RandomSource _random = RandomSource.getInstance();
private static KeyGenerator _generator = new KeyGenerator();
private Log _log;
private I2PAppContext _context;
public static KeyGenerator getInstance() {
return _generator;
public KeyGenerator(I2PAppContext context) {
_log = context.logManager().getLog(KeyGenerator.class);
_context = context;
}
public static KeyGenerator getInstance() {
return I2PAppContext.getGlobalContext().keyGenerator();
}
/** Generate a private 256 bit session key
* @return session key
@ -42,7 +48,7 @@ public class KeyGenerator {
// 256bit random # as a session key
SessionKey key = new SessionKey();
byte data[] = new byte[SessionKey.KEYSIZE_BYTES];
_random.nextBytes(data);
_context.random().nextBytes(data);
key.setData(data);
return key;
}
@ -52,7 +58,7 @@ public class KeyGenerator {
* @return pair of keys
*/
public Object[] generatePKIKeypair() {
BigInteger a = new NativeBigInteger(2048, _random);
BigInteger a = new NativeBigInteger(2048, _context.random());
BigInteger aalpha = CryptoConstants.elgg.modPow(a, CryptoConstants.elgp);
Object[] keys = new Object[2];
@ -80,7 +86,7 @@ public class KeyGenerator {
// make sure the random key is less than the DSA q
do {
x = new NativeBigInteger(160, _random);
x = new NativeBigInteger(160, _context.random());
} while (x.compareTo(CryptoConstants.dsaq) >= 0);
BigInteger y = CryptoConstants.dsag.modPow(x, CryptoConstants.dsap);
@ -118,13 +124,14 @@ public class KeyGenerator {
byte src[] = new byte[200];
RandomSource.getInstance().nextBytes(src);
I2PAppContext ctx = new I2PAppContext();
long time = 0;
for (int i = 0; i < 10; i++) {
long start = Clock.getInstance().now();
Object keys[] = KeyGenerator.getInstance().generatePKIKeypair();
long end = Clock.getInstance().now();
byte ctext[] = ElGamalEngine.getInstance().encrypt(src, (PublicKey) keys[0]);
byte ptext[] = ElGamalEngine.getInstance().decrypt(ctext, (PrivateKey) keys[1]);
byte ctext[] = ctx.elGamalEngine().encrypt(src, (PublicKey) keys[0]);
byte ptext[] = ctx.elGamalEngine().decrypt(ctext, (PrivateKey) keys[1]);
time += end - start;
if (DataHelper.eq(ptext, src))
log.debug("D(E(data)) == data");

View File

@ -27,6 +27,7 @@ import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.util.Log;
import net.i2p.I2PAppContext;
/**
* Expose the functionality to allow people to write out and read in the
@ -35,10 +36,23 @@ import net.i2p.util.Log;
*
*/
public class PersistentSessionKeyManager extends TransientSessionKeyManager {
private final static Log _log = new Log(PersistentSessionKeyManager.class);
private Log _log;
private Object _yk = YKGenerator.class;
/**
* The session key manager should only be constructed and accessed through the
* application context. This constructor should only be used by the
* appropriate application context itself.
*
*/
public PersistentSessionKeyManager(I2PAppContext context) {
super(context);
_log = context.logManager().getLog(PersistentSessionKeyManager.class);
}
private PersistentSessionKeyManager() {
this(null);
}
/**
* Write the session key data to the given stream
*
@ -46,8 +60,9 @@ public class PersistentSessionKeyManager extends TransientSessionKeyManager {
public void saveState(OutputStream out) throws IOException, DataFormatException {
Set tagSets = getInboundTagSets();
Set sessions = getOutboundSessions();
_log.info("Saving state with " + tagSets.size() + " inbound tagSets and " + sessions.size()
+ " outbound sessions");
if (_log.shouldLog(Log.INFO))
_log.info("Saving state with " + tagSets.size() + " inbound tagSets and "
+ sessions.size() + " outbound sessions");
DataHelper.writeLong(out, 4, tagSets.size());
for (Iterator iter = tagSets.iterator(); iter.hasNext();) {
@ -79,8 +94,9 @@ public class PersistentSessionKeyManager extends TransientSessionKeyManager {
sessions.add(sess);
}
_log.info("Loading state with " + tagSets.size() + " inbound tagSets and " + sessions.size()
+ " outbound sessions");
if (_log.shouldLog(Log.INFO))
_log.info("Loading state with " + tagSets.size() + " inbound tagSets and "
+ sessions.size() + " outbound sessions");
setData(tagSets, sessions);
}
@ -146,7 +162,9 @@ public class PersistentSessionKeyManager extends TransientSessionKeyManager {
}
public static void main(String args[]) {
PersistentSessionKeyManager mgr = new PersistentSessionKeyManager();
I2PAppContext ctx = new I2PAppContext();
Log log = ctx.logManager().getLog(PersistentSessionKeyManager.class);
PersistentSessionKeyManager mgr = (PersistentSessionKeyManager)ctx.sessionKeyManager();
try {
mgr.loadState(new FileInputStream("sessionKeys.dat"));
String state = mgr.renderStatusHTML();
@ -154,13 +172,13 @@ public class PersistentSessionKeyManager extends TransientSessionKeyManager {
fos.write(state.getBytes());
fos.close();
int expired = mgr.aggressiveExpire();
_log.error("Expired: " + expired);
log.error("Expired: " + expired);
String stateAfter = mgr.renderStatusHTML();
FileOutputStream fos2 = new FileOutputStream("sessionKeysAfterExpire.html");
fos2.write(stateAfter.getBytes());
fos2.close();
} catch (Throwable t) {
_log.error("Error loading/storing sessionKeys", t);
log.error("Error loading/storing sessionKeys", t);
}
try {
Thread.sleep(3000);

View File

@ -30,6 +30,7 @@ package net.i2p.crypto;
*/
import net.i2p.data.Hash;
import net.i2p.I2PAppContext;
/** Defines a wrapper for SHA-256 operation
*
@ -38,10 +39,9 @@ import net.i2p.data.Hash;
* @author thecrypto,jrandom
*/
public class SHA256Generator {
private static SHA256Generator _generator = new SHA256Generator();
public SHA256Generator(I2PAppContext context) {};
public static SHA256Generator getInstance() {
return _generator;
return I2PAppContext.getGlobalContext().sha();
}
static int[] K = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,

View File

@ -14,6 +14,7 @@ import java.util.Set;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
import net.i2p.data.SessionTag;
import net.i2p.I2PAppContext;
/**
* Manage the session keys and session tags used for encryption and decryption.
@ -23,12 +24,11 @@ import net.i2p.data.SessionTag;
*
*/
public class SessionKeyManager {
private final static SessionKeyManager _instance = new PersistentSessionKeyManager(); // new TransientSessionKeyManager(); // SessionKeyManager();
public final static SessionKeyManager getInstance() {
return _instance;
}
/** session key managers must be created through an app context */
protected SessionKeyManager(I2PAppContext context) {}
/** see above */
private SessionKeyManager() {}
/**
* Retrieve the session key currently associated with encryption to the target,
* or null if a new session key should be generated.

View File

@ -18,6 +18,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.PublicKey;
import net.i2p.data.SessionKey;
@ -32,7 +33,7 @@ import net.i2p.util.Log;
*
*/
class TransientSessionKeyManager extends SessionKeyManager {
private final static Log _log = new Log(TransientSessionKeyManager.class);
private Log _log;
private Map _outboundSessions; // PublicKey --> OutboundSession
private Map _inboundTagSets; // SessionTag --> TagSet
@ -52,11 +53,19 @@ class TransientSessionKeyManager extends SessionKeyManager {
public final static long SESSION_LIFETIME_MAX_MS = SESSION_TAG_DURATION_MS + 5 * 60 * 1000;
public final static int MAX_INBOUND_SESSION_TAGS = 100 * 1000; // this will consume at most 3.2M
public TransientSessionKeyManager() {
super();
/**
* The session key manager should only be constructed and accessed through the
* application context. This constructor should only be used by the
* appropriate application context itself.
*
*/
public TransientSessionKeyManager(I2PAppContext context) {
super(context);
_log = context.logManager().getLog(TransientSessionKeyManager.class);
_outboundSessions = new HashMap(64);
_inboundTagSets = new HashMap(1024);
}
private TransientSessionKeyManager() { this(null); }
/** TagSet */
protected Set getInboundTagSets() {
@ -73,8 +82,9 @@ class TransientSessionKeyManager extends SessionKeyManager {
}
protected void setData(Set inboundTagSets, Set outboundSessions) {
_log.info("Loading " + inboundTagSets.size() + " inbound tag sets, and " + outboundSessions.size()
+ " outbound sessions");
if (_log.shouldLog(Log.INFO))
_log.info("Loading " + inboundTagSets.size() + " inbound tag sets, and "
+ outboundSessions.size() + " outbound sessions");
Map tagSets = new HashMap(inboundTagSets.size());
for (Iterator iter = inboundTagSets.iterator(); iter.hasNext();) {
TagSet ts = (TagSet) iter.next();
@ -108,8 +118,10 @@ class TransientSessionKeyManager extends SessionKeyManager {
if (sess == null) return null;
long now = Clock.getInstance().now();
if (sess.getEstablishedDate() < now - SESSION_LIFETIME_MAX_MS) {
_log.info("Expiring old session key established on " + new Date(sess.getEstablishedDate())
+ " with target " + target);
if (_log.shouldLog(Log.INFO))
_log.info("Expiring old session key established on "
+ new Date(sess.getEstablishedDate())
+ " with target " + target);
return null;
} else {
return sess.getCurrentKey();
@ -137,15 +149,18 @@ class TransientSessionKeyManager extends SessionKeyManager {
public SessionTag consumeNextAvailableTag(PublicKey target, SessionKey key) {
OutboundSession sess = getSession(target);
if (sess == null) {
_log.debug("No session for " + target);
if (_log.shouldLog(Log.DEBUG))
_log.debug("No session for " + target);
return null;
}
if (sess.getCurrentKey().equals(key)) {
SessionTag nxt = sess.consumeNext();
_log.debug("Tag consumed: " + nxt);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Tag consumed: " + nxt);
return nxt;
} else {
_log.debug("Key does not match existing key, no tag");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Key does not match existing key, no tag");
return null;
}
}
@ -194,7 +209,8 @@ class TransientSessionKeyManager extends SessionKeyManager {
sess.setCurrentKey(key);
TagSet set = new TagSet(sessionTags, key);
sess.addTags(set);
_log.debug("Tags delivered to set " + set + " on session " + sess);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Tags delivered to set " + set + " on session " + sess);
}
/**
@ -215,7 +231,8 @@ class TransientSessionKeyManager extends SessionKeyManager {
TagSet tagSet = new TagSet(sessionTags, key);
for (Iterator iter = sessionTags.iterator(); iter.hasNext();) {
SessionTag tag = (SessionTag) iter.next();
_log.debug("Receiving tag " + tag + " for key " + key);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Receiving tag " + tag + " for key " + key);
synchronized (_inboundTagSets) {
_inboundTagSets.put(tag, tagSet);
}
@ -224,7 +241,8 @@ class TransientSessionKeyManager extends SessionKeyManager {
// todo: make this limit the tags by sessionKey and actually enforce the limit!
int overage = _inboundTagSets.size() - MAX_INBOUND_SESSION_TAGS;
if (overage > 0) {
_log.error("TOO MANY SESSION TAGS! " + (_inboundTagSets.size()));
if (_log.shouldLog(Log.ERROR))
_log.error("TOO MANY SESSION TAGS! " + (_inboundTagSets.size()));
}
}
@ -242,13 +260,15 @@ class TransientSessionKeyManager extends SessionKeyManager {
synchronized (_inboundTagSets) {
TagSet tagSet = (TagSet) _inboundTagSets.remove(tag);
if (tagSet == null) {
_log.debug("Cannot consume tag " + tag + " as it is not known");
if (_log.shouldLog(Log.DEBUG))
_log.debug("Cannot consume tag " + tag + " as it is not known");
return null;
} else {
tagSet.consume(tag);
}
SessionKey key = tagSet.getAssociatedKey();
_log.debug("Consuming tag " + tag + " for sessionKey " + key);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Consuming tag " + tag + " for sessionKey " + key);
return key;
}
}
@ -362,7 +382,7 @@ class TransientSessionKeyManager extends SessionKeyManager {
return buf.toString();
}
static class OutboundSession {
class OutboundSession {
private PublicKey _target;
private SessionKey _currentKey;
private long _established;
@ -406,7 +426,9 @@ class TransientSessionKeyManager extends SessionKeyManager {
TagSet set = (TagSet) sets.get(i);
dropped += set.getTags().size();
}
_log.info("Rekeyed from " + _currentKey + " to " + key + ": dropping " + dropped + " session tags");
if (_log.shouldLog(Log.INFO))
_log.info("Rekeyed from " + _currentKey + " to " + key
+ ": dropping " + dropped + " session tags");
}
}
_currentKey = key;
@ -448,7 +470,8 @@ class TransientSessionKeyManager extends SessionKeyManager {
SessionTag tag = set.consumeNext();
if (tag != null) return tag;
} else {
_log.info("TagSet from " + new Date(set.getDate()) + " expired");
if (_log.shouldLog(Log.INFO))
_log.info("TagSet from " + new Date(set.getDate()) + " expired");
}
_tagSets.remove(0);
}

View File

@ -18,6 +18,7 @@ import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import net.i2p.util.NativeBigInteger;
import net.i2p.util.RandomSource;
import net.i2p.I2PAppContext;
/**
* Precalculate the Y and K for ElGamal encryption operations.
@ -56,22 +57,23 @@ class YKGenerator {
private final static long CHECK_DELAY = 30 * 1000;
static {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
try {
int val = Integer.parseInt(System.getProperty(PROP_YK_PRECALC_MIN, DEFAULT_YK_PRECALC_MIN));
int val = Integer.parseInt(ctx.getProperty(PROP_YK_PRECALC_MIN, DEFAULT_YK_PRECALC_MIN));
MIN_NUM_BUILDERS = val;
} catch (Throwable t) {
int val = Integer.parseInt(DEFAULT_YK_PRECALC_MIN);
MIN_NUM_BUILDERS = val;
}
try {
int val = Integer.parseInt(System.getProperty(PROP_YK_PRECALC_MAX, DEFAULT_YK_PRECALC_MAX));
int val = Integer.parseInt(ctx.getProperty(PROP_YK_PRECALC_MAX, DEFAULT_YK_PRECALC_MAX));
MAX_NUM_BUILDERS = val;
} catch (Throwable t) {
int val = Integer.parseInt(DEFAULT_YK_PRECALC_MAX);
MAX_NUM_BUILDERS = val;
}
try {
int val = Integer.parseInt(System.getProperty(PROP_YK_PRECALC_DELAY, DEFAULT_YK_PRECALC_DELAY));
int val = Integer.parseInt(ctx.getProperty(PROP_YK_PRECALC_DELAY, DEFAULT_YK_PRECALC_DELAY));
CALC_DELAY = val;
} catch (Throwable t) {
int val = Integer.parseInt(DEFAULT_YK_PRECALC_DELAY);

View File

@ -35,7 +35,6 @@ import net.i2p.util.OrderedProperties;
* @author jrandom
*/
public class DataHelper {
private final static Log _log = new Log(DataHelper.class);
private final static String _equal = "="; // in UTF-8
private final static String _semicolon = ";"; // in UTF-8
@ -56,7 +55,8 @@ public class DataHelper {
* @throws IOException if there is a problem reading the data
* @return mapping
*/
public static Properties readProperties(InputStream rawStream) throws DataFormatException, IOException {
public static Properties readProperties(InputStream rawStream)
throws DataFormatException, IOException {
Properties props = new OrderedProperties();
long size = readLong(rawStream, 2);
byte data[] = new byte[(int) size];
@ -65,24 +65,18 @@ public class DataHelper {
ByteArrayInputStream in = new ByteArrayInputStream(data);
byte eqBuf[] = _equal.getBytes();
byte semiBuf[] = _semicolon.getBytes();
try {
while (in.available() > 0) {
String key = readString(in);
read = read(in, eqBuf);
if ((read != eqBuf.length) || (!eq(new String(eqBuf), _equal))) {
_log.debug("Failed eqtest [" + new String(eqBuf) + "]");
break;
}
String val = readString(in);
read = read(in, semiBuf);
if ((read != semiBuf.length) || (!eq(new String(semiBuf), _semicolon))) {
_log.debug("Failed semitest [" + new String(semiBuf) + "]");
break;
}
props.put(key, val);
while (in.available() > 0) {
String key = readString(in);
read = read(in, eqBuf);
if ((read != eqBuf.length) || (!eq(new String(eqBuf), _equal))) {
break;
}
} catch (IOException ioe) {
_log.warn("Error reading properties", ioe);
String val = readString(in);
read = read(in, semiBuf);
if ((read != semiBuf.length) || (!eq(new String(semiBuf), _semicolon))) {
break;
}
props.put(key, val);
}
return props;
}
@ -96,8 +90,8 @@ public class DataHelper {
* @throws DataFormatException if there is not enough valid data to write out
* @throws IOException if there is an IO error writing out the data
*/
public static void writeProperties(OutputStream rawStream, Properties props) throws DataFormatException,
IOException {
public static void writeProperties(OutputStream rawStream, Properties props)
throws DataFormatException, IOException {
OrderedProperties p = new OrderedProperties();
if (props != null) p.putAll(props);
ByteArrayOutputStream baos = new ByteArrayOutputStream(32);
@ -204,10 +198,10 @@ public class DataHelper {
* @throws IOException if there is an IO error reading the number
* @return number
*/
public static long readLong(InputStream rawStream, int numBytes) throws DataFormatException, IOException {
public static long readLong(InputStream rawStream, int numBytes)
throws DataFormatException, IOException {
if (numBytes > 8)
throw new DataFormatException(
"readLong doesn't currently support reading numbers > 8 bytes [as thats bigger than java's long]");
throw new DataFormatException("readLong doesn't currently support reading numbers > 8 bytes [as thats bigger than java's long]");
byte data[] = new byte[numBytes];
int num = read(rawStream, data);
if (num != numBytes)
@ -225,10 +219,14 @@ public class DataHelper {
* @throws DataFormatException if the stream doesn't contain a validly formatted number of that many bytes
* @throws IOException if there is an IO error writing to the stream
*/
public static void writeLong(OutputStream rawStream, int numBytes, long value) throws DataFormatException,
IOException {
UnsignedInteger i = new UnsignedInteger(value);
rawStream.write(i.getBytes(numBytes));
public static void writeLong(OutputStream rawStream, int numBytes, long value)
throws DataFormatException, IOException {
try {
UnsignedInteger i = new UnsignedInteger(value);
rawStream.write(i.getBytes(numBytes));
} catch (IllegalArgumentException iae) {
throw new DataFormatException("Invalid value (must be positive)", iae);
}
}
/** Read in a date from the stream as specified by the I2P data structure spec.
@ -254,7 +252,8 @@ public class DataHelper {
* @throws DataFormatException if the date is not valid
* @throws IOException if there is an IO error writing the date
*/
public static void writeDate(OutputStream out, Date date) throws DataFormatException, IOException {
public static void writeDate(OutputStream out, Date date)
throws DataFormatException, IOException {
if (date == null)
writeLong(out, 8, 0L);
else
@ -286,7 +285,8 @@ public class DataHelper {
* @throws DataFormatException if the string is not valid
* @throws IOException if there is an IO error writing the string
*/
public static void writeString(OutputStream out, String string) throws DataFormatException, IOException {
public static void writeString(OutputStream out, String string)
throws DataFormatException, IOException {
if (string == null) {
writeLong(out, 1, 0);
} else {
@ -328,7 +328,8 @@ public class DataHelper {
* @throws DataFormatException if the boolean is not valid
* @throws IOException if there is an IO error writing the boolean
*/
public static void writeBoolean(OutputStream out, Boolean bool) throws DataFormatException, IOException {
public static void writeBoolean(OutputStream out, Boolean bool)
throws DataFormatException, IOException {
if (bool == null)
writeLong(out, 1, 2);
else if (Boolean.TRUE.equals(bool))
@ -353,7 +354,6 @@ public class DataHelper {
boolean eq = (((lhs == null) && (rhs == null)) || ((lhs != null) && (lhs.equals(rhs))));
return eq;
} catch (ClassCastException cce) {
_log.warn("Error comparing [" + lhs + "] with [" + rhs + "]", cce);
return false;
}
}
@ -542,12 +542,12 @@ public class DataHelper {
out.finish();
out.flush();
byte rv[] = baos.toByteArray();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Compression of " + orig.length + " into " + rv.length + " (or " + 100.0d
* (((double) orig.length) / ((double) rv.length)) + "% savings)");
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Compression of " + orig.length + " into " + rv.length + " (or " + 100.0d
// * (((double) orig.length) / ((double) rv.length)) + "% savings)");
return rv;
} catch (IOException ioe) {
_log.error("Error compressing?!", ioe);
//_log.error("Error compressing?!", ioe);
return null;
}
}
@ -565,12 +565,12 @@ public class DataHelper {
baos.write(buf, 0, read);
}
byte rv[] = baos.toByteArray();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Decompression of " + orig.length + " into " + rv.length + " (or " + 100.0d
* (((double) rv.length) / ((double) orig.length)) + "% savings)");
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Decompression of " + orig.length + " into " + rv.length + " (or " + 100.0d
// * (((double) rv.length) / ((double) orig.length)) + "% savings)");
return rv;
} catch (IOException ioe) {
_log.error("Error decompressing?", ioe);
//_log.error("Error decompressing?", ioe);
return null;
}
}

View File

@ -25,6 +25,7 @@ public class Hash extends DataStructureImpl {
private final static Log _log = new Log(Hash.class);
private byte[] _data;
private volatile String _stringified;
private volatile String _base64ed;
public final static int HASH_LENGTH = 32;
public final static Hash FAKE_HASH = new Hash(new byte[HASH_LENGTH]);
@ -44,11 +45,13 @@ public class Hash extends DataStructureImpl {
public void setData(byte[] data) {
_data = data;
_stringified = null;
_base64ed = null;
}
public void readBytes(InputStream in) throws DataFormatException, IOException {
_data = new byte[HASH_LENGTH];
_stringified = null;
_base64ed = null;
int read = read(in, _data);
if (read != HASH_LENGTH) throw new DataFormatException("Not enough bytes to read the hash");
}
@ -82,4 +85,11 @@ public class Hash extends DataStructureImpl {
}
return _stringified;
}
public String toBase64() {
if (_base64ed == null) {
_base64ed = super.toBase64();
}
return _base64ed;
}
}

View File

@ -46,6 +46,8 @@ public class RouterInfo extends DataStructureImpl {
private volatile boolean _isValid;
private volatile String _stringified;
private volatile byte _byteified[];
private volatile int _hashCode;
private volatile boolean _hashCodeInitialized;
public RouterInfo() {
setIdentity(null);
@ -59,6 +61,7 @@ public class RouterInfo extends DataStructureImpl {
_currentRoutingKey = null;
_stringified = null;
_byteified = null;
_hashCodeInitialized = false;
}
public RouterInfo(RouterInfo old) {
@ -74,6 +77,7 @@ public class RouterInfo extends DataStructureImpl {
private void resetCache() {
_stringified = null;
_byteified = null;
_hashCodeInitialized = false;
}
/**
@ -111,6 +115,7 @@ public class RouterInfo extends DataStructureImpl {
*/
public void setPublished(long published) {
_published = published;
resetCache();
}
/**
@ -221,16 +226,20 @@ public class RouterInfo extends DataStructureImpl {
//_log.debug("verify ok? " + DSAEngine.getInstance().verifySignature(sig, bytes, getIdentity().getSigningPublicKey()));
//_log.debug("Signed data: \n" + Base64.encode(bytes));
//_log.debug("Signature: " + getSignature());
resetCache();
}
/**
* Write out the raw payload of the routerInfo, excluding the signature. This
* caches the data in memory if possible.
*
* @throws DataFormatException if the data is somehow b0rked (missing props, etc)
*/
private byte[] getBytes() throws DataFormatException {
if (_byteified != null) return _byteified;
if (_identity == null) throw new IllegalStateException("Router identity isn't set? wtf!");
if (_addresses == null) throw new IllegalStateException("Router addressess isn't set? wtf!");
if (_peers == null) throw new IllegalStateException("Router peers isn't set? wtf!");
if (_options == null) throw new IllegalStateException("Router options isn't set? wtf!");
if (_identity == null) throw new DataFormatException("Router identity isn't set? wtf!");
if (_addresses == null) throw new DataFormatException("Router addressess isn't set? wtf!");
if (_peers == null) throw new DataFormatException("Router peers isn't set? wtf!");
if (_options == null) throw new DataFormatException("Router options isn't set? wtf!");
long before = Clock.getInstance().now();
ByteArrayOutputStream out = new ByteArrayOutputStream();
@ -339,10 +348,14 @@ public class RouterInfo extends DataStructureImpl {
}
_isValid = DSAEngine.getInstance().verifySignature(_signature, data, _identity.getSigningPublicKey());
if (!_isValid) {
_log.error("Invalid [" + SHA256Generator.getInstance().calculateHash(data).toBase64()
+ "] w/ signing key: " + _identity.getSigningPublicKey(), new Exception("Signature failed"));
_log.debug("Failed data: \n" + Base64.encode(data));
_log.debug("Signature: " + getSignature());
if (_log.shouldLog(Log.ERROR))
_log.error("Invalid [" + SHA256Generator.getInstance().calculateHash(data).toBase64()
+ "] w/ signing key: " + _identity.getSigningPublicKey(),
new Exception("Signature failed"));
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("Failed data: \n" + Base64.encode(data));
_log.debug("Signature: " + getSignature());
}
}
}
@ -378,19 +391,7 @@ public class RouterInfo extends DataStructureImpl {
//if (!isValid())
// throw new DataFormatException("Data is not valid");
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
_identity.writeBytes(baos);
DataHelper.writeDate(baos, new Date(_published));
DataHelper.writeLong(baos, 1, _addresses.size());
for (Iterator iter = _addresses.iterator(); iter.hasNext();) {
RouterAddress addr = (RouterAddress) iter.next();
addr.writeBytes(baos);
}
DataHelper.writeLong(baos, 1, _peers.size());
for (Iterator iter = _peers.iterator(); iter.hasNext();) {
Hash peerHash = (Hash) iter.next();
peerHash.writeBytes(baos);
}
DataHelper.writeProperties(baos, _options);
baos.write(getBytes());
_signature.writeBytes(baos);
byte data[] = baos.toByteArray();
@ -401,16 +402,22 @@ public class RouterInfo extends DataStructureImpl {
public boolean equals(Object object) {
if ((object == null) || !(object instanceof RouterInfo)) return false;
RouterInfo info = (RouterInfo) object;
return DataHelper.eq(getAddresses(), info.getAddresses()) && DataHelper.eq(getIdentity(), info.getIdentity())
&& DataHelper.eq(getOptions(), info.getOptions()) && DataHelper.eq(getPeers(), info.getPeers())
&& DataHelper.eq(getSignature(), info.getSignature())
return DataHelper.eq(_addresses, info.getAddresses())
&& DataHelper.eq(_identity, info.getIdentity())
&& DataHelper.eq(_options, info.getOptions())
&& DataHelper.eq(_peers, info.getPeers())
&& DataHelper.eq(_signature, info.getSignature())
&& DataHelper.eq(getPublished(), info.getPublished());
}
public int hashCode() {
return DataHelper.hashCode(getAddresses()) + DataHelper.hashCode(getIdentity())
+ DataHelper.hashCode(getOptions()) + DataHelper.hashCode(getPeers())
+ DataHelper.hashCode(getSignature()) + (int) getPublished();
if (!_hashCodeInitialized) {
_hashCode = DataHelper.hashCode(_addresses) + DataHelper.hashCode(_identity)
+ DataHelper.hashCode(_options) + DataHelper.hashCode(_peers)
+ DataHelper.hashCode(_signature) + (int) getPublished();
_hashCodeInitialized = true;
}
return _hashCode;
}
public String toString() {
@ -420,17 +427,19 @@ public class RouterInfo extends DataStructureImpl {
buf.append("\n\tIdentity: ").append(getIdentity());
buf.append("\n\tSignature: ").append(getSignature());
buf.append("\n\tPublished on: ").append(new Date(getPublished()));
buf.append("\n\tAddresses: #: ").append(getAddresses().size());
for (Iterator iter = getAddresses().iterator(); iter.hasNext();) {
Set addresses = _addresses; // getAddresses()
buf.append("\n\tAddresses: #: ").append(addresses.size());
for (Iterator iter = addresses.iterator(); iter.hasNext();) {
RouterAddress addr = (RouterAddress) iter.next();
buf.append("\n\t\tAddress: ").append(addr);
}
buf.append("\n\tPeers: #: ").append(getPeers().size());
for (Iterator iter = getPeers().iterator(); iter.hasNext();) {
Set peers = _peers; // getPeers()
buf.append("\n\tPeers: #: ").append(peers.size());
for (Iterator iter = peers.iterator(); iter.hasNext();) {
Hash hash = (Hash) iter.next();
buf.append("\n\t\tPeer hash: ").append(hash);
}
Properties options = getOptions();
Properties options = _options; // getOptions();
buf.append("\n\tOptions: #: ").append(options.size());
for (Iterator iter = options.keySet().iterator(); iter.hasNext();) {
String key = (String) iter.next();

View File

@ -19,6 +19,7 @@ import net.i2p.crypto.SHA256Generator;
import net.i2p.util.Clock;
import net.i2p.util.Log;
import net.i2p.util.RandomSource;
import net.i2p.I2PAppContext;
/**
* Component to manage the munging of hashes into routing keys - given a hash,
@ -40,12 +41,17 @@ import net.i2p.util.RandomSource;
*
*/
public class RoutingKeyGenerator {
private final static RoutingKeyGenerator _instance = new RoutingKeyGenerator();
private Log _log;
private I2PAppContext _context;
public static RoutingKeyGenerator getInstance() {
return _instance;
public RoutingKeyGenerator(I2PAppContext context) {
_log = context.logManager().getLog(RoutingKeyGenerator.class);
_context = context;
}
private final static Log _log = new Log(RoutingKeyGenerator.class);
public static RoutingKeyGenerator getInstance() {
return I2PAppContext.getGlobalContext().routingKeyGenerator();
}
private byte _currentModData[];
private final static Calendar _cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"));
@ -67,7 +73,7 @@ public class RoutingKeyGenerator {
public void generateDateBasedModData() {
Date today = null;
synchronized (_cal) {
_cal.setTime(new Date(Clock.getInstance().now()));
_cal.setTime(new Date(_context.clock().now()));
_cal.set(Calendar.HOUR_OF_DAY, 0);
_cal.set(Calendar.MINUTE, 0);
_cal.set(Calendar.SECOND, 0);

View File

@ -57,8 +57,9 @@ public class UnsignedInteger {
* available immediately.
*
* @param value number to represent
* @throws IllegalArgumentException if the value is negative
*/
public UnsignedInteger(long value) {
public UnsignedInteger(long value) throws IllegalArgumentException {
_value = value;
_data = calculateBytes(value);
}
@ -86,9 +87,42 @@ public class UnsignedInteger {
}
/**
* Calculate the bytes as an unsigned integer with the most significant bit and byte in the first position
* Calculate the bytes as an unsigned integer with the most significant
* bit and byte in the first position. The return value always has at least
* one byte in it.
*
* @throws IllegalArgumentException if the value is negative
*/
private static byte[] calculateBytes(long value) {
private static byte[] calculateBytes(long value) throws IllegalArgumentException {
if (value < 0)
throw new IllegalArgumentException("unsigned integer, and you want a negative? " + value);
byte val[] = new byte[8];
val[0] = (byte)(value >>> 56);
val[1] = (byte)(value >>> 48);
val[2] = (byte)(value >>> 40);
val[3] = (byte)(value >>> 32);
val[4] = (byte)(value >>> 24);
val[5] = (byte)(value >>> 16);
val[6] = (byte)(value >>> 8);
val[7] = (byte)value;
int firstNonZero = -1;
for (int i = 0; i < val.length; i++) {
if (val[i] != 0x00) {
firstNonZero = i;
break;
}
}
if (firstNonZero == 0)
return val;
if (firstNonZero == -1)
return new byte[1]; // initialized as 0
byte rv[] = new byte[8-firstNonZero];
System.arraycopy(val, firstNonZero, rv, 0, rv.length);
return rv;
/*
BigInteger bi = new BigInteger("" + value);
byte buf[] = bi.toByteArray();
if ((buf == null) || (buf.length <= 0))
@ -99,6 +133,7 @@ public class UnsignedInteger {
byte rv[] = new byte[buf.length - trim];
System.arraycopy(buf, trim, rv, 0, rv.length);
return rv;
*/
}
/**
@ -120,6 +155,8 @@ public class UnsignedInteger {
throw new IllegalArgumentException("Value (" + _value + ") is greater than the requested number of bytes ("
+ numBytes + ")");
if (numBytes == _data.length) return _data;
byte[] data = new byte[numBytes];
System.arraycopy(_data, 0, data, numBytes - _data.length, _data.length);
return data;
@ -189,5 +226,9 @@ public class UnsignedInteger {
_log.debug(num + " turned into a byte array and back again: " + val2 + " (" + val2.getLong() + "/"
+ toString(val2.getBytes()) + ")");
_log.debug(num + " As an 8 byte array: " + toString(val2.getBytes(8)));
BigInteger bi = new BigInteger(num+"");
_log.debug(num + " As a bigInteger: 0x" + bi.toString(16));
BigInteger tbi = new BigInteger(1, calculateBytes(num));
_log.debug(num + " As a shifted : 0x" + tbi.toString(16));
}
}

View File

@ -30,6 +30,8 @@ public class I2CPMessageReader {
private I2CPMessageEventListener _listener;
private I2CPMessageReaderRunner _reader;
private Thread _readerThread;
private static volatile long __readerId = 0;
public I2CPMessageReader(InputStream stream, I2CPMessageEventListener lsnr) {
_stream = stream;
@ -37,7 +39,7 @@ public class I2CPMessageReader {
_reader = new I2CPMessageReaderRunner();
_readerThread = new I2PThread(_reader);
_readerThread.setDaemon(true);
_readerThread.setName("I2CP Reader");
_readerThread.setName("I2CP Reader " + (++__readerId));
}
public void setListener(I2CPMessageEventListener lsnr) {

View File

@ -30,7 +30,7 @@ class PersistenceHelper {
_log.error("Error formatting " + val + " into a long", nfe);
}
} else {
_log.error("Key " + prefix + name + " does not exist");
_log.warn("Key " + prefix + name + " does not exist");
}
return 0;
}

View File

@ -6,24 +6,25 @@ import java.util.Set;
import java.util.TreeSet;
import net.i2p.util.Log;
import net.i2p.I2PAppContext;
public class SimpleStatDumper {
private final static Log _log = new Log(SimpleStatDumper.class);
public static void dumpStats(int logLevel) {
public static void dumpStats(I2PAppContext context, int logLevel) {
if (!_log.shouldLog(logLevel)) return;
StringBuffer buf = new StringBuffer(4 * 1024);
dumpFrequencies(buf);
dumpRates(buf);
dumpFrequencies(context, buf);
dumpRates(context, buf);
_log.log(logLevel, buf.toString());
}
private static void dumpFrequencies(StringBuffer buf) {
Set frequencies = new TreeSet(StatManager.getInstance().getFrequencyNames());
private static void dumpFrequencies(I2PAppContext ctx, StringBuffer buf) {
Set frequencies = new TreeSet(ctx.statManager().getFrequencyNames());
for (Iterator iter = frequencies.iterator(); iter.hasNext();) {
String name = (String) iter.next();
FrequencyStat freq = StatManager.getInstance().getFrequency(name);
FrequencyStat freq = ctx.statManager().getFrequency(name);
buf.append('\n');
buf.append(freq.getGroupName()).append('.').append(freq.getName()).append(": ")
.append(freq.getDescription()).append('\n');
@ -39,11 +40,11 @@ public class SimpleStatDumper {
}
}
private static void dumpRates(StringBuffer buf) {
Set rates = new TreeSet(StatManager.getInstance().getRateNames());
private static void dumpRates(I2PAppContext ctx, StringBuffer buf) {
Set rates = new TreeSet(ctx.statManager().getRateNames());
for (Iterator iter = rates.iterator(); iter.hasNext();) {
String name = (String) iter.next();
RateStat rate = StatManager.getInstance().getRate(name);
RateStat rate = ctx.statManager().getRate(name);
buf.append('\n');
buf.append(rate.getGroupName()).append('.').append(rate.getName()).append(": ")
.append(rate.getDescription()).append('\n');

View File

@ -10,6 +10,7 @@ import java.util.TreeMap;
import java.util.TreeSet;
import net.i2p.util.Log;
import net.i2p.I2PAppContext;
/**
* Coordinate the management of various frequencies and rates within I2P components,
@ -19,18 +20,23 @@ import net.i2p.util.Log;
*
*/
public class StatManager {
private final static Log _log = new Log(StatManager.class);
private final static StatManager _instance = new StatManager();
private Log _log;
private I2PAppContext _context;
public final static StatManager getInstance() {
return _instance;
}
/** stat name to FrequencyStat */
private Map _frequencyStats;
/** stat name to RateStat */
private Map _rateStats;
private StatManager() {
/**
* The stat manager should only be constructed and accessed through the
* application context. This constructor should only be used by the
* appropriate application context itself.
*
*/
public StatManager(I2PAppContext context) {
_log = context.logManager().getLog(StatManager.class);
_context = context;
_frequencyStats = Collections.synchronizedMap(new HashMap(128));
_rateStats = Collections.synchronizedMap(new HashMap(128));
}
@ -44,6 +50,7 @@ public class StatManager {
* @param periods array of period lengths (in milliseconds)
*/
public void createFrequencyStat(String name, String description, String group, long periods[]) {
if (_frequencyStats.containsKey(name)) return;
_frequencyStats.put(name, new FrequencyStat(name, description, group, periods));
}
@ -56,6 +63,7 @@ public class StatManager {
* @param periods array of period lengths (in milliseconds)
*/
public void createRateStat(String name, String description, String group, long periods[]) {
if (_rateStats.containsKey(name)) return;
_rateStats.put(name, new RateStat(name, description, group, periods));
}

View File

@ -4,6 +4,8 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import net.i2p.I2PAppContext;
/**
* Alternate location for determining the time which takes into account an offset.
* This offset will ideally be periodically updated so as to serve as the difference
@ -12,12 +14,20 @@ import java.util.Set;
*
*/
public class Clock {
private final static Log _log = new Log(Clock.class);
private final static Clock _instance = new Clock();
public final static Clock getInstance() {
return _instance;
private I2PAppContext _context;
public Clock(I2PAppContext context) {
_context = context;
_offset = 0;
_alreadyChanged = false;
_listeners = new HashSet(64);
}
public static Clock getInstance() {
return I2PAppContext.getGlobalContext().clock();
}
/** we fetch it on demand to avoid circular dependencies (logging uses the clock) */
private Log getLog() { return _context.logManager().getLog(Clock.class); }
private volatile long _offset;
private boolean _alreadyChanged;
private Set _listeners;
@ -25,13 +35,7 @@ public class Clock {
/** if the clock is skewed by 3+ days, fuck 'em */
public final static long MAX_OFFSET = 3 * 24 * 60 * 60 * 1000;
/** if the clock skewed changes by less than 1s, ignore the update (so we don't slide all over the place) */
public final static long MIN_OFFSET_CHANGE = 30 * 1000;
private Clock() {
_offset = 0;
_alreadyChanged = false;
_listeners = new HashSet(64);
}
public final static long MIN_OFFSET_CHANGE = 10 * 1000;
/**
* Specify how far away from the "correct" time the computer is - a positive
@ -40,18 +44,18 @@ public class Clock {
*/
public void setOffset(long offsetMs) {
if ((offsetMs > MAX_OFFSET) || (offsetMs < 0 - MAX_OFFSET)) {
_log.error("Maximum offset shift exceeded [" + offsetMs + "], NOT HONORING IT");
getLog().error("Maximum offset shift exceeded [" + offsetMs + "], NOT HONORING IT");
return;
}
long delta = offsetMs - _offset;
if ((delta < MIN_OFFSET_CHANGE) && (delta > 0 - MIN_OFFSET_CHANGE)) {
_log.debug("Not changing offset since it is only " + delta + "ms");
getLog().debug("Not changing offset since it is only " + delta + "ms");
return;
}
if (_alreadyChanged)
_log.log(Log.CRIT, "Updating clock offset to " + offsetMs + "ms from " + _offset + "ms");
getLog().log(Log.CRIT, "Updating clock offset to " + offsetMs + "ms from " + _offset + "ms");
else
_log.log(Log.INFO, "Initializing clock offset to " + offsetMs + "ms from " + _offset + "ms");
getLog().log(Log.INFO, "Initializing clock offset to " + offsetMs + "ms from " + _offset + "ms");
_alreadyChanged = true;
_offset = offsetMs;
fireOffsetChanged(delta);

View File

@ -9,6 +9,11 @@ package net.i2p.util;
*
*/
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
/**
* In case its useful later...
* (e.g. w/ native programatic thread dumping, etc)
@ -16,7 +21,7 @@ package net.i2p.util;
*/
public class I2PThread extends Thread {
private static Log _log;
private static OOMEventListener _lsnr;
private static Set _listeners = new HashSet(4);
public I2PThread() {
super();
@ -38,19 +43,29 @@ public class I2PThread extends Thread {
try {
super.run();
} catch (Throwable t) {
if ((t instanceof OutOfMemoryError) && (_lsnr != null)) _lsnr.outOfMemory((OutOfMemoryError) t);
if (t instanceof OutOfMemoryError)
fireOOM((OutOfMemoryError)t);
// we cant assume log is created
if (_log == null) _log = new Log(I2PThread.class);
_log.log(Log.CRIT, "Killing thread " + getName(), t);
}
}
public static void setOOMEventListener(OOMEventListener lsnr) {
_lsnr = lsnr;
private void fireOOM(OutOfMemoryError oom) {
for (Iterator iter = _listeners.iterator(); iter.hasNext(); ) {
OOMEventListener listener = (OOMEventListener)iter.next();
listener.outOfMemory(oom);
}
}
public static OOMEventListener getOOMEventListener() {
return _lsnr;
/** register a new component that wants notification of OOM events */
public static void addOOMEventListener(OOMEventListener lsnr) {
_listeners.add(lsnr);
}
/** unregister a component that wants notification of OOM events */
public static void removeOOMEventListener(OOMEventListener lsnr) {
_listeners.remove(lsnr);
}
public interface OOMEventListener {

View File

@ -9,6 +9,9 @@ package net.i2p.util;
*
*/
import net.i2p.data.DataHelper;
import net.i2p.I2PAppContext;
/**
* Wrapper class for whatever logging system I2P uses. This class should be
* instantiated and kept as a variable for each class it is used by, ala:
@ -24,6 +27,8 @@ public class Log {
private Class _class;
private String _name;
private int _minPriority;
private LogScope _scope;
private LogManager _manager;
public final static int DEBUG = 10;
public final static int INFO = 20;
@ -65,33 +70,46 @@ public class Log {
}
public Log(Class cls) {
this(cls, null);
this(I2PAppContext.getGlobalContext().logManager(), cls, null);
_manager.addLog(this);
}
public Log(String name) {
this(null, name);
this(I2PAppContext.getGlobalContext().logManager(), null, name);
_manager.addLog(this);
}
public Log(Class cls, String name) {
Log(LogManager manager, Class cls) {
this(manager, cls, null);
}
Log(LogManager manager, String name) {
this(manager, null, name);
}
Log(LogManager manager, Class cls, String name) {
_manager = manager;
_class = cls;
_name = name;
_minPriority = DEBUG;
LogManager.getInstance().registerLog(this);
_scope = new LogScope(name, cls);
//_manager.addRecord(new LogRecord(Log.class, null, Thread.currentThread().getName(), Log.DEBUG,
// "Log created with manager " + manager + " for class " + cls, null));
}
public void log(int priority, String msg) {
if (priority >= _minPriority) {
LogManager.getInstance().addRecord(
new LogRecord(_class, _name, Thread.currentThread().getName(), priority,
msg, null));
_manager.addRecord(new LogRecord(_class, _name,
Thread.currentThread().getName(), priority,
msg, null));
}
}
public void log(int priority, String msg, Throwable t) {
if (priority >= _minPriority) {
LogManager.getInstance().addRecord(
new LogRecord(_class, _name, Thread.currentThread().getName(), priority,
msg, t));
_manager.addRecord(new LogRecord(_class, _name,
Thread.currentThread().getName(), priority,
msg, t));
}
}
@ -133,6 +151,9 @@ public class Log {
public void setMinimumPriority(int priority) {
_minPriority = priority;
//_manager.addRecord(new LogRecord(Log.class, null, Thread.currentThread().getName(), Log.DEBUG,
// "Log with manager " + _manager + " for class " + _class
// + " new priority " + toLevelString(priority), null));
}
public boolean shouldLog(int priority) {
@ -145,5 +166,32 @@ public class Log {
else
return _name;
}
public Object getScope() { return _scope; }
private static final class LogScope {
private String _scopeName;
private Class _scopeClass;
public LogScope(String name, Class cls) {
_scopeName = name;
_scopeClass = cls;
}
public int hashCode() {
if (_scopeClass != null)
return _scopeClass.hashCode();
else if (_scopeName != null)
return _scopeName.hashCode();
else
return 42;
}
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);
} else {
return false;
}
}
}
}

View File

@ -1,26 +1,24 @@
package net.i2p.util;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
import net.i2p.I2PAppContext;
/**
* Offer a glimpse into the last few console messages generated
*
*/
public class LogConsoleBuffer {
private final static LogConsoleBuffer _instance = new LogConsoleBuffer();
public final static LogConsoleBuffer getInstance() {
return _instance;
}
private I2PAppContext _context;
private List _buffer;
private LogConsoleBuffer() {
_buffer = new LinkedList();
public LogConsoleBuffer(I2PAppContext context) {
_context = context;
_buffer = new ArrayList();
}
void add(String msg) {
int lim = LogManager.getInstance().getConsoleBufferSize();
int lim = _context.logManager().getConsoleBufferSize();
synchronized (_buffer) {
while (_buffer.size() >= lim)
_buffer.remove(0);
@ -36,7 +34,7 @@ public class LogConsoleBuffer {
*/
public List getMostRecentMessages() {
synchronized (_buffer) {
return new LinkedList(_buffer);
return new ArrayList(_buffer);
}
}
}

View File

@ -19,6 +19,10 @@ import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.Map;
import java.util.HashMap;
import net.i2p.I2PAppContext;
/**
* Manages the logging system, loading (and reloading) the configuration file,
@ -31,14 +35,6 @@ public class LogManager {
public final static String CONFIG_LOCATION_PROP = "loggerConfigLocation";
public final static String FILENAME_OVERRIDE_PROP = "loggerFilenameOverride";
public final static String CONFIG_LOCATION_DEFAULT = "logger.config";
public static final LogManager getInstance() {
return _instance;
}
private static final LogManager _instance = new LogManager(System.getProperty(CONFIG_LOCATION_PROP,
CONFIG_LOCATION_DEFAULT));
private static final Log _log = new Log(LogManager.class);
/**
* These define the characters in the format line of the config file
*/
@ -65,12 +61,15 @@ public class LogManager {
public final static String DEFAULT_DEFALTLEVEL = Log.STR_DEBUG;
public final static String DEFAULT_ONSCREENLEVEL = Log.STR_DEBUG;
private I2PAppContext _context;
private Log _log;
private long _configLastRead;
private String _location;
private List _records;
private Set _limits;
private Set _logs;
private Map _logs;
private LogWriter _writer;
private int _defaultLimit;
@ -83,7 +82,63 @@ public class LogManager {
private boolean _displayOnScreen;
private int _consoleBufferSize;
private LogConsoleBuffer _consoleBuffer;
public LogManager(I2PAppContext context) {
_displayOnScreen = true;
_records = new ArrayList();
_limits = new HashSet();
_logs = new HashMap(128);
_defaultLimit = Log.DEBUG;
_configLastRead = 0;
_location = context.getProperty(CONFIG_LOCATION_PROP, CONFIG_LOCATION_DEFAULT);
_context = context;
_log = getLog(LogManager.class);
_consoleBuffer = new LogConsoleBuffer(context);
loadConfig();
_writer = new LogWriter(this);
Thread t = new I2PThread(_writer);
t.setName("LogWriter");
t.setDaemon(true);
t.start();
try {
Runtime.getRuntime().addShutdownHook(new ShutdownHook());
} catch (IllegalStateException ise) {
// shutdown in progress, fsck it
}
//System.out.println("Created logManager " + this + " with context: " + context);
}
private LogManager() {}
public Log getLog(Class cls) { return getLog(cls, null); }
public Log getLog(String name) { return getLog(null, name); }
public Log getLog(Class cls, String name) {
Log rv = null;
synchronized (_logs) {
Log newLog = new Log(this, cls, name);
if (_logs.containsKey(newLog.getScope())) {
Log oldLog = (Log)_logs.get(newLog.getScope());
rv = oldLog;
//_log.error("Duplicate log creation for " + cls);
} else {
_logs.put(newLog.getScope(), newLog);
rv = newLog;
}
}
updateLimit(rv);
return rv;
}
void addLog(Log log) {
synchronized (_logs) {
if (!_logs.containsKey(log.getScope()))
_logs.put(log.getScope(), log);
}
updateLimit(log);
}
public LogConsoleBuffer getBuffer() { return _consoleBuffer; }
public void setDisplayOnScreen(boolean yes) {
_displayOnScreen = yes;
}
@ -123,18 +178,7 @@ public class LogManager {
_records.add(record);
}
}
/**
* Called during Log construction
*
*/
void registerLog(Log log) {
synchronized (_logs) {
_logs.add(log);
}
updateLimit(log);
}
/**
* Called periodically by the log writer's thread
*
@ -148,23 +192,6 @@ public class LogManager {
///
///
private LogManager(String location) {
_displayOnScreen = true;
_location = location;
_records = new ArrayList();
_limits = new HashSet();
_logs = new HashSet();
_defaultLimit = Log.DEBUG;
_configLastRead = 0;
loadConfig();
_writer = new LogWriter();
Thread t = new I2PThread(_writer);
t.setName("LogWriter");
t.setDaemon(true);
t.start();
Runtime.getRuntime().addShutdownHook(new ShutdownHook());
}
//
//
//
@ -172,9 +199,13 @@ public class LogManager {
private void loadConfig() {
Properties p = new Properties();
File cfgFile = new File(_location);
if ((_configLastRead > 0) && (_configLastRead > cfgFile.lastModified())) {
_log.debug("Short circuiting config read");
if ((_configLastRead > 0) && (_configLastRead >= cfgFile.lastModified())) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Short circuiting config read");
return;
} else {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Loading config from " + _location);
}
FileInputStream fis = null;
try {
@ -212,7 +243,7 @@ public class LogManager {
_displayOnScreen = false;
}
String filenameOverride = System.getProperty(FILENAME_OVERRIDE_PROP);
String filenameOverride = _context.getProperty(FILENAME_OVERRIDE_PROP);
if (filenameOverride != null)
_baseLogfilename = filenameOverride;
else
@ -244,6 +275,9 @@ public class LogManager {
_consoleBufferSize = DEFAULT_CONSOLEBUFFERSIZE;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Log set to use the base log file as " + _baseLogfilename);
parseLimits(config);
}
@ -297,11 +331,11 @@ public class LogManager {
}
private void updateLimits() {
Set logs = new HashSet();
Map logs = null;
synchronized (_logs) {
logs.addAll(_logs);
logs = new HashMap(_logs);
}
for (Iterator iter = logs.iterator(); iter.hasNext();) {
for (Iterator iter = logs.values().iterator(); iter.hasNext();) {
Log log = (Log) iter.next();
updateLimit(log);
}
@ -322,10 +356,13 @@ public class LogManager {
}
}
}
if (max != null)
if (max != null) {
log.setMinimumPriority(max.getLimit());
else
} else {
//if (_log != null)
// _log.debug("The log for " + log.getClass() + " has no matching limits");
log.setMinimumPriority(_defaultLimit);
}
}
private List getLimits(Log log) {
@ -373,10 +410,11 @@ public class LogManager {
}
public static void main(String args[]) {
Log l1 = new Log("test.1");
Log l2 = new Log("test.2");
Log l21 = new Log("test.2.1");
Log l = new Log("test");
I2PAppContext ctx = new I2PAppContext();
Log l1 = ctx.logManager().getLog("test.1");
Log l2 = ctx.logManager().getLog("test.2");
Log l21 = ctx.logManager().getLog("test.2.1");
Log l = ctx.logManager().getLog("test");
l.debug("this should fail");
l.info("this should pass");
l1.warn("this should pass");

View File

@ -26,13 +26,13 @@ class LogRecordFormatter {
private final static int MAX_THREAD_LENGTH = 12;
private final static int MAX_PRIORITY_LENGTH = 5;
public static String formatRecord(LogRecord rec) {
public static String formatRecord(LogManager manager, LogRecord rec) {
StringBuffer buf = new StringBuffer();
char format[] = LogManager.getInstance()._getFormat();
char format[] = manager._getFormat();
for (int i = 0; i < format.length; ++i) {
switch ((int) format[i]) {
case (int) LogManager.DATE:
buf.append(getWhen(rec));
buf.append(getWhen(manager, rec));
break;
case (int) LogManager.CLASS:
buf.append(getWhere(rec));
@ -71,8 +71,8 @@ class LogRecordFormatter {
return toString(logRecord.getThreadName(), MAX_THREAD_LENGTH);
}
private static String getWhen(LogRecord logRecord) {
return LogManager.getInstance()._getDateFormat().format(new Date(logRecord.getDate()));
private static String getWhen(LogManager manager, LogRecord logRecord) {
return manager._getDateFormat().format(new Date(logRecord.getDate()));
}
private static String getPriority(LogRecord rec) {

View File

@ -29,8 +29,14 @@ class LogWriter implements Runnable {
private int _rotationNum = -1;
private String _logFilenamePattern;
private File _currentFile;
private LogManager _manager;
private boolean _write;
private LogWriter() {}
public LogWriter(LogManager manager) {
_manager = manager;
}
public void stopWriting() {
_write = false;
@ -46,7 +52,7 @@ class LogWriter implements Runnable {
public void flushRecords() {
try {
List records = LogManager.getInstance()._removeAll();
List records = _manager._removeAll();
for (int i = 0; i < records.size(); i++) {
LogRecord rec = (LogRecord) records.get(i);
writeRecord(rec);
@ -68,19 +74,19 @@ class LogWriter implements Runnable {
}
long now = Clock.getInstance().now();
if (now - _lastReadConfig > CONFIG_READ_ITERVAL) {
LogManager.getInstance().rereadConfig();
_manager.rereadConfig();
_lastReadConfig = now;
}
}
private void writeRecord(LogRecord rec) {
String val = LogRecordFormatter.formatRecord(rec);
String val = LogRecordFormatter.formatRecord(_manager, rec);
writeRecord(val);
if (LogManager.getInstance().getDisplayOnScreenLevel() <= rec.getPriority()) {
if (_manager.getDisplayOnScreenLevel() <= rec.getPriority()) {
// we always add to the console buffer, but only sometimes write to stdout
LogConsoleBuffer.getInstance().add(val);
if (LogManager.getInstance().displayOnScreen()) {
_manager.getBuffer().add(val);
if (_manager.displayOnScreen()) {
System.out.print(val);
}
}
@ -98,7 +104,7 @@ class LogWriter implements Runnable {
System.err.println("Error writing record, disk full?");
t.printStackTrace();
}
if (_numBytesInCurrentFile >= LogManager.getInstance()._getFileSize()) {
if (_numBytesInCurrentFile >= _manager._getFileSize()) {
rotateFile();
}
}
@ -108,7 +114,7 @@ class LogWriter implements Runnable {
*
*/
private void rotateFile() {
String pattern = LogManager.getInstance()._getBaseLogfilename();
String pattern = _manager._getBaseLogfilename();
File f = getNextFile(pattern);
_currentFile = f;
_numBytesInCurrentFile = 0;
@ -129,7 +135,7 @@ class LogWriter implements Runnable {
if (pattern.indexOf('#') < 0) {
return new File(pattern);
} else {
int max = LogManager.getInstance()._getRotationLimit();
int max = _manager._getRotationLimit();
if (_rotationNum == -1) {
return getFirstFile(pattern, max);
} else {

View File

@ -24,7 +24,8 @@ public class NativeBigInteger extends BigInteger {
_log.info("Native BigInteger library jbigi loaded");
} catch (UnsatisfiedLinkError ule) {
_nativeOk = false;
_log.warn("Native BigInteger library jbigi not loaded - using pure java", ule);
_log.log(Log.CRIT, "Native BigInteger library jbigi not loaded - using pure java");
_log.warn("jbigi not loaded", ule);
}
}

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