forked from I2P_Developers/i2p.i2p
drop most of the abandonware
This commit is contained in:
@@ -1,39 +0,0 @@
|
||||
The Heartbeat GUI loads up the stat files generated by the Heartbeat
|
||||
engine and renders them visually, offering a way to drill through different
|
||||
data points and take snapshots as things change (by saving particular stat
|
||||
files for later). The GUI itself doesn't need to be on the same machine
|
||||
as the Heartbeat engine - it pulls the stat files through any URL - even
|
||||
through the EepProxy.
|
||||
|
||||
An example Heartbeat GUI config file follows
|
||||
|
||||
# how often do we want to pull new data to render
|
||||
refreshFrequency=60
|
||||
## for each peer test we may want to include in the GUI:
|
||||
# where to find the current stat file (URL or filename)
|
||||
stat.0.location=http://dev.i2p.net/stats/heartbeatStat_khWY_30s_1kb.txt
|
||||
## optional entries for each peer test describing what we want shown
|
||||
## (and how we want it shown)
|
||||
# do we want to plot the send time (from when the ping was sent until the pong server got it)?
|
||||
stat.0.plot.current.send=true
|
||||
# do we want to plot the receive time (from when the pong was sent until reception)?
|
||||
stat.0.plot.current.receive=true
|
||||
# do we want to plot the lost messages?
|
||||
stat.0.plot.current.lost=true
|
||||
# what color should the current lines be rendered in?
|
||||
stat.0.plot.current.color=BLUE
|
||||
## optional entries for each peer test describing what averages we want
|
||||
## rendered
|
||||
# plot 1 minute send average?
|
||||
stat.0.plot.1m.send=true
|
||||
# plot 1 minute receive average?
|
||||
stat.0.plot.1m.receive=true
|
||||
# plot 1 minute lost message average?
|
||||
stat.0.plot.1m.lost=true
|
||||
# what color should the 1 minute averages be rendered as?
|
||||
stat.0.plot.1m.color=GREEN
|
||||
## repeated for all of the averaged periods, e.g.
|
||||
## stat.0.plot.30m, .60m, 1440m (1 day)
|
||||
|
||||
There may be some other options, such as where to store snapshot files, whether
|
||||
to generate PNG images, etc.
|
||||
@@ -1,122 +0,0 @@
|
||||
Heartbeat
|
||||
|
||||
Application layer tool for monitoring the long term health of the
|
||||
network by periodically testing peers, generating stats, and
|
||||
rendering them visually. The engine (both server and client) should
|
||||
work headless and seperate from the GUI, exposing the data in a simple
|
||||
to parse (and human readable) text file for each peer being tested.
|
||||
The GUI then periodically refreshes itself by loading those files (
|
||||
either locally or from a URL) and renders the current state accordingly,
|
||||
giving users a way to check that the network is alive, devs a tool to
|
||||
both monitor the state of the network and to debug different situations (by
|
||||
accessing the stat file - either live or archived).
|
||||
|
||||
The heartbeat configuration file is organized as a standard properties
|
||||
file (by default located at heartbeat.config, but that can be overridden by
|
||||
passing a filename as the first argument to the Heartbeat command):
|
||||
|
||||
# where the router is located (default is localhost)
|
||||
i2cpHost=localhost
|
||||
# I2CP port for the router (default is 7654)
|
||||
i2cpPort=4001
|
||||
# How many hops we want the router to put in our tunnels (default is 2)
|
||||
numHops=2
|
||||
# where our private destination keys are located - if this doesn't exist,
|
||||
# a new one will be created and saved there (by default, heartbeat.keys)
|
||||
privateDestinationFile=heartbeat_r2.keys
|
||||
|
||||
## peer tests configured below:
|
||||
|
||||
# destination peer for test 0
|
||||
peer.0.peer=[destination in base64]
|
||||
# where will we write out the stat data?
|
||||
peer.0.statFile=heartbeatStat_khWY_30s_1kb.txt
|
||||
# how many minutes will we keep stats for?
|
||||
peer.0.statDuration=30
|
||||
# how often will we write out new stat data (in seconds)?
|
||||
peer.0.statFrequency=60
|
||||
# how often will we send a ping to the peer (in seconds)?
|
||||
peer.0.sendFrequency=30
|
||||
# how many bytes will be included in the ping?
|
||||
peer.0.sendSize=1024
|
||||
# take a guess...
|
||||
peer.0.comment=Test with localhost sending 1KB of data every 30 seconds
|
||||
# we can keep track of a few moving averages - this value includes a whitespace
|
||||
# delimited list of numbers, each specifying a period to calculate the average
|
||||
# over (in minutes)
|
||||
peer.0.averagePeriods=1 5 30
|
||||
## repeat the peer.0.* for as many tests as desired, incrementing as necessary
|
||||
|
||||
If there are no peer.* lines, it will simply run a pong server. If any data is
|
||||
missing, it will use the defaults (though there are no defaults for peer.* lines) -
|
||||
running the Heartbeat app with no heartbeat configuration file whatsoever will create
|
||||
a new pong server (storing its keys at heartbeat.keys) and using the I2P router at
|
||||
localhost:7654.
|
||||
|
||||
The stat file generated for each set of peer.n.* lines contains the current state
|
||||
of the test, its averages, as well as any other interesting data points. An example
|
||||
stat file follows (hopefully it is self explanatory):
|
||||
|
||||
peer khWYqCETu9YtPUvGV92ocsbEW5DezhKlIG7ci8RLX3g=
|
||||
local u-9hlR1ik2hemXf0HvKMfeRgrS86CbNQh25e7XBhaQE=
|
||||
peerDest [base 64 of the full destination]
|
||||
localDest [base 64 of the full destination]
|
||||
numTunnelHops 2
|
||||
comment Test with localhost sending 30KB every 20 seconds
|
||||
sendFrequency 20
|
||||
sendSize 30720
|
||||
sessionStart 20040409.22:51:10.915
|
||||
currentTime 20040409.23:31:39.607
|
||||
numPending 2
|
||||
lifetimeSent 118
|
||||
lifetimeRecv 113
|
||||
#averages minutes sendMs recvMs numLost
|
||||
periodAverage 1 1843 771 0
|
||||
periodAverage 5 786 752 1
|
||||
periodAverage 30 855 735 3
|
||||
#action status date and time sent sendMs replyMs
|
||||
EVENT OK 20040409.23:21:44.742 691 670
|
||||
EVENT OK 20040409.23:22:05.201 671 581
|
||||
EVENT OK 20040409.23:22:26.301 1182 1452
|
||||
EVENT OK 20040409.23:22:47.322 24304 1723
|
||||
EVENT OK 20040409.23:23:08.232 2293 1081
|
||||
EVENT OK 20040409.23:23:29.332 1392 641
|
||||
EVENT OK 20040409.23:23:50.262 641 761
|
||||
EVENT OK 20040409.23:24:11.102 651 701
|
||||
EVENT OK 20040409.23:24:31.401 841 621
|
||||
EVENT OK 20040409.23:24:52.061 651 681
|
||||
EVENT OK 20040409.23:25:12.480 701 1623
|
||||
EVENT OK 20040409.23:25:32.990 1442 1212
|
||||
EVENT OK 20040409.23:25:54.230 591 631
|
||||
EVENT OK 20040409.23:26:14.620 620 691
|
||||
EVENT OK 20040409.23:26:35.199 1793 1432
|
||||
EVENT OK 20040409.23:26:56.570 661 641
|
||||
EVENT OK 20040409.23:27:17.200 641 660
|
||||
EVENT OK 20040409.23:27:38.120 611 921
|
||||
EVENT OK 20040409.23:27:58.699 831 621
|
||||
EVENT OK 20040409.23:28:19.559 801 661
|
||||
EVENT OK 20040409.23:28:40.279 601 611
|
||||
EVENT OK 20040409.23:29:00.648 601 621
|
||||
EVENT OK 20040409.23:29:21.288 701 661
|
||||
EVENT LOST 20040409.23:29:41.828
|
||||
EVENT LOST 20040409.23:30:02.327
|
||||
EVENT LOST 20040409.23:30:22.656
|
||||
EVENT OK 20040409.23:31:24.305 1843 771
|
||||
|
||||
The actual ping and pong messages sent are formatted trivially -
|
||||
ping messages contain
|
||||
$from $series $type $sentOn $size $payload
|
||||
while pong messages contain
|
||||
$from $series $type $sentOn $receivedOn $size $payload
|
||||
|
||||
$series is a number describing the sending client's test (so that you can
|
||||
ping the same peer with different configurations concurrently, varying things
|
||||
like the frequency and size of the message, window, etc).
|
||||
|
||||
They are sent as raw binary messages though, so see I2PAdapter.sendPing(..)
|
||||
and I2PAdapter.sendPong(..) for the details.
|
||||
|
||||
To get valid measurements, of course, you will want to make sure that
|
||||
both the heartbeat client and pong server have synchronized clocks (even
|
||||
more so than I2P requires). It is highly recommended that only NTP
|
||||
synchronized peers be used for heartbeat tests.
|
||||
@@ -1,66 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="heartbeat">
|
||||
<target name="all" depends="clean, buildGUI" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="buildGUI" depends="build, jarGUI" />
|
||||
<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" deprecation="on" source="1.3" target="1.3" destdir="./build/obj" includes="**/*.java" excludes="net/i2p/heartbeat/gui/**" classpath="../../../core/java/build/i2p.jar" />
|
||||
</target>
|
||||
<target name="compileGUI">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac debug="true" source="1.3" target="1.3" deprecation="on" destdir="./build/obj">
|
||||
<src path="src/" />
|
||||
<classpath path="../../../core/java/build/i2p.jar" />
|
||||
<classpath path="../../jfreechart/jfreechart-0.9.17/lib/jcommon-0.9.2.jar" />
|
||||
<classpath path="../../jfreechart/jfreechart-0.9.17/lib/log4j-1.2.8.jar" />
|
||||
<classpath path="../../jfreechart/jfreechart-0.9.17/jfreechart-0.9.17.jar" />
|
||||
</javac>
|
||||
</target>
|
||||
<target name="jar" depends="compile">
|
||||
<jar destfile="./build/heartbeat.jar" basedir="./build/obj" includes="**/*.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.heartbeat.Heartbeat" />
|
||||
<attribute name="Class-Path" value="i2p.jar heartbeat.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
<target name="jarGUI" depends="compileGUI">
|
||||
<copy file="../../jfreechart/jfreechart-0.9.17/jfreechart-0.9.17.jar" todir="build/" />
|
||||
<copy file="../../jfreechart/jfreechart-0.9.17/lib/log4j-1.2.8.jar" todir="build/" />
|
||||
<copy file="../../jfreechart/jfreechart-0.9.17/lib/jcommon-0.9.2.jar" todir="build/" />
|
||||
<jar destfile="./build/heartbeatGUI.jar" basedir="./build/obj" includes="**">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.heartbeat.gui.HeartbeatMonitor" />
|
||||
<attribute name="Class-Path" value="log4j-1.2.8.jar jcommon-0.9.2.jar jfreechart-0.9.17.jar heartbeatGUI.jar i2p.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
<echo message="You will need to copy the log4j, jcommon, and jfreechart jar files into your lib dir" />
|
||||
</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 heartbeat monitor" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../../core/java/" target="cleandep" />
|
||||
<ant dir="../../../core/java/" target="cleandep" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
||||
@@ -1,468 +0,0 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Define the configuration for testing against one particular peer as a client
|
||||
*/
|
||||
public class ClientConfig {
|
||||
private static final Log _log = new Log(ClientConfig.class);
|
||||
private Destination _peer;
|
||||
private Destination _us;
|
||||
private String _statFile;
|
||||
private int _statDuration;
|
||||
private int _statFrequency;
|
||||
private int _sendFrequency;
|
||||
private int _sendSize;
|
||||
private int _numHops;
|
||||
private String _comment;
|
||||
private int _averagePeriods[];
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_PREFIX = "peer.";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_PEER = ".peer";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_STATFILE = ".statFile";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_STATDURATION = ".statDuration";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_STATFREQUENCY = ".statFrequency";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_SENDFREQUENCY = ".sendFrequency";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_SENDSIZE = ".sendSize";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_COMMENT = ".comment";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_AVERAGEPERIODS = ".averagePeriods";
|
||||
|
||||
/**
|
||||
* Default constructor...
|
||||
*/
|
||||
public ClientConfig() {
|
||||
this(null, null, null, -1, -1, -1, -1, 0, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a dummy client config to be fetched from the specified location
|
||||
* @param location the location to fetch from
|
||||
*/
|
||||
public ClientConfig(String location) {
|
||||
this(null, null, location, -1, -1, -1, -1, 0, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param peer who we will test against
|
||||
* @param us who we are
|
||||
* @param statLocation where the stat data should be stored/fetched
|
||||
* @param duration how many minutes to keep events for
|
||||
* @param statFreq how often to write out stats
|
||||
* @param sendFreq how often to send pings
|
||||
* @param sendSize how large the pings should be
|
||||
* @param numHops how many hops is the current Heartbeat app using
|
||||
* @param comment describe this test
|
||||
* @param averagePeriods list of minutes to summarize over
|
||||
*/
|
||||
public ClientConfig(Destination peer, Destination us, String statLocation, int duration, int statFreq, int sendFreq,
|
||||
int sendSize, int numHops, String comment, int averagePeriods[]) {
|
||||
_peer = peer;
|
||||
_us = us;
|
||||
_statFile = statLocation;
|
||||
_statDuration = duration;
|
||||
_statFrequency = statFreq;
|
||||
_sendFrequency = sendFreq;
|
||||
_sendSize = sendSize;
|
||||
_numHops = numHops;
|
||||
_comment = comment;
|
||||
_averagePeriods = averagePeriods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the peer to test against
|
||||
*
|
||||
* @return the Destination (peer)
|
||||
*/
|
||||
public Destination getPeer() {
|
||||
return _peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the peer to test against
|
||||
*
|
||||
* @param peer the Destination (peer)
|
||||
*/
|
||||
public void setPeer(Destination peer) {
|
||||
_peer = peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves who we are when we test
|
||||
*
|
||||
* @return the Destination (us)
|
||||
*/
|
||||
public Destination getUs() {
|
||||
return _us;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets who we are when we test
|
||||
*
|
||||
* @param us the Destination (us)
|
||||
*/
|
||||
public void setUs(Destination us) {
|
||||
_us = us;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the location to write the current stats to
|
||||
*
|
||||
* @return the name of the file
|
||||
*/
|
||||
public String getStatFile() {
|
||||
return _statFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the location we write the current stats to
|
||||
*
|
||||
* @param statFile the name of the file
|
||||
*/
|
||||
public void setStatFile(String statFile) {
|
||||
_statFile = statFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how many minutes of statistics should be maintained within the window for this client
|
||||
*
|
||||
* @return the number of minutes
|
||||
*/
|
||||
public int getStatDuration() {
|
||||
return _statDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how many minutes of statistics should be maintained within the window for this client
|
||||
*
|
||||
* @param durationMinutes the number of minutes
|
||||
*/
|
||||
public void setStatDuration(int durationMinutes) {
|
||||
_statDuration = durationMinutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how frequently the stats are written out (in seconds)
|
||||
*
|
||||
* @return the frequency in seconds
|
||||
*/
|
||||
public int getStatFrequency() {
|
||||
return _statFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how frequently the stats are written out (in seconds)
|
||||
*
|
||||
* @param freqSeconds the frequency in seconds
|
||||
*/
|
||||
public void setStatFrequency(int freqSeconds) {
|
||||
_statFrequency = freqSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how frequenty we send messages to the peer (in seconds)
|
||||
*
|
||||
* @return the frequency in seconds
|
||||
*/
|
||||
public int getSendFrequency() {
|
||||
return _sendFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how frequenty we send messages to the peer (in seconds)
|
||||
*
|
||||
* @param freqSeconds the frequency in seconds
|
||||
*/
|
||||
public void setSendFrequency(int freqSeconds) {
|
||||
_sendFrequency = freqSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how many bytes the ping messages should be (min values ~700, max ~32KB)
|
||||
*
|
||||
* @return the size in bytes
|
||||
*/
|
||||
public int getSendSize() {
|
||||
return _sendSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how many bytes the ping messages should be (min values ~700, max ~32KB)
|
||||
*
|
||||
* @param numBytes the size in bytes
|
||||
*/
|
||||
public void setSendSize(int numBytes) {
|
||||
_sendSize = numBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the brief, 1 line description of the test. Useful comments are along the lines of "The peer is located on a fast router and connection with 2
|
||||
* hop tunnels".
|
||||
*
|
||||
* @return the brief comment
|
||||
*/
|
||||
public String getComment() {
|
||||
return _comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a brief, 1 line description (comment) of the test.
|
||||
*
|
||||
* @param comment the brief comment
|
||||
*/
|
||||
public void setComment(String comment) {
|
||||
_comment = comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the periods that the client's tests should be averaged over.
|
||||
*
|
||||
* @return list of periods (in minutes) that the data should be averaged over, or null
|
||||
*/
|
||||
public int[] getAveragePeriods() {
|
||||
return _averagePeriods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the periods that the client's tests should be averaged over.
|
||||
*
|
||||
* @param periods the list of periods (in minutes) that the data should be averaged over, or null
|
||||
*/
|
||||
public void setAveragePeriods(int periods[]) {
|
||||
_averagePeriods = periods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure we're keeping track of the average over the given time period.
|
||||
*
|
||||
* @param minutes how many minutes to monitor
|
||||
*/
|
||||
public void addAveragePeriod(int minutes) {
|
||||
if (_averagePeriods != null) {
|
||||
for (int i = 0; i < _averagePeriods.length; i++) {
|
||||
if (_averagePeriods[i] == minutes)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int numPeriods = 1;
|
||||
if (_averagePeriods != null)
|
||||
numPeriods += _averagePeriods.length;
|
||||
int periods[] = new int[numPeriods];
|
||||
if (_averagePeriods != null)
|
||||
System.arraycopy(_averagePeriods, 0, periods, 0, _averagePeriods.length);
|
||||
periods[periods.length-1] = minutes;
|
||||
Arrays.sort(periods);
|
||||
_averagePeriods = periods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how many hops this test engine is configured to use for its outbound and inbound tunnels
|
||||
*
|
||||
* @return the number of hops
|
||||
*/
|
||||
public int getNumHops() {
|
||||
return _numHops;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how many hops this test engine is configured to use for its outbound and inbound tunnels
|
||||
*
|
||||
* @param numHops the number of hops
|
||||
*/
|
||||
public void setNumHops(int numHops) {
|
||||
_numHops = numHops;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the client config from the properties specified, deriving the current config entry from the peer number.
|
||||
*
|
||||
* @param clientConfig the properties to load from
|
||||
* @param peerNum the number associated with the peer
|
||||
* @return true if it was loaded correctly, false if there were errors
|
||||
*/
|
||||
public boolean load(Properties clientConfig, int peerNum) {
|
||||
if ((clientConfig == null) || (peerNum < 0)) return false;
|
||||
String peerVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_PEER);
|
||||
String statFileVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_STATFILE);
|
||||
String statDurationVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_STATDURATION);
|
||||
String statFrequencyVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_STATFREQUENCY);
|
||||
String sendFrequencyVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_SENDFREQUENCY);
|
||||
String sendSizeVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_SENDSIZE);
|
||||
String commentVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_COMMENT);
|
||||
String periodsVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_AVERAGEPERIODS);
|
||||
|
||||
if ((peerVal == null) || (statFileVal == null) || (statDurationVal == null) || (statFrequencyVal == null)
|
||||
|| (sendFrequencyVal == null) || (sendSizeVal == null)) {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Peer number " + peerNum + " does not exist");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
int duration = getInt(statDurationVal);
|
||||
int statFreq = getInt(statFrequencyVal);
|
||||
int sendFreq = getInt(sendFrequencyVal);
|
||||
int sendSize = getInt(sendSizeVal);
|
||||
|
||||
if ((duration <= 0) || (statFreq <= 0) || (sendFreq < 0) || (sendSize <= 0)) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("Invalid client config: duration [" + statDurationVal + "] stat frequency ["
|
||||
+ statFrequencyVal + "] send frequency [" + sendFrequencyVal + "] send size ["
|
||||
+ sendSizeVal + "]");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
statFileVal = statFileVal.trim();
|
||||
if (statFileVal.length() <= 0) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("Stat file is blank for peer " + peerNum);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(peerVal);
|
||||
|
||||
if (commentVal == null) {
|
||||
commentVal = "";
|
||||
}
|
||||
|
||||
commentVal = commentVal.trim();
|
||||
commentVal = commentVal.replace('\n', '_');
|
||||
|
||||
List periods = new ArrayList(4);
|
||||
if (periodsVal != null) {
|
||||
StringTokenizer tok = new StringTokenizer(periodsVal);
|
||||
while (tok.hasMoreTokens()) {
|
||||
String periodVal = tok.nextToken();
|
||||
int minutes = getInt(periodVal);
|
||||
if (minutes > 0) {
|
||||
periods.add(new Integer(minutes));
|
||||
}
|
||||
}
|
||||
}
|
||||
int avgPeriods[] = new int[periods.size()];
|
||||
for (int i = 0; i < periods.size(); i++) {
|
||||
avgPeriods[i] = ((Integer) periods.get(i)).intValue();
|
||||
}
|
||||
|
||||
_comment = commentVal;
|
||||
_statDuration = duration;
|
||||
_statFrequency = statFreq;
|
||||
_sendFrequency = sendFreq;
|
||||
_sendSize = sendSize;
|
||||
_statFile = statFileVal;
|
||||
_peer = d;
|
||||
_averagePeriods = avgPeriods;
|
||||
return true;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Peer destination for " + peerNum + " was invalid: " + peerVal);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the client config to the properties specified, deriving the current config entry from the peer number.
|
||||
*
|
||||
* @param clientConfig the properties to store to
|
||||
* @param peerNum the number associated with the peer
|
||||
* @return true if it was stored correctly, false if there were errors
|
||||
*/
|
||||
public boolean store(Properties clientConfig, int peerNum) {
|
||||
if ((_peer == null) || (_sendFrequency < 0) || (_sendSize <= 0) || (_statDuration <= 0)
|
||||
|| (_statFrequency <= 0) || (_statFile == null)) { return false; }
|
||||
|
||||
String comment = _comment;
|
||||
if (comment == null) {
|
||||
comment = "";
|
||||
}
|
||||
|
||||
comment = comment.trim();
|
||||
comment = comment.replace('\n', '_');
|
||||
|
||||
StringBuffer buf = new StringBuffer(32);
|
||||
if (_averagePeriods != null) {
|
||||
for (int i = 0; i < _averagePeriods.length; i++) {
|
||||
buf.append(_averagePeriods[i]).append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_PEER, _peer.toBase64());
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_STATFILE, _statFile);
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_STATDURATION, _statDuration + "");
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_STATFREQUENCY, _statFrequency + "");
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_SENDFREQUENCY, _sendFrequency + "");
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_SENDSIZE, _sendSize + "");
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_COMMENT, comment);
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_AVERAGEPERIODS, buf.toString());
|
||||
return true;
|
||||
}
|
||||
|
||||
private static final int getInt(String val) {
|
||||
if (val == null) return -1;
|
||||
try {
|
||||
int i = Integer.parseInt(val);
|
||||
return i;
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Value [" + val + "] is not a valid integer");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Responsible for actually conducting the tests, coordinating the storing of the
|
||||
* stats, and the management of the rates. This has its own thread specific for
|
||||
* pumping data around as well.
|
||||
*
|
||||
*/
|
||||
class ClientEngine {
|
||||
private static final Log _log = new Log(ClientEngine.class);
|
||||
/** who can send our pings? */
|
||||
private Heartbeat _heartbeat;
|
||||
/** actual test state */
|
||||
private PeerData _data;
|
||||
/** have we been stopped? */
|
||||
private boolean _active;
|
||||
/** used to generate engine IDs */
|
||||
private static int __id = 0;
|
||||
/** this engine's id, unique to the {test,sendingClient,startTime} */
|
||||
private int _id;
|
||||
private static PeerDataWriter writer = new PeerDataWriter();
|
||||
|
||||
/**
|
||||
* Create a new engine that will send its pings through the given heartbeat
|
||||
* system, and will coordinate the test according to the configuration specified.
|
||||
* @param heartbeat the Heartbeat to send pings through
|
||||
* @param config the Configuration to load configuration from =p
|
||||
*/
|
||||
public ClientEngine(Heartbeat heartbeat, ClientConfig config) {
|
||||
_heartbeat = heartbeat;
|
||||
_data = new PeerData(config);
|
||||
_active = false;
|
||||
_id = ++__id;
|
||||
}
|
||||
|
||||
/** stop sending any more pings or writing any more state */
|
||||
public void stopEngine() {
|
||||
_active = false;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Stopping engine talking to peer " + _data.getConfig().getPeer().calculateHash().toBase64());
|
||||
}
|
||||
|
||||
/** start up the test (this does not block, as it fires up the test thread) */
|
||||
public void startEngine() {
|
||||
_active = true;
|
||||
I2PThread t = new I2PThread(new ClientRunner());
|
||||
t.setName("HeartbeatClient " + _id);
|
||||
t.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Who are we testing?
|
||||
* @return the Destination (peer) we're testing
|
||||
*/
|
||||
public Destination getPeer() {
|
||||
return _data.getConfig().getPeer();
|
||||
}
|
||||
|
||||
/**
|
||||
* What is our series identifier (used to locally identify a test)
|
||||
* @return the series identifier
|
||||
*/
|
||||
public int getSeriesNum() {
|
||||
return _id;
|
||||
}
|
||||
|
||||
/**
|
||||
* receive notification from the heartbeat system that a pong was received in
|
||||
* reply to a ping we have sent.
|
||||
*
|
||||
* @param sentOn when did we send the ping?
|
||||
* @param replyOn when did the peer send the pong?
|
||||
*/
|
||||
public void receivePong(long sentOn, long replyOn) {
|
||||
_data.pongReceived(sentOn, replyOn);
|
||||
}
|
||||
|
||||
/** fire off a new ping */
|
||||
private void doSend() {
|
||||
long now = Clock.getInstance().now();
|
||||
_data.addPing(now);
|
||||
_heartbeat.sendPing(_data.getConfig().getPeer(), _id, now, _data.getConfig().getSendSize());
|
||||
}
|
||||
|
||||
/** our actual heartbeat pumper - this drives the test */
|
||||
private class ClientRunner implements Runnable {
|
||||
|
||||
/**
|
||||
* @see java.lang.Runnable#run()
|
||||
*/
|
||||
public void run() {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Starting engine talking to peer " + _data.getConfig().getPeer().calculateHash().toBase64());
|
||||
|
||||
// when do we need to send the next PING?
|
||||
long nextSend = Clock.getInstance().now();
|
||||
// when do we need to write out the next state data?
|
||||
long nextWrite = Clock.getInstance().now();
|
||||
|
||||
while (_active) {
|
||||
|
||||
if (Clock.getInstance().now() >= nextSend) {
|
||||
doSend();
|
||||
nextSend = Clock.getInstance().now() + _data.getConfig().getSendFrequency() * 1000;
|
||||
}
|
||||
|
||||
if (Clock.getInstance().now() >= nextWrite) {
|
||||
boolean written = writer.persist(_data);
|
||||
if (!written) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error("Unable to write the client state data");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Client state data written");
|
||||
}
|
||||
}
|
||||
|
||||
_data.cleanup();
|
||||
|
||||
long timeToWait = nextSend - Clock.getInstance().now();
|
||||
if (timeToWait > 0) {
|
||||
try {
|
||||
Thread.sleep(timeToWait);
|
||||
} catch (InterruptedException ie) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Main driver for the heartbeat engine, loading 0 or more tests, firing
|
||||
* up a ClientEngine for each, and serving as a pong server. If there isn't
|
||||
* a configuration file, or if the configuration file doesn't specify any tests,
|
||||
* it simply sits around as a pong server, passively responding to whatever is
|
||||
* sent its way. <p />
|
||||
*
|
||||
* The config file format is examplified below:
|
||||
* <pre>
|
||||
* # where the router is located (default is localhost)
|
||||
* i2cpHost=localhost
|
||||
* # I2CP port for the router (default is 7654)
|
||||
* i2cpPort=4001
|
||||
* # How many hops we want the router to put in our tunnels (default is 2)
|
||||
* numHops=2
|
||||
* # where our private destination keys are located - if this doesn't exist,
|
||||
* # a new one will be created and saved there (by default, heartbeat.keys)
|
||||
* privateDestinationFile=heartbeat_r2.keys
|
||||
* # where do we want to export the plain base64 of our destination?
|
||||
* publicDestinationFile=heartbeat_r2.txt
|
||||
*
|
||||
* ## peer tests configured below:
|
||||
*
|
||||
* # destination peer for test 0
|
||||
* peer.0.peer=[destination in base64]
|
||||
* # where will we write out the stat data?
|
||||
* peer.0.statFile=heartbeatStat_khWY_30s_1kb.txt
|
||||
* # how many minutes will we keep stats for?
|
||||
* peer.0.statDuration=30
|
||||
* # how often will we write out new stat data (in seconds)?
|
||||
* peer.0.statFrequency=60
|
||||
* # how often will we send a ping to the peer (in seconds)?
|
||||
* peer.0.sendFrequency=30
|
||||
* # how many bytes will be included in the ping?
|
||||
* peer.0.sendSize=1024
|
||||
* # take a guess...
|
||||
* peer.0.comment=Test with localhost sending 1KB of data every 30 seconds
|
||||
* # we can keep track of a few moving averages - this value includes a whitespace
|
||||
* # delimited list of numbers, each specifying a period to calculate the average
|
||||
* # over (in minutes)
|
||||
* peer.0.averagePeriods=1 5 30
|
||||
* ## repeat the peer.0.* for as many tests as desired, incrementing as necessary
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public class Heartbeat {
|
||||
private static final Log _log = new Log(Heartbeat.class);
|
||||
/** location containing this heartbeat's config */
|
||||
private String _configFile;
|
||||
/** clientNum (Integer) to ClientConfig mapping */
|
||||
private Map _clientConfigs;
|
||||
/** series num (Integer) to ClientEngine mapping */
|
||||
private Map _clientEngines;
|
||||
/** helper class for managing our I2P send/receive and message formatting */
|
||||
private I2PAdapter _adapter;
|
||||
/** our own callback that the I2PAdapter notifies on ping or pong messages */
|
||||
private PingPongAdapter _eventAdapter;
|
||||
|
||||
/** if there are no command line arguments, load the config from "heartbeat.config" */
|
||||
public static final String CONFIG_FILE_DEFAULT = "heartbeat.config";
|
||||
|
||||
/**
|
||||
* build up a new heartbeat manager, but don't actually do anything
|
||||
* @param configFile the name of the configuration file
|
||||
*/
|
||||
public Heartbeat(String configFile) {
|
||||
_configFile = configFile;
|
||||
_clientConfigs = new HashMap();
|
||||
_clientEngines = new HashMap();
|
||||
_eventAdapter = new PingPongAdapter();
|
||||
_adapter = new I2PAdapter();
|
||||
_adapter.setListener(_eventAdapter);
|
||||
}
|
||||
|
||||
private Heartbeat() {
|
||||
}
|
||||
|
||||
/** load up the config data (but don't build any engines or start them up) */
|
||||
public void loadConfig() {
|
||||
Properties props = new Properties();
|
||||
FileInputStream fin = null;
|
||||
File configFile = new File(_configFile);
|
||||
if (configFile.exists()) {
|
||||
try {
|
||||
fin = new FileInputStream(_configFile);
|
||||
props.load(fin);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error reading the config data", ioe);
|
||||
}
|
||||
} finally {
|
||||
if (fin != null) try {
|
||||
fin.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadBaseConfig(props);
|
||||
loadClientConfigs(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* send a ping message to the peer
|
||||
*
|
||||
* @param peer peer to ping
|
||||
* @param seriesNum id used to keep track of multiple pings (of different size/frequency) to a peer
|
||||
* @param now current time to be sent in the ping (so we can watch for it in the pong)
|
||||
* @param size total message size to send
|
||||
*/
|
||||
void sendPing(Destination peer, int seriesNum, long now, int size) {
|
||||
if (_adapter.getIsConnected()) _adapter.sendPing(peer, seriesNum, now, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* load up the base data (I2CP config, etc)
|
||||
* @param props the properties to load from
|
||||
*/
|
||||
private void loadBaseConfig(Properties props) {
|
||||
_adapter.loadConfig(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* load up all of the test config data
|
||||
* @param props the properties to load from
|
||||
* */
|
||||
private void loadClientConfigs(Properties props) {
|
||||
int i = 0;
|
||||
while (true) {
|
||||
ClientConfig config = new ClientConfig();
|
||||
if (!config.load(props, i)) {
|
||||
break;
|
||||
}
|
||||
_clientConfigs.put(new Integer(i), config);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/** connect to the network */
|
||||
private void connect() {
|
||||
boolean connected = _adapter.connect();
|
||||
if (!connected) _log.error("Unable to connect to the router");
|
||||
}
|
||||
|
||||
/** disconnect from the network */
|
||||
private void disconnect() { /* UNUSED */
|
||||
_adapter.disconnect();
|
||||
}
|
||||
|
||||
/** start up all of the tests */
|
||||
public void startEngines() {
|
||||
for (Iterator iter = _clientConfigs.values().iterator(); iter.hasNext();) {
|
||||
ClientConfig config = (ClientConfig) iter.next();
|
||||
ClientEngine engine = new ClientEngine(this, config);
|
||||
config.setUs(_adapter.getLocalDestination());
|
||||
config.setNumHops(_adapter.getNumHops());
|
||||
_clientEngines.put(new Integer(engine.getSeriesNum()), engine);
|
||||
engine.startEngine();
|
||||
}
|
||||
}
|
||||
|
||||
/** stop all of the tests */
|
||||
public void stopEngines() {
|
||||
for (Iterator iter = _clientEngines.values().iterator(); iter.hasNext();) {
|
||||
ClientEngine engine = (ClientEngine) iter.next();
|
||||
engine.stopEngine();
|
||||
}
|
||||
_clientEngines.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire up a new heartbeat system, waiting until, well, forever. Builds
|
||||
* a new heartbeat system, loads the config, connects to the network, starts
|
||||
* the engines, and then sits back and relaxes, responding to any pings and
|
||||
* running any tests. <p />
|
||||
*
|
||||
* <code> <b>Usage: </b> Heartbeat [<i>configFileName</i>]</code> <p />
|
||||
* @param args the list of args passed to the program from the command-line
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
String configFile = CONFIG_FILE_DEFAULT;
|
||||
if (args.length == 1) {
|
||||
configFile = args[0];
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Starting up with config file " + configFile);
|
||||
}
|
||||
Heartbeat heartbeat = new Heartbeat(configFile);
|
||||
heartbeat.loadConfig();
|
||||
heartbeat.connect();
|
||||
heartbeat.startEngines();
|
||||
Object o = new Object();
|
||||
while (true) {
|
||||
try {
|
||||
synchronized (o) {
|
||||
o.wait();
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive event notification from the I2PAdapter
|
||||
*
|
||||
*/
|
||||
private class PingPongAdapter implements I2PAdapter.PingPongEventListener {
|
||||
/**
|
||||
* We were pinged, so always just send a pong back.
|
||||
*
|
||||
* @param from who sent us the ping?
|
||||
* @param seriesNum what series did the sender specify?
|
||||
* @param sentOn when did the sender say they sent their ping?
|
||||
* @param data arbitrary payload data
|
||||
*/
|
||||
public void receivePing(Destination from, int seriesNum, Date sentOn, byte[] data) {
|
||||
if (_adapter.getIsConnected()) {
|
||||
_adapter.sendPong(from, seriesNum, sentOn, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We received a pong, so find the right client engine and tell it about the pong.
|
||||
*
|
||||
* @param from who sent us the pong
|
||||
* @param seriesNum our client ID
|
||||
* @param sentOn when did we send the ping?
|
||||
* @param replyOn when did they send their pong?
|
||||
* @param data the arbitrary data we sent in the ping (that they sent back in the pong)
|
||||
*/
|
||||
public void receivePong(Destination from, int seriesNum, Date sentOn, Date replyOn, byte[] data) {
|
||||
ClientEngine engine = (ClientEngine) _clientEngines.get(new Integer(seriesNum));
|
||||
if (engine.getPeer().equals(from)) {
|
||||
engine.receivePong(sentOn.getTime(), replyOn.getTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,604 +0,0 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Tie-in to the I2P SDK for the Heartbeat system, talking to the I2PSession and
|
||||
* dealing with the raw ping and pong messages.
|
||||
*
|
||||
*/
|
||||
class I2PAdapter {
|
||||
private final static Log _log = new Log(I2PAdapter.class);
|
||||
/** I2CP host */
|
||||
private String _i2cpHost;
|
||||
/** I2CP port */
|
||||
private int _i2cpPort;
|
||||
/** how long do we want our tunnels to be? */
|
||||
private int _numHops;
|
||||
/** filename containing the heartbeat engine's private destination info */
|
||||
private String _privateDestFile;
|
||||
/** filename to store the heartbeat engine's public destination in base64*/
|
||||
private String _publicDestFile;
|
||||
/** our destination */
|
||||
private Destination _localDest;
|
||||
/** who do we tell? */
|
||||
private PingPongEventListener _listener;
|
||||
/** how do we talk to the router */
|
||||
private I2PSession _session;
|
||||
/** object that receives our i2cp notifications from the session and tells us */
|
||||
private I2PListener _i2pListener; /* UNUSED */
|
||||
|
||||
/**
|
||||
* This config property tells us where the private destination data for our
|
||||
* connection (or if it doesn't exist, where will we save it)
|
||||
*/
|
||||
private static final String DEST_FILE_PROP = "privateDestinationFile";
|
||||
/** by default, the private destination data is in "heartbeat.keys" */
|
||||
private static final String DEST_FILE_DEFAULT = "heartbeat.keys";
|
||||
/** where will we export the public destination in base 64? */
|
||||
private static final String PUBLIC_DEST_FILE_PROP = "publicDestinationFile";
|
||||
/** where will we export the public destination in base 64? */
|
||||
private static final String PUBLIC_DEST_FILE_DEFAULT = "heartbeat.txt";
|
||||
/** This config property defines where the I2P router is */
|
||||
private static final String I2CP_HOST_PROP = "i2cpHost";
|
||||
/** by default, the I2P host is "localhost" */
|
||||
private static final String I2CP_HOST_DEFAULT = "localhost";
|
||||
/** This config property defines the I2CP port on the router */
|
||||
private static final String I2CP_PORT_PROP = "i2cpPort";
|
||||
/** by default, the I2CP port is 7654 */
|
||||
private static final int I2CP_PORT_DEFAULT = 7654;
|
||||
|
||||
/** This property defines how many hops we want in our tunnels. */
|
||||
public static final String NUMHOPS_PROP = "numHops";
|
||||
/** by default, use 2 hop tunnels */
|
||||
public static final int NUMHOPS_DEFAULT = 2;
|
||||
|
||||
/**
|
||||
* Constructs an I2PAdapter . . .
|
||||
*/
|
||||
public I2PAdapter() {
|
||||
_privateDestFile = null;
|
||||
_publicDestFile = null;
|
||||
_i2cpHost = null;
|
||||
_i2cpPort = -1;
|
||||
_localDest = null;
|
||||
_listener = null;
|
||||
_session = null;
|
||||
_numHops = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* who are we?
|
||||
* @return the destination (us)
|
||||
*/
|
||||
public Destination getLocalDestination() {
|
||||
return _localDest;
|
||||
}
|
||||
|
||||
/**
|
||||
* who gets notified when we receive a ping or a pong?
|
||||
* @return the event listener who gets notified
|
||||
*/
|
||||
public PingPongEventListener getListener() {
|
||||
return _listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets who gets notified when we receive a ping or a pong
|
||||
* @param listener the event listener to get notified
|
||||
*/
|
||||
public void setListener(PingPongEventListener listener) {
|
||||
_listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* how many hops do we want in our tunnels?
|
||||
* @return the number of hops
|
||||
*/
|
||||
public int getNumHops() {
|
||||
return _numHops;
|
||||
}
|
||||
|
||||
/**
|
||||
* are we connected?
|
||||
* @return true or false . . .
|
||||
*/
|
||||
public boolean getIsConnected() {
|
||||
return _session != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read in all of the config data
|
||||
* @param props the properties to load from
|
||||
*/
|
||||
void loadConfig(Properties props) {
|
||||
String privDestFile = props.getProperty(DEST_FILE_PROP, DEST_FILE_DEFAULT);
|
||||
String pubDestFile = props.getProperty(PUBLIC_DEST_FILE_PROP, PUBLIC_DEST_FILE_DEFAULT);
|
||||
String host = props.getProperty(I2CP_HOST_PROP, I2CP_HOST_DEFAULT);
|
||||
String port = props.getProperty(I2CP_PORT_PROP, "" + I2CP_PORT_DEFAULT);
|
||||
String numHops = props.getProperty(NUMHOPS_PROP, "" + NUMHOPS_DEFAULT);
|
||||
|
||||
int portNum = -1;
|
||||
try {
|
||||
portNum = Integer.parseInt(port);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("Invalid I2CP port specified [" + port + "]");
|
||||
}
|
||||
portNum = I2CP_PORT_DEFAULT;
|
||||
}
|
||||
int hops = -1;
|
||||
try {
|
||||
hops = Integer.parseInt(numHops);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("Invalid # hops specified [" + numHops + "]");
|
||||
}
|
||||
hops = NUMHOPS_DEFAULT;
|
||||
}
|
||||
|
||||
_numHops = hops;
|
||||
_privateDestFile = privDestFile;
|
||||
_publicDestFile = pubDestFile;
|
||||
_i2cpHost = host;
|
||||
_i2cpPort = portNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* write out the config to the props
|
||||
* @param props the properties to write to
|
||||
*/
|
||||
void storeConfig(Properties props) {
|
||||
if (_privateDestFile != null) {
|
||||
props.setProperty(DEST_FILE_PROP, _privateDestFile);
|
||||
} else {
|
||||
props.setProperty(DEST_FILE_PROP, DEST_FILE_DEFAULT);
|
||||
}
|
||||
|
||||
if (_publicDestFile != null) {
|
||||
props.setProperty(PUBLIC_DEST_FILE_PROP, _publicDestFile);
|
||||
} else {
|
||||
props.setProperty(PUBLIC_DEST_FILE_PROP, PUBLIC_DEST_FILE_DEFAULT);
|
||||
}
|
||||
|
||||
if (_i2cpHost != null) {
|
||||
props.setProperty(I2CP_HOST_PROP, _i2cpHost);
|
||||
} else {
|
||||
props.setProperty(I2CP_HOST_PROP, I2CP_HOST_DEFAULT);
|
||||
}
|
||||
|
||||
if (_i2cpPort > 0) {
|
||||
props.setProperty(I2CP_PORT_PROP, "" + _i2cpPort);
|
||||
} else {
|
||||
props.setProperty(I2CP_PORT_PROP, "" + I2CP_PORT_DEFAULT);
|
||||
}
|
||||
|
||||
props.setProperty(NUMHOPS_PROP, "" + _numHops);
|
||||
}
|
||||
|
||||
private static final int TYPE_PING = 0;
|
||||
private static final int TYPE_PONG = 1;
|
||||
|
||||
/**
|
||||
* send a ping message to the peer
|
||||
*
|
||||
* @param peer peer to ping
|
||||
* @param seriesNum id used to keep track of multiple pings (of different size/frequency) to a peer
|
||||
* @param now current time to be sent in the ping (so we can watch for it in the pong)
|
||||
* @param size total message size to send
|
||||
*
|
||||
* @throws IllegalStateException if we are not connected to the router
|
||||
*/
|
||||
public void sendPing(Destination peer, int seriesNum, long now, int size) {
|
||||
if (_session == null) throw new IllegalStateException("Not connected to the router");
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(size);
|
||||
try {
|
||||
_localDest.writeBytes(baos);
|
||||
DataHelper.writeLong(baos, 2, seriesNum);
|
||||
DataHelper.writeLong(baos, 1, TYPE_PING);
|
||||
DataHelper.writeDate(baos, new Date(now));
|
||||
int padding = size - baos.size();
|
||||
byte paddingData[] = new byte[padding];
|
||||
I2PAppContext.getGlobalContext().random().nextBytes(paddingData);
|
||||
//Arrays.fill(paddingData, (byte) 0x2A);
|
||||
DataHelper.writeLong(baos, 2, padding);
|
||||
baos.write(paddingData);
|
||||
boolean sent = _session.sendMessage(peer, baos.toByteArray());
|
||||
if (!sent) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error sending the ping to " + peer.calculateHash().toBase64() + " for series "
|
||||
+ seriesNum);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Ping sent to " + peer.calculateHash().toBase64() + " for series " + seriesNum);
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error sending the ping", ioe);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error writing out the ping message", dfe);
|
||||
}
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error writing out the ping message", ise);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* send a pong message to the peer
|
||||
*
|
||||
* @param peer peer to pong
|
||||
* @param seriesNum id given to us in the ping
|
||||
* @param sentOn date the peer said they sent us the message
|
||||
* @param data payload the peer sent us in the ping
|
||||
*
|
||||
* @throws IllegalStateException if we are not connected to the router
|
||||
*/
|
||||
public void sendPong(Destination peer, int seriesNum, Date sentOn, byte data[]) {
|
||||
if (_session == null) throw new IllegalStateException("Not connected to the router");
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(data.length + 768);
|
||||
try {
|
||||
_localDest.writeBytes(baos);
|
||||
DataHelper.writeLong(baos, 2, seriesNum);
|
||||
DataHelper.writeLong(baos, 1, TYPE_PONG);
|
||||
DataHelper.writeDate(baos, sentOn);
|
||||
DataHelper.writeDate(baos, new Date(Clock.getInstance().now()));
|
||||
DataHelper.writeLong(baos, 2, data.length);
|
||||
baos.write(data);
|
||||
boolean sent = _session.sendMessage(peer, baos.toByteArray());
|
||||
if (!sent) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error sending the pong to " + peer.calculateHash().toBase64() + " for series "
|
||||
+ seriesNum + " which was sent on " + sentOn);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Pong sent to " + peer.calculateHash().toBase64() + " for series " + seriesNum
|
||||
+ " which was sent on " + sentOn);
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error sending the ping", ioe);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error writing out the pong message", dfe);
|
||||
}
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error writing out the pong message", ise);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received this data from I2P - parse it into a ping or a pong
|
||||
* and notify accordingly
|
||||
* @param data the data to handle
|
||||
*/
|
||||
private void handleMessage(byte data[]) {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(data);
|
||||
try {
|
||||
Destination from = new Destination();
|
||||
from.readBytes(bais);
|
||||
int series = (int) DataHelper.readLong(bais, 2);
|
||||
long type = DataHelper.readLong(bais, 1);
|
||||
Date sentOn = DataHelper.readDate(bais);
|
||||
Date receivedOn = null;
|
||||
if (type == TYPE_PONG) {
|
||||
receivedOn = DataHelper.readDate(bais);
|
||||
}
|
||||
int size = (int) DataHelper.readLong(bais, 2);
|
||||
byte payload[] = new byte[size];
|
||||
int read = DataHelper.read(bais, payload);
|
||||
if (read != size) { throw new IOException("Malformed payload - read " + read + " instead of " + size); }
|
||||
|
||||
if (_listener == null) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Listener isn't set, but we received a valid message of type " + type + " sent from "
|
||||
+ from.calculateHash().toBase64());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == TYPE_PING) {
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Ping received from " + from.calculateHash().toBase64() + " on series " + series
|
||||
+ " sent on " + sentOn + " containing " + size + " bytes");
|
||||
}
|
||||
_listener.receivePing(from, series, sentOn, payload);
|
||||
} else if (type == TYPE_PONG) {
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Pong received from " + from.calculateHash().toBase64() + " on series " + series
|
||||
+ " sent on " + sentOn + " with pong sent on " + receivedOn + " containing " + size
|
||||
+ " bytes");
|
||||
}
|
||||
_listener.receivePong(from, series, sentOn, receivedOn, payload);
|
||||
} else {
|
||||
throw new IOException("Invalid message type " + type);
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error handling the message", ioe);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error parsing the message", dfe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* connect to the I2P router and either authenticate ourselves with the
|
||||
* destination we're given, or create a new one and write that to the
|
||||
* destination file.
|
||||
*
|
||||
* @return true if we connect successfully, false otherwise
|
||||
*/
|
||||
boolean connect() {
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
Destination us = null;
|
||||
File destFile = new File(_privateDestFile);
|
||||
us = verifyDestination(client, destFile);
|
||||
if (us == null) return false;
|
||||
|
||||
// if we're here, we got a destination. lets connect
|
||||
FileInputStream fin = null;
|
||||
try {
|
||||
fin = new FileInputStream(destFile);
|
||||
Properties options = getOptions();
|
||||
I2PSession session = client.createSession(fin, options);
|
||||
I2PListener lsnr = new I2PListener();
|
||||
session.setSessionListener(lsnr);
|
||||
session.connect();
|
||||
_localDest = session.getMyDestination();
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("I2CP Session created and connected as " + _localDest.calculateHash().toBase64());
|
||||
}
|
||||
_session = session;
|
||||
_i2pListener = lsnr;
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error connecting", ise);
|
||||
}
|
||||
return false;
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error loading the destionation", ioe);
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
if (fin != null) try {
|
||||
fin.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* load, verify, or create a destination
|
||||
*
|
||||
* @param client the client
|
||||
* @param destFile the file holding the destination
|
||||
* @return the destination loaded, or null if there was an error
|
||||
*/
|
||||
private Destination verifyDestination(I2PClient client, File destFile) {
|
||||
Destination us = null;
|
||||
FileInputStream fin = null;
|
||||
if (destFile.exists()) {
|
||||
try {
|
||||
fin = new FileInputStream(destFile);
|
||||
us = new Destination();
|
||||
us.readBytes(fin);
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Existing destination loaded: [" + us.toBase64() + "]");
|
||||
}
|
||||
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(_publicDestFile);
|
||||
fos.write(us.toBase64().getBytes());
|
||||
fos.flush();
|
||||
} catch (IOException fioe) {
|
||||
_log.error("Error writing out the plain destination to [" + _publicDestFile + "]", fioe);
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException fioe) {}
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
if (fin != null) try {
|
||||
fin.close();
|
||||
} catch (IOException ioe2) {
|
||||
}
|
||||
fin = null;
|
||||
destFile.delete();
|
||||
us = null;
|
||||
} catch (DataFormatException dfe) {
|
||||
if (fin != null) try {
|
||||
fin.close();
|
||||
} catch (IOException ioe2) {
|
||||
}
|
||||
fin = null;
|
||||
destFile.delete();
|
||||
us = null;
|
||||
} finally {
|
||||
if (fin != null) try {
|
||||
fin.close();
|
||||
} catch (IOException ioe2) {
|
||||
}
|
||||
fin = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (us == null) {
|
||||
// need to create a new one
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(destFile);
|
||||
us = client.createDestination(fos);
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("New destination created: [" + us.toBase64() + "]");
|
||||
}
|
||||
fos.close();
|
||||
|
||||
try {
|
||||
fos = new FileOutputStream(_publicDestFile);
|
||||
fos.write(us.toBase64().getBytes());
|
||||
fos.flush();
|
||||
} catch (IOException fioe) {
|
||||
_log.error("Error writing out the plain destination to [" + _publicDestFile + "]", fioe);
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException fioe) {}
|
||||
fos = null;
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error writing out the destination keys being created", ioe);
|
||||
}
|
||||
return null;
|
||||
} catch (I2PException ie) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error creating the destination", ie);
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
if (fos != null) try {
|
||||
fos.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return us;
|
||||
}
|
||||
|
||||
/**
|
||||
* I2PSession connect options
|
||||
* @return the options as Properties
|
||||
*/
|
||||
private Properties getOptions() {
|
||||
Properties props = new Properties();
|
||||
// this should be BEST_EFFORT, but i'm too lazy to update the code to handle tracking
|
||||
// sessionTags and sessionKeys, marking them as delivered on pong.
|
||||
props.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
|
||||
props.setProperty(I2PClient.PROP_TCP_HOST, _i2cpHost);
|
||||
props.setProperty(I2PClient.PROP_TCP_PORT, _i2cpPort + "");
|
||||
props.setProperty("tunnels.depthInbound", "" + _numHops);
|
||||
props.setProperty("tunnels.depthOutbound", "" + _numHops);
|
||||
return props;
|
||||
}
|
||||
|
||||
/** disconnect from the I2P router */
|
||||
void disconnect() {
|
||||
if (_session != null) {
|
||||
try {
|
||||
_session.destroySession();
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error destroying the session", ise);
|
||||
}
|
||||
}
|
||||
_session = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an event notification system for receiving pings and pongs
|
||||
*
|
||||
*/
|
||||
public interface PingPongEventListener {
|
||||
/**
|
||||
* receive a ping message from the peer
|
||||
*
|
||||
* @param from peer that sent us the ping
|
||||
* @param seriesNum id the peer sent us in the ping
|
||||
* @param sentOn date the peer said they sent us the message
|
||||
* @param data payload from the ping
|
||||
*/
|
||||
void receivePing(Destination from, int seriesNum, Date sentOn, byte data[]);
|
||||
|
||||
/**
|
||||
* receive a pong message from the peer
|
||||
*
|
||||
* @param from peer that sent us the pong
|
||||
* @param seriesNum id the peer sent us in the pong (that we sent them in the ping)
|
||||
* @param sentOn when we sent out the ping
|
||||
* @param replyOn when they sent out the pong
|
||||
* @param data payload from the ping/pong
|
||||
*/
|
||||
void receivePong(Destination from, int seriesNum, Date sentOn, Date replyOn, byte data[]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive data from the session and pass it along to handleMessage for parsing/dispersal
|
||||
*
|
||||
*/
|
||||
private class I2PListener implements I2PSessionListener {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.client.I2PSessionListener#disconnected(net.i2p.client.I2PSession)
|
||||
*/
|
||||
public void disconnected(I2PSession session) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Session disconnected");
|
||||
}
|
||||
disconnect();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.client.I2PSessionListener#errorOccurred(net.i2p.client.I2PSession, java.lang.String, java.lang.Throwable)
|
||||
*/
|
||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error("Error occurred: " + message, error);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.client.I2PSessionListener#reportAbuse(net.i2p.client.I2PSession, int)
|
||||
*/
|
||||
public void reportAbuse(I2PSession session, int severity) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error("Abuse reported with severity " + String.valueOf(severity));
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.client.I2PSessionListener#messageAvailable(net.i2p.client.I2PSession, int, long)
|
||||
*/
|
||||
public void messageAvailable(I2PSession session, int msgId, long size) {
|
||||
try {
|
||||
byte data[] = session.receiveMessage(msgId);
|
||||
handleMessage(data);
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error("Error receiving the message", ise);
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,412 +0,0 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Contain the current window of data for a particular series of ping/pong stats
|
||||
* sent to a peer. This should be periodically kept clean by calling cleanup()
|
||||
* to timeout expired pings and to drop data outside the window.
|
||||
*
|
||||
*/
|
||||
public class PeerData {
|
||||
private final static Log _log = new Log(PeerData.class);
|
||||
/** peer / sequence / config in this data series */
|
||||
private ClientConfig _peer;
|
||||
/** date sent (Long) to EventDataPoint containing the datapoints sent in the current period */
|
||||
private Map _dataPoints;
|
||||
/** date sent (Long) to EventDataPoint containing pings that haven't yet timed out or been ponged */
|
||||
private TreeMap _pendingPings;
|
||||
private long _sessionStart;
|
||||
private long _lifetimeSent;
|
||||
private long _lifetimeReceived;
|
||||
/** rate averaging the time to send over a variety of periods */
|
||||
private RateStat _sendRate;
|
||||
/** rate averaging the time to receive over a variety of periods */
|
||||
private RateStat _receiveRate;
|
||||
/** rate averaging the frequency of lost messages over a variety of periods */
|
||||
private RateStat _lostRate;
|
||||
|
||||
/** how long we wait before timing out pending pings (30 seconds) */
|
||||
private static final long TIMEOUT_PERIOD = 60 * 1000;
|
||||
|
||||
/** synchronize on this when updating _dataPoints or _pendingPings */
|
||||
private Object _updateLock = new Object();
|
||||
|
||||
/**
|
||||
* Creates a PeerData . . .
|
||||
* @param config configuration to load from
|
||||
*/
|
||||
public PeerData(ClientConfig config) {
|
||||
_peer = config;
|
||||
_dataPoints = new TreeMap();
|
||||
_pendingPings = new TreeMap();
|
||||
_sessionStart = Clock.getInstance().now();
|
||||
_lifetimeSent = 0;
|
||||
_lifetimeReceived = 0;
|
||||
_sendRate = new RateStat("sendRate", "How long it takes to send", "peer",
|
||||
getPeriods(config.getAveragePeriods()));
|
||||
_receiveRate = new RateStat("receiveRate", "How long it takes to receive", "peer",
|
||||
getPeriods(config.getAveragePeriods()));
|
||||
_lostRate = new RateStat("lostRate", "How frequently we lose messages", "peer",
|
||||
getPeriods(config.getAveragePeriods()));
|
||||
}
|
||||
|
||||
/**
|
||||
* turn the periods (# minutes) into rate periods (# milliseconds)
|
||||
* @param periods (in minutes)
|
||||
* @return an array of periods (in milliseconds)
|
||||
*/
|
||||
private static long[] getPeriods(int periods[]) {
|
||||
long rv[] = null;
|
||||
if (periods == null) periods = new int[0];
|
||||
rv = new long[periods.length];
|
||||
for (int i = 0; i < periods.length; i++)
|
||||
rv[i] = (long) periods[i] * 60 * 1000; // they're in minutes
|
||||
Arrays.sort(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* how many pings are still outstanding?
|
||||
* @return the number of pings outstanding
|
||||
*/
|
||||
public int getPendingCount() {
|
||||
synchronized (_updateLock) {
|
||||
return _pendingPings.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* how many data points are available in the current window?
|
||||
* @return the number of datapoints available
|
||||
*/
|
||||
public int getDataPointCount() {
|
||||
synchronized (_updateLock) {
|
||||
return _dataPoints.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* when did this test begin?
|
||||
* @return when the test began
|
||||
*/
|
||||
public long getSessionStart() { return _sessionStart; }
|
||||
|
||||
/**
|
||||
* sets when the test began
|
||||
* @param when when it began
|
||||
*/
|
||||
public void setSessionStart(long when) { _sessionStart = when; }
|
||||
|
||||
/**
|
||||
* how many pings have we sent for this test?
|
||||
* @return the number of pings sent
|
||||
*/
|
||||
public long getLifetimeSent() { return _lifetimeSent; }
|
||||
|
||||
/**
|
||||
* how many pongs have we received for this test?
|
||||
* @return the number of pings received
|
||||
*/
|
||||
public long getLifetimeReceived() { return _lifetimeReceived; }
|
||||
|
||||
/**
|
||||
* @return the client configuration
|
||||
*/
|
||||
public ClientConfig getConfig() {
|
||||
return _peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* What periods are we averaging the data over (in minutes)?
|
||||
* @return the periods as an array of ints (in minutes)
|
||||
*/
|
||||
public int[] getAveragePeriods() {
|
||||
return (_peer.getAveragePeriods() != null ? _peer.getAveragePeriods() : new int[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* average time to send over the given period.
|
||||
*
|
||||
* @param period number of minutes to retrieve the average for
|
||||
* @return milliseconds average, or -1 if we dont track that period
|
||||
*/
|
||||
public double getAverageSendTime(int period) {
|
||||
return getAverage(_sendRate, period);
|
||||
}
|
||||
|
||||
/**
|
||||
* average time to receive over the given period.
|
||||
*
|
||||
* @param period number of minutes to retrieve the average for
|
||||
* @return milliseconds average, or -1 if we dont track that period
|
||||
*/
|
||||
public double getAverageReceiveTime(int period) {
|
||||
return getAverage(_receiveRate, period);
|
||||
}
|
||||
|
||||
/**
|
||||
* number of lost messages over the given period.
|
||||
*
|
||||
* @param period number of minutes to retrieve the average for
|
||||
* @return number of lost messages in the period, or -1 if we dont track that period
|
||||
*/
|
||||
public double getLostMessages(int period) {
|
||||
Rate rate = _lostRate.getRate(period * 60 * 1000);
|
||||
if (rate == null) return -1;
|
||||
return rate.getCurrentTotalValue();
|
||||
}
|
||||
|
||||
private double getAverage(RateStat stat, int period) {
|
||||
Rate rate = stat.getRate(period * 60 * 1000);
|
||||
if (rate == null) return -1;
|
||||
return rate.getAverageValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an ordered list of data points in the current window (after doing a cleanup)
|
||||
*
|
||||
* @return list of EventDataPoint objects
|
||||
*/
|
||||
public List getDataPoints() {
|
||||
cleanup();
|
||||
synchronized (_updateLock) {
|
||||
return new ArrayList(_dataPoints.values());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We have sent the peer a ping on this series (using the send time as given)
|
||||
* @param dateSent when the ping was sent
|
||||
*/
|
||||
public void addPing(long dateSent) {
|
||||
EventDataPoint sent = new EventDataPoint(dateSent);
|
||||
synchronized (_updateLock) {
|
||||
_pendingPings.put(new Long(dateSent), sent);
|
||||
}
|
||||
_lifetimeSent++;
|
||||
}
|
||||
|
||||
/**
|
||||
* we have received a pong from the peer on this series
|
||||
*
|
||||
* @param dateSent when we sent the ping
|
||||
* @param pongSent when the peer received the ping and sent the pong
|
||||
*/
|
||||
public void pongReceived(long dateSent, long pongSent) {
|
||||
long now = Clock.getInstance().now();
|
||||
synchronized (_updateLock) {
|
||||
if (_pendingPings.size() <= 0) {
|
||||
_log.warn("Pong received (sent at " + dateSent + ", " + (now-dateSent)
|
||||
+ "ms ago, pong delay " + (pongSent-dateSent) + "ms, pong receive delay "
|
||||
+ (now-pongSent) + "ms)");
|
||||
return;
|
||||
}
|
||||
Long first = (Long)_pendingPings.firstKey();
|
||||
EventDataPoint data = (EventDataPoint)_pendingPings.remove(new Long(dateSent));
|
||||
|
||||
if (data != null) {
|
||||
data.setPongReceived(now);
|
||||
data.setPongSent(pongSent);
|
||||
data.setWasPonged(true);
|
||||
locked_addDataPoint(data);
|
||||
|
||||
if (dateSent != first.longValue()) {
|
||||
_log.error("Out of order delivery: received " + dateSent
|
||||
+ " but the first pending is " + first.longValue()
|
||||
+ " (delta " + (dateSent - first.longValue()) + ")");
|
||||
} else {
|
||||
_log.info("In order delivery for " + dateSent + " in ping "
|
||||
+ _peer.getComment());
|
||||
}
|
||||
} else {
|
||||
_log.warn("Pong received, but no matching ping? ping sent at = " + dateSent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_sendRate.addData(pongSent - dateSent, 0);
|
||||
_receiveRate.addData(now - pongSent, 0);
|
||||
_lifetimeReceived++;
|
||||
}
|
||||
|
||||
protected void addDataPoint(EventDataPoint data) {
|
||||
synchronized (_updateLock) {
|
||||
locked_addDataPoint(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void locked_addDataPoint(EventDataPoint data) {
|
||||
Object val = _dataPoints.put(new Long(data.getPingSent()), data);
|
||||
if (val != null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Duplicate data point received: " + data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* drop all datapoints outside the window we're watching, and timeout all
|
||||
* pending pings not ponged in the TIMEOUT_PERIOD, both updating the lost message
|
||||
* rate and coallescing all of the rates.
|
||||
*
|
||||
*/
|
||||
public void cleanup() {
|
||||
long dropBefore = Clock.getInstance().now() - _peer.getStatDuration() * 60 * 1000;
|
||||
long timeoutBefore = Clock.getInstance().now() - TIMEOUT_PERIOD;
|
||||
long numDropped = 0;
|
||||
long numTimedOut = 0;
|
||||
|
||||
synchronized (_updateLock) {
|
||||
numDropped = locked_dropExpired(dropBefore);
|
||||
numTimedOut = locked_timeoutPending(timeoutBefore);
|
||||
}
|
||||
|
||||
_lostRate.addData(numTimedOut, 0);
|
||||
|
||||
_receiveRate.coalesceStats();
|
||||
_sendRate.coalesceStats();
|
||||
_lostRate.coalesceStats();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Peer data cleaned up " + numTimedOut + " timed out pings and removed " + numDropped
|
||||
+ " old entries");
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop all data points that are already too old for us to be interested in
|
||||
*
|
||||
* @param when the earliest ping send time we care about
|
||||
* @return number of data points dropped
|
||||
*/
|
||||
private int locked_dropExpired(long when) {
|
||||
Set toDrop = new HashSet(4);
|
||||
// drop the failed and really old
|
||||
for (Iterator iter = _dataPoints.keySet().iterator(); iter.hasNext(); ) {
|
||||
Long pingTime = (Long)iter.next();
|
||||
if (pingTime.longValue() < when)
|
||||
toDrop.add(pingTime);
|
||||
}
|
||||
for (Iterator iter = toDrop.iterator(); iter.hasNext(); ) {
|
||||
_dataPoints.remove(iter.next());
|
||||
}
|
||||
return toDrop.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* timeout and remove all pings that were sent before the given time,
|
||||
* moving them from the set of pending pings to the set of data points
|
||||
*
|
||||
* @param when the earliest ping send time we care about
|
||||
* @return number of pings timed out
|
||||
*/
|
||||
private int locked_timeoutPending(long when) {
|
||||
Set toDrop = new HashSet(4);
|
||||
for (Iterator iter = _pendingPings.keySet().iterator(); iter.hasNext(); ) {
|
||||
Long pingTime = (Long)iter.next();
|
||||
if (pingTime.longValue() < when) {
|
||||
toDrop.add(pingTime);
|
||||
EventDataPoint point = (EventDataPoint)_pendingPings.get(pingTime);
|
||||
point.setWasPonged(false);
|
||||
locked_addDataPoint(point);
|
||||
}
|
||||
}
|
||||
for (Iterator iter = toDrop.iterator(); iter.hasNext(); ) {
|
||||
_pendingPings.remove(iter.next());
|
||||
}
|
||||
return toDrop.size();
|
||||
}
|
||||
|
||||
/** actual data point for the peer */
|
||||
public class EventDataPoint {
|
||||
private boolean _wasPonged;
|
||||
private long _pingSent;
|
||||
private long _pongSent;
|
||||
private long _pongReceived;
|
||||
|
||||
/**
|
||||
* Creates an EventDataPoint
|
||||
*/
|
||||
public EventDataPoint() { this(-1); }
|
||||
|
||||
/**
|
||||
* Creates an EventDataPoint with pingtime associated with it =)
|
||||
* @param pingSentOn the time a ping was sent
|
||||
*/
|
||||
public EventDataPoint(long pingSentOn) {
|
||||
_wasPonged = false;
|
||||
_pingSent = pingSentOn;
|
||||
_pongSent = -1;
|
||||
_pongReceived = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* when did we send this ping?
|
||||
* @return the time the ping was sent
|
||||
*/
|
||||
public long getPingSent() { return _pingSent; }
|
||||
|
||||
/**
|
||||
* sets when we sent this ping
|
||||
* @param when when we sent the ping
|
||||
*/
|
||||
public void setPingSent(long when) { _pingSent = when; }
|
||||
|
||||
/**
|
||||
* when did the peer receive the ping?
|
||||
* @return the time the ping was receieved
|
||||
*/
|
||||
public long getPongSent() {
|
||||
return _pongSent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time the peer received the ping
|
||||
* @param when the time to set
|
||||
*/
|
||||
public void setPongSent(long when) {
|
||||
_pongSent = when;
|
||||
}
|
||||
|
||||
/**
|
||||
* when did we receive the peer's pong?
|
||||
* @return the time we receieved the pong
|
||||
*/
|
||||
public long getPongReceived() {
|
||||
return _pongReceived;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time the peer's pong was receieved
|
||||
* @param when the time to set
|
||||
*/
|
||||
public void setPongReceived(long when) {
|
||||
_pongReceived = when;
|
||||
}
|
||||
|
||||
/**
|
||||
* did the peer reply in time?
|
||||
* @return true or false, whether we got a reply in time */
|
||||
public boolean getWasPonged() {
|
||||
return _wasPonged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether we receieved the peer's reply in time
|
||||
* @param pong true or false
|
||||
*/
|
||||
public void setWasPonged(boolean pong) {
|
||||
_wasPonged = pong;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Actually write out the stats for peer test
|
||||
*
|
||||
*/
|
||||
public class PeerDataWriter {
|
||||
private final static Log _log = new Log(PeerDataWriter.class);
|
||||
|
||||
/**
|
||||
* persist the peer state to the location specified in the peer config
|
||||
*
|
||||
* @param data the peer data to persist
|
||||
* @return true if it was persisted correctly, false on error
|
||||
*/
|
||||
public boolean persist(PeerData data) {
|
||||
String filename = data.getConfig().getStatFile();
|
||||
File statFile = new File(filename);
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(statFile);
|
||||
persist(data, fos);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error persisting the peer data for "
|
||||
+ data.getConfig().getPeer().calculateHash().toBase64(), ioe);
|
||||
return false;
|
||||
} finally {
|
||||
if (fos != null) try {
|
||||
fos.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* persists the peer state to the output stream
|
||||
* @param data the peer data to persist
|
||||
* @param out where to persist the data
|
||||
* @return true if it was persisted correctly [always (as implemented)], false on error
|
||||
* @throws IOException
|
||||
*/
|
||||
public boolean persist(PeerData data, OutputStream out) throws IOException {
|
||||
String header = getHeader(data);
|
||||
|
||||
out.write(header.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);
|
||||
out.write(line.getBytes());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private String getHeader(PeerData data) {
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
buf.append("peer \t").append(data.getConfig().getPeer().calculateHash().toBase64()).append('\n');
|
||||
buf.append("local \t").append(data.getConfig().getUs().calculateHash().toBase64()).append('\n');
|
||||
buf.append("peerDest \t").append(data.getConfig().getPeer().toBase64()).append('\n');
|
||||
buf.append("localDest \t").append(data.getConfig().getUs().toBase64()).append('\n');
|
||||
buf.append("numTunnelHops\t").append(data.getConfig().getNumHops()).append('\n');
|
||||
buf.append("comment \t").append(data.getConfig().getComment()).append('\n');
|
||||
buf.append("sendFrequency\t").append(data.getConfig().getSendFrequency()).append('\n');
|
||||
buf.append("sendSize \t").append(data.getConfig().getSendSize()).append('\n');
|
||||
buf.append("sessionStart \t").append(getTime(data.getSessionStart())).append('\n');
|
||||
buf.append("currentTime \t").append(getTime(Clock.getInstance().now())).append('\n');
|
||||
buf.append("numPending \t").append(data.getPendingCount()).append('\n');
|
||||
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\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('\t');
|
||||
double rtt = data.getAverageSendTime(periods[i])
|
||||
+ data.getAverageReceiveTime(periods[i]);
|
||||
buf.append(getNum(rtt)).append('\n');
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private String getEvent(PeerData.EventDataPoint point) {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("EVENT\t");
|
||||
if (point.getWasPonged())
|
||||
buf.append("OK\t");
|
||||
else
|
||||
buf.append("LOST\t");
|
||||
buf.append(getTime(point.getPingSent())).append('\t');
|
||||
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();
|
||||
}
|
||||
|
||||
private final SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd.HH:mm:ss.SSS", Locale.UK);
|
||||
|
||||
/**
|
||||
* Converts a time (long) to text
|
||||
* @param when the time to convert
|
||||
* @return the textual representation
|
||||
*/
|
||||
public String getTime(long when) {
|
||||
synchronized (_fmt) {
|
||||
return _fmt.format(new Date(when));
|
||||
}
|
||||
}
|
||||
|
||||
private final DecimalFormat _numFmt = new DecimalFormat("#0", new DecimalFormatSymbols(Locale.UK));
|
||||
|
||||
/**
|
||||
* Converts a number (double) to text
|
||||
* @param val the number to convert
|
||||
* @return the textual representation
|
||||
*/
|
||||
public String getNum(double val) {
|
||||
synchronized (_numFmt) {
|
||||
return _numFmt.format(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTabbedPane;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Render the control widgets (refresh/load/snapshot and the
|
||||
* tabbed panel with the plot config data)
|
||||
*
|
||||
*/
|
||||
class HeartbeatControlPane extends JPanel {
|
||||
private final static Log _log = new Log(HeartbeatControlPane.class);
|
||||
private HeartbeatMonitorGUI _gui;
|
||||
private JTabbedPane _configPane;
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
private final static Color LIGHT_BLUE = new Color(180, 180, 255); /* UNUSED */
|
||||
private final static Color BLACK = new Color(0, 0, 0);
|
||||
private Color _background = WHITE;
|
||||
private Color _foreground = BLACK;
|
||||
|
||||
/**
|
||||
* Constructs a control panel onto the gui
|
||||
* @param gui the gui the panel is associated with
|
||||
*/
|
||||
public HeartbeatControlPane(HeartbeatMonitorGUI gui) {
|
||||
_gui = gui;
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a test to the panel
|
||||
* @param config the configuration for the test
|
||||
*/
|
||||
public void addTest(PeerPlotConfig config) {
|
||||
_configPane.addTab(config.getTitle(), null, new JScrollPane(new PeerPlotConfigPane(config, this)), config.getSummary());
|
||||
_configPane.setBackgroundAt(_configPane.getTabCount()-1, _background);
|
||||
_configPane.setForegroundAt(_configPane.getTabCount()-1, _foreground);
|
||||
_gui.pack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a test from the panel
|
||||
* @param config the configuration for the test
|
||||
*/
|
||||
public void removeTest(PeerPlotConfig config) {
|
||||
_gui.getMonitor().getState().removeTest(config);
|
||||
int index = _configPane.indexOfTab(config.getTitle());
|
||||
if (index >= 0)
|
||||
_configPane.removeTabAt(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback: when tests have changed
|
||||
*/
|
||||
public void testsUpdated() {
|
||||
List knownNames = new ArrayList(8);
|
||||
for (int i = 0; i < _gui.getMonitor().getState().getTestCount(); i++) {
|
||||
PeerPlotState state = _gui.getMonitor().getState().getTest(i);
|
||||
String title = state.getPlotConfig().getTitle();
|
||||
knownNames.add(state.getPlotConfig().getTitle());
|
||||
if (_configPane.indexOfTab(title) >= 0) {
|
||||
_log.debug("We already know about [" + title + "]");
|
||||
} else {
|
||||
_log.info("The test [" + title + "] is new to us");
|
||||
PeerPlotConfigPane pane = new PeerPlotConfigPane(state.getPlotConfig(), this);
|
||||
_configPane.addTab(state.getPlotConfig().getTitle(), null, new JScrollPane(pane), state.getPlotConfig().getSummary());
|
||||
_configPane.setBackgroundAt(_configPane.getTabCount()-1, _background);
|
||||
_configPane.setForegroundAt(_configPane.getTabCount()-1, _foreground);
|
||||
}
|
||||
}
|
||||
List toRemove = new ArrayList(4);
|
||||
for (int i = 0; i < _configPane.getTabCount(); i++) {
|
||||
if (knownNames.contains(_configPane.getTitleAt(i))) {
|
||||
// noop
|
||||
} else {
|
||||
toRemove.add(_configPane.getTitleAt(i));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < toRemove.size(); i++) {
|
||||
String title = (String)toRemove.get(i);
|
||||
_log.info("Removing test [" + title + "]");
|
||||
_configPane.removeTabAt(_configPane.indexOfTab(title));
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeComponents() {
|
||||
if (_gui != null)
|
||||
setBackground(_gui.getBackground());
|
||||
else
|
||||
setBackground(_background);
|
||||
setLayout(new BorderLayout());
|
||||
HeartbeatMonitorCommandBar bar = new HeartbeatMonitorCommandBar(_gui);
|
||||
bar.setBackground(getBackground());
|
||||
add(bar, BorderLayout.NORTH);
|
||||
_configPane = new JTabbedPane(JTabbedPane.LEFT);
|
||||
_configPane.setBackground(_background);
|
||||
//add(_configPane, BorderLayout.CENTER);
|
||||
add(_configPane, BorderLayout.SOUTH);
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* The HeartbeatMonitor, complete with main()! Act now, and it's only 5 easy
|
||||
* payments of $19.95 (plus shipping and handling)! You heard me, only _5_
|
||||
* easy payments of $19.95 (plus shipping and handling)! <p />
|
||||
*
|
||||
* (fine print: something about some states in the US requiring the addition
|
||||
* of sales tax... or something) <p />
|
||||
*
|
||||
* (finer print: Satan owns you. Deal with it.) <p />
|
||||
*
|
||||
* (even finer print: usage: <code>HeartbeatMonitor [configFilename]</code>)
|
||||
*/
|
||||
public class HeartbeatMonitor implements PeerPlotStateFetcher.FetchStateReceptor, PeerPlotConfig.UpdateListener {
|
||||
private final static Log _log = new Log(HeartbeatMonitor.class);
|
||||
private HeartbeatMonitorState _state;
|
||||
private HeartbeatMonitorGUI _gui;
|
||||
|
||||
/**
|
||||
* Delegating constructor.
|
||||
* @see HeartbeatMonitor#HeartbeatMonitor(String)
|
||||
*/
|
||||
public HeartbeatMonitor() { this(null); }
|
||||
|
||||
/**
|
||||
* Creates a HeartbeatMonitor . . .
|
||||
* @param configFilename the configuration file to read from
|
||||
*/
|
||||
public HeartbeatMonitor(String configFilename) {
|
||||
_state = new HeartbeatMonitorState(configFilename);
|
||||
_gui = new HeartbeatMonitorGUI(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the game rollin'
|
||||
*/
|
||||
public void runMonitor() {
|
||||
loadConfig();
|
||||
I2PThread t = new I2PThread(new HeartbeatMonitorRunner(this));
|
||||
t.setName("HeartbeatMonitor");
|
||||
t.setDaemon(false);
|
||||
t.start();
|
||||
_log.debug("Monitor started");
|
||||
}
|
||||
|
||||
/**
|
||||
* give us all the data/config available
|
||||
* @return the current state (data/config)
|
||||
*/
|
||||
HeartbeatMonitorState getState() {
|
||||
return _state;
|
||||
}
|
||||
|
||||
/** for all of the peer tests being monitored, refetch the data and rerender */
|
||||
void refetchData() {
|
||||
_log.debug("Refetching data");
|
||||
for (int i = 0; i < _state.getTestCount(); i++)
|
||||
PeerPlotStateFetcher.fetchPeerPlotState(this, _state.getTest(i));
|
||||
}
|
||||
|
||||
/** (re)load the config defining what peer tests we are monitoring (and how to render) */
|
||||
void loadConfig() {
|
||||
//for (int i = 0; i < 10; i++) {
|
||||
// load("fake" + i);
|
||||
//}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads config data
|
||||
* @param location the name of the location to load data from
|
||||
*/
|
||||
public void load(String location) {
|
||||
PeerPlotConfig cfg = new PeerPlotConfig(location);
|
||||
cfg.addListener(this);
|
||||
PeerPlotState state = new PeerPlotState(cfg);
|
||||
PeerPlotStateFetcher.fetchPeerPlotState(this, state);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see PeerPlotStateFetcher.FetchStateReceptor#peerPlotStateFetched
|
||||
*/
|
||||
public synchronized void peerPlotStateFetched(PeerPlotState state) {
|
||||
_state.addTest(state);
|
||||
_gui.stateUpdated();
|
||||
}
|
||||
|
||||
/**
|
||||
* store the config defining what peer tests we are monitoring (and how to render)
|
||||
*/
|
||||
void storeConfig() {}
|
||||
|
||||
/**
|
||||
* And now, the main function, the one you've all been waiting for! . . .
|
||||
* @param args da args. Should take 1, which is the location to load config data from
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
Thread.currentThread().setName("HeartbeatMonitor.main");
|
||||
if (args.length == 1)
|
||||
new HeartbeatMonitor(args[0]).runMonitor();
|
||||
else
|
||||
new HeartbeatMonitor().runMonitor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the config is updated
|
||||
* @param config the updated config
|
||||
*/
|
||||
public void configUpdated(PeerPlotConfig config) {
|
||||
_log.debug("Config updated, revamping the gui");
|
||||
_gui.stateUpdated();
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
class HeartbeatMonitorCommandBar extends JPanel {
|
||||
private HeartbeatMonitorGUI _gui;
|
||||
private JComboBox _refreshRate;
|
||||
private JTextField _location;
|
||||
|
||||
/**
|
||||
* Constructs a command bar onto the gui
|
||||
* @param gui the gui the command bar is associated with
|
||||
*/
|
||||
public HeartbeatMonitorCommandBar(HeartbeatMonitorGUI gui) {
|
||||
_gui = gui;
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
private void refreshChanged(ItemEvent evt) {}
|
||||
private void loadCalled() {
|
||||
_gui.getMonitor().load(_location.getText());
|
||||
}
|
||||
|
||||
private void browseCalled() {
|
||||
JFileChooser chooser = new JFileChooser(_location.getText());
|
||||
chooser.setBackground(_gui.getBackground());
|
||||
chooser.setMultiSelectionEnabled(false);
|
||||
int rv = chooser.showDialog(this, "Load");
|
||||
if (rv == JFileChooser.APPROVE_OPTION)
|
||||
_gui.getMonitor().load(chooser.getSelectedFile().getAbsolutePath());
|
||||
}
|
||||
|
||||
private void initializeComponents() {
|
||||
_refreshRate = new JComboBox(new DefaultComboBoxModel(new Object[] {"10 second refresh", "30 second refresh", "1 minute refresh", "5 minute refresh"}));
|
||||
_refreshRate.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent evt) { refreshChanged(evt); } });
|
||||
_refreshRate.setEnabled(false);
|
||||
_refreshRate.setBackground(_gui.getBackground());
|
||||
//add(_refreshRate);
|
||||
JLabel loadLabel = new JLabel("Load from: ");
|
||||
loadLabel.setBackground(_gui.getBackground());
|
||||
add(loadLabel);
|
||||
_location = new JTextField(20);
|
||||
_location.setToolTipText("Either specify a local filename or a fully qualified URL");
|
||||
_location.setBackground(_gui.getBackground());
|
||||
add(_location);
|
||||
JButton browse = new JButton("Browse...");
|
||||
browse.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { browseCalled(); } });
|
||||
browse.setBackground(_gui.getBackground());
|
||||
add(browse);
|
||||
JButton load = new JButton("Load");
|
||||
load.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { loadCalled(); } });
|
||||
load.setBackground(_gui.getBackground());
|
||||
add(load);
|
||||
setBackground(_gui.getBackground());
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JScrollPane;
|
||||
|
||||
class HeartbeatMonitorGUI extends JFrame {
|
||||
private HeartbeatMonitor _monitor;
|
||||
private HeartbeatPlotPane _plotPane;
|
||||
private HeartbeatControlPane _controlPane;
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
private Color _background = WHITE;
|
||||
|
||||
/**
|
||||
* Creates the GUI for all youz who be too shoopid for text based shitz
|
||||
* @param monitor the monitor the gui operates over
|
||||
*/
|
||||
public HeartbeatMonitorGUI(HeartbeatMonitor monitor) {
|
||||
super("Heartbeat Monitor");
|
||||
_monitor = monitor;
|
||||
initializeComponents();
|
||||
pack();
|
||||
//setResizable(false);
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
HeartbeatMonitor getMonitor() { return _monitor; }
|
||||
|
||||
/** build up all our widgets */
|
||||
private void initializeComponents() {
|
||||
getContentPane().setLayout(new BorderLayout());
|
||||
|
||||
setBackground(_background);
|
||||
|
||||
_plotPane = new JFreeChartHeartbeatPlotPane(this); // new HeartbeatPlotPane(this);
|
||||
_plotPane.setBackground(_background);
|
||||
//JScrollPane pane = new JScrollPane(_plotPane);
|
||||
//pane.setBackground(_background);
|
||||
getContentPane().add(new JScrollPane(_plotPane), BorderLayout.CENTER);
|
||||
|
||||
_controlPane = new HeartbeatControlPane(this);
|
||||
_controlPane.setBackground(_background);
|
||||
getContentPane().add(_controlPane, BorderLayout.SOUTH);
|
||||
|
||||
//JSplitPane pane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, new JScrollPane(_plotPane), new JScrollPane(_controlPane));
|
||||
//getContentPane().add(pane, BorderLayout.CENTER);
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
initializeMenus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback: when the state of the world changes . . .
|
||||
*/
|
||||
public void stateUpdated() {
|
||||
_controlPane.testsUpdated();
|
||||
_plotPane.stateUpdated();
|
||||
}
|
||||
|
||||
private void exitCalled() {
|
||||
_monitor.getState().setWasKilled(true);
|
||||
setVisible(false);
|
||||
System.exit(0);
|
||||
}
|
||||
private void loadConfigCalled() {}
|
||||
private void saveConfigCalled() {}
|
||||
private void loadSnapshotCalled() {}
|
||||
private void saveSnapshotCalled() {}
|
||||
|
||||
private void initializeMenus() {
|
||||
JMenuBar bar = new JMenuBar();
|
||||
JMenu fileMenu = new JMenu("File");
|
||||
JMenuItem loadConfig = new JMenuItem("Load config");
|
||||
loadConfig.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { loadConfigCalled(); } });
|
||||
JMenuItem saveConfig = new JMenuItem("Save config");
|
||||
saveConfig.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { saveConfigCalled(); } });
|
||||
JMenuItem saveSnapshot = new JMenuItem("Save snapshot");
|
||||
saveSnapshot.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { saveSnapshotCalled(); } });
|
||||
JMenuItem loadSnapshot = new JMenuItem("Load snapshot");
|
||||
loadSnapshot.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { loadSnapshotCalled(); } });
|
||||
JMenuItem exit = new JMenuItem("Exit");
|
||||
exit.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { exitCalled(); } });
|
||||
|
||||
fileMenu.add(loadConfig);
|
||||
fileMenu.add(saveConfig);
|
||||
fileMenu.add(loadSnapshot);
|
||||
fileMenu.add(saveSnapshot);
|
||||
fileMenu.add(exit);
|
||||
bar.add(fileMenu);
|
||||
setJMenuBar(bar);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Periodically fire off necessary events (instructing the heartbeat monitor when
|
||||
* to refetch the data, etc). This is the only active thread in the heartbeat
|
||||
* monitor (outside the swing/jvm threads)
|
||||
*/
|
||||
class HeartbeatMonitorRunner implements Runnable {
|
||||
private final static Log _log = new Log(HeartbeatMonitorRunner.class);
|
||||
private HeartbeatMonitor _monitor;
|
||||
|
||||
/**
|
||||
* Creates the thread . . .
|
||||
* @param monitor the monitor the thread runs over
|
||||
*/
|
||||
public HeartbeatMonitorRunner(HeartbeatMonitor monitor) {
|
||||
_monitor = monitor;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Runnable#run()
|
||||
*/
|
||||
public void run() {
|
||||
while (!_monitor.getState().getWasKilled()) {
|
||||
_monitor.refetchData();
|
||||
try { Thread.sleep(_monitor.getState().getRefreshRateMs()); } catch (InterruptedException ie) {}
|
||||
}
|
||||
_log.info("Stopping the heartbeat monitor runner");
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* manage the current state of the GUI - all data points, as well as any
|
||||
* rendering or configuration options.
|
||||
*/
|
||||
class HeartbeatMonitorState {
|
||||
private String _configFile;
|
||||
private List _peerPlotState;
|
||||
private int _currentPeerPlotConfig;
|
||||
private int _refreshRateMs;
|
||||
private boolean _killed;
|
||||
|
||||
/** by default, refresh every 30 seconds */
|
||||
private final static int DEFAULT_REFRESH_RATE = 30*1000;
|
||||
/** where do we load/store config info from? */
|
||||
private final static String DEFAULT_CONFIG_FILE = "heartbeatMonitor.config";
|
||||
|
||||
/**
|
||||
* A delegating constructor.
|
||||
* @see HeartbeatMonitorState#HeartbeatMonitorState(String)
|
||||
*/
|
||||
public HeartbeatMonitorState() { this(DEFAULT_CONFIG_FILE); }
|
||||
|
||||
/**
|
||||
* Constructs the state, loading from the specified location
|
||||
* @param configFile the name of the file to load info from
|
||||
*/
|
||||
public HeartbeatMonitorState(String configFile) {
|
||||
_peerPlotState = Collections.synchronizedList(new ArrayList());
|
||||
_refreshRateMs = DEFAULT_REFRESH_RATE;
|
||||
_configFile = configFile;
|
||||
_killed = false;
|
||||
_currentPeerPlotConfig = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* how many tests are we monitoring?
|
||||
* @return the number of tests
|
||||
*/
|
||||
public int getTestCount() { return _peerPlotState.size(); }
|
||||
|
||||
/**
|
||||
* Retrieves the current info of a test for a certain peer . . .
|
||||
* @param peer a number associated with a certain peer
|
||||
* @return the test data
|
||||
*/
|
||||
public PeerPlotState getTest(int peer) { return (PeerPlotState)_peerPlotState.get(peer); }
|
||||
|
||||
/**
|
||||
* Adds a test . . .
|
||||
* @param peerState the test (by state) to add . . .
|
||||
*/
|
||||
public void addTest(PeerPlotState peerState) {
|
||||
if (!_peerPlotState.contains(peerState))
|
||||
_peerPlotState.add(peerState);
|
||||
}
|
||||
/**
|
||||
* Removes a test . . .
|
||||
* @param peerState the test (by state) to remove . . .
|
||||
*/
|
||||
public void removeTest(PeerPlotState peerState) { _peerPlotState.remove(peerState); }
|
||||
|
||||
/**
|
||||
* Removes a test . . .
|
||||
* @param peerConfig the test (by config) to remove . . .
|
||||
*/
|
||||
public void removeTest(PeerPlotConfig peerConfig) {
|
||||
for (int i = 0; i < getTestCount(); i++) {
|
||||
PeerPlotState state = getTest(i);
|
||||
if (state.getPlotConfig() == peerConfig) {
|
||||
removeTest(state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* which of the tests are we currently editing/viewing?
|
||||
* @return the number associated with the test
|
||||
*/
|
||||
public int getPeerPlotConfig() { return _currentPeerPlotConfig; }
|
||||
|
||||
/**
|
||||
* Sets the test we are currently editting/viewing
|
||||
* @param whichTest the number associated with the test
|
||||
*/
|
||||
public void setPeerPlotConfig(int whichTest) { _currentPeerPlotConfig = whichTest; }
|
||||
|
||||
/**
|
||||
* how frequently should we update the data?
|
||||
* @return the current frequency (in milliseconds)
|
||||
*/
|
||||
public int getRefreshRateMs() { return _refreshRateMs; }
|
||||
|
||||
/**
|
||||
* Sets how frequently we should update data
|
||||
* @param ms the frequency (in milliseconds)
|
||||
*/
|
||||
public void setRefreshRateMs(int ms) { _refreshRateMs = ms; }
|
||||
|
||||
/**
|
||||
* where is our config stored?
|
||||
* @return the name of the config file
|
||||
*/
|
||||
public String getConfigFile() { return _configFile; }
|
||||
|
||||
/**
|
||||
* Sets where our config is stored
|
||||
* @param filename the name of the config file
|
||||
*/
|
||||
public void setConfigFile(String filename) { _configFile = filename; }
|
||||
|
||||
/**
|
||||
* have we been shut down?
|
||||
* @return true if we have, false otherwise
|
||||
*/
|
||||
public boolean getWasKilled() { return _killed; }
|
||||
|
||||
/**
|
||||
* Sets if we have been shutdown or not
|
||||
* @param killed true if we've been shutdown, false otherwise
|
||||
*/
|
||||
public void setWasKilled(boolean killed) { _killed = killed; }
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextArea;
|
||||
|
||||
import net.i2p.heartbeat.PeerDataWriter;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Render the graph and legend
|
||||
*/
|
||||
class HeartbeatPlotPane extends JPanel {
|
||||
private final static Log _log = new Log(HeartbeatPlotPane.class);
|
||||
protected HeartbeatMonitorGUI _gui;
|
||||
private JTextArea _text;
|
||||
|
||||
/**
|
||||
* Constructs the plot pane
|
||||
* @param gui the gui the pane is attached to
|
||||
*/
|
||||
public HeartbeatPlotPane(HeartbeatMonitorGUI gui) {
|
||||
_gui = gui;
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback: when things change . . .
|
||||
*/
|
||||
public void stateUpdated() {
|
||||
StringBuffer buf = new StringBuffer(32*1024);
|
||||
PeerDataWriter writer = new PeerDataWriter();
|
||||
|
||||
for (int i = 0; i < _gui.getMonitor().getState().getTestCount(); i++) {
|
||||
StaticPeerData data = _gui.getMonitor().getState().getTest(i).getCurrentData();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
|
||||
try {
|
||||
writer.persist(data, baos);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("wtf, error writing to a byte array?", ioe);
|
||||
}
|
||||
buf.append(new String(baos.toByteArray())).append("\n\n\n");
|
||||
}
|
||||
|
||||
_text.setText(buf.toString());
|
||||
}
|
||||
|
||||
protected void initializeComponents() {
|
||||
setBackground(_gui.getBackground());
|
||||
//Dimension size = new Dimension(800, 600);
|
||||
_text = new JTextArea("",30,80); // 16, 60);
|
||||
_text.setAutoscrolls(true);
|
||||
_text.setEditable(false);
|
||||
// _text.setLineWrap(true);
|
||||
// add(new JScrollPane(_text));
|
||||
add(_text);
|
||||
// add(new JScrollPane(_text, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS));
|
||||
// setPreferredSize(size);
|
||||
}
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
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;
|
||||
|
||||
class JFreeChartAdapter {
|
||||
private final static Log _log = new Log(JFreeChartAdapter.class); /* UNUSED */
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
|
||||
ChartPanel createPanel(HeartbeatMonitorState state) {
|
||||
ChartPanel panel = new ChartPanel(createChart(state));
|
||||
panel.setDisplayToolTips(true);
|
||||
panel.setEnforceFileExtensions(true);
|
||||
panel.setHorizontalZoom(true);
|
||||
panel.setVerticalZoom(true);
|
||||
panel.setMouseZoomable(true, true);
|
||||
panel.getChart().setBackgroundPaint(WHITE);
|
||||
return panel;
|
||||
}
|
||||
|
||||
JFreeChart createChart(HeartbeatMonitorState state) {
|
||||
Plot plot = createPlot(state);
|
||||
JFreeChart chart = new JFreeChart("I2P Heartbeat performance", Font.getFont("arial"), plot, true);
|
||||
return chart;
|
||||
}
|
||||
|
||||
void updateChart(ChartPanel panel, HeartbeatMonitorState state) {
|
||||
XYPlot plot = (XYPlot)panel.getChart().getPlot();
|
||||
plot.setDataset(getCollection(state));
|
||||
updateLines(plot, state);
|
||||
}
|
||||
|
||||
private long getFirst(HeartbeatMonitorState state) { /* UNUSED */
|
||||
long first = -1;
|
||||
for (int i = 0; i < state.getTestCount(); i++) {
|
||||
List dataPoints = state.getTest(i).getCurrentData().getDataPoints();
|
||||
if ( (dataPoints != null) && (dataPoints.size() > 0) ) {
|
||||
PeerData.EventDataPoint data = (PeerData.EventDataPoint)dataPoints.get(0);
|
||||
if ( (first < 0) || (first > data.getPingSent()) )
|
||||
first = data.getPingSent();
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
Plot createPlot(HeartbeatMonitorState state) {
|
||||
XYItemRenderer renderer = new XYLineAndShapeRenderer(); // new XYDotRenderer(); //
|
||||
XYPlot plot = new XYPlot(getCollection(state), new DateAxis(), new NumberAxis("ms"), renderer);
|
||||
updateLines(plot, state);
|
||||
return plot;
|
||||
}
|
||||
|
||||
private void updateLines(XYPlot plot, HeartbeatMonitorState state) {
|
||||
if (true) return;
|
||||
if (state == null) return;
|
||||
for (int i = 0; i < state.getTestCount(); i++) {
|
||||
PeerPlotConfig config = state.getTest(i).getPlotConfig();
|
||||
PeerPlotConfig.PlotSeriesConfig curConfig = config.getCurrentSeriesConfig();
|
||||
XYSeriesCollection col = ((XYSeriesCollection)plot.getDataset());
|
||||
for (int j = 0; j < col.getSeriesCount(); j++) {
|
||||
//XYItemRenderer renderer = plot.getRendererForDataset(col.getSeries(j));
|
||||
XYItemRenderer renderer = plot.getRendererForDataset(col);
|
||||
if (col.getSeriesName(j).startsWith(config.getTitle() + " send")) {
|
||||
if (curConfig.getPlotSendTime()) {
|
||||
//renderer.setPaint(curConfig.getPlotLineColor());
|
||||
}
|
||||
}
|
||||
if (col.getSeriesName(j).startsWith(config.getTitle() + " receive")) {
|
||||
if (curConfig.getPlotReceiveTime()) {
|
||||
//renderer.setPaint(curConfig.getPlotLineColor());
|
||||
}
|
||||
}
|
||||
if (col.getSeriesName(j).startsWith(config.getTitle() + " lost")) {
|
||||
if (curConfig.getPlotLostMessages()) {
|
||||
//renderer.setPaint(curConfig.getPlotLineColor());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XYSeriesCollection getCollection(HeartbeatMonitorState state) {
|
||||
XYSeriesCollection col = new XYSeriesCollection();
|
||||
if (state != null) {
|
||||
for (int i = 0; i < state.getTestCount(); i++) {
|
||||
addTest(col, state.getTest(i));
|
||||
}
|
||||
} else {
|
||||
XYSeries series = new XYSeries("latency", false, false);
|
||||
series.add(System.currentTimeMillis(), 0);
|
||||
col.addSeries(series);
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
void addTest(XYSeriesCollection col, PeerPlotState state) {
|
||||
PeerPlotConfig config = state.getPlotConfig();
|
||||
PeerPlotConfig.PlotSeriesConfig curConfig = config.getCurrentSeriesConfig();
|
||||
addLines(col, curConfig, config.getTitle(), state.getCurrentData().getDataPoints());
|
||||
addAverageLines(col, config, config.getTitle(), state.getCurrentData());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param col the collection of xy series to add to
|
||||
* @param config preferences for how to display this test
|
||||
* @param lineName minimal name of the test (e.g. "jxHa.32KB.60s")
|
||||
* @param data List of PeerData.EventDataPoint describing all of the events in the test
|
||||
*/
|
||||
void addLines(XYSeriesCollection col, PeerPlotConfig.PlotSeriesConfig config, String lineName, List data) {
|
||||
if (config.getPlotSendTime()) {
|
||||
XYSeries sendSeries = getSendSeries(data);
|
||||
sendSeries.setName(lineName + " send");
|
||||
sendSeries.setDescription("milliseconds for the ping to reach the peer");
|
||||
col.addSeries(sendSeries);
|
||||
}
|
||||
if (config.getPlotReceiveTime()) {
|
||||
XYSeries recvSeries = getReceiveSeries(data);
|
||||
recvSeries.setName(lineName + " receive");
|
||||
recvSeries.setDescription("milliseconds for the peer's pong to reach the sender");
|
||||
col.addSeries(recvSeries);
|
||||
}
|
||||
if (config.getPlotLostMessages()) {
|
||||
XYSeries lostSeries = getLostSeries(data);
|
||||
lostSeries.setName(lineName + " lost");
|
||||
lostSeries.setDescription("number of ping/pong messages lost");
|
||||
col.addSeries(lostSeries);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a data series for each average that we're configured to render
|
||||
*
|
||||
* @param col the collection of xy series to add to
|
||||
* @param config preferences for how to display this test
|
||||
* @param lineName minimal name of the test (e.g. "jxHa.32KB.60s")
|
||||
* @param data List of PeerData.EventDataPoint describing all of the events in the test
|
||||
*/
|
||||
void addAverageLines(XYSeriesCollection col, PeerPlotConfig config, String lineName, StaticPeerData data) {
|
||||
if (data.getDataPointCount() <= 0) return;
|
||||
PeerData.EventDataPoint start = (PeerData.EventDataPoint)data.getDataPoints().get(0);
|
||||
PeerData.EventDataPoint finish = (PeerData.EventDataPoint)data.getDataPoints().get(data.getDataPointCount()-1);
|
||||
|
||||
List configs = config.getAverageSeriesConfigs();
|
||||
|
||||
for (int i = 0; i < configs.size(); i++) {
|
||||
PeerPlotConfig.PlotSeriesConfig cfg = (PeerPlotConfig.PlotSeriesConfig)configs.get(i);
|
||||
int minutes = (int)cfg.getPeriod()/(60*1000);
|
||||
if (cfg.getPlotSendTime()) {
|
||||
double time = data.getAverageSendTime(minutes);
|
||||
if (time > 0) {
|
||||
XYSeries series = new XYSeries(lineName + " send " + minutes + "m avg [" + time + "]", false, false);
|
||||
series.add(start.getPingSent(), time);
|
||||
series.add(finish.getPingSent(), time);
|
||||
series.setDescription("send time, averaged over the last " + minutes + " minutes");
|
||||
col.addSeries(series);
|
||||
}
|
||||
}
|
||||
if (cfg.getPlotReceiveTime()) {
|
||||
double time = data.getAverageReceiveTime(minutes);
|
||||
if (time > 0) {
|
||||
XYSeries series = new XYSeries(lineName + " receive " + minutes + "m avg[" + time + "]", false, false);
|
||||
series.add(start.getPingSent(), time);
|
||||
series.add(finish.getPingSent(), time);
|
||||
series.setDescription("receive time, averaged over the last " + minutes + " minutes");
|
||||
col.addSeries(series);
|
||||
}
|
||||
}
|
||||
if (cfg.getPlotLostMessages()) {
|
||||
double num = data.getLostMessages(minutes);
|
||||
if (num > 0) {
|
||||
XYSeries series = new XYSeries(lineName + " lost messages (" + num + " in " + minutes + "m)", false, false);
|
||||
series.add(start.getPingSent(), num);
|
||||
series.add(finish.getPingSent(), num);
|
||||
series.setDescription("number of messages lost in the last " + minutes + " minutes");
|
||||
col.addSeries(series);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XYSeries getSendSeries(List data) {
|
||||
XYSeries series = new XYSeries("sent", false, false);
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
PeerData.EventDataPoint point = (PeerData.EventDataPoint)data.get(i);
|
||||
if (point.getWasPonged()) {
|
||||
series.add(point.getPingSent(), point.getPongSent()-point.getPingSent());
|
||||
} else {
|
||||
// series.add(data.getPingSent(), 0);
|
||||
}
|
||||
}
|
||||
return series;
|
||||
}
|
||||
|
||||
XYSeries getReceiveSeries(List data) {
|
||||
XYSeries series = new XYSeries("receive", false, false);
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
PeerData.EventDataPoint point = (PeerData.EventDataPoint)data.get(i);
|
||||
if (point.getWasPonged()) {
|
||||
series.add(point.getPingSent(), point.getPongReceived()-point.getPongSent());
|
||||
} else {
|
||||
// series.add(data.getPingSent(), 0);
|
||||
}
|
||||
}
|
||||
return series;
|
||||
}
|
||||
XYSeries getLostSeries(List data) {
|
||||
XYSeries series = new XYSeries("lost", false, false);
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
PeerData.EventDataPoint point = (PeerData.EventDataPoint)data.get(i);
|
||||
if (point.getWasPonged()) {
|
||||
//series.add(point.getPingSent(), 0);
|
||||
} else {
|
||||
series.add(point.getPingSent(), 1);
|
||||
}
|
||||
}
|
||||
return series;
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JScrollPane;
|
||||
|
||||
import org.jfree.chart.ChartPanel;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Render the graph and legend
|
||||
*
|
||||
*/
|
||||
class JFreeChartHeartbeatPlotPane extends HeartbeatPlotPane {
|
||||
private final static Log _log = new Log(JFreeChartHeartbeatPlotPane.class); /* UNUSED */
|
||||
private ChartPanel _panel;
|
||||
private JFreeChartAdapter _adapter;
|
||||
|
||||
/**
|
||||
* Creates a JFreeChart plot pane for the given gui
|
||||
* @param gui the heartbeat monitor gui
|
||||
*/
|
||||
public JFreeChartHeartbeatPlotPane(HeartbeatMonitorGUI gui) {
|
||||
super(gui);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the state is updated
|
||||
*/
|
||||
public void stateUpdated() {
|
||||
if (_panel == null) {
|
||||
remove(0); // remove the dummy
|
||||
|
||||
_adapter = new JFreeChartAdapter();
|
||||
_panel = _adapter.createPanel(_gui.getMonitor().getState());
|
||||
_panel.setBackground(_gui.getBackground());
|
||||
add(new JScrollPane(_panel), BorderLayout.CENTER);
|
||||
_gui.pack();
|
||||
} else {
|
||||
_adapter.updateChart(_panel, _gui.getMonitor().getState());
|
||||
//_gui.pack();
|
||||
}
|
||||
}
|
||||
|
||||
protected void initializeComponents() {
|
||||
// noop
|
||||
setLayout(new BorderLayout());
|
||||
add(new JLabel(), BorderLayout.CENTER);
|
||||
//dummy.setBackground(_gui.getBackground());
|
||||
//dummy.setPreferredSize(new Dimension(800,600));
|
||||
//add(dummy);
|
||||
|
||||
//add(_panel);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,367 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.heartbeat.ClientConfig;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Configure how we want to render a particular clientConfig in the GUI
|
||||
*/
|
||||
class PeerPlotConfig {
|
||||
private final static Log _log = new Log(PeerPlotConfig.class); /* UNUSED */
|
||||
/** where can we find the current state/data (either as a filename or a URL)? */
|
||||
private String _location;
|
||||
/** what test are we defining the plot data for? */
|
||||
private ClientConfig _config;
|
||||
/** how should we render the current data set? */
|
||||
private PlotSeriesConfig _currentSeriesConfig;
|
||||
/** how should we render the various averages available? */
|
||||
private List _averageSeriesConfigs;
|
||||
private Set _listeners;
|
||||
private boolean _disabled;
|
||||
|
||||
/**
|
||||
* Delegating constructor . . .
|
||||
* @param location the name of the file/URL to get the data from
|
||||
*/
|
||||
public PeerPlotConfig(String location) {
|
||||
this(location, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a config =)
|
||||
* @param location the location of the file/URL to get the data from
|
||||
* @param config the client's configuration
|
||||
* @param currentSeriesConfig the series config
|
||||
* @param averageSeriesConfigs the average
|
||||
*/
|
||||
public PeerPlotConfig(String location, ClientConfig config, PlotSeriesConfig currentSeriesConfig, List averageSeriesConfigs) {
|
||||
_location = location;
|
||||
if (config == null)
|
||||
config = new ClientConfig(location);
|
||||
_config = config;
|
||||
if (currentSeriesConfig != null)
|
||||
_currentSeriesConfig = currentSeriesConfig;
|
||||
else
|
||||
_currentSeriesConfig = new PlotSeriesConfig(0);
|
||||
|
||||
if (averageSeriesConfigs != null) {
|
||||
_averageSeriesConfigs = averageSeriesConfigs;
|
||||
} else {
|
||||
rebuildAverageSeriesConfigs();
|
||||
}
|
||||
_listeners = Collections.synchronizedSet(new HashSet(2));
|
||||
_disabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 'Rebuilds' the average series stuff from the client configuration
|
||||
*/
|
||||
public void rebuildAverageSeriesConfigs() {
|
||||
int periods[] = _config.getAveragePeriods();
|
||||
if (periods == null) {
|
||||
_averageSeriesConfigs = Collections.synchronizedList(new ArrayList(0));
|
||||
} else {
|
||||
Arrays.sort(periods);
|
||||
_averageSeriesConfigs = Collections.synchronizedList(new ArrayList(periods.length));
|
||||
for (int i = 0; i < periods.length; i++) {
|
||||
_averageSeriesConfigs.add(new PlotSeriesConfig(periods[i]*60*1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an average period
|
||||
* @param minutes the number of minutes averaged over
|
||||
*/
|
||||
public void addAverage(int minutes) {
|
||||
_config.addAveragePeriod(minutes);
|
||||
|
||||
TreeMap ordered = new TreeMap();
|
||||
for (int i = 0; i < _averageSeriesConfigs.size(); i++) {
|
||||
PlotSeriesConfig cfg = (PlotSeriesConfig)_averageSeriesConfigs.get(i);
|
||||
ordered.put(new Long(cfg.getPeriod()), cfg);
|
||||
}
|
||||
Long period = new Long(minutes*60*1000);
|
||||
if (!ordered.containsKey(period))
|
||||
ordered.put(period, new PlotSeriesConfig(minutes*60*1000));
|
||||
|
||||
List cfgs = Collections.synchronizedList(new ArrayList(ordered.size()));
|
||||
for (Iterator iter = ordered.values().iterator(); iter.hasNext(); )
|
||||
cfgs.add(iter.next());
|
||||
|
||||
_averageSeriesConfigs = cfgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Where is the current state data supposed to be found? This must either be a
|
||||
* local file path or a URL
|
||||
* @return the current location
|
||||
*/
|
||||
public String getLocation() { return _location; }
|
||||
|
||||
/**
|
||||
* The location the current state data is supposed to be found. This must either be
|
||||
* a local file path or a URL
|
||||
* @param location the location
|
||||
*/
|
||||
public void setLocation(String location) {
|
||||
_location = location;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* What are we configuring?
|
||||
* @return the client configuration
|
||||
*/
|
||||
public ClientConfig getClientConfig() { return _config; }
|
||||
|
||||
/**
|
||||
* Sets what we are currently configuring
|
||||
* @param config the new config
|
||||
*/
|
||||
public void setClientConfig(ClientConfig config) {
|
||||
_config = config;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* How do we want to render the current data set?
|
||||
* @return the way we currently render the data
|
||||
*/
|
||||
public PlotSeriesConfig getCurrentSeriesConfig() { return _currentSeriesConfig; }
|
||||
|
||||
/**
|
||||
* Sets how we want to render the current data set.
|
||||
* @param config the new config
|
||||
*/
|
||||
public void setCurrentSeriesConfig(PlotSeriesConfig config) {
|
||||
_currentSeriesConfig = config;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* How do we want to render the averages?
|
||||
* @return the way we currently render the averages
|
||||
*/
|
||||
public List getAverageSeriesConfigs() { return _averageSeriesConfigs; }
|
||||
|
||||
/**
|
||||
* Sets how we want to render the averages
|
||||
* @param configs the new configs
|
||||
*/
|
||||
public void setAverageSeriesConfigs(List configs) { _averageSeriesConfigs = configs; }
|
||||
|
||||
/**
|
||||
* four char description of the peer
|
||||
* @return the name
|
||||
*/
|
||||
public String getPeerName() {
|
||||
Destination peer = getClientConfig().getPeer();
|
||||
if (peer == null)
|
||||
return "????";
|
||||
|
||||
return peer.calculateHash().toBase64().substring(0, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* title: name.packetsize.sendfrequency
|
||||
* @return the title
|
||||
*/
|
||||
public String getTitle() {
|
||||
return getPeerName() + '.' + getSize() + '.' + getClientConfig().getSendFrequency();
|
||||
}
|
||||
|
||||
/**
|
||||
* summary. includes:name, size, sendfrequency, and # of hops
|
||||
* @return the summary
|
||||
*/
|
||||
public String getSummary() {
|
||||
return "Send peer " + getPeerName() + ' ' + getSize() + " every " +
|
||||
getClientConfig().getSendFrequency() + " seconds through " +
|
||||
getClientConfig().getNumHops() + "-hop tunnels";
|
||||
}
|
||||
|
||||
private String getSize() {
|
||||
int bytes = getClientConfig().getSendSize();
|
||||
if (bytes < 1024)
|
||||
return bytes + "b";
|
||||
|
||||
return bytes/1024 + "kb";
|
||||
}
|
||||
|
||||
/**
|
||||
* we've got someone who wants to be notified of changes to the plot config
|
||||
* @param lsnr the listener to be added
|
||||
*/
|
||||
public void addListener(UpdateListener lsnr) { _listeners.add(lsnr); }
|
||||
|
||||
/**
|
||||
* remove a listener
|
||||
* @param lsnr the listener to remove
|
||||
*/
|
||||
public void removeListener(UpdateListener lsnr) { _listeners.remove(lsnr); }
|
||||
|
||||
void fireUpdate() {
|
||||
if (_disabled) return;
|
||||
for (Iterator iter = _listeners.iterator(); iter.hasNext(); ) {
|
||||
((UpdateListener)iter.next()).configUpdated(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables notification of events listeners
|
||||
* @see PeerPlotConfig#fireUpdate()
|
||||
*/
|
||||
public void disableEvents() { _disabled = true; }
|
||||
|
||||
/**
|
||||
* Enables notification of events listeners
|
||||
* @see PeerPlotConfig#fireUpdate()
|
||||
*/
|
||||
public void enableEvents() { _disabled = false; }
|
||||
|
||||
/**
|
||||
* How do we want to render a particular dataset (either the current or the averaged values)?
|
||||
*/
|
||||
public class PlotSeriesConfig {
|
||||
private long _period;
|
||||
private boolean _plotSendTime;
|
||||
private boolean _plotReceiveTime;
|
||||
private boolean _plotLostMessages;
|
||||
private Color _plotLineColor;
|
||||
|
||||
/**
|
||||
* Delegating constructor . . .
|
||||
* @param period the period for the config
|
||||
* (0 for current, otherwise # of milliseconds being averaged over)
|
||||
*/
|
||||
public PlotSeriesConfig(long period) {
|
||||
this(period, false, false, false, null);
|
||||
if (period <= 0) {
|
||||
_plotSendTime = true;
|
||||
_plotReceiveTime = true;
|
||||
_plotLostMessages = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a config for the rendering of a particular dataset)
|
||||
* @param period the period for the config
|
||||
* (0 for current, otherwise # of milliseconds being averaged over)
|
||||
* @param plotSend do we plot send times?
|
||||
* @param plotReceive do we plot receive times?
|
||||
* @param plotLost do we plot lost packets?
|
||||
* @param plotColor in what color?
|
||||
*/
|
||||
public PlotSeriesConfig(long period, boolean plotSend, boolean plotReceive, boolean plotLost, Color plotColor) {
|
||||
_period = period;
|
||||
_plotSendTime = plotSend;
|
||||
_plotReceiveTime = plotReceive;
|
||||
_plotLostMessages = plotLost;
|
||||
_plotLineColor = plotColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the plot config this plot series config is a part of
|
||||
* @return the plot config
|
||||
*/
|
||||
public PeerPlotConfig getPlotConfig() { return PeerPlotConfig.this; }
|
||||
|
||||
/**
|
||||
* What period is this series config describing?
|
||||
* @return 0 for current, otherwise # milliseconds that are being averaged over
|
||||
*/
|
||||
public long getPeriod() { return _period; }
|
||||
|
||||
/**
|
||||
* Sets the period this series config is describing
|
||||
* @param period the period
|
||||
* (0 for current, otherwise # milliseconds that are being averaged over)
|
||||
*/
|
||||
public void setPeriod(long period) {
|
||||
_period = period;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we render the time to send (ping to peer)?
|
||||
* @return true or false . . .
|
||||
*/
|
||||
public boolean getPlotSendTime() { return _plotSendTime; }
|
||||
|
||||
/**
|
||||
* Sets whether we render the time to send (ping to peer) or not
|
||||
* @param shouldPlot true or false
|
||||
*/
|
||||
public void setPlotSendTime(boolean shouldPlot) {
|
||||
_plotSendTime = shouldPlot;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we render the time to receive (peer pong to us)?
|
||||
* @return true or false . . .
|
||||
*/
|
||||
public boolean getPlotReceiveTime() { return _plotReceiveTime; }
|
||||
|
||||
/**
|
||||
* Sets whether we render the time to receive (peer pong to us)
|
||||
* @param shouldPlot true or false
|
||||
*/
|
||||
public void setPlotReceiveTime(boolean shouldPlot) {
|
||||
_plotReceiveTime = shouldPlot;
|
||||
fireUpdate();
|
||||
}
|
||||
/**
|
||||
* Should we render the number of messages lost (ping sent, no pong received in time)?
|
||||
* @return true or false . . .
|
||||
*/
|
||||
public boolean getPlotLostMessages() { return _plotLostMessages; }
|
||||
|
||||
/**
|
||||
* Sets whether we render the number of messages lost (ping sent, no pong received in time) or not
|
||||
* @param shouldPlot true or false
|
||||
*/
|
||||
public void setPlotLostMessages(boolean shouldPlot) {
|
||||
_plotLostMessages = shouldPlot;
|
||||
fireUpdate();
|
||||
}
|
||||
/**
|
||||
* What color should we plot the data with?
|
||||
* @return the color
|
||||
*/
|
||||
public Color getPlotLineColor() { return _plotLineColor; }
|
||||
|
||||
/**
|
||||
* Sets the color we should plot the data with
|
||||
* @param color the color to use
|
||||
*/
|
||||
public void setPlotLineColor(Color color) {
|
||||
_plotLineColor = color;
|
||||
fireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for listening to updates . . .
|
||||
*/
|
||||
public interface UpdateListener {
|
||||
/**
|
||||
* @param config the peer plot config that changes
|
||||
* @see PeerPlotConfig#fireUpdate()
|
||||
*/
|
||||
void configUpdated(PeerPlotConfig config);
|
||||
}
|
||||
}
|
||||
@@ -1,371 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JColorChooser;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
class PeerPlotConfigPane extends JPanel implements PeerPlotConfig.UpdateListener {
|
||||
private final static Log _log = new Log(PeerPlotConfigPane.class);
|
||||
private PeerPlotConfig _config;
|
||||
private HeartbeatControlPane _parent;
|
||||
private JLabel _title;
|
||||
private JButton _delete;
|
||||
private JLabel _fromLabel;
|
||||
private JTextField _from;
|
||||
private JTextArea _comments;
|
||||
private JLabel _peerLabel;
|
||||
private JTextField _peerKey;
|
||||
private JLabel _localLabel;
|
||||
private JTextField _localKey;
|
||||
private OptionLine _options[];
|
||||
private Random _rnd = new Random();
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
private Color _background = WHITE;
|
||||
|
||||
/**
|
||||
* Constructs a pane
|
||||
* @param config the plot config it represents
|
||||
* @param pane the pane this one is attached to
|
||||
*/
|
||||
public PeerPlotConfigPane(PeerPlotConfig config, HeartbeatControlPane pane) {
|
||||
_config = config;
|
||||
_parent = pane;
|
||||
if (_parent != null)
|
||||
_background = _parent.getBackground();
|
||||
_config.addListener(this);
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
/** called when the user wants to stop monitoring this test */
|
||||
private void delete() {
|
||||
_parent.removeTest(_config);
|
||||
}
|
||||
|
||||
private void initializeComponents() {
|
||||
buildComponents();
|
||||
placeComponents(this);
|
||||
refreshView();
|
||||
//setBorder(new BevelBorder(BevelBorder.RAISED));
|
||||
setBackground(_background);
|
||||
}
|
||||
|
||||
/**
|
||||
* place all the gui components onto the given panel
|
||||
* @param body the panel to place the components on
|
||||
*/
|
||||
private void placeComponents(JPanel body) {
|
||||
body.setLayout(new GridBagLayout());
|
||||
GridBagConstraints cts = new GridBagConstraints();
|
||||
|
||||
// row 0: title + delete
|
||||
cts.gridx = 0;
|
||||
cts.gridy = 0;
|
||||
cts.gridwidth = 5;
|
||||
cts.anchor = GridBagConstraints.WEST;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
body.add(_title, cts);
|
||||
cts.gridx = 5;
|
||||
cts.gridwidth = 1;
|
||||
cts.anchor = GridBagConstraints.NORTHWEST;
|
||||
cts.fill = GridBagConstraints.BOTH;
|
||||
body.add(_delete, cts);
|
||||
|
||||
// row 1: from + location
|
||||
cts.gridx = 0;
|
||||
cts.gridy = 1;
|
||||
cts.gridwidth = 1;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
body.add(_fromLabel, cts);
|
||||
cts.gridx = 1;
|
||||
cts.gridwidth = 5;
|
||||
cts.fill = GridBagConstraints.BOTH;
|
||||
body.add(_from, cts);
|
||||
|
||||
// row 2: comment
|
||||
cts.gridx = 0;
|
||||
cts.gridy = 2;
|
||||
cts.gridwidth = 6;
|
||||
cts.fill = GridBagConstraints.BOTH;
|
||||
body.add(_comments, cts);
|
||||
|
||||
// row 3: peer + peerKey
|
||||
cts.gridx = 0;
|
||||
cts.gridy = 3;
|
||||
cts.gridwidth = 1;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
body.add(_peerLabel, cts);
|
||||
cts.gridx = 1;
|
||||
cts.gridwidth = 5;
|
||||
cts.fill = GridBagConstraints.BOTH;
|
||||
body.add(_peerKey, cts);
|
||||
|
||||
// row 4: local + localKey
|
||||
cts.gridx = 0;
|
||||
cts.gridy = 4;
|
||||
cts.gridwidth = 1;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
body.add(_localLabel, cts);
|
||||
cts.gridx = 1;
|
||||
cts.gridwidth = 5;
|
||||
cts.fill = GridBagConstraints.BOTH;
|
||||
body.add(_localKey, cts);
|
||||
|
||||
// row 5-N: data row
|
||||
for (int i = 0; i < _options.length; i++) {
|
||||
cts.gridx = 0;
|
||||
cts.gridy = 5 + i;
|
||||
cts.gridwidth = 1;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
cts.anchor = GridBagConstraints.WEST;
|
||||
if (_options[i]._durationMinutes <= 0)
|
||||
body.add(new JLabel("Data: "), cts);
|
||||
else
|
||||
body.add(new JLabel(_options[i]._durationMinutes + "m avg: "), cts);
|
||||
|
||||
cts.gridx = 1;
|
||||
body.add(_options[i]._send, cts);
|
||||
cts.gridx = 2;
|
||||
body.add(_options[i]._recv, cts);
|
||||
cts.gridx = 3;
|
||||
body.add(_options[i]._lost, cts);
|
||||
cts.gridx = 4;
|
||||
body.add(_options[i]._all, cts);
|
||||
cts.gridx = 5;
|
||||
body.add(_options[i]._color, cts);
|
||||
}
|
||||
}
|
||||
|
||||
/** build all of the gui components */
|
||||
private void buildComponents() {
|
||||
_title = new JLabel(_config.getSummary());
|
||||
_title.setBackground(_background);
|
||||
_delete = new JButton("Delete");
|
||||
_delete.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { delete(); } });
|
||||
_delete.setEnabled(false);
|
||||
_delete.setBackground(_background);
|
||||
_fromLabel = new JLabel("Location: ");
|
||||
_fromLabel.setBackground(_background);
|
||||
_from = new JTextField(_config.getLocation());
|
||||
_from.setEditable(false);
|
||||
_from.setBackground(_background);
|
||||
_comments = new JTextArea(_config.getClientConfig().getComment(), 2, 20);
|
||||
// _comments = new JTextArea(_config.getClientConfig().getComment(), 2, 40);
|
||||
_comments.setEditable(false);
|
||||
_comments.setBackground(_background);
|
||||
_peerLabel = new JLabel("Peer: ");
|
||||
_peerLabel.setBackground(_background);
|
||||
_peerKey = new JTextField(_config.getClientConfig().getPeer().toBase64(), 8);
|
||||
_peerKey.setBackground(_background);
|
||||
_localLabel = new JLabel("Local: ");
|
||||
_localLabel.setBackground(_background);
|
||||
_localKey = new JTextField(_config.getClientConfig().getUs().toBase64(), 8);
|
||||
_localKey.setBackground(_background);
|
||||
|
||||
int averagedPeriods[] = _config.getClientConfig().getAveragePeriods();
|
||||
if (averagedPeriods == null)
|
||||
averagedPeriods = new int[0];
|
||||
|
||||
_options = new OptionLine[1 + averagedPeriods.length];
|
||||
_options[0] = new OptionLine(0);
|
||||
for (int i = 0; i < averagedPeriods.length; i++) {
|
||||
_options[1+i] = new OptionLine(averagedPeriods[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/** the settings have changed - revise */
|
||||
private void refreshView() {
|
||||
for (int i = 0; i < _options.length; i++) {
|
||||
PeerPlotConfig.PlotSeriesConfig cfg = getConfig(_options[i]._durationMinutes);
|
||||
if (cfg == null) {
|
||||
_log.warn("Config for minutes " + _options[i]._durationMinutes + " was not found?");
|
||||
continue;
|
||||
}
|
||||
//_log.debug("Refreshing view for minutes ["+ _options[i]._durationMinutes + "]: send [" +
|
||||
// _options[i]._send.isSelected() + "/" + cfg.getPlotSendTime() + "] recv [" +
|
||||
// _options[i]._recv.isSelected() + "/" + cfg.getPlotReceiveTime() + "] lost [" +
|
||||
// _options[i]._lost.isSelected() + "/" + cfg.getPlotLostMessages() + "]");
|
||||
_options[i]._send.setSelected(cfg.getPlotSendTime());
|
||||
_options[i]._recv.setSelected(cfg.getPlotReceiveTime());
|
||||
_options[i]._lost.setSelected(cfg.getPlotLostMessages());
|
||||
if (cfg.getPlotLineColor() != null)
|
||||
_options[i]._color.setBackground(cfg.getPlotLineColor());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* find the right config for the given period
|
||||
* @param minutes the minutes to locate the config by
|
||||
* @return the config for the given period, or null
|
||||
*/
|
||||
private PeerPlotConfig.PlotSeriesConfig getConfig(int minutes) {
|
||||
if (minutes <= 0)
|
||||
return _config.getCurrentSeriesConfig();
|
||||
|
||||
List configs = _config.getAverageSeriesConfigs();
|
||||
for (int i = 0; i < configs.size(); i++) {
|
||||
PeerPlotConfig.PlotSeriesConfig cfg = (PeerPlotConfig.PlotSeriesConfig)configs.get(i);
|
||||
if (cfg.getPeriod() == minutes * 60*1000)
|
||||
return cfg;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* notified that the config has been updated
|
||||
* @param config the config that was been updated
|
||||
*/
|
||||
public void configUpdated(PeerPlotConfig config) { refreshView(); }
|
||||
|
||||
private class ChooseColor implements ActionListener {
|
||||
private int _minutes;
|
||||
private JButton _button;
|
||||
|
||||
/**
|
||||
* @param minutes the minutes (line) to change the color of...
|
||||
* @param button the associated button
|
||||
*/
|
||||
public ChooseColor(int minutes, JButton button) {
|
||||
_minutes = minutes;
|
||||
_button = button;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
|
||||
*/
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
PeerPlotConfig.PlotSeriesConfig cfg = getConfig(_minutes);
|
||||
Color origColor = null;
|
||||
if (cfg != null)
|
||||
origColor = cfg.getPlotLineColor();
|
||||
Color color = JColorChooser.showDialog(PeerPlotConfigPane.this, "What color should this line be?", origColor);
|
||||
if (color != null) {
|
||||
if (cfg != null)
|
||||
cfg.setPlotLineColor(color);
|
||||
_button.setBackground(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class OptionLine {
|
||||
int _durationMinutes;
|
||||
JCheckBox _send;
|
||||
JCheckBox _recv;
|
||||
JCheckBox _lost;
|
||||
JCheckBox _all;
|
||||
JButton _color;
|
||||
|
||||
/**
|
||||
* Creates an OptionLine.
|
||||
* @param durationMinutes the minutes =)
|
||||
*/
|
||||
public OptionLine(int durationMinutes) {
|
||||
_durationMinutes = durationMinutes;
|
||||
_send = new JCheckBox("send time");
|
||||
_send.setBackground(_background);
|
||||
_recv = new JCheckBox("receive time");
|
||||
_recv.setBackground(_background);
|
||||
_lost = new JCheckBox("lost messages");
|
||||
_lost.setBackground(_background);
|
||||
_all = new JCheckBox("all");
|
||||
_all.setBackground(_background);
|
||||
_color = new JButton("color");
|
||||
int r = _rnd.nextInt(255);
|
||||
if (r < 0) r = -r;
|
||||
int g = _rnd.nextInt(255);
|
||||
if (g < 0) g = -g;
|
||||
int b = _rnd.nextInt(255);
|
||||
if (b < 0) b = -b;
|
||||
//_color.setBackground(new Color(r, g, b));
|
||||
_color.setBackground(_background);
|
||||
|
||||
_send.addActionListener(new UpdateListener(OptionLine.this, _durationMinutes));
|
||||
_recv.addActionListener(new UpdateListener(OptionLine.this, _durationMinutes));
|
||||
_lost.addActionListener(new UpdateListener(OptionLine.this, _durationMinutes));
|
||||
_all.addActionListener(new UpdateListener(OptionLine.this, _durationMinutes));
|
||||
_color.addActionListener(new ChooseColor(durationMinutes, _color));
|
||||
_color.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateListener implements ActionListener {
|
||||
private OptionLine _line;
|
||||
private int _minutes;
|
||||
|
||||
/**
|
||||
* Update Listener constructor . . .
|
||||
* @param line the line
|
||||
* @param minutes the minutes
|
||||
*/
|
||||
public UpdateListener(OptionLine line, int minutes) {
|
||||
_line = line;
|
||||
_minutes = minutes;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
|
||||
*/
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
PeerPlotConfig.PlotSeriesConfig cfg = getConfig(_minutes);
|
||||
|
||||
cfg.getPlotConfig().disableEvents();
|
||||
_log.debug("Updating data for minutes ["+ _line._durationMinutes + "]: send [" +
|
||||
_line._send.isSelected() + "/" + cfg.getPlotSendTime() + "] recv [" +
|
||||
_line._recv.isSelected() + "/" + cfg.getPlotReceiveTime() + "] lost [" +
|
||||
_line._lost.isSelected() + "/" + cfg.getPlotLostMessages() + "]: config = " + cfg);
|
||||
|
||||
boolean force = _line._all.isSelected();
|
||||
cfg.setPlotSendTime(_line._send.isSelected() || force);
|
||||
cfg.setPlotReceiveTime(_line._recv.isSelected() || force);
|
||||
cfg.setPlotLostMessages(_line._lost.isSelected() || force);
|
||||
cfg.getPlotConfig().enableEvents();
|
||||
cfg.getPlotConfig().fireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unit test stuff
|
||||
* @param args da arsg
|
||||
*/
|
||||
public final static void main(String args[]) {
|
||||
Test t = new Test();
|
||||
t.runTest();
|
||||
}
|
||||
|
||||
private final static class Test implements PeerPlotStateFetcher.FetchStateReceptor {
|
||||
/**
|
||||
* Runs da test
|
||||
*/
|
||||
public void runTest() {
|
||||
PeerPlotConfig cfg = new PeerPlotConfig("C:\\testnet\\r2\\heartbeatStat_10s_30kb.txt");
|
||||
PeerPlotState state = new PeerPlotState(cfg);
|
||||
PeerPlotStateFetcher.fetchPeerPlotState(this, state);
|
||||
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.heartbeat.gui.PeerPlotStateFetcher.FetchStateReceptor#peerPlotStateFetched(net.i2p.heartbeat.gui.PeerPlotState)
|
||||
*/
|
||||
public void peerPlotStateFetched(PeerPlotState state) {
|
||||
javax.swing.JFrame f = new javax.swing.JFrame("Test");
|
||||
f.getContentPane().add(new JScrollPane(new PeerPlotConfigPane(state.getPlotConfig(), null)));
|
||||
f.pack();
|
||||
f.setVisible(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
|
||||
/**
|
||||
* Current data + plot config for a particular test
|
||||
*
|
||||
*/
|
||||
class PeerPlotState {
|
||||
private StaticPeerData _currentData;
|
||||
private PeerPlotConfig _plotConfig;
|
||||
|
||||
/**
|
||||
* Delegating constructor . . .
|
||||
* @see PeerPlotState#PeerPlotState(PeerPlotConfig, StaticPeerData)
|
||||
*/
|
||||
public PeerPlotState() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegating constructor . . .
|
||||
* @param config plot config
|
||||
* @see PeerPlotState#PeerPlotState(PeerPlotConfig, StaticPeerData)
|
||||
*/
|
||||
public PeerPlotState(PeerPlotConfig config) {
|
||||
this(config, new StaticPeerData(config.getClientConfig()));
|
||||
}
|
||||
/**
|
||||
* Creates a PeerPlotState
|
||||
* @param config plot config
|
||||
* @param data peer data
|
||||
*/
|
||||
public PeerPlotState(PeerPlotConfig config, StaticPeerData data) {
|
||||
_plotConfig = config;
|
||||
_currentData = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an average
|
||||
* @param minutes mins averaged over
|
||||
* @param sendMs how much later did the peer receieve
|
||||
* @param recvMs how much later did we receieve
|
||||
* @param lost how many were lost
|
||||
*/
|
||||
public void addAverage(int minutes, int sendMs, int recvMs, int lost) {
|
||||
// make sure we've got the config entry for the average
|
||||
_plotConfig.addAverage(minutes);
|
||||
// add the data point...
|
||||
_currentData.addAverage(minutes, sendMs, recvMs, lost);
|
||||
}
|
||||
|
||||
/**
|
||||
* we successfully got a ping/pong through
|
||||
*
|
||||
* @param sendTime when did the ping get sent?
|
||||
* @param sendMs how much later did the peer receive the ping?
|
||||
* @param recvMs how much later than that did we receive the pong?
|
||||
*/
|
||||
public void addSuccess(long sendTime, int sendMs, int recvMs) {
|
||||
_currentData.addData(sendTime, sendMs, recvMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* we lost a ping/pong
|
||||
*
|
||||
* @param sendTime when did we send the ping?
|
||||
*/
|
||||
public void addLost(long sendTime) {
|
||||
_currentData.addData(sendTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* data set to render
|
||||
* @return the data set
|
||||
*/
|
||||
public StaticPeerData getCurrentData() { return _currentData; }
|
||||
|
||||
/**
|
||||
* Sets the data set to render
|
||||
* @param data the data set
|
||||
*/
|
||||
public void setCurrentData(StaticPeerData data) { _currentData = data; }
|
||||
|
||||
/**
|
||||
* configuration options on how to render the data set
|
||||
* @return the config options
|
||||
*/
|
||||
public PeerPlotConfig getPlotConfig() { return _plotConfig; }
|
||||
|
||||
/**
|
||||
* Sets the configuration options on how to render the data
|
||||
* @param config the config options
|
||||
*/
|
||||
public void setPlotConfig(PeerPlotConfig config) { _plotConfig = config; }
|
||||
}
|
||||
@@ -1,363 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Locale;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
class PeerPlotStateFetcher {
|
||||
private final static Log _log = new Log(PeerPlotStateFetcher.class);
|
||||
|
||||
/**
|
||||
* Fetch and fill the specified state structure
|
||||
* @param receptor the 'receptor' (callbacks)
|
||||
* @param state the state
|
||||
*/
|
||||
public static void fetchPeerPlotState(FetchStateReceptor receptor, PeerPlotState state) {
|
||||
I2PThread t = new I2PThread(new Fetcher(receptor, state));
|
||||
t.setDaemon(true);
|
||||
t.setName("Fetch state from " + state.getPlotConfig().getLocation());
|
||||
t.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback stuff . . .
|
||||
*/
|
||||
public interface FetchStateReceptor {
|
||||
/**
|
||||
* Called when a peer plot state is fetched
|
||||
* @param state state that was fetched
|
||||
*/
|
||||
void peerPlotStateFetched(PeerPlotState state);
|
||||
}
|
||||
|
||||
private static class Fetcher implements Runnable {
|
||||
private PeerPlotState _state;
|
||||
private FetchStateReceptor _receptor;
|
||||
|
||||
/**
|
||||
* Creates a Fetcher thread
|
||||
* @param receptor the 'receptor' (callbacks)
|
||||
* @param state the state
|
||||
*/
|
||||
public Fetcher(FetchStateReceptor receptor, PeerPlotState state) {
|
||||
_state = state;
|
||||
_receptor = receptor;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Runnable#run()
|
||||
*/
|
||||
public void run() {
|
||||
String loc = _state.getPlotConfig().getLocation();
|
||||
_log.debug("Load called [" + loc + "]");
|
||||
InputStream in = null;
|
||||
try {
|
||||
try {
|
||||
URL location = new URL(loc);
|
||||
in = location.openStream();
|
||||
} catch (MalformedURLException mue) {
|
||||
_log.debug("Not a url [" + loc + "]");
|
||||
in = null;
|
||||
}
|
||||
|
||||
if (in == null)
|
||||
in = new FileInputStream(loc);
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
String line = null;
|
||||
while ( (line = reader.readLine()) != null) {
|
||||
handleLine(line);
|
||||
}
|
||||
|
||||
if (valid())
|
||||
_receptor.peerPlotStateFetched(_state);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error retrieving from the location [" + loc + "]", ioe);
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check to make sure we've got everything we need
|
||||
* @return true [always]
|
||||
*/
|
||||
boolean valid() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* handle a line from the data set - these can be formatted in one of the
|
||||
* following ways. <p />
|
||||
*
|
||||
* <pre>
|
||||
* peer khWYqCETu9YtPUvGV92ocsbEW5DezhKlIG7ci8RLX3g=
|
||||
* local u-9hlR1ik2hemXf0HvKMfeRgrS86CbNQh25e7XBhaQE=
|
||||
* peerDest [base 64 of the full destination]
|
||||
* localDest [base 64 of the full destination]
|
||||
* numTunnelHops 2
|
||||
* comment Test with localhost sending 30KB every 20 seconds
|
||||
* sendFrequency 20
|
||||
* sendSize 30720
|
||||
* sessionStart 20040409.22:51:10.915
|
||||
* currentTime 20040409.23:31:39.607
|
||||
* numPending 2
|
||||
* lifetimeSent 118
|
||||
* lifetimeRecv 113
|
||||
* #averages minutes sendMs recvMs numLost
|
||||
* periodAverage 1 1843 771 0
|
||||
* periodAverage 5 786 752 1
|
||||
* periodAverage 30 855 735 3
|
||||
* #action status date and time sent sendMs replyMs
|
||||
* EVENT OK 20040409.23:21:44.742 691 670
|
||||
* EVENT OK 20040409.23:22:05.201 671 581
|
||||
* EVENT OK 20040409.23:22:26.301 1182 1452
|
||||
* EVENT OK 20040409.23:22:47.322 24304 1723
|
||||
* EVENT OK 20040409.23:23:08.232 2293 1081
|
||||
* EVENT OK 20040409.23:23:29.332 1392 641
|
||||
* EVENT OK 20040409.23:23:50.262 641 761
|
||||
* EVENT OK 20040409.23:24:11.102 651 701
|
||||
* EVENT OK 20040409.23:24:31.401 841 621
|
||||
* EVENT OK 20040409.23:24:52.061 651 681
|
||||
* EVENT OK 20040409.23:25:12.480 701 1623
|
||||
* EVENT OK 20040409.23:25:32.990 1442 1212
|
||||
* EVENT OK 20040409.23:25:54.230 591 631
|
||||
* EVENT OK 20040409.23:26:14.620 620 691
|
||||
* EVENT OK 20040409.23:26:35.199 1793 1432
|
||||
* EVENT OK 20040409.23:26:56.570 661 641
|
||||
* EVENT OK 20040409.23:27:17.200 641 660
|
||||
* EVENT OK 20040409.23:27:38.120 611 921
|
||||
* EVENT OK 20040409.23:27:58.699 831 621
|
||||
* EVENT OK 20040409.23:28:19.559 801 661
|
||||
* EVENT OK 20040409.23:28:40.279 601 611
|
||||
* EVENT OK 20040409.23:29:00.648 601 621
|
||||
* EVENT OK 20040409.23:29:21.288 701 661
|
||||
* EVENT LOST 20040409.23:29:41.828
|
||||
* EVENT LOST 20040409.23:30:02.327
|
||||
* EVENT LOST 20040409.23:30:22.656
|
||||
* EVENT OK 20040409.23:31:24.305 1843 771
|
||||
* </pre>
|
||||
*
|
||||
* @param line (see above)
|
||||
*/
|
||||
private void handleLine(String line) {
|
||||
if (line.startsWith("peerDest"))
|
||||
handlePeerDest(line);
|
||||
else if (line.startsWith("localDest"))
|
||||
handleLocalDest(line);
|
||||
else if (line.startsWith("numTunnelHops"))
|
||||
handleNumTunnelHops(line);
|
||||
else if (line.startsWith("comment"))
|
||||
handleComment(line);
|
||||
else if (line.startsWith("sendFrequency"))
|
||||
handleSendFrequency(line);
|
||||
else if (line.startsWith("sendSize"))
|
||||
handleSendSize(line);
|
||||
else if (line.startsWith("periodAverage"))
|
||||
handlePeriodAverage(line);
|
||||
else if (line.startsWith("EVENT"))
|
||||
handleEvent(line);
|
||||
else if (line.startsWith("numPending"))
|
||||
handleNumPending(line);
|
||||
else if (line.startsWith("sessionStart"))
|
||||
handleSessionStart(line);
|
||||
else
|
||||
_log.debug("Not handled: " + line);
|
||||
}
|
||||
|
||||
private void handlePeerDest(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String destKey = tok.nextToken();
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(destKey);
|
||||
_state.getPlotConfig().getClientConfig().setPeer(d);
|
||||
_log.debug("Setting the peer to " + d.calculateHash().toBase64());
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Unable to parse the peerDest line: [" + line + "]", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLocalDest(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String destKey = tok.nextToken();
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(destKey);
|
||||
_state.getPlotConfig().getClientConfig().setUs(d);
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Unable to parse the localDest line: [" + line + "]", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleComment(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
StringBuffer buf = new StringBuffer(line.length()-32);
|
||||
while (tok.hasMoreTokens())
|
||||
buf.append(tok.nextToken()).append(' ');
|
||||
_state.getPlotConfig().getClientConfig().setComment(buf.toString());
|
||||
}
|
||||
|
||||
private void handleNumTunnelHops(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String num = tok.nextToken();
|
||||
try {
|
||||
int val = Integer.parseInt(num);
|
||||
_state.getPlotConfig().getClientConfig().setNumHops(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the numTunnelHops line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleNumPending(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String num = tok.nextToken();
|
||||
try {
|
||||
int val = Integer.parseInt(num);
|
||||
_state.getCurrentData().setPendingCount(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the numPending line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSendFrequency(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String num = tok.nextToken();
|
||||
try {
|
||||
int val = Integer.parseInt(num);
|
||||
_state.getPlotConfig().getClientConfig().setSendFrequency(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the sendFrequency line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSendSize(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String num = tok.nextToken();
|
||||
try {
|
||||
int val = Integer.parseInt(num);
|
||||
_state.getPlotConfig().getClientConfig().setSendSize(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the sendSize line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSessionStart(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String date = tok.nextToken();
|
||||
try {
|
||||
long when = getDate(date);
|
||||
_state.getCurrentData().setSessionStart(when);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the sessionStart line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePeriodAverage(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
try {
|
||||
// periodAverage minutes sendMs recvMs numLost
|
||||
int min = Integer.parseInt(tok.nextToken());
|
||||
int send = Integer.parseInt(tok.nextToken());
|
||||
int recv = Integer.parseInt(tok.nextToken());
|
||||
int lost = Integer.parseInt(tok.nextToken());
|
||||
_state.addAverage(min, send, recv, lost);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the sendSize line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEvent(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
|
||||
// * EVENT OK 20040409.23:29:21.288 701 661
|
||||
// * EVENT LOST 20040409.23:29:41.828
|
||||
tok.nextToken(); // ignore first two
|
||||
tok.nextToken();
|
||||
try {
|
||||
long when = getDate(tok.nextToken());
|
||||
if (when < 0) {
|
||||
_log.error("Invalid EVENT line: [" + line + "]");
|
||||
return;
|
||||
}
|
||||
if (tok.hasMoreTokens()) {
|
||||
int sendMs = Integer.parseInt(tok.nextToken());
|
||||
int recvMs = Integer.parseInt(tok.nextToken());
|
||||
_state.addSuccess(when, sendMs, recvMs);
|
||||
} else {
|
||||
_state.addLost(when);
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the EVENT line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private static final SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd.HH:mm:ss.SSS", Locale.UK);
|
||||
private long getDate(String date) {
|
||||
synchronized (_fmt) {
|
||||
try {
|
||||
return _fmt.parse(date).getTime();
|
||||
} catch (ParseException pe) {
|
||||
_log.error("Unable to parse the date [" + date + "]", pe);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fakeRun() { /* UNUSED */
|
||||
try {
|
||||
Destination peer = new Destination();
|
||||
Destination us = new Destination();
|
||||
peer.fromBase64("3RPLOkQGlq8anNyNWhjbMyHxpAvUyUJKbiUejI80DnPR59T3blc7-XrBhQ2iPbf-BRAR~v1j34Kpba1eDyhPk2gevsE6ULO1irarJ3~C9WcQH2wAbNiVwfWqbh6onQ~YmkSpGNwGHD6ytwbvTyXeBJ" +
|
||||
"cS8e6gmfNN-sYLn1aQu8UqWB3D6BmTfLtyS3eqWVk66Nrzmwy8E1Hvq5z~1lukYb~cyiDO1oZHAOLyUQtd9eN16yJY~2SRG8LiscpPMl9nSJUr6fmXMUubW-M7QGFH82Om-735PJUk6WMy1Hi9Vgh4Pxhdl7g" +
|
||||
"fqGRWioFABdhcypb7p1Ca77p73uabLDFK-SjIYmdj7TwSdbNa6PCmzEvCEW~IZeZmnZC5B6pK30AdmD9vc641wUGce9xTJVfNRupf5L7pSsVIISix6FkKQk-FTW2RsZKLbuMCYMaPzLEx5gzODEqtI6Jf2teM" +
|
||||
"d5xCz51RPayDJl~lJ-W0IWYfosnjM~KxYaqc4agviBuF5ZWeAAAA");
|
||||
us.fromBase64("W~JFpqSH8uopylox2V5hMbpcHSsb-dJkSKvdJ1vj~KQcUFJWXFyfbetBAukcGH5S559aK9oslU0qbVoMDlJITVC4OXfXSnVbJBP1IhsK8SvjSYicjmIi2fA~k4HvSh9Wxu~bg8yo~jgfHA8tjYpp" +
|
||||
"K9QKc56BpkJb~hx0nNGy4Ny9eW~6A5AwAmHvwdt5NqcREYRMjRd63dMGm8BcEe-6FbOyMo3dnIFcETWAe8TCeoMxm~S1n~6Jlinw3ETxv-L6lQkhFFWnC5zyzQ~4JhVxxT3taTMYXg8td4CBGmrS078jcjW63" +
|
||||
"rlSiQgZBlYfN3iEYmurhuIEV9NXRcmnMrBOQUAoXPpVuRIxJbaQNDL71FO2iv424n4YjKs84suAho34GGQKq7WoL5V5KQgihfcl0f~xne-qP3FtpoPFeyA9x-sA2JWDAsxoZlfvgkiP5eyOn23prT9TJK47HC" +
|
||||
"VilHSV11uTVaC4Jc5YsjoBCZadWbgQnMCKlZ4jk-bLE1PSWLg7AAAA");
|
||||
_state.getPlotConfig().getClientConfig().setPeer(peer);
|
||||
_state.getPlotConfig().getClientConfig().setUs(us);
|
||||
_state.getPlotConfig().getClientConfig().setNumHops(2);
|
||||
_state.getPlotConfig().getClientConfig().setComment("we do stuff\nreally nifty stuff. really");
|
||||
_state.getPlotConfig().getClientConfig().setAveragePeriods(new int[] { 1, 5, 30, 60 });
|
||||
int rnd = new java.util.Random().nextInt();
|
||||
if (rnd > 0)
|
||||
rnd = rnd % 10;
|
||||
else
|
||||
rnd = (-rnd) % 10;
|
||||
_state.getPlotConfig().getClientConfig().setSendFrequency(rnd);
|
||||
_state.getPlotConfig().getClientConfig().setSendSize(16*1024);
|
||||
_state.getPlotConfig().getClientConfig().setStatDuration(10);
|
||||
_state.getPlotConfig().rebuildAverageSeriesConfigs();
|
||||
_state.setCurrentData(new StaticPeerData(_state.getPlotConfig().getClientConfig()));
|
||||
|
||||
_receptor.peerPlotStateFetched(_state);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.heartbeat.ClientConfig;
|
||||
import net.i2p.heartbeat.PeerData;
|
||||
|
||||
/**
|
||||
* Raw data points for a test
|
||||
*/
|
||||
class StaticPeerData extends PeerData {
|
||||
private int _pending;
|
||||
/** Integer (period, in minutes) to Integer (milliseconds) for sending a ping */
|
||||
private Map _averageSendTimes;
|
||||
/** Integer (period, in minutes) to Integer (milliseconds) for receiving a pong */
|
||||
private Map _averageReceiveTimes;
|
||||
/** Integer (period, in minutes) to Integer (num messages) of how many messages were lost on average */
|
||||
private Map _lostMessages;
|
||||
|
||||
/**
|
||||
* Creates a static peer data with a specified client config ... duh
|
||||
* @param config the client config
|
||||
*/
|
||||
public StaticPeerData(ClientConfig config) {
|
||||
super(config);
|
||||
_averageSendTimes = new HashMap(4);
|
||||
_averageReceiveTimes = new HashMap(4);
|
||||
_lostMessages = new HashMap(4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds averaged data
|
||||
* @param minutes the minutes (averaged over)
|
||||
* @param sendMs the send time (ping) in milliseconds
|
||||
* @param recvMs the receive time (pong) in milliseconds
|
||||
* @param lost the number lost
|
||||
*/
|
||||
public void addAverage(int minutes, int sendMs, int recvMs, int lost) {
|
||||
_averageSendTimes.put(new Integer(minutes), new Integer(sendMs));
|
||||
_averageReceiveTimes.put(new Integer(minutes), new Integer(recvMs));
|
||||
_lostMessages.put(new Integer(minutes), new Integer(lost));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number pending
|
||||
* @param numPending the number pending
|
||||
*/
|
||||
public void setPendingCount(int numPending) { _pending = numPending; }
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.heartbeat.PeerData#setSessionStart(long)
|
||||
*/
|
||||
public void setSessionStart(long when) { super.setSessionStart(when); }
|
||||
|
||||
/**
|
||||
* Adds data
|
||||
* @param sendTime the time it was sent
|
||||
* @param sendMs the send time (ping) in milliseconds
|
||||
* @param recvMs the receive time (pong) in milliseconds
|
||||
*/
|
||||
public void addData(long sendTime, int sendMs, int recvMs) {
|
||||
PeerData.EventDataPoint dataPoint = new PeerData.EventDataPoint(sendTime);
|
||||
dataPoint.setPongSent(sendTime + sendMs);
|
||||
dataPoint.setPongReceived(sendTime + sendMs + recvMs);
|
||||
dataPoint.setWasPonged(true);
|
||||
addDataPoint(dataPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds data
|
||||
* @param sendTime the time it was sent
|
||||
*/
|
||||
public void addData(long sendTime) {
|
||||
PeerData.EventDataPoint dataPoint = new PeerData.EventDataPoint(sendTime);
|
||||
dataPoint.setWasPonged(false);
|
||||
addDataPoint(dataPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* how many pings are still outstanding?
|
||||
* @return the number of pings outstanding
|
||||
*/
|
||||
public int getPendingCount() { return _pending; }
|
||||
|
||||
|
||||
/**
|
||||
* average time to send over the given period.
|
||||
*
|
||||
* @param period number of minutes to retrieve the average for
|
||||
* @return milliseconds average, or -1 if we dont track that period
|
||||
*/
|
||||
public double getAverageSendTime(int period) {
|
||||
Integer i = (Integer)_averageSendTimes.get(new Integer(period));
|
||||
if (i == null)
|
||||
return -1;
|
||||
|
||||
return i.doubleValue();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* average time to receive over the given period.
|
||||
*
|
||||
* @param period number of minutes to retrieve the average for
|
||||
* @return milliseconds average, or -1 if we dont track that period
|
||||
*/
|
||||
public double getAverageReceiveTime(int period) {
|
||||
Integer i = (Integer)_averageReceiveTimes.get(new Integer(period));
|
||||
if (i == null)
|
||||
return -1;
|
||||
|
||||
return i.doubleValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* number of lost messages over the given period.
|
||||
*
|
||||
* @param period number of minutes to retrieve the average for
|
||||
* @return number of lost messages in the period, or -1 if we dont track that period
|
||||
*/
|
||||
public double getLostMessages(int period) {
|
||||
Integer i = (Integer)_lostMessages.get(new Integer(period));
|
||||
if (i == null)
|
||||
return -1;
|
||||
|
||||
return i.doubleValue();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.heartbeat.PeerData#cleanup()
|
||||
*/
|
||||
public void cleanup() {}
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
@@ -1,11 +0,0 @@
|
||||
$Id$
|
||||
|
||||
the i2p/apps/httptunnel module is the root of the
|
||||
HTTPTunnel application, and everything within it
|
||||
is released according to the terms of the I2P
|
||||
license policy. That means everything contained
|
||||
within the i2p/apps/httptunnel module is released
|
||||
under the GPL plus the java exception unless
|
||||
otherwise marked. Alternate licenses that may be
|
||||
used include BSD, Cryptix, and MIT, as well as
|
||||
code granted into the public domain.
|
||||
@@ -1,47 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="httptunnel">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../ministreaming/java/" target="build" />
|
||||
<!-- ministreaming will build core -->
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac
|
||||
srcdir="./src"
|
||||
debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="./build/obj"
|
||||
classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="compile">
|
||||
<jar destfile="./build/httptunnel.jar" basedir="./build/obj" includes="**/*.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.httptunnel.HTTPTunnel" />
|
||||
<attribute name="Class-Path" value="i2p.jar mstreaming.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="./src:../../../core/java/src:../../ministreaming/java/src" destdir="./build/javadoc"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
splitindex="true"
|
||||
windowtitle="HTTPTunnel" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<!-- ministreaming will clean core -->
|
||||
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<!-- ministreaming will clean core -->
|
||||
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
||||
@@ -1,87 +0,0 @@
|
||||
package net.i2p.httptunnel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Listens on a port for HTTP connections.
|
||||
*/
|
||||
public class HTTPListener extends Thread {
|
||||
|
||||
private static final Log _log = new Log(HTTPListener.class);
|
||||
|
||||
private int port;
|
||||
private String listenHost;
|
||||
private SocketManagerProducer smp;
|
||||
|
||||
/**
|
||||
* A public constructor. It contstructs things. In this case,
|
||||
* it constructs a nice HTTPListener, for all your listening on
|
||||
* HTTP needs. Yep. That's right.
|
||||
* @param smp A SocketManagerProducer, producing Sockets, no doubt
|
||||
* @param port A port, to connect to.
|
||||
* @param listenHost A host, to connect to.
|
||||
*/
|
||||
|
||||
public HTTPListener(SocketManagerProducer smp, int port, String listenHost) {
|
||||
this.smp = smp;
|
||||
this.port = port;
|
||||
start();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Thread#run()
|
||||
*/
|
||||
public void run() {
|
||||
try {
|
||||
InetAddress lh = listenHost == null ? null : InetAddress.getByName(listenHost);
|
||||
ServerSocket ss = new ServerSocket(port, 0, lh);
|
||||
while (true) {
|
||||
Socket s = ss.accept();
|
||||
new HTTPSocketHandler(this, s);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while accepting connections", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean proxyUsed = false;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
if (true) return false; // FIXME: check a config option here
|
||||
|
||||
if (proxyUsed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
proxyUsed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The SocketManagerProducer being used.
|
||||
*/
|
||||
public SocketManagerProducer getSMP() {
|
||||
return smp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs with HTTP 1.1 flair that a feature isn't implemented.
|
||||
* @param out The stream the text goes to.
|
||||
* @deprecated
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handleNotImplemented(OutputStream out) throws IOException {
|
||||
out.write(("HTTP/1.1 200 Document following\n\n" + "<h1>Feature not implemented</h1>").getBytes("ISO-8859-1"));
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package net.i2p.httptunnel;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.httptunnel.handler.RootHandler;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Handles a single HTTP socket connection.
|
||||
*/
|
||||
public class HTTPSocketHandler extends Thread {
|
||||
|
||||
private static final Log _log = new Log(HTTPSocketHandler.class);
|
||||
|
||||
private Socket s;
|
||||
private HTTPListener httpl;
|
||||
private RootHandler h;
|
||||
|
||||
/**
|
||||
* A public constructor.
|
||||
* @param httpl An HTTPListener, to listen for HTTP, no doubt
|
||||
* @param s A socket.
|
||||
*/
|
||||
public HTTPSocketHandler(HTTPListener httpl, Socket s) {
|
||||
this.httpl = httpl;
|
||||
this.s = s;
|
||||
h = RootHandler.getInstance();
|
||||
start();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Thread#run()
|
||||
*/
|
||||
public void run() {
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
in = new BufferedInputStream(s.getInputStream());
|
||||
out = new BufferedOutputStream(s.getOutputStream());
|
||||
Request req = new Request(in);
|
||||
h.handle(req, httpl, out);
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while handling data", ex);
|
||||
} finally {
|
||||
try {
|
||||
if (in != null) in.close();
|
||||
if (out != null) {
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
s.close();
|
||||
} catch (IOException ex) {
|
||||
_log.error("IOException in finalizer", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/*
|
||||
* HTTPTunnel
|
||||
* (c) 2003 - 2004 mihi
|
||||
*
|
||||
* 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, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* In addition, as a special exception, mihi gives permission to link
|
||||
* the code of this program with the proprietary Java implementation
|
||||
* provided by Sun (or other vendors as well), and distribute linked
|
||||
* combinations including the two. You must obey the GNU General
|
||||
* Public License in all respects for all of the code used other than
|
||||
* the proprietary Java implementation. If you modify this file, you
|
||||
* may extend this exception to your version of the file, but you are
|
||||
* not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
package net.i2p.httptunnel;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
|
||||
/**
|
||||
* HTTPTunnel main class.
|
||||
*/
|
||||
public class HTTPTunnel {
|
||||
|
||||
/**
|
||||
* Create a HTTPTunnel instance.
|
||||
*
|
||||
* @param initialManagers a list of socket managers to use
|
||||
* @param maxManagers how many managers to have in the cache
|
||||
* @param shouldThrowAwayManagers whether to throw away a manager after use
|
||||
* @param listenPort which port to listen on
|
||||
*/
|
||||
public HTTPTunnel(I2PSocketManager[] initialManagers, int maxManagers, boolean shouldThrowAwayManagers,
|
||||
int listenPort) {
|
||||
this(initialManagers, maxManagers, shouldThrowAwayManagers, listenPort, "127.0.0.1", 7654);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a HTTPTunnel instance.
|
||||
*
|
||||
* @param initialManagers a list of socket managers to use
|
||||
* @param maxManagers how many managers to have in the cache
|
||||
* @param shouldThrowAwayManagers whether to throw away a manager after use
|
||||
* @param listenPort which port to listen on
|
||||
* @param i2cpAddress the I2CP address
|
||||
* @param i2cpPort the I2CP port
|
||||
*/
|
||||
public HTTPTunnel(I2PSocketManager[] initialManagers, int maxManagers, boolean shouldThrowAwayManagers,
|
||||
int listenPort, String i2cpAddress, int i2cpPort) {
|
||||
SocketManagerProducer smp = new SocketManagerProducer(initialManagers, maxManagers, shouldThrowAwayManagers,
|
||||
i2cpAddress, i2cpPort);
|
||||
new HTTPListener(smp, listenPort, "127.0.0.1");
|
||||
}
|
||||
|
||||
/**
|
||||
* The all important main function, allowing HTTPTunnel to be
|
||||
* stand-alone, a program in it's own right, and all that jazz.
|
||||
* @param args A list of String passed to the program
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
String host = "127.0.0.1";
|
||||
int port = 7654, max = 1;
|
||||
boolean throwAwayManagers = false;
|
||||
if (args.length > 1) {
|
||||
if (args.length == 4) {
|
||||
host = args[2];
|
||||
port = Integer.parseInt(args[3]);
|
||||
} else if (args.length != 2) {
|
||||
showInfo();
|
||||
return;
|
||||
}
|
||||
max = Integer.parseInt(args[1]);
|
||||
} else if (args.length != 1) {
|
||||
showInfo();
|
||||
return;
|
||||
}
|
||||
if (max == 0) {
|
||||
max = 1;
|
||||
} else if (max < 0) {
|
||||
max = -max;
|
||||
throwAwayManagers = true;
|
||||
}
|
||||
new HTTPTunnel(null, max, throwAwayManagers, Integer.parseInt(args[0]), host, port);
|
||||
}
|
||||
|
||||
private static void showInfo() {
|
||||
System.out.println("Usage: java HTTPTunnel <listenPort> [<max> " + "[<i2cphost> <i2cpport>]]\n"
|
||||
+ " <listenPort> port to listen for browsers\n"
|
||||
+ " <max> max number of SocketMangers in pool, " + "use neg. number\n"
|
||||
+ " to use each SocketManager only once " + "(default: 1)\n"
|
||||
+ " <i2cphost> host to connect to the router " + "(default: 127.0.0.1)\n"
|
||||
+ " <i2cpport> port to connect to the router " + "(default: 7654)");
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
package net.i2p.httptunnel;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.StringReader;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* A HTTP request (GET or POST). This will be passed to a hander for
|
||||
* handling it.
|
||||
*/
|
||||
public class Request {
|
||||
|
||||
private static final Log _log = new Log(Request.class);
|
||||
|
||||
// all strings are forced to be ISO-8859-1 encoding
|
||||
private String method;
|
||||
private String url;
|
||||
private String proto;
|
||||
private String params;
|
||||
private String postData;
|
||||
|
||||
/**
|
||||
* A constructor, creating a request from an InputStream
|
||||
* @param in InputStream from which we "read-in" a Request
|
||||
* @throws IOException
|
||||
*/
|
||||
public Request(InputStream in) throws IOException {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(in, "ISO-8859-1"));
|
||||
String line = br.readLine();
|
||||
if (line == null) { // no data at all
|
||||
method = null;
|
||||
_log.error("Connection but no data");
|
||||
return;
|
||||
}
|
||||
int pos = line.indexOf(" ");
|
||||
if (pos == -1) {
|
||||
method = line;
|
||||
url = "";
|
||||
_log.error("Malformed HTTP request: " + line);
|
||||
} else {
|
||||
method = line.substring(0, pos);
|
||||
url = line.substring(pos + 1);
|
||||
}
|
||||
proto = "";
|
||||
pos = url.indexOf(" ");
|
||||
if (pos != -1) {
|
||||
proto = url.substring(pos); // leading space intended
|
||||
url = url.substring(0, pos);
|
||||
}
|
||||
StringBuffer sb = new StringBuffer(512);
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (line.length() == 0) break;
|
||||
sb.append(line).append("\r\n");
|
||||
}
|
||||
params = sb.toString(); // no leading empty line!
|
||||
sb = new StringBuffer();
|
||||
// hack for POST requests, ripped from HttpClient
|
||||
// this won't work for large POSTDATA
|
||||
// FIXME: do this better, please.
|
||||
if (!method.equals("GET")) {
|
||||
while (br.ready()) { // empty the buffer (POST requests)
|
||||
int i = br.read();
|
||||
if (i != -1) {
|
||||
sb.append((char) i);
|
||||
}
|
||||
}
|
||||
postData = sb.toString();
|
||||
} else {
|
||||
postData = "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A Request as an array of bytes of a String in ISO-8859-1 format
|
||||
* @throws IOException
|
||||
*/
|
||||
public byte[] toByteArray() throws IOException {
|
||||
if (method == null) return null;
|
||||
return toISO8859_1String().getBytes("ISO-8859-1");
|
||||
|
||||
}
|
||||
|
||||
private String toISO8859_1String() throws IOException {
|
||||
if (method == null) return null;
|
||||
return method + " " + url + proto + "\r\n" + params + "\r\n" + postData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the URL of the request
|
||||
*/
|
||||
public String getURL() {
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL of the Request
|
||||
* @param newURL the new URL
|
||||
*/
|
||||
public void setURL(String newURL) {
|
||||
url = newURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of a param.
|
||||
* @param name The name of the param
|
||||
* @return The value of the param, or null
|
||||
*/
|
||||
public String getParam(String name) {
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(new StringReader(params));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (line.startsWith(name)) { return line.substring(name.length()); }
|
||||
}
|
||||
return null;
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error getting parameter", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a param.
|
||||
* @param name the name of the param
|
||||
* @param value the value to be set
|
||||
*/
|
||||
public void setParam(String name, String value) {
|
||||
try {
|
||||
StringBuffer sb = new StringBuffer(params.length() + value.length());
|
||||
BufferedReader br = new BufferedReader(new StringReader(params));
|
||||
String line;
|
||||
boolean replaced = false;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (line.startsWith(name)) {
|
||||
replaced = true;
|
||||
if (value == null) continue; // kill param
|
||||
line = name + value;
|
||||
}
|
||||
sb.append(line).append("\r\n");
|
||||
}
|
||||
if (!replaced && value != null) {
|
||||
sb.append(name).append(value).append("\r\n");
|
||||
}
|
||||
params = sb.toString();
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error getting parameter", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
package net.i2p.httptunnel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
|
||||
/**
|
||||
* Produces SocketManagers in a thread and gives them to those who
|
||||
* need them.
|
||||
*/
|
||||
public class SocketManagerProducer extends Thread {
|
||||
|
||||
private ArrayList myManagers = new ArrayList();
|
||||
private HashMap usedManagers = new HashMap();
|
||||
private int port;
|
||||
private String host;
|
||||
private int maxManagers;
|
||||
private boolean shouldThrowAwayManagers;
|
||||
|
||||
/**
|
||||
* Public constructor creating a SocketManagerProducer
|
||||
* @param initialManagers a list of socket managers to use
|
||||
* @param maxManagers how many managers to have in the cache
|
||||
* @param shouldThrowAwayManagers whether to throw away a manager after use
|
||||
* @param host which host to listen on
|
||||
* @param port which port to listen on
|
||||
*/
|
||||
public SocketManagerProducer(I2PSocketManager[] initialManagers, int maxManagers, boolean shouldThrowAwayManagers,
|
||||
String host, int port) {
|
||||
if (maxManagers < 1) { throw new IllegalArgumentException("maxManagers < 1"); }
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.shouldThrowAwayManagers = shouldThrowAwayManagers;
|
||||
if (initialManagers != null) {
|
||||
myManagers.addAll(Arrays.asList(initialManagers));
|
||||
}
|
||||
this.maxManagers = maxManagers;
|
||||
this.shouldThrowAwayManagers = shouldThrowAwayManagers;
|
||||
setDaemon(true);
|
||||
start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread producing new SocketManagers.
|
||||
*/
|
||||
public void run() {
|
||||
while (true) {
|
||||
synchronized (this) {
|
||||
// without mcDonalds mode, we most probably need no
|
||||
// new managers.
|
||||
while (!shouldThrowAwayManagers && myManagers.size() == maxManagers) {
|
||||
myWait();
|
||||
}
|
||||
}
|
||||
// produce a new manager, regardless whether it is needed
|
||||
// or not. Do not synchronized this part, since it can be
|
||||
// quite time-consuming.
|
||||
I2PSocketManager newManager = I2PSocketManagerFactory.createManager(host, port, new Properties());
|
||||
// when done, check if it is needed.
|
||||
synchronized (this) {
|
||||
while (myManagers.size() == maxManagers) {
|
||||
myWait();
|
||||
}
|
||||
myManagers.add(newManager);
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a manager for connecting to a given destination. Each
|
||||
* destination will always get the same manager.
|
||||
*
|
||||
* @param dest the destination to connect to
|
||||
* @return the SocketManager to use
|
||||
*/
|
||||
public synchronized I2PSocketManager getManager(String dest) {
|
||||
I2PSocketManager result = (I2PSocketManager) usedManagers.get(dest);
|
||||
if (result == null) {
|
||||
result = getManager();
|
||||
usedManagers.put(dest, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a "new" SocketManager. Depending on the anonymity settings,
|
||||
* this can be a completely new one or one randomly selected from
|
||||
* a pool.
|
||||
*
|
||||
* @return the SocketManager to use
|
||||
*/
|
||||
public synchronized I2PSocketManager getManager() {
|
||||
while (myManagers.size() == 0) {
|
||||
myWait(); // no manager here, so wait until one is produced
|
||||
}
|
||||
int which = (int) (Math.random() * myManagers.size());
|
||||
I2PSocketManager result = (I2PSocketManager) myManagers.get(which);
|
||||
if (shouldThrowAwayManagers) {
|
||||
myManagers.remove(which);
|
||||
notifyAll();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until InterruptedException
|
||||
*/
|
||||
public void myWait() {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package net.i2p.httptunnel.filter;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Chain multiple filters. Decorator pattern...
|
||||
*/
|
||||
public class ChainFilter implements Filter {
|
||||
|
||||
private static final Log _log = new Log(ChainFilter.class);
|
||||
|
||||
private Collection filters; // perhaps protected?
|
||||
|
||||
/**
|
||||
* @param filters A collection (list) of filters to chain to
|
||||
*/
|
||||
public ChainFilter(Collection filters) {
|
||||
this.filters = filters;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.httptunnel.filter.Filter#filter(byte[])
|
||||
*/
|
||||
public byte[] filter(byte[] toFilter) {
|
||||
byte[] buf = toFilter;
|
||||
for (Iterator it = filters.iterator(); it.hasNext();) {
|
||||
Filter f = (Filter) it.next();
|
||||
buf = f.filter(buf);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.httptunnel.filter.Filter#finish()
|
||||
*/
|
||||
public byte[] finish() {
|
||||
// this is a bit complicated. Think about it...
|
||||
try {
|
||||
byte[] buf = EMPTY;
|
||||
for (Iterator it = filters.iterator(); it.hasNext();) {
|
||||
Filter f = (Filter) it.next();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
if (buf.length != 0) {
|
||||
baos.write(f.filter(buf));
|
||||
}
|
||||
baos.write(f.finish());
|
||||
buf = baos.toByteArray();
|
||||
}
|
||||
return buf;
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error chaining filters", ex);
|
||||
return EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package net.i2p.httptunnel.filter;
|
||||
|
||||
/**
|
||||
* A generic filtering interface.
|
||||
*/
|
||||
public interface Filter {
|
||||
|
||||
/**
|
||||
* An empty byte array.
|
||||
*/
|
||||
public static final byte[] EMPTY = new byte[0];
|
||||
|
||||
/**
|
||||
* Filter some data. Not all filtered data need to be returned.
|
||||
* @param toFilter the bytes that are to be filtered.
|
||||
* @return the filtered data
|
||||
*/
|
||||
public byte[] filter(byte[] toFilter);
|
||||
|
||||
/**
|
||||
* Data stream has finished. Return all of the rest data.
|
||||
* @return the rest of the data
|
||||
*/
|
||||
public byte[] finish();
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package net.i2p.httptunnel.filter;
|
||||
|
||||
/**
|
||||
* A filter letting everything pass as is.
|
||||
*/
|
||||
public class NullFilter implements Filter {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.httptunnel.filter.Filter#filter(byte[])
|
||||
*/
|
||||
public byte[] filter(byte[] toFilter) {
|
||||
return toFilter;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.httptunnel.filter.Filter#finish()
|
||||
*/
|
||||
public byte[] finish() {
|
||||
return EMPTY;
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
package net.i2p.httptunnel.handler;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.SocketException;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.httptunnel.HTTPListener;
|
||||
import net.i2p.httptunnel.Request;
|
||||
import net.i2p.httptunnel.SocketManagerProducer;
|
||||
import net.i2p.httptunnel.filter.Filter;
|
||||
import net.i2p.httptunnel.filter.NullFilter;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Handler for browsing Eepsites.
|
||||
*/
|
||||
public class EepHandler {
|
||||
|
||||
private static final Log _log = new Log(EepHandler.class);
|
||||
private static I2PAppContext _context = new I2PAppContext();
|
||||
|
||||
protected ErrorHandler errorHandler;
|
||||
|
||||
/* package private */EepHandler(ErrorHandler eh) {
|
||||
errorHandler = eh;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param req the Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @param destination destination as a string, (subject to naming
|
||||
* service lookup)
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out,
|
||||
/* boolean fromProxy, */String destination) throws IOException {
|
||||
SocketManagerProducer smp = httpl.getSMP();
|
||||
Destination dest = _context.namingService().lookup(destination);
|
||||
if (dest == null) {
|
||||
errorHandler.handle(req, httpl, out, "Could not lookup host: " + destination);
|
||||
return;
|
||||
}
|
||||
I2PSocketManager sm = smp.getManager(destination);
|
||||
Filter f = new NullFilter(); //FIXME: use other filter
|
||||
req.setParam("Host: ", dest.toBase64());
|
||||
if (!handle(req, f, out, dest, sm)) {
|
||||
errorHandler.handle(req, httpl, out, "Unable to reach peer");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param req the Request to send out
|
||||
* @param f a Filter to apply to the bytes retrieved from the Destination
|
||||
* @param out where to write the results
|
||||
* @param dest the Destination of the Request
|
||||
* @param sm an I2PSocketManager, to get a socket for the Destination
|
||||
* @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 {
|
||||
I2PSocket s = null;
|
||||
boolean written = false;
|
||||
try {
|
||||
synchronized (sm) {
|
||||
s = sm.connect(dest, new I2PSocketOptions());
|
||||
}
|
||||
InputStream in = new BufferedInputStream(s.getInputStream());
|
||||
OutputStream sout = new BufferedOutputStream(s.getOutputStream());
|
||||
sout.write(req.toByteArray());
|
||||
sout.flush();
|
||||
byte[] buffer = new byte[16384], filtered;
|
||||
int len;
|
||||
while ((len = in.read(buffer)) != -1) {
|
||||
if (len != buffer.length) {
|
||||
byte[] b2 = new byte[len];
|
||||
System.arraycopy(buffer, 0, b2, 0, len);
|
||||
filtered = f.filter(b2);
|
||||
} else {
|
||||
filtered = f.filter(buffer);
|
||||
}
|
||||
written = true;
|
||||
out.write(filtered);
|
||||
}
|
||||
filtered = f.finish();
|
||||
written = true;
|
||||
out.write(filtered);
|
||||
out.flush();
|
||||
} catch (SocketException ex) {
|
||||
_log.error("Error while handling eepsite request");
|
||||
return written;
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while handling eepsite request");
|
||||
return written;
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error while handling eepsite request");
|
||||
return written;
|
||||
} finally {
|
||||
if (s != null) s.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package net.i2p.httptunnel.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.httptunnel.HTTPListener;
|
||||
import net.i2p.httptunnel.Request;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Handler for general error messages.
|
||||
*/
|
||||
public class ErrorHandler {
|
||||
|
||||
private static final Log _log = new Log(ErrorHandler.class); /* UNUSED */
|
||||
|
||||
/* package private */ErrorHandler() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param req the Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @param error the error that happened
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out, String error) throws IOException {
|
||||
// FIXME: Make nicer messages for more likely errors.
|
||||
out
|
||||
.write(("HTTP/1.1 500 Internal Server Error\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n\r\n")
|
||||
.getBytes("ISO-8859-1"));
|
||||
out
|
||||
.write(("<html><head><title>" + error + "</title></head><body><h1>" + error
|
||||
+ "</h1>An internal error occurred while " + "handling a request by HTTPTunnel:<br><b>" + error + "</b><h2>Complete request:</h2><b>---</b><br><i><pre>\r\n")
|
||||
.getBytes("ISO-8859-1"));
|
||||
out.write(req.toByteArray());
|
||||
out.write(("</pre></i><br><b>---</b></body></html>").getBytes("ISO-8859-1"));
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package net.i2p.httptunnel.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.httptunnel.HTTPListener;
|
||||
import net.i2p.httptunnel.Request;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Handler for requests that do not require any connection to anyone
|
||||
* (except errors).
|
||||
*/
|
||||
public class LocalHandler {
|
||||
|
||||
private static final Log _log = new Log(LocalHandler.class); /* UNUSED */
|
||||
|
||||
/* package private */LocalHandler() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param req the Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out
|
||||
/*, boolean fromProxy */) throws IOException {
|
||||
//FIXME: separate multiple pages, not only a start page
|
||||
//FIXME: provide some info on this page
|
||||
out
|
||||
.write(("HTTP/1.1 200 Document following\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n\r\n"
|
||||
+ "<html><head><title>Welcome to I2P HTTPTunnel</title>"
|
||||
+ "</head><body><h1>Welcome to I2P HTTPTunnel</h1>You can "
|
||||
+ "browse Eepsites by adding an eepsite name to the request." + "</body></html>")
|
||||
.getBytes("ISO-8859-1"));
|
||||
out.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently always throws an IO Exception
|
||||
* @param req the Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handleProxyConfWarning(Request req, HTTPListener httpl, OutputStream out) throws IOException {
|
||||
//FIXME
|
||||
throw new IOException("jrandom ate the deprecated method. mooo");
|
||||
//httpl.handleNotImplemented(out);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently always throws an IO Exception
|
||||
* @param req the Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handleHTTPWarning(Request req, HTTPListener httpl, OutputStream out /*, boolean fromProxy */)
|
||||
throws IOException {
|
||||
// FIXME
|
||||
throw new IOException("jrandom ate the deprecated method. mooo");
|
||||
//httpl.handleNotImplemented(out);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package net.i2p.httptunnel.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.httptunnel.HTTPListener;
|
||||
import net.i2p.httptunnel.Request;
|
||||
import net.i2p.httptunnel.SocketManagerProducer;
|
||||
import net.i2p.httptunnel.filter.Filter;
|
||||
import net.i2p.httptunnel.filter.NullFilter;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Handler for proxying "normal" HTTP requests.
|
||||
*/
|
||||
public class ProxyHandler extends EepHandler {
|
||||
|
||||
private static final Log _log = new Log(ErrorHandler.class); /* UNUSED */
|
||||
private static I2PAppContext _context = new I2PAppContext();
|
||||
|
||||
/* package private */ProxyHandler(ErrorHandler eh) {
|
||||
super(eh);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param req a Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out
|
||||
/*, boolean fromProxy */) throws IOException {
|
||||
SocketManagerProducer smp = httpl.getSMP();
|
||||
Destination dest = findProxy();
|
||||
if (dest == null) {
|
||||
errorHandler.handle(req, httpl, out, "Could not find proxy");
|
||||
return;
|
||||
}
|
||||
// one manager for all proxy requests
|
||||
I2PSocketManager sm = smp.getManager("--proxy--");
|
||||
Filter f = new NullFilter(); //FIXME: use other filter
|
||||
if (!handle(req, f, out, dest, sm)) {
|
||||
errorHandler.handle(req, httpl, out, "Unable to reach peer");
|
||||
}
|
||||
}
|
||||
|
||||
private Destination findProxy() {
|
||||
//FIXME!
|
||||
return _context.namingService().lookup("squid.i2p");
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package net.i2p.httptunnel.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.httptunnel.HTTPListener;
|
||||
import net.i2p.httptunnel.Request;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Main handler for all requests. Dispatches requests to other handlers.
|
||||
*/
|
||||
public class RootHandler {
|
||||
|
||||
private static final Log _log = new Log(RootHandler.class); /* UNUSED */
|
||||
|
||||
private RootHandler() {
|
||||
errorHandler = new ErrorHandler();
|
||||
localHandler = new LocalHandler();
|
||||
proxyHandler = new ProxyHandler(errorHandler);
|
||||
eepHandler = new EepHandler(errorHandler);
|
||||
}
|
||||
|
||||
private ErrorHandler errorHandler;
|
||||
private ProxyHandler proxyHandler;
|
||||
private LocalHandler localHandler;
|
||||
private EepHandler eepHandler;
|
||||
|
||||
private static RootHandler instance;
|
||||
|
||||
/**
|
||||
* Singleton stuff
|
||||
* @return the one and only instance, yay!
|
||||
*/
|
||||
public static synchronized RootHandler getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new RootHandler();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* The _ROOT_ handler: it passes its workload off to the other handlers.
|
||||
* @param req a Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out) throws IOException {
|
||||
String url = req.getURL();
|
||||
System.out.println(url);
|
||||
/* boolean byProxy = false; */
|
||||
int pos;
|
||||
if (url.startsWith("http://")) { // access via proxy
|
||||
/* byProxy=true; */
|
||||
if (httpl.firstProxyUse()) {
|
||||
localHandler.handleProxyConfWarning(req, httpl, out);
|
||||
return;
|
||||
}
|
||||
url = url.substring(7);
|
||||
pos = url.indexOf("/");
|
||||
String host;
|
||||
|
||||
if (pos == -1) {
|
||||
errorHandler.handle(req, httpl, out, "No host end in URL");
|
||||
return;
|
||||
}
|
||||
|
||||
host = url.substring(0, pos);
|
||||
url = url.substring(pos);
|
||||
if ("i2p".equals(host) || "i2p.i2p".equals(host)) {
|
||||
// normal request; go on below...
|
||||
} else if (host.endsWith(".i2p")) {
|
||||
// "old" service request, send a redirect...
|
||||
out.write(("HTTP/1.1 302 Moved\r\nLocation: " + "http://i2p.i2p/" + host + url + "\r\n\r\n").getBytes("ISO-8859-1"));
|
||||
return;
|
||||
} else {
|
||||
// this is for proxying to the real web
|
||||
proxyHandler.handle(req, httpl, out /*, true */);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (url.equals("/")) { // main page
|
||||
url = "/_/local/index";
|
||||
} else if (!url.startsWith("/")) {
|
||||
errorHandler.handle(req, httpl, out, "No leading slash in URL: " + url);
|
||||
return;
|
||||
}
|
||||
String dest;
|
||||
url = url.substring(1);
|
||||
pos = url.indexOf("/");
|
||||
if (pos == -1) {
|
||||
dest = url;
|
||||
url = "/";
|
||||
} else {
|
||||
dest = url.substring(0, pos);
|
||||
url = url.substring(pos);
|
||||
}
|
||||
req.setURL(url);
|
||||
if (dest.equals("_")) { // no eepsite
|
||||
if (url.startsWith("/local/")) { // local request
|
||||
req.setURL(url.substring(6));
|
||||
localHandler.handle(req, httpl, out /*, byProxy */);
|
||||
} else if (url.startsWith("/http/")) { // http warning
|
||||
localHandler.handleHTTPWarning(req, httpl, out /*, byProxy */);
|
||||
} else if (url.startsWith("/proxy/")) { // http proxying
|
||||
req.setURL("http://" + url.substring(7));
|
||||
proxyHandler.handle(req, httpl, out /*, byProxy */);
|
||||
} else {
|
||||
errorHandler.handle(req, httpl, out, "No local handler for this URL: " + url);
|
||||
}
|
||||
} else {
|
||||
eepHandler.handle(req, httpl, out, /* byProxy, */dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,590 +0,0 @@
|
||||
The code for the GUI applications netviewer and the
|
||||
heartbeat GUI have been released into the public domain,
|
||||
but they make use of the LGPL JFreeChart library (which
|
||||
in turn depends upon the APL log4j library). These
|
||||
external components, contained within the files:
|
||||
lib/jfreechart-0.9.17.jar
|
||||
lib/jcommon-0.9.2.jar
|
||||
lib/log4j-1.2.8.jar
|
||||
were retrieved and built from the source at
|
||||
http://www.jfree.org/jfreechart/jfreechart-0.9.17.zip
|
||||
|
||||
As a whole, the netviewer and heartbeat GUI applications
|
||||
therefore must state:
|
||||
This product includes software developed by the
|
||||
Apache Software Foundation (http://www.apache.org/).
|
||||
|
||||
The LGPL just makes us state prominently that we use LGPL'ed
|
||||
code (the JFreeChart code), and since we make no modifications
|
||||
to it, section 6.b of the LGPL seems to apply.
|
||||
|
||||
The relevent licenses are shown below.
|
||||
|
||||
*****************************************************************
|
||||
For the jfreechart-0.9.17.jar and jcommon-0.9.2.jar, the
|
||||
LGPL is relevent:
|
||||
*****************************************************************
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
|
||||
|
||||
*****************************************************************
|
||||
For the file log4j-1.2.8.jar, the APL is relevent:
|
||||
*****************************************************************
|
||||
/* ====================================================================
|
||||
*
|
||||
* The Apache Software License, Version 1.1
|
||||
*
|
||||
* Copyright (c) 2003 The Apache Software Foundation. All rights
|
||||
* reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. The end-user documentation included with the redistribution, if
|
||||
* any, must include the following acknowlegement:
|
||||
* "This product includes software developed by the
|
||||
* Apache Software Foundation (http://www.apache.org/)."
|
||||
* Alternately, this acknowlegement may appear in the software itself,
|
||||
* if and wherever such third-party acknowlegements normally appear.
|
||||
*
|
||||
* 4. The names "The Apache Logging Services Project", "log4j", and "Apache Software
|
||||
* Foundation" must not be used to endorse or promote products derived
|
||||
* from this software without prior written permission. For written
|
||||
* permission, please contact apache@apache.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "Apache"
|
||||
* nor may "Apache" appear in their names without prior written
|
||||
* permission of the Apache Group.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
|
||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
* [Additional notices, if required by prior licensing conditions]
|
||||
*
|
||||
*/
|
||||
@@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="jfreechart">
|
||||
<target name="all">
|
||||
<echo message="The code in the JFreeChart software contains LGPL and APL licensed software," />
|
||||
<echo message="and is not necessary for using I2P. However, there is a seperate GUI for the " />
|
||||
<echo message="heartbeat and netmonitor applications that uses this, so the retrieval of that " />
|
||||
<echo message="code from the JFreeChart distribution is being made available (though we make no" />
|
||||
<echo message="modifications to the code used here whatsoever - it is simply used by the public domain GUIs. " />
|
||||
<echo message="If you would like to fetch the code, run the ant task 'fetchJfreechart'" />
|
||||
<echo message="If you would like to build the code, run the ant task 'build'" />
|
||||
<echo message="If you would like to delete the code, run the ant task 'clean'" />
|
||||
</target>
|
||||
<target name="build">
|
||||
<ant dir="./jfreechart-0.9.17/" antfile="ant/build.xml" target="compile" />
|
||||
</target>
|
||||
<target name="fetchJfreechart">
|
||||
<mkdir dir="./lib" />
|
||||
<get src="http://www.jfree.org/jfreechart/jfreechart-0.9.17.zip" verbose="true" dest="jfreechart-0.9.17.zip" />
|
||||
<unzip src="jfreechart-0.9.17.zip" dest="." />
|
||||
</target>
|
||||
<target name="builddep" />
|
||||
<target name="compile" />
|
||||
<target name="jar" />
|
||||
<target name="clean">
|
||||
<delete dir="./jfreechart-0.9.17/" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean" />
|
||||
<target name="distclean" depends="clean" />
|
||||
</project>
|
||||
@@ -1,39 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="myi2p">
|
||||
<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" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="./build/obj"
|
||||
classpath="../../../core/java/build/i2p.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="compile">
|
||||
<jar destfile="./build/myi2p.jar" basedir="./build/obj" includes="**/*.class" />
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="./src:../../../core/java/src" destdir="./build/javadoc"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
splitindex="true"
|
||||
windowtitle="MyI2P" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
||||
@@ -1,116 +0,0 @@
|
||||
package net.i2p.myi2p;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
/**
|
||||
* Packages up a message for delivery. The raw format of the message within a
|
||||
* repliable datagram is
|
||||
* <code>MyI2P $maj.$min $service $type\n$payload</code>
|
||||
* where <code>$maj.$min</code> is currently 1.0, $service is the type of MyI2P
|
||||
* service, $type is the type of message within that service, and $payload is
|
||||
* the data specific to that type.
|
||||
*
|
||||
*/
|
||||
public class MyI2PMessage {
|
||||
private Destination _peer;
|
||||
private String _service;
|
||||
private String _type;
|
||||
private byte[] _payload;
|
||||
|
||||
private static final byte[] MESSAGE_PREFIX = "MyI2P 1.0 ".getBytes();
|
||||
|
||||
/**
|
||||
* Build a new MyI2P message to be sent.
|
||||
*
|
||||
* @param to address to send the message
|
||||
* @param service what MyI2P service is involved
|
||||
* @param type type of message within that service is involved
|
||||
* @param data payload of the message to deliver
|
||||
*/
|
||||
public MyI2PMessage(Destination to, String service, String type, byte data[]) {
|
||||
_peer = to;
|
||||
_service = service;
|
||||
_type = type;
|
||||
_payload = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read in the MyI2P data from the given datagram info.
|
||||
*
|
||||
* @param from authenticated from address
|
||||
* @param dgramPayload raw MyI2P formatted message
|
||||
* @throws IllegalArgumentException if the message is not a valid MyI2P message
|
||||
*/
|
||||
public MyI2PMessage(Destination from, byte dgramPayload[]) throws IllegalArgumentException {
|
||||
_peer = from;
|
||||
int index = 0;
|
||||
while (index < dgramPayload.length) {
|
||||
if (index >= MESSAGE_PREFIX.length) break;
|
||||
if (dgramPayload[index] != MESSAGE_PREFIX[index])
|
||||
throw new IllegalArgumentException("Invalid payload (not a MyI2P message)");
|
||||
index++;
|
||||
}
|
||||
|
||||
// $service $type\n$payload
|
||||
StringBuffer service = new StringBuffer(8);
|
||||
while (index < dgramPayload.length) {
|
||||
if (dgramPayload[index] == ' ') {
|
||||
_service = service.toString();
|
||||
index++;
|
||||
break;
|
||||
} else if (dgramPayload[index] == '\n') {
|
||||
throw new IllegalArgumentException("Ran into newline while reading the service");
|
||||
} else {
|
||||
service.append((char)dgramPayload[index]);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
StringBuffer type = new StringBuffer(8);
|
||||
while (index < dgramPayload.length) {
|
||||
if (dgramPayload[index] == '\n') {
|
||||
_type = type.toString();
|
||||
index++;
|
||||
break;
|
||||
} else {
|
||||
service.append((char)dgramPayload[index]);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
_payload = new byte[dgramPayload.length-index];
|
||||
System.arraycopy(dgramPayload, index, _payload, 0, _payload.length);
|
||||
}
|
||||
|
||||
/** who is this message from or who is it going to? */
|
||||
public Destination getPeer() { return _peer; }
|
||||
/** what MyI2P service is this bound for (addressBook, blog, etc)? */
|
||||
public String getServiceType() { return _service; }
|
||||
/** within that service, what type of message is this? */
|
||||
public String getMessageType() { return _type; }
|
||||
/** what is the raw data for the particular message? */
|
||||
public byte[] getPayload() { return _payload; }
|
||||
|
||||
/**
|
||||
* Retrieve the raw payload, suitable for wrapping in an I2PDatagramMaker
|
||||
* and sending to another MyI2P node.
|
||||
*
|
||||
* @throws IllegalStateException if some data is missing
|
||||
*/
|
||||
public byte[] toRawPayload() throws IllegalStateException {
|
||||
if (_service == null) throw new IllegalStateException("Service is null");
|
||||
if (_type == null) throw new IllegalStateException("Type is null");
|
||||
if (_payload == null) throw new IllegalStateException("Payload is null");
|
||||
|
||||
byte service[] = _service.getBytes();
|
||||
byte type[] = _type.getBytes();
|
||||
byte rv[] = new byte[MESSAGE_PREFIX.length + service.length + 1 + type.length + 1 + _payload.length];
|
||||
System.arraycopy(MESSAGE_PREFIX, 0, rv, 0, MESSAGE_PREFIX.length);
|
||||
System.arraycopy(service, 0, rv, MESSAGE_PREFIX.length, service.length);
|
||||
rv[MESSAGE_PREFIX.length + service.length] = ' ';
|
||||
System.arraycopy(type, 0, rv, MESSAGE_PREFIX.length + service.length + 1, type.length);
|
||||
rv[MESSAGE_PREFIX.length + service.length + 1 + type.length] = '\n';
|
||||
System.arraycopy(_payload, 0, rv, MESSAGE_PREFIX.length + service.length + 1 + type.length + 1, _payload.length);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
package net.i2p.myi2p;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Main controller for a MyI2P node, coordinating all network communication and
|
||||
* distributing messages to the appropriate services.
|
||||
*
|
||||
*/
|
||||
public class Node {
|
||||
private static List _nodes = new ArrayList(1);
|
||||
/**
|
||||
* Return a list of Node instances that are currently
|
||||
* operating in the JVM
|
||||
*/
|
||||
private static List nodes() {
|
||||
synchronized (_nodes) {
|
||||
return new ArrayList(_nodes);
|
||||
}
|
||||
}
|
||||
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private NodeAdapter _adapter;
|
||||
/**
|
||||
* contains configuration properties, such where our router is, what
|
||||
* services to run, etc
|
||||
*
|
||||
*/
|
||||
private Properties _config;
|
||||
/** filename where _config is stored */
|
||||
private String _configFile = DEFAULT_CONFIG_FILE;
|
||||
/** mapping of service name (String) to Service for all services loaded */
|
||||
private Map _services;
|
||||
|
||||
private static final String DEFAULT_CONFIG_FILE = "myi2p.config";
|
||||
private static final String DEFAULT_KEY_FILE = "myi2p.keys";
|
||||
private static final String PROP_KEY_FILE = "keyFile";
|
||||
|
||||
public Node(I2PAppContext context) {
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(Node.class);
|
||||
_config = new Properties();
|
||||
_services = new HashMap(1);
|
||||
if (_log.shouldLog(Log.CRIT))
|
||||
_log.log(Log.CRIT, "Node created");
|
||||
_adapter = new NodeAdapter(_context, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main driver for the node. Usage: <code>Node [configFile]</code>
|
||||
*
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
String filename = DEFAULT_CONFIG_FILE;
|
||||
if ( (args != null) && (args.length == 1) )
|
||||
filename = args[0];
|
||||
Node node = new Node(I2PAppContext.getGlobalContext());
|
||||
node.setConfigFile(filename);
|
||||
node.loadConfig();
|
||||
node.startup();
|
||||
while (true) {
|
||||
//try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
//node.shutdown();
|
||||
//if (true) return;
|
||||
synchronized (node) {
|
||||
try { node.wait(); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Properties getConfig() {
|
||||
synchronized (_config) {
|
||||
return new Properties(_config);
|
||||
}
|
||||
}
|
||||
public void setConfig(Properties props) {
|
||||
synchronized (_config) {
|
||||
_config.clear();
|
||||
_config.putAll(props);
|
||||
}
|
||||
}
|
||||
|
||||
public String getConfigFile() { return _configFile; }
|
||||
public void setConfigFile(String filename) { _configFile = filename; }
|
||||
|
||||
/**
|
||||
* Load up the config and all of the services
|
||||
*
|
||||
*/
|
||||
public void loadConfig() {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
File cfgFile = new File(_configFile);
|
||||
if (cfgFile.exists()) {
|
||||
fis = new FileInputStream(cfgFile);
|
||||
Properties props = new Properties();
|
||||
props.load(fis);
|
||||
setConfig(props);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Config loaded from " + _configFile);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Config file " + _configFile + " does not exist, so we aren't going to do much");
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error reading config file " + _configFile, ioe);
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot up the node, connect to the router, and start all the services
|
||||
*
|
||||
*/
|
||||
public void startup() {
|
||||
boolean connected = connect();
|
||||
if (connected) {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
|
||||
public void run() { shutdown(); }
|
||||
}));
|
||||
loadServices();
|
||||
startServices();
|
||||
synchronized (_nodes) {
|
||||
_nodes.add(this);
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Node started");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Unable to connect, startup didn't do much");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop any connections to the network and stop all services
|
||||
*
|
||||
*/
|
||||
public void shutdown() {
|
||||
disconnect();
|
||||
stopServices();
|
||||
synchronized (_nodes) {
|
||||
_nodes.remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message from the node to the peer as specified in the message
|
||||
*
|
||||
* @return true if it was sent
|
||||
*/
|
||||
public boolean sendMessage(MyI2PMessage msg) {
|
||||
return _adapter.sendMessage(msg);
|
||||
}
|
||||
|
||||
private void loadServices() {
|
||||
Properties config = getConfig();
|
||||
int i = 0;
|
||||
while (true) {
|
||||
String classname = config.getProperty("service."+i+".classname");
|
||||
if ( (classname == null) || (classname.trim().length() <= 0) )
|
||||
break;
|
||||
boolean ok = loadService("service." + i + ".", config);
|
||||
if (ok) i++;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(i + " services loaded");
|
||||
}
|
||||
|
||||
private boolean loadService(String prefix, Properties config) {
|
||||
String classname = config.getProperty(prefix + "classname");
|
||||
String type = config.getProperty(prefix + "type");
|
||||
if (type == null) return false;
|
||||
|
||||
Properties opts = new Properties();
|
||||
int i = 0;
|
||||
while (true) {
|
||||
String name = config.getProperty(prefix + "option." + i + ".name");
|
||||
String value = config.getProperty(prefix + "option." + i + ".value");
|
||||
if ( (name == null) || (name.trim().length() <= 0) || (value == null) || (value.trim().length() <= 0) )
|
||||
break;
|
||||
opts.setProperty(name.trim(), value.trim());
|
||||
i++;
|
||||
}
|
||||
|
||||
try {
|
||||
Class cls = Class.forName(classname);
|
||||
Object obj = cls.newInstance();
|
||||
if (obj instanceof Service) {
|
||||
Service service = (Service)obj;
|
||||
service.setType(type);
|
||||
service.setOptions(opts);
|
||||
service.setNode(this);
|
||||
service.setContext(_context);
|
||||
synchronized (_services) {
|
||||
_services.put(type, service);
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Service " + type + " loaded");
|
||||
return true;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error loading service " + type + ": not a service [" + classname + "]");
|
||||
}
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error loading service " + type + ": class " + classname + " is invalid", cnfe);
|
||||
} catch (InstantiationException ie) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error instantiating service " + type + ": class " + classname + " could not be created", ie);
|
||||
} catch (IllegalAccessException iae) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error creating service " + type + ": class " + classname + " could not be accessed", iae);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean connect() {
|
||||
Properties config = getConfig();
|
||||
File keyFile = new File(config.getProperty(PROP_KEY_FILE, DEFAULT_KEY_FILE));
|
||||
return _adapter.connect(config, keyFile);
|
||||
}
|
||||
|
||||
private void disconnect() {
|
||||
_adapter.disconnect();
|
||||
}
|
||||
|
||||
private void startServices() {
|
||||
for (Iterator iter = _services.values().iterator(); iter.hasNext(); ) {
|
||||
Service service = (Service)iter.next();
|
||||
service.startup();
|
||||
}
|
||||
}
|
||||
private void stopServices() {
|
||||
for (Iterator iter = _services.values().iterator(); iter.hasNext(); ) {
|
||||
Service service = (Service)iter.next();
|
||||
service.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void handleMessage(MyI2PMessage msg) {
|
||||
Service service = (Service)_services.get(msg.getServiceType());
|
||||
if (service == null) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Message received for an unknown service ["
|
||||
+ msg.getServiceType() + "] from "
|
||||
+ msg.getPeer().calculateHash().toBase64());
|
||||
} else {
|
||||
service.receiveMessage(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
package net.i2p.myi2p;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.datagram.I2PDatagramDissector;
|
||||
import net.i2p.client.datagram.I2PDatagramMaker;
|
||||
import net.i2p.client.datagram.I2PInvalidDatagramException;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Bind the MyI2P node to the I2P network, handling messages, sessions,
|
||||
* etc.
|
||||
*
|
||||
*/
|
||||
public class NodeAdapter implements I2PSessionListener {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private Node _node;
|
||||
private I2PSession _session;
|
||||
|
||||
public NodeAdapter(I2PAppContext context, Node node) {
|
||||
_node = node;
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(NodeAdapter.class);
|
||||
}
|
||||
|
||||
boolean sendMessage(MyI2PMessage msg) {
|
||||
if (_session == null) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Cannot send the message, as we are not connected");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
I2PDatagramMaker builder = new I2PDatagramMaker(_session);
|
||||
byte dgram[] = builder.makeI2PDatagram(msg.toRawPayload());
|
||||
return _session.sendMessage(msg.getPeer(), dgram);
|
||||
} catch (IllegalStateException ise) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("MyI2PMessage was not valid", ise);
|
||||
return false;
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error sending to the peer", ise);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the network using the current I2CP config and the private
|
||||
* key file specified in the node config. If the file does not exist, a new
|
||||
* destination will be created.
|
||||
*
|
||||
* @param config MyI2P node and I2CP configuration
|
||||
* @param keyFile file to load the private keystream from (if it doesn't
|
||||
* exist, a new one will be created and stored at that location)
|
||||
*
|
||||
* @return true if connection was successful, false otherwise
|
||||
*/
|
||||
boolean connect(Properties config, File keyFile) {
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
if (!keyFile.exists()) {
|
||||
File parent = keyFile.getParentFile();
|
||||
if (parent != null) parent.mkdirs();
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(keyFile);
|
||||
Destination dest = client.createDestination(fos);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("New destination created ["
|
||||
+ dest.calculateHash().toBase64()
|
||||
+ "] with keys at " + keyFile);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error writing new keystream to " + keyFile, ioe);
|
||||
return false;
|
||||
} catch (I2PException ie) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Internal error creating new destination", ie);
|
||||
return false;
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(keyFile);
|
||||
_session = client.createSession(fis, config);
|
||||
if (_session == null) {
|
||||
_log.error("wtf, why did it create a null session?");
|
||||
return false;
|
||||
}
|
||||
_session.setSessionListener(this);
|
||||
_session.connect();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("I2P session created");
|
||||
return true;
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Unable to read the keystream from " + keyFile, ioe);
|
||||
return false;
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Unable to connect to the router", ise);
|
||||
return false;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
void disconnect() {
|
||||
if (_session != null) {
|
||||
try {
|
||||
_session.destroySession();
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error destroying the session in shutdown", ise);
|
||||
}
|
||||
_session = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void disconnected(I2PSession session) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Session disconnected");
|
||||
}
|
||||
|
||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Session error occurred - " + message, error);
|
||||
}
|
||||
|
||||
public void messageAvailable(I2PSession session, int msgId, long size) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("message available [" + msgId + "/"+ size + " bytes]");
|
||||
|
||||
try {
|
||||
byte data[] = session.receiveMessage(msgId);
|
||||
I2PDatagramDissector dissector = new I2PDatagramDissector();
|
||||
dissector.loadI2PDatagram(data);
|
||||
try {
|
||||
MyI2PMessage msg = new MyI2PMessage(dissector.getSender(), dissector.getPayload());
|
||||
_node.handleMessage(msg);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Message is a valid datagram but invalid MyI2P message", iae);
|
||||
}
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error retrieving message payload for message " + msgId, ise);
|
||||
} catch (I2PInvalidDatagramException iide) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Message received was not a valid repliable datagram", iide);
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Message received was a corrupt repliable datagram", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
public void reportAbuse(I2PSession session, int severity) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("abuse occurred");
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package net.i2p.myi2p;
|
||||
|
||||
import java.util.Properties;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Defines a service that can operate within a MyI2P node, responding to
|
||||
* messages and performing whatever tasks are necessary.
|
||||
*
|
||||
*/
|
||||
public interface Service {
|
||||
/** what type of message will this service respond to? */
|
||||
public String getType();
|
||||
public void setType(String type);
|
||||
|
||||
/** what node is this service hooked into */
|
||||
public Node getNode();
|
||||
public void setNode(Node node);
|
||||
|
||||
/** what options specific to this node does the service have? */
|
||||
public Properties getOptions();
|
||||
public void setOptions(Properties opts);
|
||||
|
||||
/** give the service a scope */
|
||||
public I2PAppContext getContext();
|
||||
public void setContext(I2PAppContext context);
|
||||
|
||||
/** called when a message is received for the service */
|
||||
public void receiveMessage(MyI2PMessage msg);
|
||||
|
||||
/** start the service up - the node is ready */
|
||||
public void startup();
|
||||
/** shut the service down - the node is going offline */
|
||||
public void shutdown();
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package net.i2p.myi2p;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.myi2p.Service;
|
||||
import net.i2p.myi2p.Node;
|
||||
import net.i2p.myi2p.MyI2PMessage;
|
||||
|
||||
/**
|
||||
* Base service implementation
|
||||
*
|
||||
*/
|
||||
public abstract class ServiceImpl implements Service {
|
||||
private I2PAppContext _context;
|
||||
private Node _node;
|
||||
private Properties _options;
|
||||
private String _serviceType;
|
||||
|
||||
public ServiceImpl() {
|
||||
_context = null;
|
||||
_node = null;
|
||||
_options = null;
|
||||
_serviceType = null;
|
||||
}
|
||||
|
||||
// base inspectors / mutators
|
||||
public Node getNode() { return _node; }
|
||||
public void setNode(Node node) { _node = node; }
|
||||
public I2PAppContext getContext() { return _context; }
|
||||
public void setContext(I2PAppContext context) { _context = context; }
|
||||
public Properties getOptions() { return _options; }
|
||||
public void setOptions(Properties opts) { _options = opts; }
|
||||
public String getType() { return _serviceType; }
|
||||
public void setType(String type) { _serviceType = type; }
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package net.i2p.myi2p.address;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
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.DataFormatException;
|
||||
|
||||
/**
|
||||
* Main lookup component for maintaining references to other I2P destinations.
|
||||
*
|
||||
*/
|
||||
public class AddressBook {
|
||||
private I2PAppContext _context;
|
||||
/** Name (String) to AddressBookEntry */
|
||||
private Map _entries;
|
||||
/**
|
||||
* List of NameReference that has been received but whose preferred
|
||||
* name conflicts with an existing entry.
|
||||
*/
|
||||
private List _conflictingReferences;
|
||||
|
||||
public AddressBook(I2PAppContext context) {
|
||||
_context = context;
|
||||
_entries = new HashMap(16);
|
||||
_conflictingReferences = new ArrayList(0);
|
||||
}
|
||||
|
||||
/** retrieve a list of entry names (strings) */
|
||||
public Set getEntryNames() {
|
||||
synchronized (_entries) {
|
||||
return new HashSet(_entries.keySet());
|
||||
}
|
||||
}
|
||||
public AddressBookEntry getEntry(String name) {
|
||||
synchronized (_entries) {
|
||||
return (AddressBookEntry)_entries.get(name);
|
||||
}
|
||||
}
|
||||
public AddressBookEntry addEntry(AddressBookEntry entry) {
|
||||
synchronized (_entries) {
|
||||
return (AddressBookEntry)_entries.put(entry.getLocalName(), entry);
|
||||
}
|
||||
}
|
||||
public void removeEntry(String name) {
|
||||
synchronized (_entries) {
|
||||
_entries.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
public int getConflictingReferenceCount() {
|
||||
synchronized (_conflictingReferences) {
|
||||
return _conflictingReferences.size();
|
||||
}
|
||||
}
|
||||
public NameReference getConflictingReference(int index) {
|
||||
synchronized (_conflictingReferences) {
|
||||
return (NameReference)_conflictingReferences.get(index);
|
||||
}
|
||||
}
|
||||
public void addConflictingReference(NameReference ref) {
|
||||
synchronized (_conflictingReferences) {
|
||||
_conflictingReferences.add(ref);
|
||||
}
|
||||
}
|
||||
public void removeConflictingReference(int index) {
|
||||
synchronized (_conflictingReferences) {
|
||||
_conflictingReferences.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
public void read(InputStream in) throws IOException {
|
||||
try {
|
||||
int numEntries = (int)DataHelper.readLong(in, 2);
|
||||
if (numEntries < 0) throw new IOException("Corrupt AddressBook - " + numEntries + " entries?");
|
||||
for (int i = 0; i < numEntries; i++) {
|
||||
AddressBookEntry entry = new AddressBookEntry(_context);
|
||||
entry.read(in);
|
||||
addEntry(entry);
|
||||
}
|
||||
int numConflicting = (int)DataHelper.readLong(in, 2);
|
||||
if (numConflicting < 0) throw new IOException("Corrupt AddressBook - " + numConflicting + " conflicting?");
|
||||
for (int i = 0; i < numConflicting; i++) {
|
||||
NameReference ref = new NameReference(_context);
|
||||
ref.read(in);
|
||||
addConflictingReference(ref);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new IOException("Corrupt address book - " + dfe.getMessage());
|
||||
}
|
||||
}
|
||||
public void write(OutputStream out) throws IOException {
|
||||
try {
|
||||
synchronized (_entries) {
|
||||
DataHelper.writeLong(out, 2, _entries.size());
|
||||
for (Iterator iter = _entries.values().iterator(); iter.hasNext(); ) {
|
||||
AddressBookEntry entry = (AddressBookEntry)iter.next();
|
||||
entry.write(out);
|
||||
}
|
||||
}
|
||||
synchronized (_conflictingReferences) {
|
||||
DataHelper.writeLong(out, 2, _conflictingReferences.size());
|
||||
for (int i = 0; i < _conflictingReferences.size(); i++) {
|
||||
NameReference ref = (NameReference)_conflictingReferences.get(i);
|
||||
ref.write(out);
|
||||
}
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new IOException("Corrupt address book - " + dfe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Entries: " + _entries.size() + " conflicting: " + _conflictingReferences.size();
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
package net.i2p.myi2p.address;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataFormatException;
|
||||
|
||||
/**
|
||||
* Implements a local address book entry, pointing at a known secure
|
||||
* NameReference as well as an optional Subscription.
|
||||
*
|
||||
*/
|
||||
public class AddressBookEntry {
|
||||
private I2PAppContext _context;
|
||||
private String _localName;
|
||||
private Properties _options;
|
||||
private NameReference _reference;
|
||||
private Subscription _subscription;
|
||||
private long _addedOn;
|
||||
|
||||
public AddressBookEntry(I2PAppContext context) {
|
||||
_context = context;
|
||||
_localName = null;
|
||||
_options = new Properties();
|
||||
_reference = null;
|
||||
_subscription = null;
|
||||
_addedOn = context.clock().now();
|
||||
}
|
||||
|
||||
/** Local (unique) name we use to reference the given destination */
|
||||
public String getLocalName() { return _localName; }
|
||||
public void setLocalName(String name) { _localName = name; }
|
||||
|
||||
public Properties getOptions() {
|
||||
synchronized (_options) {
|
||||
return new Properties(_options);
|
||||
}
|
||||
}
|
||||
public void setOptions(Properties props) {
|
||||
synchronized (_options) {
|
||||
_options.clear();
|
||||
if (props != null)
|
||||
_options.putAll(props);
|
||||
}
|
||||
}
|
||||
|
||||
/** Secure name reference, provided by the destination */
|
||||
public NameReference getNameReference() { return _reference; }
|
||||
public void setNameReference(NameReference ref) { _reference = ref; }
|
||||
|
||||
/**
|
||||
* If specified, the details of our subscription to the MyI2P address
|
||||
* book at the referenced destination.
|
||||
*
|
||||
*/
|
||||
public Subscription getSubscription() { return _subscription; }
|
||||
public void setSubscription(Subscription sub) { _subscription = sub; }
|
||||
|
||||
/** When this entry was added */
|
||||
public long getAddedOn() { return _addedOn; }
|
||||
public void setAddedOn(long when) { _addedOn = when; }
|
||||
|
||||
/** load the data from the stream */
|
||||
public void read(InputStream in) throws IOException {
|
||||
try {
|
||||
Boolean localNameDefined = DataHelper.readBoolean(in);
|
||||
if ( (localNameDefined != null) && (localNameDefined.booleanValue()) )
|
||||
_localName = DataHelper.readString(in);
|
||||
else
|
||||
_localName = null;
|
||||
|
||||
Date when = DataHelper.readDate(in);
|
||||
if (when == null)
|
||||
_addedOn = -1;
|
||||
else
|
||||
_addedOn = when.getTime();
|
||||
|
||||
Properties props = DataHelper.readProperties(in);
|
||||
setOptions(props);
|
||||
|
||||
Boolean refDefined = DataHelper.readBoolean(in);
|
||||
if ( (refDefined != null) && (refDefined.booleanValue()) ) {
|
||||
_reference = new NameReference(_context);
|
||||
_reference.read(in);
|
||||
} else {
|
||||
_reference = null;
|
||||
}
|
||||
|
||||
Boolean subDefined = DataHelper.readBoolean(in);
|
||||
if ( (subDefined != null) && (subDefined.booleanValue()) ) {
|
||||
Subscription sub = new Subscription(_context);
|
||||
sub.read(in);
|
||||
_subscription = sub;
|
||||
} else {
|
||||
_subscription = null;
|
||||
}
|
||||
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new IOException("Corrupt subscription: " + dfe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** persist the data to the stream */
|
||||
public void write(OutputStream out) throws IOException {
|
||||
try {
|
||||
if ( (_localName != null) && (_localName.trim().length() > 0) ) {
|
||||
DataHelper.writeBoolean(out, Boolean.TRUE);
|
||||
DataHelper.writeString(out, _localName);
|
||||
} else {
|
||||
DataHelper.writeBoolean(out, Boolean.FALSE);
|
||||
}
|
||||
|
||||
DataHelper.writeDate(out, new Date(_addedOn));
|
||||
|
||||
synchronized (_options) {
|
||||
DataHelper.writeProperties(out, _options);
|
||||
}
|
||||
|
||||
if (_reference != null) {
|
||||
DataHelper.writeBoolean(out, Boolean.TRUE);
|
||||
_reference.write(out);
|
||||
} else {
|
||||
DataHelper.writeBoolean(out, Boolean.FALSE);
|
||||
}
|
||||
|
||||
if (_subscription != null) {
|
||||
DataHelper.writeBoolean(out, Boolean.TRUE);
|
||||
_subscription.write(out);
|
||||
} else {
|
||||
DataHelper.writeBoolean(out, Boolean.FALSE);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new IOException("Corrupt subscription: " + dfe.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package net.i2p.myi2p.address;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import net.i2p.myi2p.Service;
|
||||
import net.i2p.myi2p.ServiceImpl;
|
||||
import net.i2p.myi2p.Node;
|
||||
import net.i2p.myi2p.MyI2PMessage;
|
||||
|
||||
/**
|
||||
* Main service handler / coordinator for the MyI2P address book.
|
||||
*
|
||||
*/
|
||||
public class AddressBookService extends ServiceImpl {
|
||||
private Log _log;
|
||||
private AddressBook _addressBook;
|
||||
/** contains a mapping of event time (Long) to description (String) */
|
||||
private Map _activityLog;
|
||||
private String _addressBookFile;
|
||||
|
||||
private static String PROP_ADDRESSBOOK_FILE = "datafile";
|
||||
private static String DEFAULT_ADDRESSBOOK_FILE = "addressbook.dat";
|
||||
|
||||
public static final String SERVICE_TYPE = "AddressBook";
|
||||
public String getType() { return SERVICE_TYPE; }
|
||||
|
||||
public void startup() {
|
||||
_log = getContext().logManager().getLog(AddressBookService.class);
|
||||
|
||||
_addressBookFile = getOptions().getProperty(PROP_ADDRESSBOOK_FILE, DEFAULT_ADDRESSBOOK_FILE);
|
||||
File file = new File(_addressBookFile);
|
||||
|
||||
if (file.exists()) {
|
||||
loadData(file);
|
||||
} else {
|
||||
_addressBook = new AddressBook(getContext());
|
||||
_activityLog = new HashMap(16);
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
File file = new File(_addressBookFile);
|
||||
storeData(file);
|
||||
}
|
||||
|
||||
public void receiveMessage(MyI2PMessage msg) {
|
||||
_log.info("Received a " + msg.getMessageType() + " from "
|
||||
+ msg.getPeer().calculateHash().toBase64()
|
||||
+ new String(msg.getPayload()));
|
||||
}
|
||||
|
||||
/** load everything from disk */
|
||||
private void loadData(File dataFile) {
|
||||
AddressBookServiceData data = new AddressBookServiceData(getContext());
|
||||
data.load(dataFile);
|
||||
if (data.getErrorMessage() != null) {
|
||||
_log.warn(data.getErrorMessage(), data.getError());
|
||||
_addressBook = new AddressBook(getContext());
|
||||
_activityLog = new HashMap(16);
|
||||
} else {
|
||||
_addressBook = data.getAddressBook();
|
||||
_activityLog = data.getActivityLog();
|
||||
}
|
||||
}
|
||||
|
||||
/** persist everything to disk */
|
||||
private void storeData(File dataFile) {
|
||||
AddressBookServiceData data = new AddressBookServiceData(getContext());
|
||||
data.setActivityLog(_activityLog);
|
||||
data.setAddressBook(_addressBook);
|
||||
data.store(dataFile);
|
||||
if (data.getErrorMessage() != null) {
|
||||
_log.warn(data.getErrorMessage(), data.getError());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package net.i2p.myi2p.address;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Component for loading and storing the service data to disk
|
||||
*
|
||||
*/
|
||||
public class AddressBookServiceData {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private AddressBook _addressBook;
|
||||
private Map _activityLog;
|
||||
private Exception _error;
|
||||
private String _errorMessage;
|
||||
|
||||
public AddressBookServiceData(I2PAppContext context) {
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(AddressBookServiceData.class);
|
||||
_addressBook = null;
|
||||
_activityLog = null;
|
||||
_error = null;
|
||||
_errorMessage = null;
|
||||
}
|
||||
|
||||
public AddressBook getAddressBook() { return _addressBook; }
|
||||
public void setAddressBook(AddressBook book) { _addressBook = book; }
|
||||
public Map getActivityLog() { return _activityLog; }
|
||||
public void setActivityLog(Map log) { _activityLog = log; }
|
||||
|
||||
public Exception getError() { return _error; }
|
||||
public String getErrorMessage() { return _errorMessage; }
|
||||
|
||||
public void load(File from) {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(from);
|
||||
AddressBook addressBook = new AddressBook(_context);
|
||||
addressBook.read(fis);
|
||||
_addressBook = addressBook;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Address book: " + addressBook);
|
||||
Properties props = DataHelper.readProperties(fis);
|
||||
Map log = new HashMap(props.size());
|
||||
for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
String event = props.getProperty(key);
|
||||
long when = 0;
|
||||
try {
|
||||
when = Long.parseLong(key);
|
||||
while (log.containsKey(new Long(when)))
|
||||
when++;
|
||||
log.put(new Long(when), event);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Activity log: on " + new Date(when) + ": " + event);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Corrupt activity log entry: when=" + key, nfe);
|
||||
}
|
||||
}
|
||||
_activityLog = log;
|
||||
} catch (Exception e) {
|
||||
_error = e;
|
||||
_errorMessage = "Error reading the address book from " + from;
|
||||
}
|
||||
}
|
||||
|
||||
public void store(File to) {
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(to);
|
||||
_addressBook.write(fos);
|
||||
Properties props = new Properties();
|
||||
for (Iterator iter = _activityLog.keySet().iterator(); iter.hasNext(); ) {
|
||||
Long when = (Long)iter.next();
|
||||
String msg = (String)_activityLog.get(when);
|
||||
props.setProperty(when.toString(), msg);
|
||||
}
|
||||
DataHelper.writeProperties(fos, props);
|
||||
} catch (Exception e) {
|
||||
_error = e;
|
||||
_errorMessage = "Error writing the address book to " + to;
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
package net.i2p.myi2p.address;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* CreateEntryCLI addressBookFile referenceFile localName subscriptionFrequencyHours [ key=value]*
|
||||
*/
|
||||
public class CreateEntryCLI {
|
||||
private I2PAppContext _context;
|
||||
private String _args[];
|
||||
private String _addressBook;
|
||||
private String _referenceFile;
|
||||
private String _localName;
|
||||
private int _subscriptionFrequencyHours;
|
||||
private Properties _options;
|
||||
|
||||
public CreateEntryCLI(String args[]) {
|
||||
_context = new I2PAppContext();
|
||||
_args = args;
|
||||
_options = new Properties();
|
||||
}
|
||||
|
||||
public void execute() {
|
||||
if (parseArgs())
|
||||
doExecute();
|
||||
else
|
||||
System.err.println("Usage: CreateEntryCLI addressBookFile referenceFile localName subscriptionFrequencyHours[ key=value]*");
|
||||
}
|
||||
|
||||
private boolean parseArgs() {
|
||||
if ( (_args == null) || (_args.length < 3) )
|
||||
return false;
|
||||
_addressBook = _args[0];
|
||||
_referenceFile = _args[1];
|
||||
_localName = _args[2];
|
||||
try {
|
||||
_subscriptionFrequencyHours = Integer.parseInt(_args[3]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 4; i < _args.length; i++) {
|
||||
int eq = _args[i].indexOf('=');
|
||||
if ( (eq <= 0) || (eq >= _args[i].length() - 1) )
|
||||
continue;
|
||||
String key = _args[i].substring(0,eq);
|
||||
String val = _args[i].substring(eq+1);
|
||||
_options.setProperty(key, val);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void doExecute() {
|
||||
AddressBookServiceData data = new AddressBookServiceData(_context);
|
||||
File f = new File(_addressBook);
|
||||
if (f.exists()) {
|
||||
data.load(f);
|
||||
if (data.getError() != null) {
|
||||
if (data.getErrorMessage() != null)
|
||||
System.err.println(data.getErrorMessage());
|
||||
data.getError().printStackTrace();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
data.setAddressBook(new AddressBook(_context));
|
||||
data.setActivityLog(new HashMap());
|
||||
}
|
||||
NameReference ref = null;
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(_referenceFile);
|
||||
ref = new NameReference(_context);
|
||||
ref.read(fis);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Name reference under " + _referenceFile + " is corrupt");
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
AddressBook book = data.getAddressBook();
|
||||
Map activityLog = data.getActivityLog();
|
||||
|
||||
AddressBookEntry oldEntry = book.getEntry(_localName);
|
||||
|
||||
AddressBookEntry entry = new AddressBookEntry(_context);
|
||||
entry.setLocalName(_localName);
|
||||
entry.setNameReference(ref);
|
||||
|
||||
Subscription sub = new Subscription(_context);
|
||||
sub.setQueryFrequencyMinutes(60*_subscriptionFrequencyHours);
|
||||
|
||||
entry.setSubscription(sub);
|
||||
entry.setOptions(_options);
|
||||
|
||||
if (oldEntry == null) {
|
||||
book.addEntry(entry);
|
||||
System.out.println("New address book entry added for " + entry.getLocalName());
|
||||
activityLog.put(new Long(_context.clock().now()), "New address book entry added for " + entry.getLocalName());
|
||||
} else {
|
||||
Destination oldDest = oldEntry.getNameReference().getDestination();
|
||||
if (oldDest.equals(ref.getDestination())) {
|
||||
if (ref.getSequenceNum() < oldEntry.getNameReference().getSequenceNum()) {
|
||||
System.err.println("Not updating the address book - newer reference for " + entry.getLocalName() + " exists");
|
||||
return;
|
||||
} else {
|
||||
// same or newer rev
|
||||
if (null != entry.getSubscription()) {
|
||||
if (null != oldEntry.getSubscription()) {
|
||||
entry.getSubscription().setLastQueryAttempt(oldEntry.getSubscription().getLastQueryAttempt());
|
||||
entry.getSubscription().setLastQuerySuccess(oldEntry.getSubscription().getLastQuerySuccess());
|
||||
}
|
||||
}
|
||||
book.addEntry(entry);
|
||||
System.err.println("Updating the options and subscription for an existing reference to " + entry.getLocalName());
|
||||
activityLog.put(new Long(_context.clock().now()), "Updating options and subscription for " + entry.getLocalName());
|
||||
}
|
||||
} else {
|
||||
book.addConflictingReference(ref);
|
||||
System.out.println("Old entry exists for " + _localName + " - adding a conflicting reference");
|
||||
System.out.println("Existing entry points to " + oldEntry.getNameReference().getDestination().calculateHash().toBase64());
|
||||
System.out.println("New entry points to " + entry.getNameReference().getDestination().calculateHash().toBase64());
|
||||
|
||||
activityLog.put(new Long(_context.clock().now()), "Adding conflicting reference for " + entry.getLocalName());
|
||||
}
|
||||
}
|
||||
|
||||
data.setAddressBook(book);
|
||||
data.setActivityLog(activityLog);
|
||||
|
||||
data.store(f);
|
||||
if (data.getError() != null) {
|
||||
if (data.getErrorMessage() != null)
|
||||
System.err.println(data.getErrorMessage());
|
||||
data.getError().printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
CreateEntryCLI cli = new CreateEntryCLI(args);
|
||||
cli.execute();
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
package net.i2p.myi2p.address;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* CreateNameReferenceCLI outputFile privateDestFile preferredName sequenceNum serviceType[ key=value]*
|
||||
*/
|
||||
public class CreateNameReferenceCLI {
|
||||
private I2PAppContext _context;
|
||||
private String _args[];
|
||||
private String _outputFile;
|
||||
private String _destFile;
|
||||
private String _preferredName;
|
||||
private long _sequenceNum;
|
||||
private String _serviceType;
|
||||
private Properties _options;
|
||||
|
||||
public CreateNameReferenceCLI(String[] args) {
|
||||
_context = new I2PAppContext();
|
||||
_args = args;
|
||||
_options = new Properties();
|
||||
}
|
||||
|
||||
public void execute() {
|
||||
if (parseArgs())
|
||||
doExecute();
|
||||
else
|
||||
System.err.println("Usage: CreateNameReferenceCLI outputFile privateDestFile preferredName sequenceNum serviceType[ key=value]*");
|
||||
}
|
||||
|
||||
private boolean parseArgs() {
|
||||
if ( (_args == null) || (_args.length < 4) )
|
||||
return false;
|
||||
_outputFile = _args[0];
|
||||
_destFile = _args[1];
|
||||
_preferredName = _args[2];
|
||||
try {
|
||||
_sequenceNum = Long.parseLong(_args[3]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return false;
|
||||
}
|
||||
_serviceType = _args[4];
|
||||
|
||||
for (int i = 5; i < _args.length; i++) {
|
||||
int eq = _args[i].indexOf('=');
|
||||
if ( (eq <= 0) || (eq >= _args[i].length() - 1) )
|
||||
continue;
|
||||
String key = _args[i].substring(0,eq);
|
||||
String val = _args[i].substring(eq+1);
|
||||
_options.setProperty(key, val);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void doExecute() {
|
||||
Destination dest = null;
|
||||
SigningPrivateKey priv = null;
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(_destFile);
|
||||
dest = new Destination();
|
||||
dest.readBytes(fis);
|
||||
PrivateKey whocares = new PrivateKey();
|
||||
whocares.readBytes(fis);
|
||||
priv = new SigningPrivateKey();
|
||||
priv.readBytes(fis);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Destination private keys under " + _destFile + " are corrupt");
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
NameReference ref = new NameReference(_context);
|
||||
ref.setDestination(dest);
|
||||
ref.setPreferredName(_preferredName);
|
||||
ref.setSequenceNum(_sequenceNum);
|
||||
ref.setServiceType(_serviceType);
|
||||
if (_options != null) {
|
||||
for (Iterator iter = _options.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
String val = _options.getProperty(key);
|
||||
ref.setOption(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
ref.sign(priv);
|
||||
} catch (IllegalStateException ise) {
|
||||
System.err.println("Error signing the new reference");
|
||||
ise.printStackTrace();
|
||||
}
|
||||
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(_outputFile);
|
||||
ref.write(fos);
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("Error writing out the new reference");
|
||||
ioe.printStackTrace();
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
System.out.println("Reference created at " + _outputFile);
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
CreateNameReferenceCLI cli = new CreateNameReferenceCLI(args);
|
||||
cli.execute();
|
||||
}
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
package net.i2p.myi2p.address;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Define a verified and immutable reference to a particular I2P destination.
|
||||
*
|
||||
*/
|
||||
public class NameReference {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private Destination _destination;
|
||||
private String _preferredName;
|
||||
private String _serviceType;
|
||||
private long _sequenceNum;
|
||||
private Properties _options;
|
||||
private Signature _signature;
|
||||
|
||||
public static final byte[] VERSION_PREFIX = "MyI2P_NameReference_1.0".getBytes();
|
||||
|
||||
public NameReference(I2PAppContext context) {
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(NameReference.class);
|
||||
_destination = null;
|
||||
_preferredName = null;
|
||||
_serviceType = null;
|
||||
_sequenceNum = -1;
|
||||
_options = new Properties();
|
||||
_signature = null;
|
||||
}
|
||||
|
||||
/** retrieve the destination this reference points at */
|
||||
public Destination getDestination() { return _destination; }
|
||||
public void setDestination(Destination dest) { _destination = dest; }
|
||||
|
||||
/** retrieve the name this destination would like to be called */
|
||||
public String getPreferredName() { return _preferredName; }
|
||||
public void setPreferredName(String name) { _preferredName = name; }
|
||||
|
||||
/** retrieve the type of service at this destination (eepsite, ircd, etc) */
|
||||
public String getServiceType() { return _serviceType; }
|
||||
public void setServiceType(String type) { _serviceType = type; }
|
||||
|
||||
/**
|
||||
* data point to allow the reference to be updated. The reference with the
|
||||
* larger sequence number should clobber an older reference.
|
||||
*
|
||||
*/
|
||||
public long getSequenceNum() { return _sequenceNum; }
|
||||
public void setSequenceNum(long num) { _sequenceNum = num; }
|
||||
|
||||
/** Get a list of option names (strings) */
|
||||
public Set getOptionNames() { return new HashSet(_options.keySet()); }
|
||||
|
||||
/** Access any options published in the reference */
|
||||
public String getOption(String name) { return (String)_options.getProperty(name); }
|
||||
|
||||
/**
|
||||
* Specify a particular value for an option. If the value is null, the
|
||||
* entry is removed. The name cannot have an '=' or newline, and the
|
||||
* value cannot have a newline.
|
||||
*
|
||||
* @throws IllegalArgumentException if the name or value is illegal
|
||||
*/
|
||||
public void setOption(String name, String value) {
|
||||
if (name == null) throw new IllegalArgumentException("Missing name");
|
||||
if (name.indexOf('=') != -1)
|
||||
throw new IllegalArgumentException("Name cannot have an = sign");
|
||||
if (name.indexOf('\n') != -1)
|
||||
throw new IllegalArgumentException("Name cannot have a newline");
|
||||
|
||||
if (value != null) {
|
||||
if (value.indexOf('\n') != -1)
|
||||
throw new IllegalArgumentException("Values do not allow newlines");
|
||||
_options.setProperty(name, value);
|
||||
} else {
|
||||
_options.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
/** Access the DSA signature authenticating this reference */
|
||||
public Signature getSignature() { return _signature; }
|
||||
public void setSignature(Signature sig) { _signature = sig; }
|
||||
|
||||
/** Verify the DSA signature, returning true if it is valid, false otherwise */
|
||||
public boolean validate() {
|
||||
try {
|
||||
byte raw[] = toSignableByteArray();
|
||||
return _context.dsa().verifySignature(_signature, raw, _destination.getSigningPublicKey());
|
||||
} catch (IllegalStateException ise) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign the data
|
||||
*
|
||||
* @throws IllegalStateException if the data is invalid
|
||||
*/
|
||||
public void sign(SigningPrivateKey key) throws IllegalStateException {
|
||||
byte signable[] = toSignableByteArray();
|
||||
_signature = _context.dsa().sign(signable, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the full serialized and signed version of this name reference
|
||||
*
|
||||
* @throws IllegalStateException if the signature or other data is invalid
|
||||
*/
|
||||
public void write(OutputStream out) throws IllegalStateException, IOException {
|
||||
if (_signature == null) throw new IllegalStateException("No signature");
|
||||
byte signable[] = toSignableByteArray();
|
||||
out.write(signable);
|
||||
try {
|
||||
_signature.writeBytes(out);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new IllegalStateException("Signature was corrupt - " + dfe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the signable (but not including the signature) name reference
|
||||
*
|
||||
* @throws IllegalStateException if the data is invalid
|
||||
*/
|
||||
private byte[] toSignableByteArray() throws IllegalStateException {
|
||||
if (_sequenceNum < 0) throw new IllegalStateException("Sequence number is invalid");
|
||||
if (_preferredName == null) throw new IllegalStateException("Preferred name is invalid");
|
||||
if (_serviceType == null) throw new IllegalStateException("Service type is invalid");
|
||||
if (_options == null) throw new IllegalStateException("Options not constructed");
|
||||
if (_destination == null) throw new IllegalStateException("Destination not specified");
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
|
||||
try {
|
||||
baos.write(VERSION_PREFIX);
|
||||
_destination.writeBytes(baos);
|
||||
DataHelper.writeLong(baos, 4, _sequenceNum);
|
||||
DataHelper.writeString(baos, _preferredName);
|
||||
DataHelper.writeString(baos, _serviceType);
|
||||
DataHelper.writeProperties(baos, _options); // sorts alphabetically
|
||||
return baos.toByteArray();
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Corrupted trying to write entry", dfe);
|
||||
return null;
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("IOError writing to memory?", ioe);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a full signed reference from the given stream
|
||||
*
|
||||
* @throws DataFormatException if the reference is corrupt
|
||||
* @throws IOException if there is an error reading the stream
|
||||
*/
|
||||
public void read(InputStream in) throws DataFormatException, IOException {
|
||||
byte versionBuf[] = new byte[VERSION_PREFIX.length];
|
||||
int len = DataHelper.read(in, versionBuf);
|
||||
if (len != versionBuf.length)
|
||||
throw new IllegalArgumentException("Version length too short ("+ len + ")");
|
||||
if (!DataHelper.eq(versionBuf, VERSION_PREFIX))
|
||||
throw new IllegalArgumentException("Version mismatch (" + new String(versionBuf) + ")");
|
||||
Destination dest = new Destination();
|
||||
dest.readBytes(in);
|
||||
long seq = DataHelper.readLong(in, 4);
|
||||
String name = DataHelper.readString(in);
|
||||
String type = DataHelper.readString(in);
|
||||
Properties opts = DataHelper.readProperties(in);
|
||||
Signature sig = new Signature();
|
||||
sig.readBytes(in);
|
||||
|
||||
// ok, nothing b0rked
|
||||
_destination = dest;
|
||||
_sequenceNum = seq;
|
||||
_preferredName = name;
|
||||
_serviceType = type;
|
||||
_options = opts;
|
||||
_signature = sig;
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package net.i2p.myi2p.address;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataFormatException;
|
||||
|
||||
/**
|
||||
* Contains the preferences for subscribing to a particular peer's address
|
||||
* book.
|
||||
*/
|
||||
public class Subscription {
|
||||
private I2PAppContext _context;
|
||||
private int _queryFrequencyMinutes;
|
||||
private long _lastQueryAttempt;
|
||||
private long _lastQuerySuccess;
|
||||
|
||||
/** no subscription more often than 4 times a day */
|
||||
public static final int MIN_FREQUENCY = 6*60*60*1000;
|
||||
|
||||
public Subscription(I2PAppContext context) {
|
||||
_context = context;
|
||||
}
|
||||
|
||||
/** how often do we want to query the peer (in minutes) */
|
||||
public int getQueryFrequencyMinutes() { return _queryFrequencyMinutes; }
|
||||
public void setQueryFrequencyMinutes(int freq) { _queryFrequencyMinutes = freq; }
|
||||
|
||||
/** when did we last successfully query the peer */
|
||||
public long getLastQuerySuccess() { return _lastQuerySuccess; }
|
||||
public void setLastQuerySuccess(long when) { _lastQuerySuccess = when; }
|
||||
|
||||
/** when did we last attempt to query the peer */
|
||||
public long getLastQueryAttempt() { return _lastQueryAttempt; }
|
||||
public void setLastQueryAttempt(long when) { _lastQueryAttempt = when; }
|
||||
|
||||
/** load the data from the stream */
|
||||
public void read(InputStream in) throws IOException {
|
||||
try {
|
||||
int freq = (int)DataHelper.readLong(in, 2);
|
||||
Date attempt = DataHelper.readDate(in);
|
||||
Date success = DataHelper.readDate(in);
|
||||
_queryFrequencyMinutes = (freq < MIN_FREQUENCY ? MIN_FREQUENCY : freq);
|
||||
_lastQueryAttempt = (attempt != null ? attempt.getTime() : -1);
|
||||
_lastQuerySuccess = (success != null ? success.getTime() : -1);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new IOException("Corrupt subscription: " + dfe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** persist the data to the stream */
|
||||
public void write(OutputStream out) throws IOException {
|
||||
try {
|
||||
DataHelper.writeLong(out, 2, _queryFrequencyMinutes);
|
||||
DataHelper.writeDate(out, new Date(_lastQueryAttempt));
|
||||
DataHelper.writeDate(out, new Date(_lastQuerySuccess));
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new IOException("Corrupt subscription: " + dfe.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
keyFile=myi2p.keys
|
||||
service.0.classname=net.i2p.myi2p.address.AddressBookService
|
||||
service.0.type=AddressBook
|
||||
@@ -1,130 +0,0 @@
|
||||
# dropped jobs
|
||||
statGroup.0.name=droppedJobs
|
||||
statGroup.0.detail.0.name=num dropped jobs (minute)
|
||||
statGroup.0.detail.0.option=stat_jobQueue.droppedJobs.60m
|
||||
statGroup.0.detail.0.field=3
|
||||
statGroup.0.detail.1.name=num dropped jobs (hour)
|
||||
statGroup.0.detail.1.option=stat_jobQueue.droppedJobs.60h
|
||||
statGroup.0.detail.1.field=3
|
||||
#
|
||||
statGroup.1.name=encryptTime
|
||||
statGroup.1.detail.0.name=encryption time avg ms (minute)
|
||||
statGroup.1.detail.0.option=stat_crypto.elGamal.encrypt.60s
|
||||
statGroup.1.detail.0.field=0
|
||||
statGroup.1.detail.1.name=num encryptions (minute)
|
||||
statGroup.1.detail.1.option=stat_crypto.elGamal.encrypt.60s
|
||||
statGroup.1.detail.1.field=7
|
||||
statGroup.1.detail.2.name=encryption time avg ms (hour)
|
||||
statGroup.1.detail.2.option=stat_crypto.elGamal.encrypt.60s
|
||||
statGroup.1.detail.2.field=0
|
||||
statGroup.1.detail.3.name=num encryptions (hour)
|
||||
statGroup.1.detail.3.option=stat_crypto.elGamal.encrypt.60s
|
||||
statGroup.1.detail.3.field=7
|
||||
#
|
||||
statGroup.2.name=processingTime
|
||||
statGroup.2.detail.0.name=process time avg ms (minute)
|
||||
statGroup.2.detail.0.option=stat_transport.sendProcessingTime.60s
|
||||
statGroup.2.detail.0.field=0
|
||||
statGroup.2.detail.1.name=process events (minute)
|
||||
statGroup.2.detail.1.option=stat_transport.sendProcessingTime.60s
|
||||
statGroup.2.detail.1.field=7
|
||||
statGroup.2.detail.2.name=process time avg ms (hour)
|
||||
statGroup.2.detail.2.option=stat_transport.sendProcessingTime.60m
|
||||
statGroup.2.detail.2.field=0
|
||||
statGroup.2.detail.3.name=process events(hour)
|
||||
statGroup.2.detail.3.option=stat_transport.sendProcessingTime.60m
|
||||
statGroup.2.detail.3.field=7
|
||||
#
|
||||
statGroup.3.name=jobInfo
|
||||
statGroup.3.detail.0.name=job run avg ms (minute)
|
||||
statGroup.3.detail.0.option=stat_jobQueue.jobRun.60s
|
||||
statGroup.3.detail.0.field=0
|
||||
statGroup.3.detail.1.name=job lag avg ms (minute)
|
||||
statGroup.3.detail.1.option=stat_jobQueue.jobLag.60s
|
||||
statGroup.3.detail.1.field=0
|
||||
statGroup.3.detail.2.name=job count (minute)
|
||||
statGroup.3.detail.2.option=stat_jobQueue.jobRun.60s
|
||||
statGroup.3.detail.2.field=7
|
||||
statGroup.3.detail.3.name=job run avg ms (hour)
|
||||
statGroup.3.detail.3.option=stat_jobQueue.jobRun.60m
|
||||
statGroup.3.detail.3.field=0
|
||||
statGroup.3.detail.4.name=job lag avg ms (hour)
|
||||
statGroup.3.detail.4.option=stat_jobQueue.jobLag.60m
|
||||
statGroup.3.detail.4.field=0
|
||||
statGroup.3.detail.5.name=job count (hour)
|
||||
statGroup.3.detail.5.option=stat_jobQueue.jobRun.60m
|
||||
statGroup.3.detail.5.field=7
|
||||
#
|
||||
statGroup.4.name=tunnels
|
||||
statGroup.4.detail.0.name=participating tunnels count (5 minutes)
|
||||
statGroup.4.detail.0.option=stat_tunnel.participatingTunnels.5m
|
||||
statGroup.4.detail.0.field=0
|
||||
statGroup.4.detail.1.name=participating tunnels joined (5 minutes)
|
||||
statGroup.4.detail.1.option=stat_tunnel.participatingTunnels.5m
|
||||
statGroup.4.detail.1.field=3
|
||||
statGroup.4.detail.2.name=participating tunnels count (hour)
|
||||
statGroup.4.detail.2.option=stat_tunnel.participatingTunnels.60m
|
||||
statGroup.4.detail.2.field=0
|
||||
statGroup.4.detail.3.name=participating tunnels joined (hour)
|
||||
statGroup.4.detail.3.option=stat_tunnel.participatingTunnels.60m
|
||||
statGroup.4.detail.3.field=3
|
||||
#
|
||||
statGroup.5.name=transfer
|
||||
statGroup.5.detail.0.name=messages sent (5 minutes)
|
||||
statGroup.5.detail.0.option=stat_transport.sendMessageSize.5m
|
||||
statGroup.5.detail.0.field=7
|
||||
statGroup.5.detail.1.name=send message size avg (5 minutes)
|
||||
statGroup.5.detail.1.option=stat_transport.sendMessageSize.5m
|
||||
statGroup.5.detail.1.field=0
|
||||
statGroup.5.detail.2.name=messages sent (hour)
|
||||
statGroup.5.detail.2.option=stat_transport.sendMessageSize.60m
|
||||
statGroup.5.detail.2.field=7
|
||||
statGroup.5.detail.3.name=send message size avg (hour)
|
||||
statGroup.5.detail.3.option=stat_transport.sendMessageSize.60m
|
||||
statGroup.5.detail.3.field=0
|
||||
statGroup.5.detail.4.name=messages received (5 minutes)
|
||||
statGroup.5.detail.4.option=stat_transport.receiveMessageSize.5m
|
||||
statGroup.5.detail.4.field=7
|
||||
statGroup.5.detail.5.name=receive message size avg (5 minutes)
|
||||
statGroup.5.detail.5.option=stat_transport.receiveMessageSize.5m
|
||||
statGroup.5.detail.5.field=0
|
||||
statGroup.5.detail.6.name=messages received (hour)
|
||||
statGroup.5.detail.6.option=stat_transport.receiveMessageSize.60m
|
||||
statGroup.5.detail.6.field=7
|
||||
statGroup.5.detail.7.name=receive message size avg (hour)
|
||||
statGroup.5.detail.7.option=stat_transport.receiveMessageSize.60m
|
||||
statGroup.5.detail.7.field=0
|
||||
#
|
||||
statGroup.6.name=networkDbHandling
|
||||
statGroup.6.detail.0.name=lookups received (5 minutes)
|
||||
statGroup.6.detail.0.option=stat_netDb.lookupsReceived.5m
|
||||
statGroup.6.detail.0.field=3
|
||||
statGroup.6.detail.1.name=lookups handled (5 minutes)
|
||||
statGroup.6.detail.1.option=stat_netDb.lookupsHandled.5m
|
||||
statGroup.6.detail.1.field=3
|
||||
statGroup.6.detail.2.name=lookups matched (5 minutes)
|
||||
statGroup.6.detail.2.option=stat_netDb.lookupsReceived.5m
|
||||
statGroup.6.detail.2.field=3
|
||||
statGroup.6.detail.3.name=lookups received (hour)
|
||||
statGroup.6.detail.3.option=stat_netDb.lookupsReceived.60m
|
||||
statGroup.6.detail.3.field=3
|
||||
statGroup.6.detail.4.name=lookups handled (hour)
|
||||
statGroup.6.detail.4.option=stat_netDb.lookupsHandled.60m
|
||||
statGroup.6.detail.4.field=3
|
||||
statGroup.6.detail.5.name=lookups matched (hour)
|
||||
statGroup.6.detail.5.option=stat_netDb.lookupsReceived.60m
|
||||
statGroup.6.detail.5.field=3
|
||||
#
|
||||
statGroup.7.name=networkDbActivity
|
||||
statGroup.7.detail.0.name=lookups sent (hour)
|
||||
statGroup.7.detail.0.option=stat_netDb.successPeers.60m
|
||||
statGroup.7.detail.0.field=3
|
||||
statGroup.7.detail.1.name=lookup peers (hour)
|
||||
statGroup.7.detail.1.option=stat_netDb.successPeers.60m
|
||||
statGroup.7.detail.1.field=0
|
||||
statGroup.7.detail.2.name=db store sent (5 minutes)
|
||||
statGroup.7.detail.2.option=stat_netDb.storeSent.5m
|
||||
statGroup.7.detail.2.field=3
|
||||
statGroup.7.detail.3.name=db store sent (hour)
|
||||
statGroup.7.detail.3.option=stat_netDb.storeSent.60m
|
||||
statGroup.7.detail.3.field=3
|
||||
@@ -1,69 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="netmonitor">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../../core/java/" target="build" />
|
||||
</target>
|
||||
<target name="buildGUI" depends="build, jarGUI" />
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac srcdir="./src" debug="true" source="1.3" target="1.3" deprecation="on" destdir="./build/obj" includes="net/**/*.java" excludes="net/i2p/netmonitor/gui/**" classpath="../../../core/java/build/i2p.jar" />
|
||||
</target>
|
||||
|
||||
<target name="compileGUI" depends="builddep">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac debug="true" source="1.3" target="1.3" deprecation="on" destdir="./build/obj">
|
||||
<src path="src/" />
|
||||
<classpath path="../../../core/java/build/i2p.jar" />
|
||||
<classpath path="../../jfreechart/jfreechart-0.9.17/lib/jcommon-0.9.2.jar" />
|
||||
<classpath path="../../jfreechart/jfreechart-0.9.17/lib/log4j-1.2.8.jar" />
|
||||
<classpath path="../../jfreechart/jfreechart-0.9.17/jfreechart-0.9.17.jar" />
|
||||
</javac>
|
||||
</target>
|
||||
|
||||
<target name="jarGUI" depends="compileGUI">
|
||||
<copy file="../../jfreechart/jfreechart-0.9.17/jfreechart-0.9.17.jar" todir="build/" />
|
||||
<copy file="../../jfreechart/jfreechart-0.9.17/lib/log4j-1.2.8.jar" todir="build/" />
|
||||
<copy file="../../jfreechart/jfreechart-0.9.17/lib/jcommon-0.9.2.jar" todir="build/" />
|
||||
<jar destfile="./build/netviewer.jar" basedir="./build/obj" includes="**">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.netmonitor.gui.NetViewer" />
|
||||
<attribute name="Class-Path" value="log4j-1.2.8.jar jcommon-0.9.2.jar jfreechart-0.9.17.jar netviewer.jar i2p.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
<echo message="You will need to copy the log4j, jcommon, and jfreechart jar files into your lib dir" />
|
||||
</target>
|
||||
|
||||
<target name="jar" depends="compile">
|
||||
<jar destfile="./build/netmonitor.jar" basedir="./build/obj" includes="**/*.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.netmonitor.NetMonitor" />
|
||||
<attribute name="Class-Path" value="i2p.jar netmonitor.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 netmonitor" />
|
||||
</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" />
|
||||
<delete dir="./lib/" />
|
||||
</target>
|
||||
</project>
|
||||
@@ -1,245 +0,0 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Pull out important data from the published routerInfo and stash it away
|
||||
* in the netMonitor
|
||||
*
|
||||
*/
|
||||
class DataHarvester {
|
||||
private static final Log _log = new Log(DataHarvester.class);
|
||||
private static final DataHarvester _instance = new DataHarvester();
|
||||
public static final DataHarvester getInstance() { return _instance; }
|
||||
/**
|
||||
* Contains the list of StatGroup objects loaded from the harvest.config file
|
||||
* {@see StatGroupLoader} where each statGroup defines a set of stats to pull
|
||||
* from each router's options.
|
||||
*
|
||||
*/
|
||||
private List _statGroups;
|
||||
|
||||
/**
|
||||
* Where are we reading the stat groups from? For now, "harvester.config".
|
||||
*/
|
||||
private static final String STAT_GROUP_CONFIG_FILENAME = "harvester.config";
|
||||
|
||||
protected DataHarvester() {
|
||||
_statGroups = StatGroupLoader.loadStatGroups(STAT_GROUP_CONFIG_FILENAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Harvest all of the data from the peers and store it in the monitor.
|
||||
*
|
||||
* @param peers list of RouterInfo structures to harvest from
|
||||
*/
|
||||
public void harvestData(NetMonitor monitor, List peers) {
|
||||
for (int i = 0; i < peers.size(); i++) {
|
||||
harvestData(monitor, (RouterInfo)peers.get(i), peers);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull out all the data we can for the specified peer
|
||||
*
|
||||
* @param peer who are we focusing on in this pass
|
||||
* @param peers everyone on the network, to co
|
||||
*/
|
||||
private void harvestData(NetMonitor monitor, RouterInfo peer, List peers) {
|
||||
_log.info("Harvest the data from " + peer.getIdentity().getHash().toBase64());
|
||||
harvestRank(monitor, peer, peers);
|
||||
harvestRankAs(monitor, peer);
|
||||
harvestGroups(monitor, peer);
|
||||
}
|
||||
|
||||
/**
|
||||
* How does the peer rank other routers? Stored in the peer summary as
|
||||
* "rankAs", containing 4 longs (numFast, numReliable, numNotFailing, numFailing)
|
||||
*
|
||||
* @param peer who is doing the ranking
|
||||
*/
|
||||
private void harvestRankAs(NetMonitor monitor, RouterInfo peer) {
|
||||
int numFast = 0;
|
||||
int numHighCapacity = 0;
|
||||
int numNotFailing = 0;
|
||||
int numFailing = 0;
|
||||
|
||||
Properties props = peer.getOptions();
|
||||
for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
if (key.startsWith("profile.")) {
|
||||
String val = (String)props.get(key);
|
||||
if (val.indexOf("fast") != -1)
|
||||
numFast++;
|
||||
else if (val.indexOf("highCapacity") != -1)
|
||||
numHighCapacity++;
|
||||
else if (val.indexOf("notFailing") != -1)
|
||||
numNotFailing++;
|
||||
else if (val.indexOf("failing") != -1)
|
||||
numFailing++;
|
||||
}
|
||||
}
|
||||
|
||||
long rankAs[] = new long[4];
|
||||
rankAs[0] = numFast;
|
||||
rankAs[1] = numHighCapacity;
|
||||
rankAs[2] = numNotFailing;
|
||||
rankAs[3] = numFailing;
|
||||
String description = "how we rank peers";
|
||||
String valDescr[] = new String[4];
|
||||
valDescr[0] = "# peers we rank as fast";
|
||||
valDescr[1] = "# peers we rank as high capacity";
|
||||
valDescr[2] = "# peers we rank as not failing";
|
||||
valDescr[3] = "# peers we rank as failing";
|
||||
monitor.addData(peer.getIdentity().getHash().toBase64(), "rankAs", description, valDescr, peer.getPublished(), rankAs);
|
||||
}
|
||||
|
||||
/**
|
||||
* How do other peers rank the current peer? Stored in the peer summary as
|
||||
* "rank", containing 4 longs (numFast, numReliable, numNotFailing, numFailing)
|
||||
*
|
||||
* @param peer who do we want to check the network's perception of
|
||||
* @param peers peers whose rankings we will use
|
||||
*/
|
||||
private void harvestRank(NetMonitor monitor, RouterInfo peer, List peers) {
|
||||
int numFast = 0;
|
||||
int numHighCapacity = 0;
|
||||
int numNotFailing = 0;
|
||||
int numFailing = 0;
|
||||
|
||||
// now count 'em
|
||||
for (int i = 0; i < peers.size(); i++) {
|
||||
RouterInfo cur = (RouterInfo)peers.get(i);
|
||||
if (peer == cur) continue;
|
||||
String prop = "profile." + peer.getIdentity().getHash().toBase64().replace('=', '_');
|
||||
String val = cur.getOptions().getProperty(prop);
|
||||
if ( (val == null) || (val.length() <= 0) ) continue;
|
||||
if (val.indexOf("fast") != -1)
|
||||
numFast++;
|
||||
else if (val.indexOf("highCapacity") != -1)
|
||||
numHighCapacity++;
|
||||
else if (val.indexOf("notFailing") != -1)
|
||||
numNotFailing++;
|
||||
else if (val.indexOf("failing") != -1)
|
||||
numFailing++;
|
||||
}
|
||||
|
||||
long rank[] = new long[4];
|
||||
rank[0] = numFast;
|
||||
rank[1] = numHighCapacity;
|
||||
rank[2] = numNotFailing;
|
||||
rank[3] = numFailing;
|
||||
String description = "how peers rank us";
|
||||
String valDescr[] = new String[4];
|
||||
valDescr[0] = "# peers ranking us as fast";
|
||||
valDescr[1] = "# peers ranking us as high capacity";
|
||||
valDescr[2] = "# peers ranking us as not failing";
|
||||
valDescr[3] = "# peers ranking us as failing";
|
||||
// we use the current date, not the published date, since this sample doesnt come from them
|
||||
monitor.addData(peer.getIdentity().getHash().toBase64(), "rank", description, valDescr, Clock.getInstance().now(), rank);
|
||||
}
|
||||
|
||||
/**
|
||||
* Harvest all data points from the peer
|
||||
*
|
||||
*/
|
||||
private void harvestGroups(NetMonitor monitor, RouterInfo peer) {
|
||||
_log.debug("Harvesting group data for " + peer.getIdentity().getHash().toBase64());
|
||||
for (int i = 0; i < _statGroups.size(); i++) {
|
||||
StatGroup group = (StatGroup)_statGroups.get(i);
|
||||
harvestGroup(monitor, peer, group);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Harvest the data points for the given group from the peer and toss them
|
||||
* into the monitor
|
||||
*
|
||||
*/
|
||||
private void harvestGroup(NetMonitor monitor, RouterInfo peer, StatGroup group) {
|
||||
_log.debug("Harvesting group data for " + peer.getIdentity().getHash().toBase64() + " / " + group.getDescription());
|
||||
double values[] = harvestGroupValues(peer, group);
|
||||
if (values == null) return;
|
||||
|
||||
String valDescr[] = new String[group.getStatCount()];
|
||||
for (int i = 0; i < group.getStatCount(); i++)
|
||||
valDescr[i] = group.getStat(i).getStatDescription();
|
||||
monitor.addData(peer.getIdentity().getHash().toBase64(), group.getDescription(), group.getDescription(), valDescr, peer.getPublished(), values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull up a list of all values associated with the group (in the order that the
|
||||
* group specifies).
|
||||
*
|
||||
* @return values or null on error
|
||||
*/
|
||||
private double[] harvestGroupValues(RouterInfo peer, StatGroup group) {
|
||||
List values = new ArrayList(8);
|
||||
for (int i = 0; i < group.getStatCount(); i++) {
|
||||
StatGroup.StatDescription stat = group.getStat(i);
|
||||
double val = getDouble(peer, stat.getOptionName(), stat.getOptionField());
|
||||
if (val == -1)
|
||||
return null;
|
||||
else
|
||||
values.add(new Double(val));
|
||||
}
|
||||
double rv[] = new double[values.size()];
|
||||
for (int i = 0; i < values.size(); i++)
|
||||
rv[i] = ((Double)values.get(i)).doubleValue();
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull a value from the peer's option as a double, assuming the standard semicolon
|
||||
* delimited formatting
|
||||
*
|
||||
* @param peer peer to query
|
||||
* @param key peer option to check
|
||||
* @param index 0-based index into the semicolon delimited values to pull out
|
||||
* @return value, or -1 if there was an error
|
||||
*/
|
||||
private static final double getDouble(RouterInfo peer, String key, int index) {
|
||||
String val = peer.getOptions().getProperty(key);
|
||||
if (val == null) return -1;
|
||||
StringTokenizer tok = new StringTokenizer(val, ";");
|
||||
for (int i = 0; i < index; i++) {
|
||||
if (!tok.hasMoreTokens()) return -1;
|
||||
tok.nextToken(); // ignore
|
||||
}
|
||||
if (!tok.hasMoreTokens()) return -1;
|
||||
String cur = tok.nextToken();
|
||||
try {
|
||||
return getDoubleValue(cur);
|
||||
} catch (ParseException pe) {
|
||||
_log.warn("Unable to parse out the double from field " + index + " out of " + val + " for " + key, pe);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/** this mimics the format used in the router's StatisticsManager */
|
||||
private static final DecimalFormat _numFmt = new DecimalFormat("###,###,###,###,##0.00", new DecimalFormatSymbols(Locale.UK));
|
||||
|
||||
/**
|
||||
* Converts a number (double) to text
|
||||
* @param val the number to convert
|
||||
* @return the textual representation
|
||||
*/
|
||||
private static final double getDoubleValue(String val) throws ParseException {
|
||||
synchronized (_numFmt) {
|
||||
Number n = _numFmt.parse(val);
|
||||
return n.doubleValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Main driver for the app that harvests data about the performance of the network,
|
||||
* building summaries for each peer that change over time. <p />
|
||||
*
|
||||
* Usage: <code>NetMonitor [configFilename] [--routers filename[,filename]*] [--netDbURL url] </code> <p />
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class NetMonitor {
|
||||
private static final Log _log = new Log(NetMonitor.class);
|
||||
public static final String CONFIG_LOCATION_DEFAULT = "netmonitor.config";
|
||||
public static final String HARVEST_DELAY_PROP = "harvestDelaySeconds";
|
||||
public static final int HARVEST_DELAY_DEFAULT = 5*60;
|
||||
public static final String EXPORT_DELAY_PROP = "exportDelaySeconds";
|
||||
public static final int EXPORT_DELAY_DEFAULT = 120;
|
||||
public static final String SUMMARY_DURATION_PROP = "summaryDurationHours";
|
||||
public static final int SUMMARY_DURATION_DEFAULT = 72;
|
||||
public static final String NETDB_DIR_PROP = "netDbDir";
|
||||
public static final String NETDB_DIR_DEFAULT = "netDb";
|
||||
public static final String EXPORT_DIR_PROP = "exportDir";
|
||||
public static final String EXPORT_DIR_DEFAULT = "monitorData";
|
||||
private String _configLocation;
|
||||
private int _harvestDelay;
|
||||
private int _exportDelay;
|
||||
private String _exportDir;
|
||||
private String _netDbDir;
|
||||
private String _netDbURL;
|
||||
private String _explicitRouters;
|
||||
private int _summaryDurationHours;
|
||||
private boolean _isRunning;
|
||||
private Map _peerSummaries;
|
||||
|
||||
public NetMonitor() {
|
||||
this(CONFIG_LOCATION_DEFAULT, null, null);
|
||||
}
|
||||
public NetMonitor(String configLocation) {
|
||||
this(configLocation, null, null);
|
||||
}
|
||||
public NetMonitor(String configLocation, String explicitFilenames, String url) {
|
||||
_configLocation = configLocation;
|
||||
_explicitRouters = explicitFilenames;
|
||||
_netDbURL = url;
|
||||
_peerSummaries = new HashMap(32);
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
/** read and call parse */
|
||||
private void loadConfig() {
|
||||
Properties props = new Properties();
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(_configLocation);
|
||||
props.load(fis);
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Error loading the net monitor config", ioe);
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
parseConfig(props);
|
||||
}
|
||||
|
||||
/** interpret the config elements and shove 'em in the vars */
|
||||
private void parseConfig(Properties props) {
|
||||
String val = props.getProperty(HARVEST_DELAY_PROP, ""+HARVEST_DELAY_DEFAULT);
|
||||
try {
|
||||
_harvestDelay = Integer.parseInt(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.warn("Error parsing the harvest delay [" + val + "]", nfe);
|
||||
_harvestDelay = HARVEST_DELAY_DEFAULT;
|
||||
}
|
||||
|
||||
val = props.getProperty(EXPORT_DELAY_PROP, ""+EXPORT_DELAY_DEFAULT);
|
||||
try {
|
||||
_exportDelay = Integer.parseInt(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.warn("Error parsing the export delay [" + val + "]", nfe);
|
||||
_exportDelay = EXPORT_DELAY_DEFAULT;
|
||||
}
|
||||
|
||||
val = props.getProperty(SUMMARY_DURATION_PROP, ""+SUMMARY_DURATION_DEFAULT);
|
||||
try {
|
||||
_summaryDurationHours = Integer.parseInt(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.warn("Error parsing the summary duration [" + val + "]", nfe);
|
||||
_summaryDurationHours = SUMMARY_DURATION_DEFAULT;
|
||||
}
|
||||
|
||||
_netDbDir = props.getProperty(NETDB_DIR_PROP, NETDB_DIR_DEFAULT);
|
||||
_exportDir = props.getProperty(EXPORT_DIR_PROP, EXPORT_DIR_DEFAULT);
|
||||
}
|
||||
|
||||
public void startMonitor() {
|
||||
_isRunning = true;
|
||||
I2PThread t = new I2PThread(new NetMonitorRunner(this));
|
||||
t.setName("DataHarvester");
|
||||
t.setPriority(I2PThread.MIN_PRIORITY);
|
||||
t.setDaemon(false);
|
||||
t.start();
|
||||
}
|
||||
|
||||
public void stopMonitor() { _isRunning = false; }
|
||||
public boolean isRunning() { return _isRunning; }
|
||||
/** how many seconds should we wait between harvestings? */
|
||||
public int getHarvestDelay() { return _harvestDelay; }
|
||||
/** how many seconds should we wait between exporting the data? */
|
||||
public int getExportDelay() { return _exportDelay; }
|
||||
/** where should we export the data? */
|
||||
public String getExportDir() { return _exportDir; }
|
||||
public void setExportDir(String dir) { _exportDir = dir; }
|
||||
public int getSummaryDurationHours() { return _summaryDurationHours; }
|
||||
/** where should we read the data from? */
|
||||
public String getNetDbDir() { return _netDbDir; }
|
||||
/** if specified, contains a set of filenames we want to harvest routerInfo data from */
|
||||
public String getExplicitRouters() { return _explicitRouters; }
|
||||
/** if specified, contains a URL to fetch references from */
|
||||
public String getNetDbURL() { return _netDbURL; }
|
||||
|
||||
/**
|
||||
* what peers are we keeping track of?
|
||||
*
|
||||
* @return list of peer names (H(routerIdentity).toBase64())
|
||||
*/
|
||||
public List getPeers() {
|
||||
synchronized (_peerSummaries) {
|
||||
return new ArrayList(_peerSummaries.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
/** what data do we have for the peer? */
|
||||
public PeerSummary getSummary(String peer) {
|
||||
synchronized (_peerSummaries) {
|
||||
return (PeerSummary)_peerSummaries.get(peer);
|
||||
}
|
||||
}
|
||||
|
||||
/** keep track of the given stat on the given peer */
|
||||
public void addData(String peer, String stat, String descr, String valDescr[], long sampleDate, double val[]) {
|
||||
synchronized (_peerSummaries) {
|
||||
if (!_peerSummaries.containsKey(peer))
|
||||
_peerSummaries.put(peer, new PeerSummary(peer));
|
||||
PeerSummary summary = (PeerSummary)_peerSummaries.get(peer);
|
||||
summary.addData(stat, descr, valDescr, sampleDate, val);
|
||||
}
|
||||
}
|
||||
|
||||
/** keep track of the given stat on the given peer */
|
||||
public void addData(String peer, String stat, String descr, String valDescr[], long sampleDate, long val[]) {
|
||||
synchronized (_peerSummaries) {
|
||||
if (!_peerSummaries.containsKey(peer))
|
||||
_peerSummaries.put(peer, new PeerSummary(peer));
|
||||
PeerSummary summary = (PeerSummary)_peerSummaries.get(peer);
|
||||
summary.addData(stat, descr, valDescr, sampleDate, val);
|
||||
}
|
||||
}
|
||||
|
||||
/** keep track of the loaded summary, overwriting any existing summary for the specified peer */
|
||||
public void addSummary(PeerSummary summary) {
|
||||
synchronized (_peerSummaries) {
|
||||
Object rv = _peerSummaries.put(summary.getPeer(), summary);
|
||||
if (rv != summary) _log.error("Updating the peer summary changed objects! old = " + rv + " new = " + summary);
|
||||
}
|
||||
}
|
||||
|
||||
public void importData() {
|
||||
_log.debug("Running import");
|
||||
File dataDir = new File(getExportDir());
|
||||
if (!dataDir.exists()) return;
|
||||
File dataFiles[] = dataDir.listFiles(new FilenameFilter() {
|
||||
public boolean accept(File f, String name) {
|
||||
return name.endsWith(".txt");
|
||||
}
|
||||
});
|
||||
if (dataFiles == null) return;
|
||||
for (int i = 0; i < dataFiles.length; i++) {
|
||||
FileInputStream fis = null;
|
||||
boolean delete = false;
|
||||
try {
|
||||
fis = new FileInputStream(dataFiles[i]);
|
||||
PeerSummaryReader.getInstance().read(this, fis);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error reading the data file " + dataFiles[i].getAbsolutePath(), ioe);
|
||||
delete = true;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
if (delete) dataFiles[i].delete();
|
||||
}
|
||||
}
|
||||
_log.debug(dataFiles.length + " summaries imported");
|
||||
}
|
||||
|
||||
/** drop all the old summary data */
|
||||
public void coalesceData() {
|
||||
synchronized (_peerSummaries) {
|
||||
for (Iterator iter = _peerSummaries.values().iterator(); iter.hasNext(); ) {
|
||||
PeerSummary summary = (PeerSummary)iter.next();
|
||||
summary.coalesceData(_summaryDurationHours * 60*60*1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* main driver for the netMonitor. the usage is:
|
||||
* <code>NetMonitor [configFilename] [--routers filename[,filename]*] [--netDbURL url]</code>
|
||||
*/
|
||||
public static final void main(String args[]) {
|
||||
String cfgLocation = CONFIG_LOCATION_DEFAULT;
|
||||
String explicitFilenames = null;
|
||||
String explicitURL = null;
|
||||
switch (args.length) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
cfgLocation = args[0];
|
||||
break;
|
||||
case 2:
|
||||
if ("--routers".equalsIgnoreCase(args[0]))
|
||||
explicitFilenames = args[1];
|
||||
else
|
||||
explicitURL = args[1];
|
||||
break;
|
||||
case 3:
|
||||
cfgLocation = args[0];
|
||||
if ("--routers".equalsIgnoreCase(args[1]))
|
||||
explicitFilenames = args[2];
|
||||
else
|
||||
explicitURL = args[2];
|
||||
break;
|
||||
default:
|
||||
System.err.println("Usage: NetMonitor [configFilename] [--routers filename[,filename]*] [--netDbURL url]");
|
||||
return;
|
||||
}
|
||||
new NetMonitor(cfgLocation, explicitFilenames, explicitURL).startMonitor();
|
||||
}
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Active process that drives the monitoring by periodically rading the
|
||||
* netDb dir, pumping the router data throug the data harvester, updating
|
||||
* the state, and coordinating the export.
|
||||
*
|
||||
*/
|
||||
class NetMonitorRunner implements Runnable {
|
||||
private static final Log _log = new Log(NetMonitorRunner.class);
|
||||
private NetMonitor _monitor;
|
||||
/**
|
||||
* @param monitor who do we give our data to?
|
||||
*/
|
||||
public NetMonitorRunner(NetMonitor monitor) {
|
||||
_monitor = monitor;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
runImport();
|
||||
long now = Clock.getInstance().now();
|
||||
long nextHarvest = now;
|
||||
long nextExport = now + _monitor.getExportDelay() * 1000;
|
||||
while (_monitor.isRunning()) {
|
||||
now = Clock.getInstance().now();
|
||||
_monitor.coalesceData();
|
||||
if (now >= nextHarvest) {
|
||||
runHarvest();
|
||||
nextHarvest = now + _monitor.getHarvestDelay() * 1000;
|
||||
}
|
||||
if (now >= nextExport) {
|
||||
runExport();
|
||||
nextExport = now + _monitor.getExportDelay() * 1000;
|
||||
}
|
||||
pauseHarvesting();
|
||||
}
|
||||
}
|
||||
|
||||
private void runHarvest() {
|
||||
try {
|
||||
List routers = getRouters();
|
||||
DataHarvester.getInstance().harvestData(_monitor, routers);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Unhandled exception harvesting the data", t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all of the available RouterInfo structures
|
||||
*
|
||||
*/
|
||||
private List getRouters() {
|
||||
if (_monitor.getNetDbURL() != null)
|
||||
return fetchRouters(_monitor.getNetDbURL());
|
||||
|
||||
File routers[] = listRouters();
|
||||
List rv = new ArrayList(64);
|
||||
if (routers != null) {
|
||||
for (int i = 0; i < routers.length; i++) {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(routers[i]);
|
||||
RouterInfo ri = new RouterInfo();
|
||||
ri.readBytes(fis);
|
||||
rv.add(ri);
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.warn("Unable to parse the routerInfo from " + routers[i].getAbsolutePath(), dfe);
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Unable to read the routerInfo from " + routers[i].getAbsolutePath(), ioe);
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
private List fetchRouters(String seedURL) {
|
||||
List rv = new ArrayList();
|
||||
try {
|
||||
URL dir = new URL(seedURL);
|
||||
String content = new String(readURL(dir));
|
||||
Set urls = new HashSet();
|
||||
int cur = 0;
|
||||
while (true) {
|
||||
int start = content.indexOf("href=\"routerInfo-", cur);
|
||||
if (start < 0)
|
||||
break;
|
||||
|
||||
int end = content.indexOf(".dat\">", start);
|
||||
String name = content.substring(start+"href=\"routerInfo-".length(), end);
|
||||
urls.add(name);
|
||||
cur = end + 1;
|
||||
}
|
||||
|
||||
for (Iterator iter = urls.iterator(); iter.hasNext(); ) {
|
||||
rv.add(fetchSeed((String)iter.next()));
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error fetching routers from " + seedURL, t);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private RouterInfo fetchSeed(String peer) throws Exception {
|
||||
URL url = new URL("http://i2p.net/i2pdb/routerInfo-" + peer + ".dat");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Fetching seed from " + url.toExternalForm());
|
||||
|
||||
byte data[] = readURL(url);
|
||||
RouterInfo info = new RouterInfo();
|
||||
try {
|
||||
info.fromByteArray(data);
|
||||
return info;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Router data at " + url.toExternalForm() + " was corrupt", dfe);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] readURL(URL url) throws Exception {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
|
||||
URLConnection con = url.openConnection();
|
||||
InputStream in = con.getInputStream();
|
||||
byte buf[] = new byte[1024];
|
||||
while (true) {
|
||||
int read = in.read(buf);
|
||||
if (read < 0)
|
||||
break;
|
||||
baos.write(buf, 0, read);
|
||||
}
|
||||
in.close();
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* dump the data to the filesystem
|
||||
*/
|
||||
private void runExport() {
|
||||
_log.info("Export");
|
||||
List peers = _monitor.getPeers();
|
||||
File exportDir = new File(_monitor.getExportDir());
|
||||
if (!exportDir.exists())
|
||||
exportDir.mkdirs();
|
||||
for (int i = 0; i < peers.size(); i++) {
|
||||
String peerName = (String)peers.get(i);
|
||||
PeerSummary summary = (PeerSummary)_monitor.getSummary(peerName);
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
File summaryFile = new File(exportDir, peerName + ".txt");
|
||||
fos = new FileOutputStream(summaryFile);
|
||||
PeerSummaryWriter.getInstance().write(summary, fos);
|
||||
_log.debug("Peer summary written to " + summaryFile.getAbsolutePath());
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error exporting the peer summary for " + peerName, ioe);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Unhandled exception exporting the data", t);
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read in all the peer summaries we had previously exported, overwriting any
|
||||
* existing ones in memory
|
||||
*
|
||||
*/
|
||||
private void runImport() {
|
||||
_monitor.importData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all of the routers to load
|
||||
*
|
||||
* @return list of File objects pointing at the routers around
|
||||
*/
|
||||
private File[] listRouters() {
|
||||
if (_monitor.getExplicitRouters() != null) {
|
||||
return listRoutersExplicit();
|
||||
} else {
|
||||
File dbDir = new File(_monitor.getNetDbDir());
|
||||
File files[] = dbDir.listFiles(new FilenameFilter() {
|
||||
public boolean accept(File f, String name) {
|
||||
return name.startsWith("routerInfo-");
|
||||
}
|
||||
});
|
||||
return files;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of router files that were explicitly specified by the netMonitor
|
||||
*
|
||||
*/
|
||||
private File[] listRoutersExplicit() {
|
||||
StringTokenizer tok = new StringTokenizer(_monitor.getExplicitRouters().trim(), ",");
|
||||
List rv = new ArrayList();
|
||||
while (tok.hasMoreTokens()) {
|
||||
String name = tok.nextToken();
|
||||
File cur = new File(name);
|
||||
if (cur.exists())
|
||||
rv.add(cur);
|
||||
}
|
||||
File files[] = new File[rv.size()];
|
||||
for (int i = 0; i < rv.size(); i++)
|
||||
files[i] = (File)rv.get(i);
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait the correct amount of time before harvesting again
|
||||
*
|
||||
*/
|
||||
private void pauseHarvesting() {
|
||||
try {
|
||||
Thread.sleep(_monitor.getHarvestDelay());
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
/**
|
||||
* Actual data point (though its not fully normalized, but KISS)
|
||||
*
|
||||
*/
|
||||
public class PeerStat {
|
||||
private String _statName;
|
||||
private String _description;
|
||||
private String _valueDescriptions[];
|
||||
private long _sampleDate;
|
||||
private long _lvalues[];
|
||||
private double _dvalues[];
|
||||
|
||||
public PeerStat(String name, String description, String valueDescriptions[], long sampleDate, double values[]) {
|
||||
this(name, description, valueDescriptions, sampleDate, null, values);
|
||||
}
|
||||
public PeerStat(String name, String description, String valueDescriptions[], long sampleDate, long values[]) {
|
||||
this(name, description, valueDescriptions, sampleDate, values, null);
|
||||
}
|
||||
private PeerStat(String name, String description, String valueDescriptions[], long sampleDate, long lvalues[], double dvalues[]) {
|
||||
_statName = name;
|
||||
_description = description;
|
||||
_valueDescriptions = valueDescriptions;
|
||||
_sampleDate = sampleDate;
|
||||
_lvalues = lvalues;
|
||||
_dvalues = dvalues;
|
||||
}
|
||||
|
||||
/** unique name of the stat */
|
||||
public String getStatName() { return _statName; }
|
||||
/** one line summary of the stat */
|
||||
public String getDescription() { return _description; }
|
||||
/** description of each value */
|
||||
public String getValueDescription(int index) { return _valueDescriptions[index]; }
|
||||
/** description of all values */
|
||||
public String[] getValueDescriptions() { return _valueDescriptions; }
|
||||
/** when did the router publish the info being sampled? */
|
||||
public long getSampleDate() { return _sampleDate; }
|
||||
/** if the values are integers, this contains their values */
|
||||
public long[] getLongValues() { return _lvalues; }
|
||||
/** if the values are floating point numbers, this contains their values */
|
||||
public double[] getDoubleValues() { return _dvalues; }
|
||||
/** are the values floating poing numbers? */
|
||||
public boolean getIsDouble() { return _dvalues != null; }
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* coordinate the data points summarizing the performance of a particular peer
|
||||
* within the network
|
||||
*/
|
||||
public class PeerSummary {
|
||||
private static final Log _log = new Log(PeerSummary.class);
|
||||
private String _peer;
|
||||
/** statName to a List of PeerStat elements (sorted by sample date, earliest first) */
|
||||
private Map _stats;
|
||||
/** lock on this when accessing stat data */
|
||||
private Object _coalesceLock = new Object();
|
||||
|
||||
public PeerSummary(String peer) {
|
||||
_peer = peer;
|
||||
_stats = new HashMap(16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Track a data point
|
||||
*
|
||||
* @param stat what data are we tracking?
|
||||
* @param description what does this data mean? (and what are the values?)
|
||||
* @param when what data set is this sample based off?
|
||||
* @param val actual data harvested
|
||||
*/
|
||||
public void addData(String stat, String description, String valueDescriptions[], long when, double val[]) {
|
||||
synchronized (_coalesceLock) {
|
||||
TreeMap stats = locked_getData(stat);
|
||||
stats.put(new Long(when), new PeerStat(stat, description, valueDescriptions, when, val));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Track a data point
|
||||
*
|
||||
* @param stat what data are we tracking?
|
||||
* @param description what does this data mean? (and what are the values?)
|
||||
* @param when what data set is this sample based off?
|
||||
* @param val actual data harvested
|
||||
*/
|
||||
public void addData(String stat, String description, String valueDescriptions[], long when, long val[]) {
|
||||
synchronized (_coalesceLock) {
|
||||
TreeMap stats = locked_getData(stat);
|
||||
stats.put(new Long(when), new PeerStat(stat, description, valueDescriptions, when, val));
|
||||
}
|
||||
}
|
||||
|
||||
/** get the peer's name (H(routerIdentity).toBase64()) */
|
||||
public String getPeer() { return _peer; }
|
||||
|
||||
/**
|
||||
* fetch the ordered list of PeerStat objects for the given stat (or null if
|
||||
* isn't being tracked or has no data)
|
||||
*
|
||||
*/
|
||||
public List getData(String statName) {
|
||||
synchronized (_coalesceLock) {
|
||||
return new ArrayList(((TreeMap)_stats.get(statName)).values());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the names of all of the stats that are being tracked
|
||||
*
|
||||
*/
|
||||
public Set getStatNames() {
|
||||
synchronized (_coalesceLock) {
|
||||
return new HashSet(_stats.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
/** drop old data points */
|
||||
public void coalesceData(long summaryDurationMs) {
|
||||
long earliest = Clock.getInstance().now() - summaryDurationMs;
|
||||
synchronized (_coalesceLock) {
|
||||
locked_coalesce(earliest);
|
||||
}
|
||||
}
|
||||
|
||||
/** go through all the stats and remove ones from before the given date */
|
||||
private void locked_coalesce(long earliestSampleDate) {
|
||||
if (true) return;
|
||||
for (Iterator iter = _stats.keySet().iterator(); iter.hasNext(); ) {
|
||||
String statName = (String)iter.next();
|
||||
TreeMap stats = (TreeMap)_stats.get(statName);
|
||||
while (stats.size() > 0) {
|
||||
Long when = (Long)stats.keySet().iterator().next();
|
||||
if (when.longValue() < earliestSampleDate) {
|
||||
stats.remove(when);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PeerStat elements, ordered by sample date (earliest first)
|
||||
*/
|
||||
private TreeMap locked_getData(String statName) {
|
||||
if (!_stats.containsKey(statName))
|
||||
_stats.put(statName, new TreeMap());
|
||||
return (TreeMap)_stats.get(statName);
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Load up the peer summary
|
||||
*
|
||||
*/
|
||||
class PeerSummaryReader {
|
||||
private static final Log _log = new Log(PeerSummaryReader.class);
|
||||
private static final PeerSummaryReader _instance = new PeerSummaryReader();
|
||||
public static final PeerSummaryReader getInstance() { return _instance; }
|
||||
private PeerSummaryReader() {}
|
||||
|
||||
/** */
|
||||
public void read(NetMonitor monitor, InputStream in) throws IOException {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
String line = null;
|
||||
PeerSummary summary = null;
|
||||
String curDescription = null;
|
||||
List curArgs = null;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_log.error("Error handling the data", e);
|
||||
throw new IOException("Error parsing the data");
|
||||
}
|
||||
if (summary == null)
|
||||
return;
|
||||
summary.coalesceData(monitor.getSummaryDurationHours() * 60*60*1000);
|
||||
monitor.addSummary(summary);
|
||||
}
|
||||
|
||||
private static final SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd.HH:mm:ss.SSS", Locale.UK);
|
||||
|
||||
/**
|
||||
* Converts a time (long) to text
|
||||
* @param when the time to convert
|
||||
* @return the textual representation
|
||||
*/
|
||||
public long getTime(String when) throws ParseException {
|
||||
synchronized (_fmt) {
|
||||
return _fmt.parse(when).getTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Dump various peer summaries to disk (so external apps (or good ol' vi) can
|
||||
* peek into what we're harvesting
|
||||
*
|
||||
*/
|
||||
class PeerSummaryWriter {
|
||||
private static final Log _log = new Log(PeerSummaryWriter.class);
|
||||
private static final PeerSummaryWriter _instance = new PeerSummaryWriter();
|
||||
public static final PeerSummaryWriter getInstance() { return _instance; }
|
||||
private PeerSummaryWriter() {}
|
||||
|
||||
/** write out the peer summary to the stream specified */
|
||||
public void write(PeerSummary summary, OutputStream out) throws IOException {
|
||||
StringBuffer buf = new StringBuffer(4*1024);
|
||||
buf.append("peer\t").append(summary.getPeer()).append('\n');
|
||||
TreeSet names = new TreeSet(summary.getStatNames());
|
||||
for (Iterator iter = names.iterator(); iter.hasNext(); ) {
|
||||
String statName = (String)iter.next();
|
||||
List stats = summary.getData(statName);
|
||||
for (int i = 0; i < stats.size(); i++) {
|
||||
PeerStat stat = (PeerStat)stats.get(i);
|
||||
if (i == 0) {
|
||||
buf.append("## ").append(stat.getDescription()).append('\n');
|
||||
String descr[] = stat.getValueDescriptions();
|
||||
if (descr != null) {
|
||||
for (int j = 0; j < descr.length; j++)
|
||||
buf.append("# param ").append(j).append(": ").append(descr[j]).append('\n');
|
||||
}
|
||||
}
|
||||
buf.append(statName).append('\t');
|
||||
buf.append(getTime(stat.getSampleDate())).append('\t');
|
||||
|
||||
if (stat.getIsDouble()) {
|
||||
double vals[] = stat.getDoubleValues();
|
||||
if (vals != null) {
|
||||
for (int j = 0; j < vals.length; j++)
|
||||
buf.append(vals[j]).append('\t');
|
||||
}
|
||||
} else {
|
||||
long vals[] = stat.getLongValues();
|
||||
if (vals != null) {
|
||||
for (int j = 0; j < vals.length; j++)
|
||||
buf.append(vals[j]).append('\t');
|
||||
}
|
||||
}
|
||||
buf.append('\n');
|
||||
}
|
||||
}
|
||||
String data = buf.toString();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Stat: \n" + data);
|
||||
out.write(data.getBytes());
|
||||
}
|
||||
|
||||
private static final SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd.HH:mm:ss.SSS", Locale.UK);
|
||||
|
||||
/**
|
||||
* Converts a time (long) to text
|
||||
* @param when the time to convert
|
||||
* @return the textual representation
|
||||
*/
|
||||
public String getTime(long when) {
|
||||
synchronized (_fmt) {
|
||||
return _fmt.format(new Date(when));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Stupid little structure to configure the DataHarvester's gathering of statistics.
|
||||
*
|
||||
*/
|
||||
public class StatGroup {
|
||||
private String _groupDescription;
|
||||
private List _stats;
|
||||
|
||||
public StatGroup(String description) {
|
||||
_groupDescription = description;
|
||||
_stats = new ArrayList();
|
||||
}
|
||||
|
||||
public String getDescription() { return _groupDescription; }
|
||||
public int getStatCount() { return _stats.size(); }
|
||||
public StatDescription getStat(int index) { return (StatDescription)_stats.get(index); }
|
||||
public void addStat(String description, String optionName, int optionField) {
|
||||
StatDescription descr = new StatDescription(description, optionName, optionField);
|
||||
_stats.add(descr);
|
||||
}
|
||||
|
||||
public class StatDescription {
|
||||
private String _statDescription;
|
||||
private String _optionName;
|
||||
private int _optionField;
|
||||
|
||||
public StatDescription(String descr, String optionName, int optionField) {
|
||||
_statDescription = descr;
|
||||
_optionName = optionName;
|
||||
_optionField = optionField;
|
||||
}
|
||||
|
||||
/** brief description of this data point */
|
||||
public String getStatDescription() { return _statDescription; }
|
||||
/**
|
||||
* if this is harvested from the RouterInfo's options, this specifies
|
||||
* which key in that map to pull from (or null if it isn't harvested
|
||||
* from there)
|
||||
*/
|
||||
public String getOptionName() { return _optionName; }
|
||||
/**
|
||||
* if this is harvested from the RouterInfo's options, this specifies
|
||||
* which field in value of that map to pull from (or -1 if it isn't harvested
|
||||
* from there)
|
||||
*/
|
||||
public int getOptionField() { return _optionField; }
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Load up the StatGroups from the location specified to configure the data harvester.
|
||||
* The stat groups are formatted in a simple properties file style, e.g.: <pre>
|
||||
* # dropped jobs
|
||||
* statGroup.0.name=droppedJobs
|
||||
* statGroup.0.detail.0.name=num dropped jobs (minute)
|
||||
* statGroup.0.detail.0.option=stat_jobQueue.droppedJobs.60m
|
||||
* statGroup.0.detail.0.field=3
|
||||
* statGroup.0.detail.1.name=num dropped jobs (hour)
|
||||
* statGroup.0.detail.1.option=stat_jobQueue.droppedJobs.60h
|
||||
* statGroup.0.detail.1.field=3
|
||||
* #
|
||||
* statGroup.1.name=encryptTime
|
||||
* statGroup.1.detail.0.name=encryption time avg ms (minute)
|
||||
* statGroup.1.detail.0.option=stat_crypto.elGamal.encrypt.60s
|
||||
* statGroup.1.detail.0.field=0
|
||||
* statGroup.1.detail.1.name=num encryptions (minute)
|
||||
* statGroup.1.detail.1.option=stat_crypto.elGamal.encrypt.60s
|
||||
* statGroup.1.detail.1.field=7
|
||||
* statGroup.1.detail.2.name=encryption time avg ms (hour)
|
||||
* statGroup.1.detail.2.option=stat_crypto.elGamal.encrypt.60s
|
||||
* statGroup.1.detail.2.field=0
|
||||
* statGroup.1.detail.3.name=num encryptions (hour)
|
||||
* statGroup.1.detail.3.option=stat_crypto.elGamal.encrypt.60s
|
||||
* statGroup.1.detail.3.field=7
|
||||
* </pre>
|
||||
*/
|
||||
class StatGroupLoader {
|
||||
private static final Log _log = new Log(StatGroupLoader.class);
|
||||
/**
|
||||
* Load a list of stat groups from the file specified
|
||||
*
|
||||
* @return list of StatGroup objects
|
||||
*/
|
||||
public static List loadStatGroups(String filename) {
|
||||
_log.debug("Loading stat groups from " + filename);
|
||||
FileInputStream fis = null;
|
||||
File f = new File(filename);
|
||||
try {
|
||||
fis = new FileInputStream(f);
|
||||
Properties p = new Properties();
|
||||
p.load(fis);
|
||||
_log.debug("Config loaded from " + filename);
|
||||
return loadStatGroups(p);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error loading the stat groups from " + f.getAbsolutePath(), ioe);
|
||||
return new ArrayList();
|
||||
}
|
||||
}
|
||||
|
||||
private static List loadStatGroups(Properties props) {
|
||||
List rv = new ArrayList(8);
|
||||
int groupNum = 0;
|
||||
while (true) {
|
||||
String description = props.getProperty("statGroup." + groupNum + ".name");
|
||||
if (description == null) break;
|
||||
int detailNum = 0;
|
||||
StatGroup group = new StatGroup(description);
|
||||
while (true) {
|
||||
String detailPrefix = "statGroup." + groupNum + ".detail." + detailNum + '.';
|
||||
String name = props.getProperty(detailPrefix + "name");
|
||||
if (name == null) break;
|
||||
String option = props.getProperty(detailPrefix + "option");
|
||||
if (option == null) break;
|
||||
String field = props.getProperty(detailPrefix + "field");
|
||||
if (field == null) break;
|
||||
try {
|
||||
int fieldNum = Integer.parseInt(field);
|
||||
group.addStat(name, option, fieldNum);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.warn("Unable to parse the field number from [" + field + "]", nfe);
|
||||
break;
|
||||
}
|
||||
detailNum++;
|
||||
}
|
||||
rv.add(group);
|
||||
groupNum++;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package net.i2p.netmonitor.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
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.netmonitor.PeerStat;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
class JFreeChartAdapter {
|
||||
private final static Log _log = new Log(JFreeChartAdapter.class);
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
|
||||
ChartPanel createPanel(NetViewer state) {
|
||||
ChartPanel panel = new ChartPanel(createChart(state));
|
||||
panel.setDisplayToolTips(true);
|
||||
panel.setEnforceFileExtensions(true);
|
||||
panel.setHorizontalZoom(true);
|
||||
panel.setVerticalZoom(true);
|
||||
panel.setMouseZoomable(true, true);
|
||||
panel.getChart().setBackgroundPaint(WHITE);
|
||||
return panel;
|
||||
}
|
||||
|
||||
JFreeChart createChart(NetViewer state) {
|
||||
Plot plot = createPlot(state);
|
||||
JFreeChart chart = new JFreeChart("I2P network performance", Font.getFont("arial"), plot, true);
|
||||
return chart;
|
||||
}
|
||||
|
||||
void updateChart(ChartPanel panel, NetViewer state) {
|
||||
XYPlot plot = (XYPlot)panel.getChart().getPlot();
|
||||
plot.setDataset(getCollection(state));
|
||||
}
|
||||
|
||||
Plot createPlot(NetViewer state) {
|
||||
XYItemRenderer renderer = new XYLineAndShapeRenderer(); // new XYDotRenderer(); //
|
||||
XYPlot plot = new XYPlot(getCollection(state), new DateAxis(), new NumberAxis("val"), renderer);
|
||||
return plot;
|
||||
}
|
||||
|
||||
XYSeriesCollection getCollection(NetViewer state) {
|
||||
XYSeriesCollection col = new XYSeriesCollection();
|
||||
if (state != null) {
|
||||
List names = state.getConfigNames();
|
||||
for (int i = 0; i < names.size(); i++) {
|
||||
addPeer(col, state.getConfig((String)names.get(i)));
|
||||
}
|
||||
} else {
|
||||
XYSeries series = new XYSeries("latency", false, false);
|
||||
series.add(System.currentTimeMillis(), 0);
|
||||
col.addSeries(series);
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
void addPeer(XYSeriesCollection col, PeerPlotConfig config) {
|
||||
Set statNames = config.getSummary().getStatNames();
|
||||
for (Iterator iter = statNames.iterator(); iter.hasNext(); ) {
|
||||
String statName = (String)iter.next();
|
||||
List statData = config.getSummary().getData(statName);
|
||||
if (statData.size() > 0) {
|
||||
PeerStat data = (PeerStat)statData.get(0);
|
||||
String args[] = data.getValueDescriptions();
|
||||
if (args != null) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (config.getSeriesConfig().getShouldPlotValue(statName, args[i], false))
|
||||
addLine(col, config.getPeerName(), statName, args[i], statData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
void addLine(XYSeriesCollection col, String peer, String statName, String argName, List dataPoints) {
|
||||
XYSeries series = new XYSeries(peer.substring(0, 4) + ": " + argName, false, false);
|
||||
for (int i = 0; i < dataPoints.size(); i++) {
|
||||
PeerStat data = (PeerStat)dataPoints.get(i);
|
||||
String argNames[] = data.getValueDescriptions();
|
||||
int argIndex = -1;
|
||||
for (int j = 0; j < argNames.length; j++) {
|
||||
if (argNames[j].equals(argName)) {
|
||||
argIndex = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (data.getIsDouble())
|
||||
series.add(data.getSampleDate(), data.getDoubleValues()[argIndex]);
|
||||
else
|
||||
series.add(data.getSampleDate(), data.getLongValues()[argIndex]);
|
||||
}
|
||||
col.addSeries(series);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package net.i2p.netmonitor.gui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JScrollPane;
|
||||
|
||||
import org.jfree.chart.ChartPanel;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Render the graph and legend
|
||||
*
|
||||
*/
|
||||
class JFreeChartHeartbeatPlotPane extends NetViewerPlotPane {
|
||||
private final static Log _log = new Log(JFreeChartHeartbeatPlotPane.class);
|
||||
private ChartPanel _panel;
|
||||
private JFreeChartAdapter _adapter;
|
||||
|
||||
public JFreeChartHeartbeatPlotPane(NetViewerGUI gui) {
|
||||
super(gui);
|
||||
}
|
||||
|
||||
public void stateUpdated() {
|
||||
if (_panel == null) {
|
||||
remove(0); // remove the dummy
|
||||
|
||||
_adapter = new JFreeChartAdapter();
|
||||
_panel = _adapter.createPanel(_gui.getViewer());
|
||||
_panel.setBackground(_gui.getBackground());
|
||||
JScrollPane pane = new JScrollPane(_panel);
|
||||
pane.setBackground(_gui.getBackground());
|
||||
add(pane, BorderLayout.CENTER);
|
||||
} else {
|
||||
_adapter.updateChart(_panel, _gui.getViewer());
|
||||
//_gui.pack();
|
||||
}
|
||||
}
|
||||
|
||||
protected void initializeComponents() {
|
||||
// noop
|
||||
setLayout(new BorderLayout());
|
||||
add(new JLabel(), BorderLayout.CENTER);
|
||||
//dummy.setBackground(_gui.getBackground());
|
||||
//dummy.setPreferredSize(new Dimension(800,600));
|
||||
//add(dummy);
|
||||
setPreferredSize(new Dimension(800,600));
|
||||
//add(_panel);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package net.i2p.netmonitor.gui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.netmonitor.NetMonitor;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Coordinate the visualization of the network monitor. <p />
|
||||
*
|
||||
* <b>Usage: <code>NetViewer [exportDir]</code></b> <br />
|
||||
* (exportDir is where the NetMonitor exports its state, "monitorData" by default)
|
||||
*/
|
||||
public class NetViewer {
|
||||
private static final Log _log = new Log(NetViewer.class);
|
||||
private NetMonitor _monitor;
|
||||
private NetViewerGUI _gui;
|
||||
private Map _plotConfigs;
|
||||
private boolean _isClosed;
|
||||
|
||||
public NetViewer() {
|
||||
this(NetMonitor.EXPORT_DIR_DEFAULT);
|
||||
}
|
||||
public NetViewer(String dataDir) {
|
||||
_monitor = new NetMonitor();
|
||||
_monitor.setExportDir(dataDir);
|
||||
_isClosed = false;
|
||||
_gui = new NetViewerGUI(this);
|
||||
_plotConfigs = new HashMap();
|
||||
}
|
||||
|
||||
public void runViewer() {
|
||||
I2PThread t = new I2PThread(new NetViewerRunner(this));
|
||||
t.setName("NetViewer");
|
||||
t.setDaemon(false);
|
||||
t.start();
|
||||
}
|
||||
|
||||
void refreshGUI() {
|
||||
_gui.stateUpdated();
|
||||
}
|
||||
|
||||
void reloadData() {
|
||||
_log.debug("Reloading data");
|
||||
_monitor.importData();
|
||||
refreshGUI();
|
||||
}
|
||||
|
||||
public void addConfig(String peerName, PeerPlotConfig config) {
|
||||
synchronized (_plotConfigs) {
|
||||
_plotConfigs.put(peerName, config);
|
||||
}
|
||||
}
|
||||
|
||||
public PeerPlotConfig getConfig(String peerName) {
|
||||
synchronized (_plotConfigs) {
|
||||
return (PeerPlotConfig)_plotConfigs.get(peerName);
|
||||
}
|
||||
}
|
||||
|
||||
public List getConfigNames() {
|
||||
synchronized (_plotConfigs) {
|
||||
return new ArrayList(_plotConfigs.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
public NetMonitor getMonitor() { return _monitor; }
|
||||
|
||||
long getDataLoadDelay() { return _monitor.getExportDelay(); }
|
||||
|
||||
/** has the viewer been closed? */
|
||||
public boolean getIsClosed() { return _isClosed; }
|
||||
public void setIsClosed(boolean closed) { _isClosed = closed; }
|
||||
|
||||
public static final void main(String args[]) {
|
||||
if (args.length == 1)
|
||||
new NetViewer(args[0]).runViewer();
|
||||
else
|
||||
new NetViewer().runViewer();
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package net.i2p.netmonitor.gui;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
class NetViewerCommandBar extends JPanel {
|
||||
private NetViewerGUI _gui;
|
||||
private JComboBox _refreshRate;
|
||||
private JTextField _location;
|
||||
|
||||
/**
|
||||
* Constructs a command bar onto the gui
|
||||
* @param gui the gui the command bar is associated with
|
||||
*/
|
||||
public NetViewerCommandBar(NetViewerGUI gui) {
|
||||
_gui = gui;
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
private void refreshChanged(ItemEvent evt) {}
|
||||
private void loadCalled() {
|
||||
//_gui.getMonitor().load(_location.getText());
|
||||
}
|
||||
|
||||
private void browseCalled() {
|
||||
JFileChooser chooser = new JFileChooser(_location.getText());
|
||||
chooser.setBackground(_gui.getBackground());
|
||||
chooser.setMultiSelectionEnabled(false);
|
||||
int rv = chooser.showDialog(this, "Load");
|
||||
//if (rv == JFileChooser.APPROVE_OPTION)
|
||||
// _gui.getMonitor().load(chooser.getSelectedFile().getAbsolutePath());
|
||||
}
|
||||
|
||||
private void initializeComponents() {
|
||||
_refreshRate = new JComboBox(new DefaultComboBoxModel(new Object[] {"10 second refresh", "30 second refresh", "1 minute refresh", "5 minute refresh"}));
|
||||
_refreshRate.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent evt) { refreshChanged(evt); } });
|
||||
_refreshRate.setEnabled(false);
|
||||
_refreshRate.setBackground(_gui.getBackground());
|
||||
//add(_refreshRate);
|
||||
JLabel loadLabel = new JLabel("Load from: ");
|
||||
loadLabel.setBackground(_gui.getBackground());
|
||||
add(loadLabel);
|
||||
_location = new JTextField(20);
|
||||
_location.setToolTipText("Either specify a local filename or a fully qualified URL");
|
||||
_location.setBackground(_gui.getBackground());
|
||||
_location.setEnabled(false);
|
||||
add(_location);
|
||||
JButton browse = new JButton("Browse...");
|
||||
browse.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { browseCalled(); } });
|
||||
browse.setBackground(_gui.getBackground());
|
||||
browse.setEnabled(false);
|
||||
add(browse);
|
||||
JButton load = new JButton("Load");
|
||||
load.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { loadCalled(); } });
|
||||
load.setBackground(_gui.getBackground());
|
||||
load.setEnabled(false);
|
||||
add(load);
|
||||
setBackground(_gui.getBackground());
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
package net.i2p.netmonitor.gui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTabbedPane;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Render the control widgets (refresh/load/snapshot and the
|
||||
* tabbed panel with the plot config data)
|
||||
*
|
||||
*/
|
||||
class NetViewerControlPane extends JPanel {
|
||||
private final static Log _log = new Log(NetViewerControlPane.class);
|
||||
private NetViewerGUI _gui;
|
||||
private JTabbedPane _configPane;
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
private final static Color LIGHT_BLUE = new Color(180, 180, 255);
|
||||
private final static Color BLACK = new Color(0, 0, 0);
|
||||
private Color _background = WHITE;
|
||||
private Color _foreground = BLACK;
|
||||
|
||||
/**
|
||||
* Constructs a control panel onto the gui
|
||||
* @param gui the gui the panel is associated with
|
||||
*/
|
||||
public NetViewerControlPane(NetViewerGUI gui) {
|
||||
_gui = gui;
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
/** the settings have changed - revise */
|
||||
void refreshView() {
|
||||
_gui.refreshView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback: when tests have changed
|
||||
*/
|
||||
public synchronized void stateUpdated() {
|
||||
List knownNames = new ArrayList(8);
|
||||
List peers = _gui.getViewer().getMonitor().getPeers();
|
||||
_log.debug("GUI updated with peers: " + peers);
|
||||
for (int i = 0; i < peers.size(); i++) {
|
||||
String name = (String)peers.get(i);
|
||||
String shortName = name.substring(0,4);
|
||||
knownNames.add(shortName);
|
||||
if (_configPane.indexOfTab(shortName) >= 0) {
|
||||
JScrollPane pane = (JScrollPane)_configPane.getComponentAt(_configPane.indexOfTab(shortName));
|
||||
PeerPlotConfigPane cfgPane = (PeerPlotConfigPane)pane.getViewport().getView();
|
||||
cfgPane.stateUpdated();
|
||||
_log.debug("We already know about [" + name + "]");
|
||||
} else {
|
||||
_log.info("The peer [" + name + "] is new to us");
|
||||
PeerPlotConfig cfg = new PeerPlotConfig(_gui.getViewer().getMonitor().getSummary(name));
|
||||
_gui.getViewer().addConfig(name, cfg);
|
||||
PeerPlotConfigPane pane = new PeerPlotConfigPane(cfg, this);
|
||||
JScrollPane p = new JScrollPane(pane);
|
||||
p.setBackground(_background);
|
||||
_configPane.addTab(shortName, null, p, "Peer " + name);
|
||||
_configPane.setBackgroundAt(_configPane.getTabCount()-1, _background);
|
||||
_configPane.setForegroundAt(_configPane.getTabCount()-1, _foreground);
|
||||
}
|
||||
}
|
||||
List toRemove = new ArrayList(4);
|
||||
for (int i = 0; i < _configPane.getTabCount(); i++) {
|
||||
if (knownNames.contains(_configPane.getTitleAt(i))) {
|
||||
// noop
|
||||
} else {
|
||||
toRemove.add(_configPane.getTitleAt(i));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < toRemove.size(); i++) {
|
||||
String title = (String)toRemove.get(i);
|
||||
_log.info("Removing peer [" + title + "]");
|
||||
_configPane.removeTabAt(_configPane.indexOfTab(title));
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeComponents() {
|
||||
if (_gui != null)
|
||||
setBackground(_gui.getBackground());
|
||||
else
|
||||
setBackground(_background);
|
||||
setLayout(new BorderLayout());
|
||||
NetViewerCommandBar bar = new NetViewerCommandBar(_gui);
|
||||
bar.setBackground(getBackground());
|
||||
add(bar, BorderLayout.NORTH);
|
||||
_configPane = new JTabbedPane(JTabbedPane.LEFT);
|
||||
_configPane.setBackground(_background);
|
||||
JScrollPane pane = new JScrollPane(_configPane);
|
||||
pane.setBackground(_background);
|
||||
add(pane, BorderLayout.CENTER);
|
||||
//setPreferredSize(new Dimension(800, 400));
|
||||
//setMinimumSize(new Dimension(800, 400));
|
||||
//setMaximumSize(new Dimension(800, 400));
|
||||
}
|
||||
|
||||
NetViewer getViewer() { return _gui.getViewer(); }
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package net.i2p.netmonitor.gui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSplitPane;
|
||||
|
||||
class NetViewerGUI extends JFrame {
|
||||
private NetViewer _viewer;
|
||||
private NetViewerPlotPane _plotPane;
|
||||
private NetViewerControlPane _controlPane;
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
private Color _background = WHITE;
|
||||
|
||||
/**
|
||||
* Creates the GUI for all youz who be too shoopid for text based shitz
|
||||
* @param monitor the monitor the gui operates over
|
||||
*/
|
||||
public NetViewerGUI(NetViewer viewer) {
|
||||
super("Network Viewer");
|
||||
_viewer = viewer;
|
||||
initializeComponents();
|
||||
pack();
|
||||
//setResizable(false);
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
NetViewer getViewer() { return _viewer; }
|
||||
|
||||
/** build up all our widgets */
|
||||
private void initializeComponents() {
|
||||
getContentPane().setLayout(new BorderLayout());
|
||||
|
||||
setBackground(_background);
|
||||
|
||||
_plotPane = new JFreeChartHeartbeatPlotPane(this); // // new NetViewerPlotPane(this); //
|
||||
_plotPane.setBackground(_background);
|
||||
JScrollPane pane = new JScrollPane(_plotPane);
|
||||
pane.setBackground(_background);
|
||||
//getContentPane().add(pane, BorderLayout.CENTER);
|
||||
|
||||
_controlPane = new NetViewerControlPane(this);
|
||||
_controlPane.setBackground(_background);
|
||||
//getContentPane().add(_controlPane, BorderLayout.SOUTH);
|
||||
|
||||
JSplitPane split = new JSplitPane(JSplitPane.VERTICAL_SPLIT, _plotPane, _controlPane);
|
||||
//split.setDividerLocation(0.3d);
|
||||
getContentPane().add(split, BorderLayout.CENTER);
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
initializeMenus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback: when the state of the world changes . . .
|
||||
*/
|
||||
public void stateUpdated() {
|
||||
_controlPane.stateUpdated();
|
||||
_plotPane.stateUpdated();
|
||||
//pack();
|
||||
}
|
||||
|
||||
public void refreshView() {
|
||||
_plotPane.stateUpdated();
|
||||
}
|
||||
|
||||
private void exitCalled() {
|
||||
_viewer.setIsClosed(true);
|
||||
setVisible(false);
|
||||
System.exit(0);
|
||||
}
|
||||
private void loadConfigCalled() {}
|
||||
private void saveConfigCalled() {}
|
||||
private void loadSnapshotCalled() {}
|
||||
private void saveSnapshotCalled() {}
|
||||
|
||||
private void initializeMenus() {
|
||||
JMenuBar bar = new JMenuBar();
|
||||
JMenu fileMenu = new JMenu("File");
|
||||
JMenuItem loadConfig = new JMenuItem("Load config");
|
||||
loadConfig.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { loadConfigCalled(); } });
|
||||
JMenuItem saveConfig = new JMenuItem("Save config");
|
||||
saveConfig.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { saveConfigCalled(); } });
|
||||
JMenuItem saveSnapshot = new JMenuItem("Save snapshot");
|
||||
saveSnapshot.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { saveSnapshotCalled(); } });
|
||||
JMenuItem loadSnapshot = new JMenuItem("Load snapshot");
|
||||
loadSnapshot.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { loadSnapshotCalled(); } });
|
||||
JMenuItem exit = new JMenuItem("Exit");
|
||||
exit.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { exitCalled(); } });
|
||||
|
||||
fileMenu.add(loadConfig);
|
||||
fileMenu.add(saveConfig);
|
||||
fileMenu.add(loadSnapshot);
|
||||
fileMenu.add(saveSnapshot);
|
||||
fileMenu.add(exit);
|
||||
bar.add(fileMenu);
|
||||
setJMenuBar(bar);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package net.i2p.netmonitor.gui;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextArea;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Render the graph and legend
|
||||
*/
|
||||
class NetViewerPlotPane extends JPanel {
|
||||
private final static Log _log = new Log(NetViewerPlotPane.class);
|
||||
protected NetViewerGUI _gui;
|
||||
private JTextArea _text;
|
||||
|
||||
/**
|
||||
* Constructs the plot pane
|
||||
* @param gui the gui the pane is attached to
|
||||
*/
|
||||
public NetViewerPlotPane(NetViewerGUI gui) {
|
||||
_gui = gui;
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback: when things change . . .
|
||||
*/
|
||||
public void stateUpdated() {
|
||||
StringBuffer buf = new StringBuffer(32*1024);
|
||||
buf.append("moo");
|
||||
_text.setText(buf.toString());
|
||||
}
|
||||
|
||||
protected void initializeComponents() {
|
||||
setBackground(_gui.getBackground());
|
||||
//Dimension size = new Dimension(800, 600);
|
||||
_text = new JTextArea("",30,80); // 16, 60);
|
||||
_text.setAutoscrolls(true);
|
||||
_text.setEditable(false);
|
||||
// _text.setLineWrap(true);
|
||||
// add(new JScrollPane(_text));
|
||||
add(_text);
|
||||
// add(new JScrollPane(_text, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS));
|
||||
// setPreferredSize(size);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package net.i2p.netmonitor.gui;
|
||||
|
||||
class NetViewerRunner implements Runnable {
|
||||
private NetViewer _viewer;
|
||||
|
||||
public NetViewerRunner(NetViewer viewer) {
|
||||
_viewer = viewer;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
while (!_viewer.getIsClosed()) {
|
||||
_viewer.reloadData();
|
||||
try { Thread.sleep(_viewer.getDataLoadDelay()*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
package net.i2p.netmonitor.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.netmonitor.PeerSummary;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Configure how we want to render a particular peerSummary in the GUI
|
||||
*/
|
||||
class PeerPlotConfig {
|
||||
private final static Log _log = new Log(PeerPlotConfig.class);
|
||||
/** where can we find the current state/data (either as a filename or a URL)? */
|
||||
private String _location;
|
||||
/** what test are we defining the plot data for? */
|
||||
private PeerSummary _summary;
|
||||
/** how should we render the current data set? */
|
||||
private PlotSeriesConfig _seriesConfig;
|
||||
private Set _listeners;
|
||||
private boolean _disabled;
|
||||
|
||||
/**
|
||||
* Delegating constructor . . .
|
||||
* @param location the name of the file/URL to get the data from
|
||||
*/
|
||||
public PeerPlotConfig(String location) {
|
||||
this(location, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegating constructor . . .
|
||||
* @param location the name of the file/URL to get the data from
|
||||
*/
|
||||
public PeerPlotConfig(PeerSummary summary) {
|
||||
this(null, summary, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a config =)
|
||||
* @param location the location of the file/URL to get the data from
|
||||
* @param config the client's configuration
|
||||
* @param seriesConfig the series config
|
||||
*/
|
||||
public PeerPlotConfig(String location, PeerSummary summary, PlotSeriesConfig seriesConfig) {
|
||||
_location = location;
|
||||
_summary = summary;
|
||||
if (seriesConfig != null)
|
||||
_seriesConfig = seriesConfig;
|
||||
else
|
||||
_seriesConfig = new PlotSeriesConfig();
|
||||
|
||||
_listeners = Collections.synchronizedSet(new HashSet(2));
|
||||
_disabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Where is the current state data supposed to be found? This must either be a
|
||||
* local file path or a URL
|
||||
* @return the current location
|
||||
*/
|
||||
public String getLocation() { return _location; }
|
||||
|
||||
/**
|
||||
* The location the current state data is supposed to be found. This must either be
|
||||
* a local file path or a URL
|
||||
* @param location the location
|
||||
*/
|
||||
public void setLocation(String location) {
|
||||
_location = location;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* What are we configuring?
|
||||
* @return the client configuration
|
||||
*/
|
||||
public PeerSummary getSummary() { return _summary; }
|
||||
|
||||
/**
|
||||
* Sets what we are currently configuring
|
||||
* @param config the new config
|
||||
*/
|
||||
public void setPeerSummary(PeerSummary summary) {
|
||||
_summary = summary;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* How do we want to render the current data set?
|
||||
* @return the way we currently render the data
|
||||
*/
|
||||
public PlotSeriesConfig getSeriesConfig() { return _seriesConfig; }
|
||||
|
||||
/**
|
||||
* Sets how we want to render the current data set.
|
||||
* @param config the new config
|
||||
*/
|
||||
public void setSeriesConfig(PlotSeriesConfig config) {
|
||||
_seriesConfig = config;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* four char description of the peer
|
||||
* @return the name
|
||||
*/
|
||||
public String getPeerName() { return _summary.getPeer(); }
|
||||
|
||||
/**
|
||||
* we've got someone who wants to be notified of changes to the plot config
|
||||
* @param lsnr the listener to be added
|
||||
*/
|
||||
public void addListener(UpdateListener lsnr) { _listeners.add(lsnr); }
|
||||
|
||||
/**
|
||||
* remove a listener
|
||||
* @param lsnr the listener to remove
|
||||
*/
|
||||
public void removeListener(UpdateListener lsnr) { _listeners.remove(lsnr); }
|
||||
|
||||
void fireUpdate() {
|
||||
if (_disabled) return;
|
||||
for (Iterator iter = _listeners.iterator(); iter.hasNext(); ) {
|
||||
((UpdateListener)iter.next()).configUpdated(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables notification of events listeners
|
||||
* @see PeerPlotConfig#fireUpdate()
|
||||
*/
|
||||
public void disableEvents() { _disabled = true; }
|
||||
|
||||
/**
|
||||
* Enables notification of events listeners
|
||||
* @see PeerPlotConfig#fireUpdate()
|
||||
*/
|
||||
public void enableEvents() { _disabled = false; }
|
||||
|
||||
/**
|
||||
* How do we want to render a particular dataset (either the current or the averaged values)?
|
||||
*/
|
||||
public class PlotSeriesConfig {
|
||||
private Map _shouldPlot;
|
||||
private Map _plotColors;
|
||||
|
||||
/**
|
||||
* Creates a config for the rendering of a particular dataset)
|
||||
* @param plotLost do we plot lost packets?
|
||||
* @param plotColor in what color?
|
||||
*/
|
||||
public PlotSeriesConfig() {
|
||||
_shouldPlot = new HashMap(16);
|
||||
_plotColors = new HashMap(16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the plot config this plot series config is a part of
|
||||
* @return the plot config
|
||||
*/
|
||||
public PeerPlotConfig getPlotConfig() { return PeerPlotConfig.this; }
|
||||
|
||||
public boolean getShouldPlotValue(String statName, String argName, boolean defaultVal) {
|
||||
Boolean val = (Boolean)_shouldPlot.get(statName + argName);
|
||||
if (val == null)
|
||||
return defaultVal;
|
||||
else
|
||||
return val.booleanValue();
|
||||
}
|
||||
|
||||
public void setShouldPlotValue(String statName, String argName, boolean shouldPlot) {
|
||||
_shouldPlot.put(statName + argName, new Boolean(shouldPlot));
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* What color should we plot the data with?
|
||||
* @return the color
|
||||
*/
|
||||
public Color getPlotLineColor(String statName, String argName) {
|
||||
return (Color)_plotColors.get(statName + argName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color we should plot the data with
|
||||
* @param color the color to use
|
||||
*/
|
||||
public void setPlotLineColor(String statName, String argName, Color color) {
|
||||
if (color == null)
|
||||
_plotColors.remove(statName + argName);
|
||||
else
|
||||
_plotColors.put(statName + argName, color);
|
||||
fireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for listening to updates . . .
|
||||
*/
|
||||
public interface UpdateListener {
|
||||
/**
|
||||
* @param config the peer plot config that changes
|
||||
* @see PeerPlotConfig#fireUpdate()
|
||||
*/
|
||||
void configUpdated(PeerPlotConfig config);
|
||||
}
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
package net.i2p.netmonitor.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
import net.i2p.netmonitor.PeerStat;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
class PeerPlotConfigPane extends JPanel implements PeerPlotConfig.UpdateListener {
|
||||
private final static Log _log = new Log(PeerPlotConfigPane.class);
|
||||
private PeerPlotConfig _config;
|
||||
private NetViewerControlPane _parent;
|
||||
private JLabel _peer;
|
||||
private JButton _toggleAll;
|
||||
private OptionGroup _options[];
|
||||
private Random _rnd = new Random();
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
private Color _background = WHITE;
|
||||
|
||||
/**
|
||||
* Constructs a pane
|
||||
* @param config the plot config it represents
|
||||
* @param pane the pane this one is attached to
|
||||
*/
|
||||
public PeerPlotConfigPane(PeerPlotConfig config, NetViewerControlPane pane) {
|
||||
_config = config;
|
||||
_parent = pane;
|
||||
if (_parent != null)
|
||||
_background = _parent.getBackground();
|
||||
_config.addListener(this);
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
private void initializeComponents() {
|
||||
buildComponents();
|
||||
placeComponents(this);
|
||||
refreshView();
|
||||
//setBorder(new BevelBorder(BevelBorder.RAISED));
|
||||
setBackground(_background);
|
||||
}
|
||||
|
||||
/**
|
||||
* place all the gui components onto the given panel
|
||||
* @param body the panel to place the components on
|
||||
*/
|
||||
private void placeComponents(JPanel body) {
|
||||
body.setLayout(new GridBagLayout());
|
||||
GridBagConstraints cts = new GridBagConstraints();
|
||||
|
||||
// row 0: peer name + toggle All
|
||||
cts.gridx = 0;
|
||||
cts.gridy = 0;
|
||||
cts.gridwidth = 2;
|
||||
cts.anchor = GridBagConstraints.WEST;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
body.add(_peer, cts);
|
||||
cts.gridx = 2;
|
||||
cts.gridy = 0;
|
||||
cts.gridwidth = 1;
|
||||
cts.anchor = GridBagConstraints.EAST;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
body.add(_toggleAll, cts);
|
||||
|
||||
int row = 0;
|
||||
for (int i = 0; i < _options.length; i++) {
|
||||
row++;
|
||||
cts.gridx = 0;
|
||||
cts.gridy = row;
|
||||
cts.gridwidth = 3;
|
||||
cts.weightx = 5;
|
||||
cts.fill = GridBagConstraints.BOTH;
|
||||
cts.anchor = GridBagConstraints.WEST;
|
||||
body.add(_options[i]._statName, cts);
|
||||
|
||||
for (int j = 0; j < _options[i]._statAttributes.size(); j++) {
|
||||
row++;
|
||||
cts.gridx = 0;
|
||||
cts.gridy = row;
|
||||
cts.gridwidth = 1;
|
||||
cts.weightx = 1;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
body.add(new JLabel(" "), cts);
|
||||
cts.gridx = 1;
|
||||
cts.gridy = row;
|
||||
cts.gridwidth = 1;
|
||||
cts.weightx = 5;
|
||||
cts.fill = GridBagConstraints.BOTH;
|
||||
JCheckBox box = (JCheckBox)_options[i]._statAttributes.get(j);
|
||||
box.setBackground(_background);
|
||||
body.add(box, cts);
|
||||
cts.gridx = 2;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
cts.anchor = GridBagConstraints.EAST;
|
||||
JButton toggleAttr = new JButton("Toggle all");
|
||||
toggleAttr.setBackground(_background);
|
||||
toggleAttr.addActionListener(new ToggleAttribute(_options[i].getStatName(), box.getText(), box));
|
||||
body.add(toggleAttr, cts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ToggleAttribute implements ActionListener {
|
||||
private String _statName;
|
||||
private String _attrName;
|
||||
private JCheckBox _box;
|
||||
public ToggleAttribute(String statName, String attrName, JCheckBox box) {
|
||||
_statName = statName;
|
||||
_attrName = attrName;
|
||||
_box = box;
|
||||
}
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
boolean setActive = true;
|
||||
if (_box.isSelected()) {
|
||||
setActive = false;
|
||||
}
|
||||
|
||||
List names = _parent.getViewer().getConfigNames();
|
||||
for (int i = 0; i < names.size(); i++) {
|
||||
String name = (String)names.get(i);
|
||||
PeerPlotConfig cfg = _parent.getViewer().getConfig(name);
|
||||
cfg.getSeriesConfig().setShouldPlotValue(_statName, _attrName, setActive);
|
||||
_log.debug("Setting " + _statName + "." + _attrName + " to " + setActive + " for " + name);
|
||||
}
|
||||
_parent.stateUpdated();
|
||||
_parent.refreshView();
|
||||
}
|
||||
}
|
||||
|
||||
/** build all of the gui components */
|
||||
private void buildComponents() {
|
||||
_peer = new JLabel("Router: " + _config.getPeerName());
|
||||
|
||||
_toggleAll = new JButton("Show all");
|
||||
_toggleAll.setBackground(_background);
|
||||
_toggleAll.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
_config.disableEvents();
|
||||
for (int i = 0; i < _options.length; i++) {
|
||||
for (int j = 0; j < _options[i]._statAttributes.size(); j++) {
|
||||
JCheckBox box = (JCheckBox)_options[i]._statAttributes.get(j);
|
||||
String statName = _options[i].getStatName();
|
||||
String attrName = box.getText();
|
||||
if (_toggleAll.getText().equals("Show all")) {
|
||||
box.setSelected(true);
|
||||
_config.getSeriesConfig().setShouldPlotValue(statName, attrName, true);
|
||||
} else {
|
||||
box.setSelected(false);
|
||||
_config.getSeriesConfig().setShouldPlotValue(statName, attrName, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_toggleAll.getText().equals("Show all"))
|
||||
_toggleAll.setText("Hide all");
|
||||
else
|
||||
_toggleAll.setText("Show all");
|
||||
_config.enableEvents();
|
||||
_config.fireUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
Set statNames = _config.getSummary().getStatNames();
|
||||
List options = new ArrayList(statNames.size());
|
||||
for (Iterator iter = statNames.iterator(); iter.hasNext(); ) {
|
||||
String statName = (String)iter.next();
|
||||
List data = _config.getSummary().getData(statName);
|
||||
if ( (data != null) && (data.size() > 0) ) {
|
||||
PeerStat stat = (PeerStat)data.get(0);
|
||||
String attributes[] = stat.getValueDescriptions();
|
||||
OptionGroup group = new OptionGroup(statName, attributes);
|
||||
options.add(group);
|
||||
}
|
||||
}
|
||||
TreeMap orderedOptions = new TreeMap();
|
||||
for (int i = 0; i < options.size(); i++) {
|
||||
OptionGroup grp = (OptionGroup)options.get(i);
|
||||
orderedOptions.put(grp.getStatName(), grp);
|
||||
}
|
||||
_options = new OptionGroup[options.size()];
|
||||
int i = 0;
|
||||
for (Iterator iter = orderedOptions.values().iterator(); iter.hasNext(); ) {
|
||||
OptionGroup grp = (OptionGroup)iter.next();
|
||||
_options[i] = grp;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/** the settings have changed - revise */
|
||||
private void refreshView() {
|
||||
_parent.refreshView();
|
||||
}
|
||||
|
||||
/**
|
||||
* notified that the config has been updated
|
||||
* @param config the config that was been updated
|
||||
*/
|
||||
public void configUpdated(PeerPlotConfig config) { refreshView(); }
|
||||
|
||||
public void stateUpdated() {
|
||||
for (int i = 0; i < _options.length; i++) {
|
||||
for (int j = 0; j < _options[i]._statAttributes.size(); j++) {
|
||||
JCheckBox box = (JCheckBox)_options[i]._statAttributes.get(j);
|
||||
if (_config.getSeriesConfig().getShouldPlotValue(_options[i].getStatName(), box.getText(), false))
|
||||
box.setSelected(true);
|
||||
else
|
||||
box.setSelected(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class OptionGroup {
|
||||
JTextField _statName;
|
||||
List _statAttributes;
|
||||
|
||||
public String getStatName() { return _statName.getText(); }
|
||||
|
||||
/**
|
||||
* Creates an OptionLine.
|
||||
* @param statName statistic group in question
|
||||
* @param statAttributes list of attributes we keep about this stat
|
||||
*/
|
||||
public OptionGroup(String statName, String statAttributes[]) {
|
||||
_statName = new JTextField(statName);
|
||||
_statName.setEditable(false);
|
||||
_statName.setBackground(_background);
|
||||
_statAttributes = new ArrayList(4);
|
||||
if (statAttributes != null) {
|
||||
for (int i = 0; i < statAttributes.length; i++) {
|
||||
JCheckBox box = new JCheckBox(statAttributes[i]);
|
||||
box.addActionListener(new UpdateListener(OptionGroup.this));
|
||||
_statAttributes.add(box);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateListener implements ActionListener {
|
||||
private OptionGroup _group;
|
||||
|
||||
/**
|
||||
* Update Listener constructor . . .
|
||||
* @param group the group of stats to watch
|
||||
*/
|
||||
public UpdateListener(OptionGroup group) {
|
||||
_group = group;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
|
||||
*/
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
_config.disableEvents();
|
||||
|
||||
for (int i = 0; i < _group._statAttributes.size(); i++) {
|
||||
JCheckBox box = (JCheckBox)_group._statAttributes.get(i);
|
||||
_config.getSeriesConfig().setShouldPlotValue(_group.getStatName(), box.getText(), box.isSelected());
|
||||
}
|
||||
_config.enableEvents();
|
||||
_config.fireUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
$Id$
|
||||
|
||||
the i2p/apps/phttprelay module is the root of the
|
||||
PHTTPRelay application, and everything within it
|
||||
is released according to the terms of the I2P
|
||||
license policy. That means everything contained
|
||||
within the i2p/apps/phttprelay module is released
|
||||
into the public domain unless otherwise marked.
|
||||
Alternate licenses that may be used include BSD,
|
||||
Cryptix, and MIT.
|
||||
@@ -1,43 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="phttprelay">
|
||||
<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" source="1.3" target="1.3" deprecation="on" destdir="./build/obj" classpath="../../../core/java/build/i2p.jar:lib/javax.servlet.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="compile">
|
||||
<war destfile="./build/phttprelay.war" webxml="web.xml">
|
||||
<classes dir="./build/obj/">
|
||||
<include name="**/*.class" />
|
||||
</classes>
|
||||
<lib dir="../../../core/java/build/">
|
||||
<include name="i2p.jar" />
|
||||
</lib>
|
||||
</war>
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="./src:../../../core/java/src" destdir="./build/javadoc"
|
||||
classpath="./lib/javax.servlet.jar"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
splitindex="true"
|
||||
windowtitle="I2P phttp relay" />
|
||||
</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>
|
||||
@@ -1,159 +0,0 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Jetty License</TITLE>
|
||||
</HEAD>
|
||||
<BODY BGCOLOR="#FFFFFF">
|
||||
<FONT FACE=ARIAL,HELVETICA>
|
||||
<CENTER><FONT SIZE=+3><B>Jetty License</B></FONT></CENTER>
|
||||
<CENTER><FONT SIZE=-1><B>$Revision: 1.1 $</B></FONT></CENTER>
|
||||
|
||||
<B>Preamble:</B><p>
|
||||
|
||||
The intent of this document is to state the conditions under which the
|
||||
Jetty Package may be copied, such that the Copyright Holder maintains some
|
||||
semblance of control over the development of the package, while giving the
|
||||
users of the package the right to use, distribute and make reasonable
|
||||
modifications to the Package in accordance with the goals and ideals of
|
||||
the Open Source concept as described at
|
||||
<A HREF="http://www.opensource.org">http://www.opensource.org</A>.
|
||||
<P>
|
||||
It is the intent of this license to allow commercial usage of the Jetty
|
||||
package, so long as the source code is distributed or suitable visible
|
||||
credit given or other arrangements made with the copyright holders.
|
||||
|
||||
<P><B>Definitions:</B><P>
|
||||
|
||||
<UL>
|
||||
<LI> "Jetty" refers to the collection of Java classes that are
|
||||
distributed as a HTTP server with servlet capabilities and
|
||||
associated utilities.<p>
|
||||
|
||||
<LI> "Package" refers to the collection of files distributed by the
|
||||
Copyright Holder, and derivatives of that collection of files
|
||||
created through textual modification.<P>
|
||||
|
||||
<LI> "Standard Version" refers to such a Package if it has not been
|
||||
modified, or has been modified in accordance with the wishes
|
||||
of the Copyright Holder.<P>
|
||||
|
||||
<LI> "Copyright Holder" is whoever is named in the copyright or
|
||||
copyrights for the package. <BR>
|
||||
Mort Bay Consulting Pty. Ltd. (Australia) is the "Copyright
|
||||
Holder" for the Jetty package.<P>
|
||||
|
||||
<LI> "You" is you, if you're thinking about copying or distributing
|
||||
this Package.<P>
|
||||
|
||||
<LI> "Reasonable copying fee" is whatever you can justify on the
|
||||
basis of media cost, duplication charges, time of people involved,
|
||||
and so on. (You will not be required to justify it to the
|
||||
Copyright Holder, but only to the computing community at large
|
||||
as a market that must bear the fee.)<P>
|
||||
|
||||
<LI> "Freely Available" means that no fee is charged for the item
|
||||
itself, though there may be fees involved in handling the item.
|
||||
It also means that recipients of the item may redistribute it
|
||||
under the same conditions they received it.<P>
|
||||
</UL>
|
||||
|
||||
0. The Jetty Package is Copyright (c) Mort Bay Consulting Pty. Ltd.
|
||||
(Australia) and others. Individual files in this package may contain
|
||||
additional copyright notices. The javax.servlet packages are copyright
|
||||
Sun Microsystems Inc. <P>
|
||||
|
||||
1. The Standard Version of the Jetty package is
|
||||
available from <A HREF=http://jetty.mortbay.org>http://jetty.mortbay.org</A>.<P>
|
||||
|
||||
2. You may make and distribute verbatim copies of the source form
|
||||
of the Standard Version of this Package without restriction, provided that
|
||||
you include this license and all of the original copyright notices
|
||||
and associated disclaimers.<P>
|
||||
|
||||
3. You may make and distribute verbatim copies of the compiled form of the
|
||||
Standard Version of this Package without restriction, provided that you
|
||||
include this license.<P>
|
||||
|
||||
4. You may apply bug fixes, portability fixes and other modifications
|
||||
derived from the Public Domain or from the Copyright Holder. A Package
|
||||
modified in such a way shall still be considered the Standard Version.<P>
|
||||
|
||||
5. You may otherwise modify your copy of this Package in any way, provided
|
||||
that you insert a prominent notice in each changed file stating how and
|
||||
when you changed that file, and provided that you do at least ONE of the
|
||||
following:<P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
a) Place your modifications in the Public Domain or otherwise make them
|
||||
Freely Available, such as by posting said modifications to Usenet or
|
||||
an equivalent medium, or placing the modifications on a major archive
|
||||
site such as ftp.uu.net, or by allowing the Copyright Holder to include
|
||||
your modifications in the Standard Version of the Package.<P>
|
||||
|
||||
b) Use the modified Package only within your corporation or organization.<P>
|
||||
|
||||
c) Rename any non-standard classes so the names do not conflict
|
||||
with standard classes, which must also be provided, and provide
|
||||
a separate manual page for each non-standard class that clearly
|
||||
documents how it differs from the Standard Version.<P>
|
||||
|
||||
d) Make other arrangements with the Copyright Holder.<P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
6. You may distribute modifications or subsets of this Package in source
|
||||
code or compiled form, provided that you do at least ONE of the following:<P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
a) Distribute this license and all original copyright messages, together
|
||||
with instructions (in the about dialog, manual page or equivalent) on where
|
||||
to get the complete Standard Version.<P>
|
||||
|
||||
b) Accompany the distribution with the machine-readable source of
|
||||
the Package with your modifications. The modified package must include
|
||||
this license and all of the original copyright notices and associated
|
||||
disclaimers, together with instructions on where to get the complete
|
||||
Standard Version.<P>
|
||||
|
||||
c) Make other arrangements with the Copyright Holder.<P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
7. You may charge a reasonable copying fee for any distribution of this
|
||||
Package. You may charge any fee you choose for support of this Package.
|
||||
You may not charge a fee for this Package itself. However,
|
||||
you may distribute this Package in aggregate with other (possibly
|
||||
commercial) programs as part of a larger (possibly commercial) software
|
||||
distribution provided that you meet the other distribution requirements
|
||||
of this license.<P>
|
||||
|
||||
8. Input to or the output produced from the programs of this Package
|
||||
do not automatically fall under the copyright of this Package, but
|
||||
belong to whomever generated them, and may be sold commercially, and
|
||||
may be aggregated with this Package.<P>
|
||||
|
||||
9. Any program subroutines supplied by you and linked into this Package
|
||||
shall not be considered part of this Package.<P>
|
||||
|
||||
10. The name of the Copyright Holder may not be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.<P>
|
||||
|
||||
11. This license may change with each release of a Standard Version of
|
||||
the Package. You may choose to use the license associated with version
|
||||
you are using or the license of the latest Standard Version.<P>
|
||||
|
||||
12. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.<P>
|
||||
|
||||
13. If any superior law implies a warranty, the sole remedy under such shall
|
||||
be , at the Copyright Holders option either a) return of any price paid or
|
||||
b) use or reasonable endeavours to repair or replace the software.<P>
|
||||
|
||||
14. This license shall be read under the laws of Australia. <P>
|
||||
|
||||
<center>The End</center>
|
||||
|
||||
<center><FONT size=-1>This license was derived from the <I>Artistic</I> license published
|
||||
on <a href=http://www.opensource.org>http://www.opensource.com</a></font></center>
|
||||
</FONT>
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,6 +0,0 @@
|
||||
The file javax.servlet.jar is distributed under the terms of LICENSE.html,
|
||||
which is the implementation of the java servlet classes as retrieved from
|
||||
http://jetty.mortbay.org/jetty/
|
||||
|
||||
It is only included to assist in building the phttprelay.war file on hosts
|
||||
that do not have a servlet container / implementation.
|
||||
@@ -1,114 +0,0 @@
|
||||
package net.i2p.phttprelay;
|
||||
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Check the status of previous message delivery, returning either pending or
|
||||
* unknown, where pending means that particular message ID for that particular
|
||||
* target is still on the server, and unknown means it has either not been created
|
||||
* or it has been sent successfully. It does this by sending HTTP 204 (NO CONTENT)
|
||||
* for pending, and HTTP 404 (NOT FOUND) for unknown. <p />
|
||||
*
|
||||
* This servlet should be set up in web.xml as follows:
|
||||
*
|
||||
* <servlet>
|
||||
* <servlet-name>CheckSendStatus</servlet-name>
|
||||
* <servlet-class>net.i2p.phttprelay.CheckSendStatusServlet</servlet-class>
|
||||
* <init-param>
|
||||
* <param-name>baseDir</param-name>
|
||||
* <param-value>/usr/local/jetty/phttprelayDir</param-value>
|
||||
* </init-param>
|
||||
* </servlet>
|
||||
*
|
||||
* <servlet-mapping>
|
||||
* <servlet-name>CheckSendStatus</servlet-name>
|
||||
* <url-pattern>/phttpCheckSendStatus</url-pattern>
|
||||
* </servlet-mapping>
|
||||
*
|
||||
* baseDir is the directory under which registrants and their pending messages are stored
|
||||
*
|
||||
*/
|
||||
public class CheckSendStatusServlet extends PHTTPRelayServlet {
|
||||
/* URL parameters on the check */
|
||||
|
||||
/** H(routerIdent).toBase64() of the target to receive the message */
|
||||
public final static String PARAM_SEND_TARGET = "target";
|
||||
/** msgId parameter */
|
||||
public final static String PARAM_MSG_ID = "msgId";
|
||||
|
||||
public final static String PROP_STATUS = "status";
|
||||
public final static String STATUS_PENDING = "pending";
|
||||
public final static String STATUS_UNKNOWN = "unknown";
|
||||
|
||||
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
String target = req.getParameter(PARAM_SEND_TARGET);
|
||||
String msgIdStr = req.getParameter(PARAM_MSG_ID);
|
||||
|
||||
log("Checking status of [" + target + "] message [" + msgIdStr + "]");
|
||||
if (!isKnownMessage(target, msgIdStr)) {
|
||||
log("Not known - its not pending");
|
||||
notPending(req, resp);
|
||||
return;
|
||||
} else {
|
||||
log("Known - its still pending");
|
||||
pending(req, resp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isKnownMessage(String target, String msgId) throws IOException {
|
||||
if ((target == null) || (target.trim().length() <= 0)) return false;
|
||||
if ((msgId == null) || (msgId.trim().length() <= 0)) return false;
|
||||
File identDir = getIdentDir(target);
|
||||
if (identDir.exists()) {
|
||||
File identFile = new File(identDir, "identity.dat");
|
||||
if (identFile.exists()) {
|
||||
// known and valid (maybe we need to check the file format... naw, fuck it
|
||||
File msgFile = new File(identDir, "msg" + msgId + ".dat");
|
||||
if (msgFile.exists())
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void pending(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
resp.setStatus(HttpServletResponse.SC_OK);
|
||||
ServletOutputStream out = resp.getOutputStream();
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append(PROP_STATUS).append('=').append(STATUS_PENDING).append('\n');
|
||||
out.write(buf.toString().getBytes());
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
|
||||
private void notPending(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
resp.setStatus(HttpServletResponse.SC_OK);
|
||||
ServletOutputStream out = resp.getOutputStream();
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append(PROP_STATUS).append('=').append(STATUS_UNKNOWN).append('\n');
|
||||
out.write(buf.toString().getBytes());
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package net.i2p.phttprelay;
|
||||
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Lock identities for updating messages (so that they aren't read / deleted
|
||||
* while being written)
|
||||
*
|
||||
*/
|
||||
class LockManager {
|
||||
private volatile static Set _locks = new HashSet(); // target
|
||||
|
||||
public static void lockIdent(String target) {
|
||||
while (true) {
|
||||
synchronized (_locks) {
|
||||
if (!_locks.contains(target)) {
|
||||
_locks.add(target);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
_locks.wait(1000);
|
||||
} catch (InterruptedException ie) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void unlockIdent(String target) {
|
||||
synchronized (_locks) {
|
||||
_locks.remove(target);
|
||||
_locks.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package net.i2p.phttprelay;
|
||||
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
abstract class PHTTPRelayServlet extends HttpServlet {
|
||||
private Log _log = new Log(getClass());
|
||||
protected String _baseDir;
|
||||
|
||||
/* config params */
|
||||
/*public final static String PARAM_BASEDIR = "baseDir";*/
|
||||
public final static String ENV_BASEDIR = "phttpRelay.baseDir";
|
||||
|
||||
/** match the clock fudge factor on the router, rather than importing the entire router cvs module */
|
||||
public final static long CLOCK_FUDGE_FACTOR = 1 * 60 * 1000;
|
||||
|
||||
protected String buildURL(HttpServletRequest req, String path) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append(req.getScheme()).append("://");
|
||||
buf.append(req.getServerName()).append(":").append(req.getServerPort());
|
||||
buf.append(req.getContextPath());
|
||||
buf.append(path);
|
||||
log("URL built: " + buf.toString());
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
protected File getIdentDir(String target) throws IOException {
|
||||
if ((_baseDir == null) || (target == null)) throw new IOException("dir not specified to deal with");
|
||||
File baseDir = new File(_baseDir);
|
||||
if (!baseDir.exists()) {
|
||||
boolean created = baseDir.mkdirs();
|
||||
log("Creating PHTTP Relay Base Directory: " + baseDir.getAbsolutePath() + " - ok? " + created);
|
||||
}
|
||||
File identDir = new File(baseDir, target);
|
||||
log("Ident dir: " + identDir.getAbsolutePath());
|
||||
return identDir;
|
||||
}
|
||||
|
||||
public void init(ServletConfig config) throws ServletException {
|
||||
super.init(config);
|
||||
String dir = System.getProperty(ENV_BASEDIR);
|
||||
if (dir == null) {
|
||||
_log.warn("Base directory for the polling http relay system not in the environment [" + ENV_BASEDIR + "]");
|
||||
_log.warn("Setting the base directory to ./relayDir for " + getServletName());
|
||||
_baseDir = ".relayDir";
|
||||
} else {
|
||||
_baseDir = dir;
|
||||
log("Loaded up " + getServletName() + " with base directory " + _baseDir);
|
||||
}
|
||||
}
|
||||
|
||||
public void log(String msg) {
|
||||
_log.debug(msg);
|
||||
}
|
||||
|
||||
public void log(String msg, Throwable t) {
|
||||
_log.debug(msg, t);
|
||||
}
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
package net.i2p.phttprelay;
|
||||
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import net.i2p.crypto.DSAEngine;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
/**
|
||||
* Handle poll requests for new messages - checking the poll request for a valid signature,
|
||||
* sending back all of the messages found, and after all messages are written out, delete
|
||||
* them from the local store. If the signature fails, it sends back an HTTP 403 (UNAUTHORIZED).
|
||||
* If the target is not registered, it sends back an HTTP 404 (NOT FOUND) <p />
|
||||
*
|
||||
* This servlet should be set up in web.xml as follows:
|
||||
*
|
||||
* <servlet>
|
||||
* <servlet-name>Poll</servlet-name>
|
||||
* <servlet-class>net.i2p.phttprelay.PollServlet</servlet-class>
|
||||
* <init-param>
|
||||
* <param-name>baseDir</param-name>
|
||||
* <param-value>/usr/local/jetty/phttprelayDir</param-value>
|
||||
* </init-param>
|
||||
* </servlet>
|
||||
*
|
||||
* <servlet-mapping>
|
||||
* <servlet-name>Poll</servlet-name>
|
||||
* <url-pattern>/phttpPoll</url-pattern>
|
||||
* </servlet-mapping>
|
||||
*
|
||||
* baseDir is the directory under which registrants and their pending messages are stored
|
||||
*
|
||||
*/
|
||||
public class PollServlet extends PHTTPRelayServlet {
|
||||
/* URL parameters on the check */
|
||||
|
||||
/** H(routerIdent).toBase64() of the target to receive the message */
|
||||
public final static String PARAM_SEND_TARGET = "target";
|
||||
|
||||
/** HTTP error code if the target is not known*/
|
||||
public final static int CODE_UNKNOWN = HttpServletResponse.SC_NOT_FOUND;
|
||||
/** HTTP error code if the signature failed */
|
||||
public final static int CODE_UNAUTHORIZED = HttpServletResponse.SC_UNAUTHORIZED;
|
||||
/** HTTP error code if everything is ok */
|
||||
public final static int CODE_OK = HttpServletResponse.SC_OK;
|
||||
|
||||
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
byte data[] = getData(req);
|
||||
if (data == null) return;
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(data);
|
||||
String target = getTarget(bais);
|
||||
if (target == null) {
|
||||
log("Target not specified");
|
||||
resp.sendError(CODE_UNKNOWN);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isKnown(target)) {
|
||||
resp.sendError(CODE_UNKNOWN);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAuthorized(target, bais)) {
|
||||
resp.sendError(CODE_UNAUTHORIZED);
|
||||
return;
|
||||
} else {
|
||||
log("Authorized access for target " + target);
|
||||
}
|
||||
|
||||
sendMessages(resp, target);
|
||||
}
|
||||
|
||||
private byte[] getData(HttpServletRequest req) throws ServletException, IOException {
|
||||
ServletInputStream in = req.getInputStream();
|
||||
int len = req.getContentLength();
|
||||
byte data[] = new byte[len];
|
||||
int cur = 0;
|
||||
int read = DataHelper.read(in, data);
|
||||
if (read != len) {
|
||||
log("Size read is incorrect [" + read + " instead of expected " + len + "]");
|
||||
return null;
|
||||
} else {
|
||||
log("Read data length: " + data.length + " in base64: " + Base64.encode(data));
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
private String getTarget(InputStream in) throws IOException {
|
||||
StringBuffer buf = new StringBuffer(64);
|
||||
int numBytes = 0;
|
||||
int c = 0;
|
||||
while ((c = in.read()) != -1) {
|
||||
if (c == (int) '&') break;
|
||||
buf.append((char) c);
|
||||
numBytes++;
|
||||
if (numBytes > 128) {
|
||||
log("Target didn't find the & after 128 bytes [" + buf.toString() + "]");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (buf.toString().indexOf("target=") != 0) {
|
||||
log("Did not start with target= [" + buf.toString() + "]");
|
||||
return null;
|
||||
}
|
||||
return buf.substring("target=".length());
|
||||
}
|
||||
|
||||
private void sendMessages(HttpServletResponse resp, String target) throws IOException {
|
||||
log("Before lock " + target);
|
||||
LockManager.lockIdent(target);
|
||||
log("Locked " + target);
|
||||
try {
|
||||
File identDir = getIdentDir(target);
|
||||
expire(identDir);
|
||||
File messageFiles[] = identDir.listFiles();
|
||||
resp.setStatus(CODE_OK);
|
||||
log("Sending back " + (messageFiles.length - 1) + " messages");
|
||||
ServletOutputStream out = resp.getOutputStream();
|
||||
DataHelper.writeDate(out, new Date(Clock.getInstance().now()));
|
||||
DataHelper.writeLong(out, 2, messageFiles.length - 1);
|
||||
for (int i = 0; i < messageFiles.length; i++) {
|
||||
if ("identity.dat".equals(messageFiles[i].getName())) {
|
||||
// skip
|
||||
} else {
|
||||
log("Message file " + messageFiles[i].getName() + " is " + messageFiles[i].length() + " bytes");
|
||||
DataHelper.writeLong(out, 4, messageFiles[i].length());
|
||||
writeFile(out, messageFiles[i]);
|
||||
boolean deleted = messageFiles[i].delete();
|
||||
if (!deleted) {
|
||||
log("!!!Error removing message file " + messageFiles[i].getAbsolutePath() + " - please delete!");
|
||||
}
|
||||
}
|
||||
}
|
||||
out.flush();
|
||||
out.close();
|
||||
} catch (DataFormatException dfe) {
|
||||
log("Error sending message", dfe);
|
||||
} finally {
|
||||
LockManager.unlockIdent(target);
|
||||
log("Unlocked " + target);
|
||||
}
|
||||
}
|
||||
|
||||
private final static long EXPIRE_DELAY = 60 * 1000; // expire messages every minute
|
||||
|
||||
private void expire(File identDir) throws IOException {
|
||||
File files[] = identDir.listFiles();
|
||||
long now = System.currentTimeMillis();
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
if ("identity.dat".equals(files[i].getName())) {
|
||||
continue;
|
||||
}
|
||||
if (files[i].lastModified() + EXPIRE_DELAY < now) {
|
||||
log("Expiring " + files[i].getAbsolutePath());
|
||||
files[i].delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeFile(ServletOutputStream out, File file) throws IOException {
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
try {
|
||||
byte buf[] = new byte[4096];
|
||||
while (true) {
|
||||
int read = DataHelper.read(fis, buf);
|
||||
if (read > 0)
|
||||
out.write(buf, 0, read);
|
||||
else
|
||||
break;
|
||||
}
|
||||
} finally {
|
||||
fis.close();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isKnown(String target) throws IOException {
|
||||
File identDir = getIdentDir(target);
|
||||
if (identDir.exists()) {
|
||||
File identFile = new File(identDir, "identity.dat");
|
||||
if (identFile.exists()) {
|
||||
// known and valid (maybe we need to check the file format... naw, fuck it
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAuthorized(String target, InputStream in) throws IOException {
|
||||
RouterIdentity ident = null;
|
||||
try {
|
||||
ident = getRouterIdentity(target);
|
||||
} catch (DataFormatException dfe) {
|
||||
log("Identity was not valid", dfe);
|
||||
}
|
||||
|
||||
if (ident == null) {
|
||||
log("Identity not registered");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
long val = DataHelper.readLong(in, 4);
|
||||
Signature sig = new Signature();
|
||||
sig.readBytes(in);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataHelper.writeLong(baos, 4, val);
|
||||
if (DSAEngine.getInstance().verifySignature(sig, baos.toByteArray(), ident.getSigningPublicKey())) {
|
||||
return true;
|
||||
} else {
|
||||
log("Signature does NOT match");
|
||||
return false;
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
log("Format error reading the nonce and signature", dfe);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private RouterIdentity getRouterIdentity(String target) throws IOException, DataFormatException {
|
||||
File identDir = getIdentDir(target);
|
||||
if (identDir.exists()) {
|
||||
File identFile = new File(identDir, "identity.dat");
|
||||
if (identFile.exists()) {
|
||||
// known and valid (maybe we need to check the file format... naw, fuck it
|
||||
RouterIdentity ident = new RouterIdentity();
|
||||
ident.readBytes(new FileInputStream(identFile));
|
||||
return ident;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
package net.i2p.phttprelay;
|
||||
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.util.Clock;
|
||||
|
||||
/**
|
||||
* Accept registrations for PHTTP relaying, allowing the Polling HTTP (PHTTP)
|
||||
* transport for I2P to bridge past firewalls, NATs, and proxy servers. <p />
|
||||
*
|
||||
* This servlet should be set up in web.xml as follows:
|
||||
*
|
||||
* <servlet>
|
||||
* <servlet-name>Register</servlet-name>
|
||||
* <servlet-class>net.i2p.phttprelay.RegisterServlet</servlet-class>
|
||||
* <init-param>
|
||||
* <param-name>baseDir</param-name>
|
||||
* <param-value>/usr/local/jetty/phttprelayDir</param-value>
|
||||
* </init-param>
|
||||
* <init-param>
|
||||
* <param-name>pollPath</param-name>
|
||||
* <param-value>phttpPoll</param-value>
|
||||
* </init-param>
|
||||
* <init-param>
|
||||
* <param-name>sendPath</param-name>
|
||||
* <param-value>phttpSend</param-value>
|
||||
* </init-param>
|
||||
* </servlet>
|
||||
*
|
||||
* <servlet-mapping>
|
||||
* <servlet-name>Register</servlet-name>
|
||||
* <url-pattern>/phttpRegister</url-pattern>
|
||||
* </servlet-mapping>
|
||||
*
|
||||
* baseDir is the directory under which registrants and their pending messages are stored
|
||||
* pollPath is the path under the current host that requests polling for messages should be sent
|
||||
* sendPath is the path under the current host that requests submitting messages should be sent
|
||||
*
|
||||
* The pollPath and sendPath must not start with / as they are translated ala http://host:port/[path]
|
||||
*/
|
||||
public class RegisterServlet extends PHTTPRelayServlet {
|
||||
private String _pollPath;
|
||||
private String _sendPath;
|
||||
|
||||
/* config params */
|
||||
public final static String PARAM_POLL_PATH = "pollPath";
|
||||
public final static String PARAM_SEND_PATH = "sendPath";
|
||||
|
||||
/* key=val keys sent back on registration */
|
||||
public final static String PROP_STATUS = "status";
|
||||
public final static String PROP_POLL_URL = "pollURL";
|
||||
public final static String PROP_SEND_URL = "sendURL";
|
||||
public final static String PROP_TIME_OFFSET = "timeOffset"; // ms (local-remote)
|
||||
|
||||
/* values for the PROP_STATUS */
|
||||
public final static String STATUS_FAILED = "failed";
|
||||
public final static String STATUS_REGISTERED = "registered";
|
||||
|
||||
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
ServletInputStream in = req.getInputStream();
|
||||
RouterIdentity ident = new RouterIdentity();
|
||||
try {
|
||||
Date remoteTime = DataHelper.readDate(in);
|
||||
long skew = getSkew(remoteTime);
|
||||
ident.readBytes(in);
|
||||
boolean ok = registerIdent(ident);
|
||||
sendURLs(req, resp, skew, ok);
|
||||
} catch (DataFormatException dfe) {
|
||||
log("Invalid format for router identity posted", dfe);
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
private long getSkew(Date remoteDate) {
|
||||
if (remoteDate == null) {
|
||||
log("*ERROR: remote date was null");
|
||||
return Long.MAX_VALUE;
|
||||
} else {
|
||||
long diff = Clock.getInstance().now() - remoteDate.getTime();
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean registerIdent(RouterIdentity ident) throws DataFormatException, IOException {
|
||||
File identDir = getIdentDir(ident.getHash().toBase64());
|
||||
boolean created = identDir.mkdirs();
|
||||
File identFile = new File(identDir, "identity.dat");
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(identFile);
|
||||
ident.writeBytes(fos);
|
||||
} finally {
|
||||
if (fos != null) try {
|
||||
fos.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
log("Identity registered into " + identFile.getAbsolutePath());
|
||||
return true;
|
||||
}
|
||||
|
||||
private void sendURLs(HttpServletRequest req, HttpServletResponse resp, long skew, boolean ok) throws IOException {
|
||||
ServletOutputStream out = resp.getOutputStream();
|
||||
|
||||
log("*Debug: clock skew of " + skew + "ms (local-remote)");
|
||||
|
||||
StringBuffer buf = new StringBuffer();
|
||||
if (ok) {
|
||||
buf.append(PROP_POLL_URL).append("=").append(buildURL(req, _pollPath)).append("\n");
|
||||
buf.append(PROP_SEND_URL).append("=").append(buildURL(req, _sendPath)).append("\n");
|
||||
buf.append(PROP_TIME_OFFSET).append("=").append(skew).append("\n");
|
||||
buf.append(PROP_STATUS).append("=").append(STATUS_REGISTERED).append("\n");
|
||||
} else {
|
||||
buf.append(PROP_TIME_OFFSET).append("=").append(skew).append("\n");
|
||||
buf.append(PROP_STATUS).append("=").append(STATUS_FAILED).append("\n");
|
||||
}
|
||||
out.write(buf.toString().getBytes());
|
||||
out.close();
|
||||
}
|
||||
|
||||
public void init(ServletConfig config) throws ServletException {
|
||||
super.init(config);
|
||||
|
||||
String pollPath = config.getInitParameter(PARAM_POLL_PATH);
|
||||
if (pollPath == null)
|
||||
throw new ServletException("Polling path for the registration servlet required [" + PARAM_POLL_PATH + "]");
|
||||
else
|
||||
_pollPath = pollPath;
|
||||
String sendPath = config.getInitParameter(PARAM_SEND_PATH);
|
||||
if (sendPath == null)
|
||||
throw new ServletException("Sending path for the registration servlet required [" + PARAM_SEND_PATH + "]");
|
||||
else
|
||||
_sendPath = sendPath;
|
||||
}
|
||||
}
|
||||
@@ -1,324 +0,0 @@
|
||||
package net.i2p.phttprelay;
|
||||
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Accept messages for PHTTP relaying, allowing the Polling HTTP (PHTTP)
|
||||
* transport for I2P to bridge past firewalls, NATs, and proxy servers. This
|
||||
* delivers them into the queue, returning HTTP 201 (created) if the queue is
|
||||
* known, as well as the URL at which requests can be made to check the delivery
|
||||
* status of the message. If the queue is not known, HTTP 410 (resource gone) is
|
||||
* sent back. <p />
|
||||
*
|
||||
* This servlet should be set up in web.xml as follows:
|
||||
*
|
||||
* <servlet>
|
||||
* <servlet-name>Send</servlet-name>
|
||||
* <servlet-class>net.i2p.phttprelay.SendServlet</servlet-class>
|
||||
* <init-param>
|
||||
* <param-name>baseDir</param-name>
|
||||
* <param-value>/usr/local/jetty/phttprelayDir</param-value>
|
||||
* </init-param>
|
||||
* <init-param>
|
||||
* <param-name>checkPath</param-name>
|
||||
* <param-value>phttpCheckStatus</param-value>
|
||||
* </init-param>
|
||||
* <init-param>
|
||||
* <param-name>maxMessagesPerIdent</param-name>
|
||||
* <param-value>100</param-value>
|
||||
* </init-param>
|
||||
* </servlet>
|
||||
*
|
||||
* <servlet-mapping>
|
||||
* <servlet-name>Send</servlet-name>
|
||||
* <url-pattern>/phttpSend</url-pattern>
|
||||
* </servlet-mapping>
|
||||
*
|
||||
* baseDir is the directory under which registrants and their pending messages are stored
|
||||
* checkPath is the path under the current host that requests for the status of delivery should be sent
|
||||
* maxMessagesPerIdent is the maximum number of outstanding messages per peer being relayed
|
||||
*
|
||||
* The checkPath must not start with / as they are translated ala http://host:port/[path]
|
||||
*/
|
||||
public class SendServlet extends PHTTPRelayServlet {
|
||||
private String _checkPath;
|
||||
private int _maxMessagesPerIdent;
|
||||
|
||||
/* config params */
|
||||
public final static String PARAM_CHECK_PATH = "checkPath";
|
||||
public final static String PARAM_MAX_MESSAGES_PER_IDENT = "maxMessagesPerIdent";
|
||||
|
||||
/* URL parameters on the send */
|
||||
|
||||
/** H(routerIdent).toBase64() of the target to receive the message */
|
||||
public final static String PARAM_SEND_TARGET = "target";
|
||||
/** # ms to wait for the message to be delivered before failing it */
|
||||
public final static String PARAM_SEND_TIMEOUTMS = "timeoutMs";
|
||||
/** # bytes to be sent in the message */
|
||||
public final static String PARAM_SEND_DATA_LENGTH = "dataLength";
|
||||
/** sending router's time in ms */
|
||||
public final static String PARAM_SEND_TIME = "localTime";
|
||||
|
||||
/** msgId parameter to access the check path servlet with (along side PARAM_SEND_TARGET) */
|
||||
public final static String PARAM_MSG_ID = "msgId";
|
||||
|
||||
/* key=val keys sent back on registration */
|
||||
public final static String PROP_CHECK_URL = "statusCheckURL";
|
||||
public final static String PROP_STATUS = "status";
|
||||
public final static String STATUS_OK = "accepted";
|
||||
public final static String STATUS_UNKNOWN = "unknown";
|
||||
private final static String STATUS_CLOCKSKEW = "clockSkew_";
|
||||
|
||||
/** prefix for (local-remote) */
|
||||
|
||||
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
ServletInputStream in = req.getInputStream();
|
||||
try {
|
||||
int contentLen = req.getContentLength();
|
||||
String firstLine = getFirstLine(in, contentLen);
|
||||
if (firstLine == null) { return; }
|
||||
Map params = getParameters(firstLine);
|
||||
String target = (String) params.get(PARAM_SEND_TARGET);
|
||||
String timeoutStr = (String) params.get(PARAM_SEND_TIMEOUTMS);
|
||||
String lenStr = (String) params.get(PARAM_SEND_DATA_LENGTH);
|
||||
String remoteTimeStr = (String) params.get(PARAM_SEND_TIME);
|
||||
long skew = 0;
|
||||
try {
|
||||
long remTime = Long.parseLong(remoteTimeStr);
|
||||
skew = System.currentTimeMillis() - remTime;
|
||||
} catch (Throwable t) {
|
||||
skew = Long.MAX_VALUE;
|
||||
log("*ERROR could not parse the remote time from [" + remoteTimeStr + "]");
|
||||
}
|
||||
|
||||
log("Target [" + target + "] timeout [" + timeoutStr + "] length [" + lenStr + "] skew [" + skew + "]");
|
||||
|
||||
if ((skew > CLOCK_FUDGE_FACTOR) || (skew < 0 - CLOCK_FUDGE_FACTOR)) {
|
||||
log("Attempt to send by a skewed router: skew = " + skew + "ms (local-remote)");
|
||||
failSkewed(req, resp, skew);
|
||||
}
|
||||
|
||||
if (!isValidTarget(target)) {
|
||||
log("Attempt to send to an invalid target [" + target + "]");
|
||||
fail(req, resp, "Unknown or invalid target");
|
||||
return;
|
||||
}
|
||||
|
||||
long len = -1;
|
||||
try {
|
||||
len = Long.parseLong(lenStr);
|
||||
} catch (Throwable t) {
|
||||
log("Unable to parse length parameter [" + PARAM_SEND_DATA_LENGTH + "] (" + lenStr + ")");
|
||||
fail(req, resp, "Invalid length parameter");
|
||||
return;
|
||||
}
|
||||
|
||||
int msgId = saveFile(in, resp, target, len);
|
||||
if (msgId >= 0) {
|
||||
sendSuccess(req, resp, target, msgId);
|
||||
} else {
|
||||
fail(req, resp, "Unable to queue up the message for delivery");
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getFirstLine(ServletInputStream in, int len) throws ServletException, IOException {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
int numBytes = 0;
|
||||
int c = 0;
|
||||
while ((c = in.read()) != -1) {
|
||||
if (c == (int) '\n') break;
|
||||
buf.append((char) c);
|
||||
numBytes++;
|
||||
if (numBytes > 512) {
|
||||
log("First line is > 512 bytes [" + buf.toString() + "]");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
log("First line: " + buf.toString());
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static Map getParameters(String line) {
|
||||
//StringTokenizer tok = new StringTokenizer(line, "&=", true);
|
||||
Map params = new HashMap();
|
||||
while (line != null) {
|
||||
String key = null;
|
||||
String val = null;
|
||||
int firstAmp = line.indexOf('&');
|
||||
int firstEq = line.indexOf('=');
|
||||
if (firstAmp > 0) {
|
||||
key = line.substring(0, firstEq);
|
||||
val = line.substring(firstEq + 1, firstAmp);
|
||||
line = line.substring(firstAmp + 1);
|
||||
params.put(key, val);
|
||||
} else {
|
||||
line = null;
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
private boolean isValidTarget(String target) throws IOException {
|
||||
File identDir = getIdentDir(target);
|
||||
if (identDir.exists()) {
|
||||
File identFile = new File(identDir, "identity.dat");
|
||||
if (identFile.exists()) {
|
||||
// known and valid (maybe we need to check the file format... naw, fuck it
|
||||
String files[] = identDir.list();
|
||||
// we skip 1 because of identity.dat
|
||||
if (files.length - 1 > _maxMessagesPerIdent) {
|
||||
log("Too many messages pending for " + target + ": " + (files.length - 1));
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
log("Ident directory exists, but identity does not... corrupt for " + target);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
log("Unknown ident " + target);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private int saveFile(InputStream in, HttpServletResponse resp, String target, long len) throws IOException {
|
||||
File identDir = getIdentDir(target);
|
||||
if (!identDir.exists()) return -1;
|
||||
try {
|
||||
LockManager.lockIdent(target);
|
||||
int i = 0;
|
||||
while (true) {
|
||||
File curFile = new File(identDir, "msg" + i + ".dat");
|
||||
if (!curFile.exists()) {
|
||||
boolean ok = writeFile(curFile, in, len);
|
||||
if (ok)
|
||||
return i;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
} finally {
|
||||
LockManager.unlockIdent(target);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean writeFile(File file, InputStream in, long len) throws IOException {
|
||||
long remaining = len;
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(file);
|
||||
byte buf[] = new byte[4096];
|
||||
while (remaining > 0) {
|
||||
int read = in.read(buf);
|
||||
if (read == -1) break;
|
||||
remaining -= read;
|
||||
if (read > 0) fos.write(buf, 0, read);
|
||||
}
|
||||
} finally {
|
||||
if (fos != null) {
|
||||
try {
|
||||
fos.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
if (remaining != 0) {
|
||||
log("Invalid remaining bytes [" + remaining + " out of " + len
|
||||
+ "] - perhaps message was cancelled partway through delivery? deleting " + file.getAbsolutePath());
|
||||
boolean deleted = file.delete();
|
||||
if (!deleted) log("!!!Error deleting temporary file " + file.getAbsolutePath());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void sendSuccess(HttpServletRequest req, HttpServletResponse resp, String target, int msgId)
|
||||
throws IOException {
|
||||
ServletOutputStream out = resp.getOutputStream();
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append(PROP_STATUS).append('=').append(STATUS_OK).append('\n');
|
||||
buf.append(PROP_CHECK_URL).append('=').append(buildURL(req, _checkPath));
|
||||
buf.append('?');
|
||||
buf.append(PARAM_SEND_TARGET).append('=').append(target).append("&");
|
||||
buf.append(PARAM_MSG_ID).append('=').append(msgId).append("\n");
|
||||
out.write(buf.toString().getBytes());
|
||||
out.flush();
|
||||
}
|
||||
|
||||
private void fail(HttpServletRequest req, HttpServletResponse resp, String err) throws IOException {
|
||||
ServletOutputStream out = resp.getOutputStream();
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append(PROP_STATUS).append('=').append(STATUS_UNKNOWN).append('\n');
|
||||
out.write(buf.toString().getBytes());
|
||||
out.flush();
|
||||
}
|
||||
|
||||
private void failSkewed(HttpServletRequest req, HttpServletResponse resp, long skew) throws IOException {
|
||||
ServletOutputStream out = resp.getOutputStream();
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append(PROP_STATUS).append('=').append(STATUS_CLOCKSKEW).append(skew).append('\n');
|
||||
out.write(buf.toString().getBytes());
|
||||
out.flush();
|
||||
}
|
||||
|
||||
public void init(ServletConfig config) throws ServletException {
|
||||
super.init(config);
|
||||
|
||||
String checkPath = config.getInitParameter(PARAM_CHECK_PATH);
|
||||
if (checkPath == null)
|
||||
throw new ServletException("Check status path for the sending servlet required [" + PARAM_CHECK_PATH + "]");
|
||||
else
|
||||
_checkPath = checkPath;
|
||||
|
||||
String maxMessagesPerIdentStr = config.getInitParameter(PARAM_MAX_MESSAGES_PER_IDENT);
|
||||
if (maxMessagesPerIdentStr == null)
|
||||
throw new ServletException("Max messages per ident for the sending servlet required ["
|
||||
+ PARAM_MAX_MESSAGES_PER_IDENT + "]");
|
||||
try {
|
||||
_maxMessagesPerIdent = Integer.parseInt(maxMessagesPerIdentStr);
|
||||
} catch (Throwable t) {
|
||||
throw new ServletException("Valid max messages per ident for the sending servlet required ["
|
||||
+ PARAM_MAX_MESSAGES_PER_IDENT + "]");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
String line = "target=pp0ARjQiB~IKC-0FsMUsPEMrwR3gxVBZGRYfEr1IzHI=&timeoutMs=52068&dataLength=2691&";
|
||||
Map props = getParameters(line);
|
||||
for (java.util.Iterator iter = props.keySet().iterator(); iter.hasNext();) {
|
||||
String key = (String) iter.next();
|
||||
String val = (String) props.get(key);
|
||||
System.out.println("[" + key + "] = [" + val + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!DOCTYPE web-app
|
||||
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
|
||||
"http://java.sun.com/dtd/web-app_2_3.dtd">
|
||||
|
||||
|
||||
<web-app>
|
||||
<display-name>I2P Polling HTTP Relay</display-name>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>Register</servlet-name>
|
||||
<servlet-class>net.i2p.phttprelay.RegisterServlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>pollPath</param-name>
|
||||
<param-value>/phttpPoll</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>sendPath</param-name>
|
||||
<param-value>/phttpSend</param-value>
|
||||
</init-param>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>Send</servlet-name>
|
||||
<servlet-class>net.i2p.phttprelay.SendServlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>checkPath</param-name>
|
||||
<param-value>/phttpCheckSendStatus</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>maxMessagesPerIdent</param-name>
|
||||
<param-value>100</param-value>
|
||||
</init-param>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>CheckSendStatus</servlet-name>
|
||||
<servlet-class>net.i2p.phttprelay.CheckSendStatusServlet</servlet-class>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>Poll</servlet-name>
|
||||
<servlet-class>net.i2p.phttprelay.PollServlet</servlet-class>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>Register</servlet-name>
|
||||
<url-pattern>/phttpRegister</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>Send</servlet-name>
|
||||
<url-pattern>/phttpSend</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>CheckSendStatus</servlet-name>
|
||||
<url-pattern>/phttpCheckSendStatus</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>Poll</servlet-name>
|
||||
<url-pattern>/phttpPoll</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
</web-app>
|
||||
@@ -1,278 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* A Minimal echo server.
|
||||
*
|
||||
* Copyright (c) 2004 Michael Schierl
|
||||
*
|
||||
* Licensed unter GNU General Public License.
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
|
||||
public class EchoServer extends Thread {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
ServerSocket ss = new ServerSocket(Integer.parseInt(args[0]));
|
||||
while (true) {
|
||||
Socket s = ss.accept();
|
||||
new EchoServer(s);
|
||||
}
|
||||
}
|
||||
|
||||
private Socket s;
|
||||
|
||||
public EchoServer(Socket s) {
|
||||
this.s=s;
|
||||
start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
InputStream in = s.getInputStream();
|
||||
OutputStream out = s.getOutputStream();
|
||||
byte[] b = new byte[4096];
|
||||
int len;
|
||||
while ((len = in.read(b)) != -1) {
|
||||
out.write(b, 0, len);
|
||||
}
|
||||
} catch (SocketException ex) {
|
||||
// nothing
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
// compile & run this file against i2p.jar
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.i2p.*;
|
||||
import net.i2p.client.*;
|
||||
import net.i2p.data.*;
|
||||
|
||||
public class GuaranteedBug {
|
||||
|
||||
|
||||
public void reproduce() {
|
||||
try {
|
||||
Destination d1 = null;
|
||||
// first client (receiver)
|
||||
if (true) { // smaller scope for variables ...
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
ByteArrayOutputStream keyStream =
|
||||
new ByteArrayOutputStream(512);
|
||||
d1 = client.createDestination(keyStream);
|
||||
ByteArrayInputStream in =
|
||||
new ByteArrayInputStream(keyStream.toByteArray());
|
||||
Properties opts = new Properties();
|
||||
opts.setProperty(I2PClient.PROP_RELIABILITY,
|
||||
I2PClient.PROP_RELIABILITY_GUARANTEED);
|
||||
opts.setProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
|
||||
opts.setProperty(I2PClient.PROP_TCP_PORT, "7654");
|
||||
I2PSession session = client.createSession(in, opts);
|
||||
session.connect();
|
||||
session.setSessionListener(new PacketCounter());
|
||||
}
|
||||
// second client (sender)
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
ByteArrayOutputStream keyStream = new ByteArrayOutputStream(512);
|
||||
Destination d2 = client.createDestination(keyStream);
|
||||
ByteArrayInputStream in =
|
||||
new ByteArrayInputStream(keyStream.toByteArray());
|
||||
Properties opts = new Properties();
|
||||
opts.setProperty(I2PClient.PROP_RELIABILITY,
|
||||
I2PClient.PROP_RELIABILITY_GUARANTEED);
|
||||
opts.setProperty(I2PClient.PROP_TCP_HOST, "127.0.0.1");
|
||||
opts.setProperty(I2PClient.PROP_TCP_PORT, "7654");
|
||||
I2PSession session = client.createSession(in, opts);
|
||||
session.connect();
|
||||
session.setSessionListener(new DummyListener());
|
||||
for (int i=0;i<1000; i++) {
|
||||
byte[] msg = (""+i).getBytes("ISO-8859-1");
|
||||
session.sendMessage(d1,msg);
|
||||
System.out.println(">>"+i);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
} catch (I2PException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
new GuaranteedBug().reproduce();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
public class DummyListener implements I2PSessionListener {
|
||||
public void disconnected(I2PSession session) {
|
||||
System.err.println("Disconnected: "+session);
|
||||
}
|
||||
|
||||
public void errorOccurred(I2PSession session, String message,
|
||||
Throwable error) {
|
||||
System.err.println("Error: "+session+"/"+message);
|
||||
error.printStackTrace();
|
||||
}
|
||||
|
||||
public void messageAvailable(I2PSession session, int msgId,
|
||||
long size) {
|
||||
System.err.println("Message here? "+session);
|
||||
}
|
||||
|
||||
public void reportAbuse(I2PSession session, int severity) {
|
||||
System.err.println("Abuse: "+severity+"/"+session);
|
||||
}
|
||||
}
|
||||
|
||||
public class PacketCounter extends DummyListener {
|
||||
private int lastPacket = -1;
|
||||
public void messageAvailable(I2PSession session, int msgId,
|
||||
long size) {
|
||||
try {
|
||||
byte msg[] = session.receiveMessage(msgId);
|
||||
String m = new String(msg, "ISO-8859-1");
|
||||
int no = Integer.parseInt(m);
|
||||
if (no != ++lastPacket) {
|
||||
System.out.println("ERROR: <<"+no);
|
||||
} else {
|
||||
System.out.println("<<"+no);
|
||||
}
|
||||
} catch (NumberFormatException ex) {
|
||||
ex.printStackTrace();
|
||||
} catch (I2PException ex) {
|
||||
ex.printStackTrace();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
This directory is intended for tests which are useful for testing
|
||||
I2P, but don't test any of the I2P components directly. Instead,
|
||||
tests are run on "application" level (TCP, IRC, HTTP etc.).
|
||||
|
||||
IOW: These tests may be useful for any other project that allows
|
||||
tunneling of "normal" protocols, not only I2P.
|
||||
@@ -1,88 +0,0 @@
|
||||
/**
|
||||
* A basic implementation for the EchoTestAnalyzer.
|
||||
*/
|
||||
public class BasicEchoTestAnalyzer implements EchoTestAnalyzer {
|
||||
|
||||
/**
|
||||
* How many events must appear until a detailed report is
|
||||
* printed. Default is every 20 events.
|
||||
*/
|
||||
private static int REPORT_DELAY = 20;
|
||||
|
||||
private static int SUMMARY_SIZE = 100;
|
||||
|
||||
public BasicEchoTestAnalyzer() {
|
||||
this(20, 100);
|
||||
}
|
||||
|
||||
public BasicEchoTestAnalyzer(int reportDelay, int summarySize) {
|
||||
REPORT_DELAY = reportDelay;
|
||||
SUMMARY_SIZE = summarySize;
|
||||
}
|
||||
|
||||
private int events = 0, packetLosses = 0, packetLossesDisconnect = 0, disconnects = 0, disconnectsRefused = 0,
|
||||
delayCount = 0, lastDelayPtr = 0;
|
||||
private long minDelay = Long.MAX_VALUE, maxDelay = 0, delaySum = 0;
|
||||
private long[] lastDelays = new long[SUMMARY_SIZE];
|
||||
|
||||
public synchronized void packetLossOccurred(boolean beforeDisconnect) {
|
||||
System.out.println("1: Packet lost" + (beforeDisconnect ? " before disconnect" : "") + ".");
|
||||
packetLosses++;
|
||||
if (beforeDisconnect) packetLossesDisconnect++;
|
||||
countEvent();
|
||||
}
|
||||
|
||||
public synchronized void successOccurred(long delay) {
|
||||
System.out.println("0: Delay = " + delay);
|
||||
if (delay > maxDelay) maxDelay = delay;
|
||||
if (delay < minDelay) minDelay = delay;
|
||||
delaySum += delay;
|
||||
delayCount++;
|
||||
lastDelays[lastDelayPtr++] = delay;
|
||||
lastDelayPtr %= SUMMARY_SIZE;
|
||||
countEvent();
|
||||
}
|
||||
|
||||
public synchronized void disconnected(boolean refused) {
|
||||
System.out.println("2: Disconnected" + (refused ? " (connection refused)" : "") + ".");
|
||||
disconnects++;
|
||||
if (refused) disconnectsRefused++;
|
||||
countEvent();
|
||||
}
|
||||
|
||||
private void countEvent() {
|
||||
events++;
|
||||
if (events % REPORT_DELAY == 0) {
|
||||
int packets = packetLosses + delayCount;
|
||||
long delaySummary = 0;
|
||||
for (int i = 0; i < SUMMARY_SIZE; i++) {
|
||||
delaySummary += lastDelays[i];
|
||||
}
|
||||
System.out.println("++++++++++++++++ ECHO STATISTICS +++++++++++++++++++++++++"
|
||||
+ "\n++ Number of total echo messages: "
|
||||
+ packets
|
||||
+ "\n++ No response for "
|
||||
+ packetLosses
|
||||
+ "\n++ (of which "
|
||||
+ packetLossesDisconnect
|
||||
+ " due to a disconnect)"
|
||||
+ "\n++ Disconnects: "
|
||||
+ disconnects
|
||||
+ "\n++ (of which "
|
||||
+ disconnectsRefused
|
||||
+ " due to 'connection refused')"
|
||||
+ (disconnects > 0 || true ? "\n++ Average lost packets per disconnect: "
|
||||
+ (packetLossesDisconnect / (float) disconnects) : "")
|
||||
+ "\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++"
|
||||
+ "\n++ Minimal delay: "
|
||||
+ minDelay
|
||||
+ "\n++ Average delay: "
|
||||
+ (delaySum / (float) delayCount)
|
||||
+ "\n++ Maximal delay: "
|
||||
+ maxDelay
|
||||
+ (delayCount >= SUMMARY_SIZE ? "\n++ Average delay over last " + SUMMARY_SIZE + ": "
|
||||
+ (delaySummary / (float) SUMMARY_SIZE) : "")
|
||||
+ "\n++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
|
||||
/**
|
||||
* A class that wants to analyze tests implements this interface. This
|
||||
* allows to "mix" several test values (from different echo servers)
|
||||
* as well as different algorithms for analyzing the data (for
|
||||
* jrandom: Strategy Pattern *g*).
|
||||
*/
|
||||
public interface EchoTestAnalyzer {
|
||||
|
||||
public void packetLossOccurred(boolean beforeDisconnect);
|
||||
|
||||
public void successOccurred(long delay);
|
||||
|
||||
public void disconnected(boolean refused);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
/*
|
||||
* Test for an echo server. This test is intended to be used via an
|
||||
* I2PTunnel, but should work as well on other networks that provide
|
||||
* TCP tunneling and an echo server.
|
||||
*
|
||||
* Copyright (c) 2004 Michael Schierl
|
||||
*
|
||||
* Licensed unter GNU General Public License.
|
||||
*/
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.net.ConnectException;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
|
||||
/**
|
||||
* The main engine for the EchoTester.
|
||||
*/
|
||||
public class EchoTester extends Thread {
|
||||
|
||||
/**
|
||||
* How long to wait between packets. Default is 6 seconds.
|
||||
*/
|
||||
private static long PACKET_DELAY = 6000;
|
||||
|
||||
/**
|
||||
* How many packets may be on the way before the connection is
|
||||
* seen as "broken" and disconnected.
|
||||
*/
|
||||
private static final long MAX_PACKETS_QUEUED = 50; // unused
|
||||
|
||||
private EchoTestAnalyzer eta;
|
||||
private String host;
|
||||
private int port;
|
||||
|
||||
// the following vars are synchronized via the lock.
|
||||
private Object lock = new Object();
|
||||
private long nextPacket = 0;
|
||||
private long nextUnreceived = 0;
|
||||
private boolean readerRunning = false;
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length == 3) PACKET_DELAY = Long.parseLong(args[2]);
|
||||
new EchoTester(args[0], Integer.parseInt(args[1]), new BasicEchoTestAnalyzer());
|
||||
}
|
||||
|
||||
public EchoTester(String host, int port, EchoTestAnalyzer eta) {
|
||||
this.eta = eta;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
while (true) {
|
||||
Socket s;
|
||||
try {
|
||||
s = new Socket(host, port);
|
||||
} catch (ConnectException ex) {
|
||||
eta.disconnected(true);
|
||||
Thread.sleep(PACKET_DELAY);
|
||||
continue;
|
||||
}
|
||||
System.out.println("41: Connected to " + host + ":" + port);
|
||||
synchronized (lock) {
|
||||
nextUnreceived = nextPacket;
|
||||
}
|
||||
Thread t = new ResponseReaderThread(s);
|
||||
Writer w = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
|
||||
while (true) {
|
||||
long no;
|
||||
synchronized (lock) {
|
||||
no = nextPacket++;
|
||||
}
|
||||
try {
|
||||
w.write(no + " " + System.currentTimeMillis() + "\n");
|
||||
w.flush();
|
||||
} catch (SocketException ex) {
|
||||
break;
|
||||
}
|
||||
Thread.sleep(PACKET_DELAY);
|
||||
}
|
||||
s.close();
|
||||
t.join();
|
||||
synchronized (lock) {
|
||||
if (readerRunning) {
|
||||
System.out.println("*** WHY IS THIS THREAD STILL" + " RUNNING?");
|
||||
}
|
||||
while (nextUnreceived < nextPacket) {
|
||||
nextUnreceived++;
|
||||
eta.packetLossOccurred(true);
|
||||
}
|
||||
if (nextUnreceived > nextPacket) {
|
||||
System.out.println("*** WTF? " + nextUnreceived + " > " + nextPacket);
|
||||
}
|
||||
}
|
||||
eta.disconnected(false);
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
System.exit(1); // treat these errors as fatal
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
System.exit(1); // treat these errors as fatal
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ResponseReaderThread extends Thread {
|
||||
|
||||
private Socket s;
|
||||
|
||||
public ResponseReaderThread(Socket s) {
|
||||
this.s = s;
|
||||
synchronized (lock) {
|
||||
readerRunning = true;
|
||||
}
|
||||
start();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
|
||||
String line;
|
||||
int index;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if ((index = line.indexOf(" ")) == -1) continue;
|
||||
long now, packetNumber, packetTime;
|
||||
now = System.currentTimeMillis();
|
||||
try {
|
||||
packetNumber = Long.parseLong(line.substring(0, index));
|
||||
packetTime = Long.parseLong(line.substring(index + 1));
|
||||
} catch (NumberFormatException ex) {
|
||||
System.out.println(ex.toString());
|
||||
continue;
|
||||
}
|
||||
synchronized (lock) {
|
||||
while (packetNumber > nextUnreceived) {
|
||||
nextUnreceived++;
|
||||
eta.packetLossOccurred(false);
|
||||
}
|
||||
if (nextUnreceived > packetNumber) {
|
||||
System.out.println("*** DOUBLE PACKET!");
|
||||
} else {
|
||||
nextUnreceived++;
|
||||
}
|
||||
}
|
||||
eta.successOccurred(now - packetTime);
|
||||
}
|
||||
} catch (SocketException ex) {
|
||||
// ignore
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
System.exit(0);
|
||||
}
|
||||
synchronized (lock) {
|
||||
readerRunning = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
$Id$
|
||||
|
||||
the i2p/apps/tests module is the root of application
|
||||
level tests, and everything within it is released
|
||||
according to the terms of the I2P license policy.
|
||||
That means everything contained within the
|
||||
i2p/apps/tests module is released into the public
|
||||
domain unless otherwise marked. Alternate licenses
|
||||
that may be used include GPL, GPL + java exception,
|
||||
BSD, Cryptix, and MIT.
|
||||
Reference in New Issue
Block a user