From 321c5606488fb9c687257cb5bb79ea5016306ff1 Mon Sep 17 00:00:00 2001 From: jrandom <jrandom> Date: Thu, 16 Feb 2006 09:33:53 +0000 Subject: [PATCH] drop most of the abandonware --- apps/heartbeat/doc/readme.gui.txt | 39 -- apps/heartbeat/doc/readme.txt | 122 ---- apps/heartbeat/java/build.xml | 66 -- .../src/net/i2p/heartbeat/ClientConfig.java | 468 -------------- .../src/net/i2p/heartbeat/ClientEngine.java | 133 ---- .../java/src/net/i2p/heartbeat/Heartbeat.java | 254 -------- .../src/net/i2p/heartbeat/I2PAdapter.java | 604 ------------------ .../java/src/net/i2p/heartbeat/PeerData.java | 412 ------------ .../src/net/i2p/heartbeat/PeerDataWriter.java | 142 ---- .../heartbeat/gui/HeartbeatControlPane.java | 108 ---- .../i2p/heartbeat/gui/HeartbeatMonitor.java | 116 ---- .../gui/HeartbeatMonitorCommandBar.java | 67 -- .../heartbeat/gui/HeartbeatMonitorGUI.java | 98 --- .../heartbeat/gui/HeartbeatMonitorRunner.java | 32 - .../heartbeat/gui/HeartbeatMonitorState.java | 129 ---- .../i2p/heartbeat/gui/HeartbeatPlotPane.java | 62 -- .../i2p/heartbeat/gui/JFreeChartAdapter.java | 233 ------- .../gui/JFreeChartHeartbeatPlotPane.java | 58 -- .../net/i2p/heartbeat/gui/PeerPlotConfig.java | 367 ----------- .../i2p/heartbeat/gui/PeerPlotConfigPane.java | 371 ----------- .../net/i2p/heartbeat/gui/PeerPlotState.java | 95 --- .../heartbeat/gui/PeerPlotStateFetcher.java | 363 ----------- .../net/i2p/heartbeat/gui/StaticPeerData.java | 134 ---- apps/httptunnel/doc/COPYING | 278 -------- apps/httptunnel/doc/readme.license.txt | 11 - apps/httptunnel/java/build.xml | 47 -- .../src/net/i2p/httptunnel/HTTPListener.java | 87 --- .../net/i2p/httptunnel/HTTPSocketHandler.java | 62 -- .../src/net/i2p/httptunnel/HTTPTunnel.java | 108 ---- .../java/src/net/i2p/httptunnel/Request.java | 153 ----- .../i2p/httptunnel/SocketManagerProducer.java | 120 ---- .../i2p/httptunnel/filter/ChainFilter.java | 61 -- .../src/net/i2p/httptunnel/filter/Filter.java | 25 - .../net/i2p/httptunnel/filter/NullFilter.java | 21 - .../i2p/httptunnel/handler/EepHandler.java | 113 ---- .../i2p/httptunnel/handler/ErrorHandler.java | 41 -- .../i2p/httptunnel/handler/LocalHandler.java | 67 -- .../i2p/httptunnel/handler/ProxyHandler.java | 54 -- .../i2p/httptunnel/handler/RootHandler.java | 116 ---- apps/jfreechart/GUI-licenses.txt | 590 ----------------- apps/jfreechart/build.xml | 29 - apps/myi2p/java/build.xml | 39 -- .../java/src/net/i2p/myi2p/MyI2PMessage.java | 116 ---- apps/myi2p/java/src/net/i2p/myi2p/Node.java | 266 -------- .../java/src/net/i2p/myi2p/NodeAdapter.java | 178 ------ .../myi2p/java/src/net/i2p/myi2p/Service.java | 35 - .../java/src/net/i2p/myi2p/ServiceImpl.java | 36 -- .../net/i2p/myi2p/address/AddressBook.java | 126 ---- .../i2p/myi2p/address/AddressBookEntry.java | 142 ---- .../i2p/myi2p/address/AddressBookService.java | 90 --- .../myi2p/address/AddressBookServiceData.java | 104 --- .../net/i2p/myi2p/address/CreateEntryCLI.java | 154 ----- .../myi2p/address/CreateNameReferenceCLI.java | 125 ---- .../net/i2p/myi2p/address/NameReference.java | 198 ------ .../net/i2p/myi2p/address/Subscription.java | 66 -- apps/myi2p/myi2p.config | 3 - apps/netmonitor/harvester.config | 130 ---- apps/netmonitor/java/build.xml | 69 -- .../src/net/i2p/netmonitor/DataHarvester.java | 245 ------- .../src/net/i2p/netmonitor/NetMonitor.java | 251 -------- .../net/i2p/netmonitor/NetMonitorRunner.java | 242 ------- .../java/src/net/i2p/netmonitor/PeerStat.java | 46 -- .../src/net/i2p/netmonitor/PeerSummary.java | 119 ---- .../net/i2p/netmonitor/PeerSummaryReader.java | 106 --- .../net/i2p/netmonitor/PeerSummaryWriter.java | 80 --- .../src/net/i2p/netmonitor/StatGroup.java | 53 -- .../net/i2p/netmonitor/StatGroupLoader.java | 93 --- .../i2p/netmonitor/gui/JFreeChartAdapter.java | 109 ---- .../gui/JFreeChartHeartbeatPlotPane.java | 53 -- .../src/net/i2p/netmonitor/gui/NetViewer.java | 85 --- .../netmonitor/gui/NetViewerCommandBar.java | 70 -- .../netmonitor/gui/NetViewerControlPane.java | 106 --- .../net/i2p/netmonitor/gui/NetViewerGUI.java | 105 --- .../i2p/netmonitor/gui/NetViewerPlotPane.java | 46 -- .../i2p/netmonitor/gui/NetViewerRunner.java | 16 - .../i2p/netmonitor/gui/PeerPlotConfig.java | 213 ------ .../netmonitor/gui/PeerPlotConfigPane.java | 276 -------- apps/phttprelay/doc/readme.license.txt | 10 - apps/phttprelay/java/build.xml | 43 -- apps/phttprelay/java/lib/LICENSE.html | 159 ----- apps/phttprelay/java/lib/javax.servlet.jar | Bin 74543 -> 0 bytes apps/phttprelay/java/lib/readme.txt | 6 - .../phttprelay/CheckSendStatusServlet.java | 114 ---- .../src/net/i2p/phttprelay/LockManager.java | 44 -- .../net/i2p/phttprelay/PHTTPRelayServlet.java | 75 --- .../src/net/i2p/phttprelay/PollServlet.java | 263 -------- .../net/i2p/phttprelay/RegisterServlet.java | 158 ----- .../src/net/i2p/phttprelay/SendServlet.java | 324 ---------- apps/phttprelay/java/web.xml | 71 -- apps/tests/COPYING | 278 -------- apps/tests/EchoServer.java | 44 -- apps/tests/GuaranteedBug.java | 106 --- apps/tests/README | 6 - .../echotester/BasicEchoTestAnalyzer.java | 88 --- apps/tests/echotester/EchoTestAnalyzer.java | 17 - apps/tests/echotester/EchoTester.java | 167 ----- apps/tests/readme.license.txt | 10 - 97 files changed, 12630 deletions(-) delete mode 100644 apps/heartbeat/doc/readme.gui.txt delete mode 100644 apps/heartbeat/doc/readme.txt delete mode 100644 apps/heartbeat/java/build.xml delete mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/ClientConfig.java delete mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/ClientEngine.java delete mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/Heartbeat.java delete mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/I2PAdapter.java delete mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/PeerData.java delete mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/PeerDataWriter.java delete mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatControlPane.java delete mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitor.java delete mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorCommandBar.java delete mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorGUI.java delete mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorRunner.java delete mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorState.java delete mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatPlotPane.java delete mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/JFreeChartAdapter.java delete mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/JFreeChartHeartbeatPlotPane.java delete mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotConfig.java delete mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotConfigPane.java delete mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotState.java delete mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotStateFetcher.java delete mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/StaticPeerData.java delete mode 100644 apps/httptunnel/doc/COPYING delete mode 100644 apps/httptunnel/doc/readme.license.txt delete mode 100644 apps/httptunnel/java/build.xml delete mode 100644 apps/httptunnel/java/src/net/i2p/httptunnel/HTTPListener.java delete mode 100644 apps/httptunnel/java/src/net/i2p/httptunnel/HTTPSocketHandler.java delete mode 100644 apps/httptunnel/java/src/net/i2p/httptunnel/HTTPTunnel.java delete mode 100644 apps/httptunnel/java/src/net/i2p/httptunnel/Request.java delete mode 100644 apps/httptunnel/java/src/net/i2p/httptunnel/SocketManagerProducer.java delete mode 100644 apps/httptunnel/java/src/net/i2p/httptunnel/filter/ChainFilter.java delete mode 100644 apps/httptunnel/java/src/net/i2p/httptunnel/filter/Filter.java delete mode 100644 apps/httptunnel/java/src/net/i2p/httptunnel/filter/NullFilter.java delete mode 100644 apps/httptunnel/java/src/net/i2p/httptunnel/handler/EepHandler.java delete mode 100644 apps/httptunnel/java/src/net/i2p/httptunnel/handler/ErrorHandler.java delete mode 100644 apps/httptunnel/java/src/net/i2p/httptunnel/handler/LocalHandler.java delete mode 100644 apps/httptunnel/java/src/net/i2p/httptunnel/handler/ProxyHandler.java delete mode 100644 apps/httptunnel/java/src/net/i2p/httptunnel/handler/RootHandler.java delete mode 100644 apps/jfreechart/GUI-licenses.txt delete mode 100644 apps/jfreechart/build.xml delete mode 100644 apps/myi2p/java/build.xml delete mode 100644 apps/myi2p/java/src/net/i2p/myi2p/MyI2PMessage.java delete mode 100644 apps/myi2p/java/src/net/i2p/myi2p/Node.java delete mode 100644 apps/myi2p/java/src/net/i2p/myi2p/NodeAdapter.java delete mode 100644 apps/myi2p/java/src/net/i2p/myi2p/Service.java delete mode 100644 apps/myi2p/java/src/net/i2p/myi2p/ServiceImpl.java delete mode 100644 apps/myi2p/java/src/net/i2p/myi2p/address/AddressBook.java delete mode 100644 apps/myi2p/java/src/net/i2p/myi2p/address/AddressBookEntry.java delete mode 100644 apps/myi2p/java/src/net/i2p/myi2p/address/AddressBookService.java delete mode 100644 apps/myi2p/java/src/net/i2p/myi2p/address/AddressBookServiceData.java delete mode 100644 apps/myi2p/java/src/net/i2p/myi2p/address/CreateEntryCLI.java delete mode 100644 apps/myi2p/java/src/net/i2p/myi2p/address/CreateNameReferenceCLI.java delete mode 100644 apps/myi2p/java/src/net/i2p/myi2p/address/NameReference.java delete mode 100644 apps/myi2p/java/src/net/i2p/myi2p/address/Subscription.java delete mode 100644 apps/myi2p/myi2p.config delete mode 100644 apps/netmonitor/harvester.config delete mode 100644 apps/netmonitor/java/build.xml delete mode 100644 apps/netmonitor/java/src/net/i2p/netmonitor/DataHarvester.java delete mode 100644 apps/netmonitor/java/src/net/i2p/netmonitor/NetMonitor.java delete mode 100644 apps/netmonitor/java/src/net/i2p/netmonitor/NetMonitorRunner.java delete mode 100644 apps/netmonitor/java/src/net/i2p/netmonitor/PeerStat.java delete mode 100644 apps/netmonitor/java/src/net/i2p/netmonitor/PeerSummary.java delete mode 100644 apps/netmonitor/java/src/net/i2p/netmonitor/PeerSummaryReader.java delete mode 100644 apps/netmonitor/java/src/net/i2p/netmonitor/PeerSummaryWriter.java delete mode 100644 apps/netmonitor/java/src/net/i2p/netmonitor/StatGroup.java delete mode 100644 apps/netmonitor/java/src/net/i2p/netmonitor/StatGroupLoader.java delete mode 100644 apps/netmonitor/java/src/net/i2p/netmonitor/gui/JFreeChartAdapter.java delete mode 100644 apps/netmonitor/java/src/net/i2p/netmonitor/gui/JFreeChartHeartbeatPlotPane.java delete mode 100644 apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewer.java delete mode 100644 apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewerCommandBar.java delete mode 100644 apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewerControlPane.java delete mode 100644 apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewerGUI.java delete mode 100644 apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewerPlotPane.java delete mode 100644 apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewerRunner.java delete mode 100644 apps/netmonitor/java/src/net/i2p/netmonitor/gui/PeerPlotConfig.java delete mode 100644 apps/netmonitor/java/src/net/i2p/netmonitor/gui/PeerPlotConfigPane.java delete mode 100644 apps/phttprelay/doc/readme.license.txt delete mode 100644 apps/phttprelay/java/build.xml delete mode 100644 apps/phttprelay/java/lib/LICENSE.html delete mode 100644 apps/phttprelay/java/lib/javax.servlet.jar delete mode 100644 apps/phttprelay/java/lib/readme.txt delete mode 100644 apps/phttprelay/java/src/net/i2p/phttprelay/CheckSendStatusServlet.java delete mode 100644 apps/phttprelay/java/src/net/i2p/phttprelay/LockManager.java delete mode 100644 apps/phttprelay/java/src/net/i2p/phttprelay/PHTTPRelayServlet.java delete mode 100644 apps/phttprelay/java/src/net/i2p/phttprelay/PollServlet.java delete mode 100644 apps/phttprelay/java/src/net/i2p/phttprelay/RegisterServlet.java delete mode 100644 apps/phttprelay/java/src/net/i2p/phttprelay/SendServlet.java delete mode 100644 apps/phttprelay/java/web.xml delete mode 100644 apps/tests/COPYING delete mode 100644 apps/tests/EchoServer.java delete mode 100644 apps/tests/GuaranteedBug.java delete mode 100644 apps/tests/README delete mode 100644 apps/tests/echotester/BasicEchoTestAnalyzer.java delete mode 100644 apps/tests/echotester/EchoTestAnalyzer.java delete mode 100644 apps/tests/echotester/EchoTester.java delete mode 100644 apps/tests/readme.license.txt diff --git a/apps/heartbeat/doc/readme.gui.txt b/apps/heartbeat/doc/readme.gui.txt deleted file mode 100644 index fde06a3369..0000000000 --- a/apps/heartbeat/doc/readme.gui.txt +++ /dev/null @@ -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. \ No newline at end of file diff --git a/apps/heartbeat/doc/readme.txt b/apps/heartbeat/doc/readme.txt deleted file mode 100644 index d9b667a1ad..0000000000 --- a/apps/heartbeat/doc/readme.txt +++ /dev/null @@ -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. \ No newline at end of file diff --git a/apps/heartbeat/java/build.xml b/apps/heartbeat/java/build.xml deleted file mode 100644 index 99a627956c..0000000000 --- a/apps/heartbeat/java/build.xml +++ /dev/null @@ -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> diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/ClientConfig.java b/apps/heartbeat/java/src/net/i2p/heartbeat/ClientConfig.java deleted file mode 100644 index 59b9dfc873..0000000000 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/ClientConfig.java +++ /dev/null @@ -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; - } - } -} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/ClientEngine.java b/apps/heartbeat/java/src/net/i2p/heartbeat/ClientEngine.java deleted file mode 100644 index 15230f40a7..0000000000 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/ClientEngine.java +++ /dev/null @@ -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) { - } - } - } - } - } -} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/Heartbeat.java b/apps/heartbeat/java/src/net/i2p/heartbeat/Heartbeat.java deleted file mode 100644 index c577945f63..0000000000 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/Heartbeat.java +++ /dev/null @@ -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()); - } - } - } - -} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/I2PAdapter.java b/apps/heartbeat/java/src/net/i2p/heartbeat/I2PAdapter.java deleted file mode 100644 index 35c157c7bb..0000000000 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/I2PAdapter.java +++ /dev/null @@ -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(); - } - } - } -} diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/PeerData.java b/apps/heartbeat/java/src/net/i2p/heartbeat/PeerData.java deleted file mode 100644 index 6d9e93fb38..0000000000 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/PeerData.java +++ /dev/null @@ -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; - } - } -} diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/PeerDataWriter.java b/apps/heartbeat/java/src/net/i2p/heartbeat/PeerDataWriter.java deleted file mode 100644 index ad76fd1cee..0000000000 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/PeerDataWriter.java +++ /dev/null @@ -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); - } - } -} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatControlPane.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatControlPane.java deleted file mode 100644 index d281447313..0000000000 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatControlPane.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitor.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitor.java deleted file mode 100644 index 827d93ef6d..0000000000 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitor.java +++ /dev/null @@ -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(); - } -} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorCommandBar.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorCommandBar.java deleted file mode 100644 index c95aa68a38..0000000000 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorCommandBar.java +++ /dev/null @@ -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()); - } -} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorGUI.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorGUI.java deleted file mode 100644 index 27786b1ff4..0000000000 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorGUI.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorRunner.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorRunner.java deleted file mode 100644 index 862fff271d..0000000000 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorRunner.java +++ /dev/null @@ -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"); - } -} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorState.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorState.java deleted file mode 100644 index 7de4bc9bb0..0000000000 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorState.java +++ /dev/null @@ -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; } -} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatPlotPane.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatPlotPane.java deleted file mode 100644 index 2cc122f0d7..0000000000 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatPlotPane.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/JFreeChartAdapter.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/JFreeChartAdapter.java deleted file mode 100644 index 80ef31ca7d..0000000000 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/JFreeChartAdapter.java +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/JFreeChartHeartbeatPlotPane.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/JFreeChartHeartbeatPlotPane.java deleted file mode 100644 index 7075c37d1e..0000000000 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/JFreeChartHeartbeatPlotPane.java +++ /dev/null @@ -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); - - } -} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotConfig.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotConfig.java deleted file mode 100644 index 27d8533aed..0000000000 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotConfig.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotConfigPane.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotConfigPane.java deleted file mode 100644 index aa0ab1cde3..0000000000 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotConfigPane.java +++ /dev/null @@ -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); - } - } -} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotState.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotState.java deleted file mode 100644 index d658081a39..0000000000 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotState.java +++ /dev/null @@ -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; } -} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotStateFetcher.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotStateFetcher.java deleted file mode 100644 index 82b5aa73ae..0000000000 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotStateFetcher.java +++ /dev/null @@ -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(); - } - } - } -} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/StaticPeerData.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/StaticPeerData.java deleted file mode 100644 index bcd20ca804..0000000000 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/StaticPeerData.java +++ /dev/null @@ -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() {} -} \ No newline at end of file diff --git a/apps/httptunnel/doc/COPYING b/apps/httptunnel/doc/COPYING deleted file mode 100644 index 5ec43ee156..0000000000 --- a/apps/httptunnel/doc/COPYING +++ /dev/null @@ -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. diff --git a/apps/httptunnel/doc/readme.license.txt b/apps/httptunnel/doc/readme.license.txt deleted file mode 100644 index 7f7c30bc7e..0000000000 --- a/apps/httptunnel/doc/readme.license.txt +++ /dev/null @@ -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. diff --git a/apps/httptunnel/java/build.xml b/apps/httptunnel/java/build.xml deleted file mode 100644 index 9dfe9ac589..0000000000 --- a/apps/httptunnel/java/build.xml +++ /dev/null @@ -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> diff --git a/apps/httptunnel/java/src/net/i2p/httptunnel/HTTPListener.java b/apps/httptunnel/java/src/net/i2p/httptunnel/HTTPListener.java deleted file mode 100644 index 34ad8faa11..0000000000 --- a/apps/httptunnel/java/src/net/i2p/httptunnel/HTTPListener.java +++ /dev/null @@ -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(); - } -} \ No newline at end of file diff --git a/apps/httptunnel/java/src/net/i2p/httptunnel/HTTPSocketHandler.java b/apps/httptunnel/java/src/net/i2p/httptunnel/HTTPSocketHandler.java deleted file mode 100644 index b5a15e019d..0000000000 --- a/apps/httptunnel/java/src/net/i2p/httptunnel/HTTPSocketHandler.java +++ /dev/null @@ -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); - } - } - } -} \ No newline at end of file diff --git a/apps/httptunnel/java/src/net/i2p/httptunnel/HTTPTunnel.java b/apps/httptunnel/java/src/net/i2p/httptunnel/HTTPTunnel.java deleted file mode 100644 index 4ff9325f0f..0000000000 --- a/apps/httptunnel/java/src/net/i2p/httptunnel/HTTPTunnel.java +++ /dev/null @@ -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)"); - } -} \ No newline at end of file diff --git a/apps/httptunnel/java/src/net/i2p/httptunnel/Request.java b/apps/httptunnel/java/src/net/i2p/httptunnel/Request.java deleted file mode 100644 index b28aef795e..0000000000 --- a/apps/httptunnel/java/src/net/i2p/httptunnel/Request.java +++ /dev/null @@ -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); - } - } -} \ No newline at end of file diff --git a/apps/httptunnel/java/src/net/i2p/httptunnel/SocketManagerProducer.java b/apps/httptunnel/java/src/net/i2p/httptunnel/SocketManagerProducer.java deleted file mode 100644 index ddef387f8b..0000000000 --- a/apps/httptunnel/java/src/net/i2p/httptunnel/SocketManagerProducer.java +++ /dev/null @@ -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(); - } - } -} \ No newline at end of file diff --git a/apps/httptunnel/java/src/net/i2p/httptunnel/filter/ChainFilter.java b/apps/httptunnel/java/src/net/i2p/httptunnel/filter/ChainFilter.java deleted file mode 100644 index b71a194699..0000000000 --- a/apps/httptunnel/java/src/net/i2p/httptunnel/filter/ChainFilter.java +++ /dev/null @@ -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; - } - } - -} \ No newline at end of file diff --git a/apps/httptunnel/java/src/net/i2p/httptunnel/filter/Filter.java b/apps/httptunnel/java/src/net/i2p/httptunnel/filter/Filter.java deleted file mode 100644 index d0ba2b6ff7..0000000000 --- a/apps/httptunnel/java/src/net/i2p/httptunnel/filter/Filter.java +++ /dev/null @@ -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(); -} \ No newline at end of file diff --git a/apps/httptunnel/java/src/net/i2p/httptunnel/filter/NullFilter.java b/apps/httptunnel/java/src/net/i2p/httptunnel/filter/NullFilter.java deleted file mode 100644 index a609ba5f28..0000000000 --- a/apps/httptunnel/java/src/net/i2p/httptunnel/filter/NullFilter.java +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/apps/httptunnel/java/src/net/i2p/httptunnel/handler/EepHandler.java b/apps/httptunnel/java/src/net/i2p/httptunnel/handler/EepHandler.java deleted file mode 100644 index 4e9ffb3677..0000000000 --- a/apps/httptunnel/java/src/net/i2p/httptunnel/handler/EepHandler.java +++ /dev/null @@ -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; - } -} diff --git a/apps/httptunnel/java/src/net/i2p/httptunnel/handler/ErrorHandler.java b/apps/httptunnel/java/src/net/i2p/httptunnel/handler/ErrorHandler.java deleted file mode 100644 index 35c2962817..0000000000 --- a/apps/httptunnel/java/src/net/i2p/httptunnel/handler/ErrorHandler.java +++ /dev/null @@ -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(); - } -} \ No newline at end of file diff --git a/apps/httptunnel/java/src/net/i2p/httptunnel/handler/LocalHandler.java b/apps/httptunnel/java/src/net/i2p/httptunnel/handler/LocalHandler.java deleted file mode 100644 index b2142c6102..0000000000 --- a/apps/httptunnel/java/src/net/i2p/httptunnel/handler/LocalHandler.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/apps/httptunnel/java/src/net/i2p/httptunnel/handler/ProxyHandler.java b/apps/httptunnel/java/src/net/i2p/httptunnel/handler/ProxyHandler.java deleted file mode 100644 index a0e55e931a..0000000000 --- a/apps/httptunnel/java/src/net/i2p/httptunnel/handler/ProxyHandler.java +++ /dev/null @@ -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"); - } -} \ No newline at end of file diff --git a/apps/httptunnel/java/src/net/i2p/httptunnel/handler/RootHandler.java b/apps/httptunnel/java/src/net/i2p/httptunnel/handler/RootHandler.java deleted file mode 100644 index 1cb0b26946..0000000000 --- a/apps/httptunnel/java/src/net/i2p/httptunnel/handler/RootHandler.java +++ /dev/null @@ -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); - } - } -} \ No newline at end of file diff --git a/apps/jfreechart/GUI-licenses.txt b/apps/jfreechart/GUI-licenses.txt deleted file mode 100644 index dcbe66d024..0000000000 --- a/apps/jfreechart/GUI-licenses.txt +++ /dev/null @@ -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] - * - */ diff --git a/apps/jfreechart/build.xml b/apps/jfreechart/build.xml deleted file mode 100644 index 91434d6119..0000000000 --- a/apps/jfreechart/build.xml +++ /dev/null @@ -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> diff --git a/apps/myi2p/java/build.xml b/apps/myi2p/java/build.xml deleted file mode 100644 index 6c79ddc737..0000000000 --- a/apps/myi2p/java/build.xml +++ /dev/null @@ -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> diff --git a/apps/myi2p/java/src/net/i2p/myi2p/MyI2PMessage.java b/apps/myi2p/java/src/net/i2p/myi2p/MyI2PMessage.java deleted file mode 100644 index e2b05666d7..0000000000 --- a/apps/myi2p/java/src/net/i2p/myi2p/MyI2PMessage.java +++ /dev/null @@ -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; - } -} diff --git a/apps/myi2p/java/src/net/i2p/myi2p/Node.java b/apps/myi2p/java/src/net/i2p/myi2p/Node.java deleted file mode 100644 index 71845f31e7..0000000000 --- a/apps/myi2p/java/src/net/i2p/myi2p/Node.java +++ /dev/null @@ -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); - } - } -} diff --git a/apps/myi2p/java/src/net/i2p/myi2p/NodeAdapter.java b/apps/myi2p/java/src/net/i2p/myi2p/NodeAdapter.java deleted file mode 100644 index bfb5906ece..0000000000 --- a/apps/myi2p/java/src/net/i2p/myi2p/NodeAdapter.java +++ /dev/null @@ -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"); - } -} diff --git a/apps/myi2p/java/src/net/i2p/myi2p/Service.java b/apps/myi2p/java/src/net/i2p/myi2p/Service.java deleted file mode 100644 index f18f2ebf26..0000000000 --- a/apps/myi2p/java/src/net/i2p/myi2p/Service.java +++ /dev/null @@ -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(); -} diff --git a/apps/myi2p/java/src/net/i2p/myi2p/ServiceImpl.java b/apps/myi2p/java/src/net/i2p/myi2p/ServiceImpl.java deleted file mode 100644 index f5a4ff6c07..0000000000 --- a/apps/myi2p/java/src/net/i2p/myi2p/ServiceImpl.java +++ /dev/null @@ -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; } -} diff --git a/apps/myi2p/java/src/net/i2p/myi2p/address/AddressBook.java b/apps/myi2p/java/src/net/i2p/myi2p/address/AddressBook.java deleted file mode 100644 index aa74445d2a..0000000000 --- a/apps/myi2p/java/src/net/i2p/myi2p/address/AddressBook.java +++ /dev/null @@ -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(); - } -} diff --git a/apps/myi2p/java/src/net/i2p/myi2p/address/AddressBookEntry.java b/apps/myi2p/java/src/net/i2p/myi2p/address/AddressBookEntry.java deleted file mode 100644 index b0f2138619..0000000000 --- a/apps/myi2p/java/src/net/i2p/myi2p/address/AddressBookEntry.java +++ /dev/null @@ -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()); - } - } -} diff --git a/apps/myi2p/java/src/net/i2p/myi2p/address/AddressBookService.java b/apps/myi2p/java/src/net/i2p/myi2p/address/AddressBookService.java deleted file mode 100644 index b428635075..0000000000 --- a/apps/myi2p/java/src/net/i2p/myi2p/address/AddressBookService.java +++ /dev/null @@ -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()); - } - } -} diff --git a/apps/myi2p/java/src/net/i2p/myi2p/address/AddressBookServiceData.java b/apps/myi2p/java/src/net/i2p/myi2p/address/AddressBookServiceData.java deleted file mode 100644 index 3bfaa8e74e..0000000000 --- a/apps/myi2p/java/src/net/i2p/myi2p/address/AddressBookServiceData.java +++ /dev/null @@ -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) {} - } - } -} diff --git a/apps/myi2p/java/src/net/i2p/myi2p/address/CreateEntryCLI.java b/apps/myi2p/java/src/net/i2p/myi2p/address/CreateEntryCLI.java deleted file mode 100644 index b90de3c577..0000000000 --- a/apps/myi2p/java/src/net/i2p/myi2p/address/CreateEntryCLI.java +++ /dev/null @@ -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(); - } -} diff --git a/apps/myi2p/java/src/net/i2p/myi2p/address/CreateNameReferenceCLI.java b/apps/myi2p/java/src/net/i2p/myi2p/address/CreateNameReferenceCLI.java deleted file mode 100644 index f57ede9368..0000000000 --- a/apps/myi2p/java/src/net/i2p/myi2p/address/CreateNameReferenceCLI.java +++ /dev/null @@ -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(); - } -} diff --git a/apps/myi2p/java/src/net/i2p/myi2p/address/NameReference.java b/apps/myi2p/java/src/net/i2p/myi2p/address/NameReference.java deleted file mode 100644 index c8e5971553..0000000000 --- a/apps/myi2p/java/src/net/i2p/myi2p/address/NameReference.java +++ /dev/null @@ -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; - } -} diff --git a/apps/myi2p/java/src/net/i2p/myi2p/address/Subscription.java b/apps/myi2p/java/src/net/i2p/myi2p/address/Subscription.java deleted file mode 100644 index 0680fc6a29..0000000000 --- a/apps/myi2p/java/src/net/i2p/myi2p/address/Subscription.java +++ /dev/null @@ -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()); - } - } -} diff --git a/apps/myi2p/myi2p.config b/apps/myi2p/myi2p.config deleted file mode 100644 index 2d66d576c1..0000000000 --- a/apps/myi2p/myi2p.config +++ /dev/null @@ -1,3 +0,0 @@ -keyFile=myi2p.keys -service.0.classname=net.i2p.myi2p.address.AddressBookService -service.0.type=AddressBook \ No newline at end of file diff --git a/apps/netmonitor/harvester.config b/apps/netmonitor/harvester.config deleted file mode 100644 index 91f3332245..0000000000 --- a/apps/netmonitor/harvester.config +++ /dev/null @@ -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 \ No newline at end of file diff --git a/apps/netmonitor/java/build.xml b/apps/netmonitor/java/build.xml deleted file mode 100644 index a463045561..0000000000 --- a/apps/netmonitor/java/build.xml +++ /dev/null @@ -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> diff --git a/apps/netmonitor/java/src/net/i2p/netmonitor/DataHarvester.java b/apps/netmonitor/java/src/net/i2p/netmonitor/DataHarvester.java deleted file mode 100644 index 6d14a8174f..0000000000 --- a/apps/netmonitor/java/src/net/i2p/netmonitor/DataHarvester.java +++ /dev/null @@ -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(); - } - } -} \ No newline at end of file diff --git a/apps/netmonitor/java/src/net/i2p/netmonitor/NetMonitor.java b/apps/netmonitor/java/src/net/i2p/netmonitor/NetMonitor.java deleted file mode 100644 index 5fdf1aab76..0000000000 --- a/apps/netmonitor/java/src/net/i2p/netmonitor/NetMonitor.java +++ /dev/null @@ -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(); - } -} diff --git a/apps/netmonitor/java/src/net/i2p/netmonitor/NetMonitorRunner.java b/apps/netmonitor/java/src/net/i2p/netmonitor/NetMonitorRunner.java deleted file mode 100644 index 0df06f76eb..0000000000 --- a/apps/netmonitor/java/src/net/i2p/netmonitor/NetMonitorRunner.java +++ /dev/null @@ -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) {} - } -} diff --git a/apps/netmonitor/java/src/net/i2p/netmonitor/PeerStat.java b/apps/netmonitor/java/src/net/i2p/netmonitor/PeerStat.java deleted file mode 100644 index d19e31dd27..0000000000 --- a/apps/netmonitor/java/src/net/i2p/netmonitor/PeerStat.java +++ /dev/null @@ -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; } -} \ No newline at end of file diff --git a/apps/netmonitor/java/src/net/i2p/netmonitor/PeerSummary.java b/apps/netmonitor/java/src/net/i2p/netmonitor/PeerSummary.java deleted file mode 100644 index f2e74b9e01..0000000000 --- a/apps/netmonitor/java/src/net/i2p/netmonitor/PeerSummary.java +++ /dev/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); - } -} diff --git a/apps/netmonitor/java/src/net/i2p/netmonitor/PeerSummaryReader.java b/apps/netmonitor/java/src/net/i2p/netmonitor/PeerSummaryReader.java deleted file mode 100644 index 2b67dc87a4..0000000000 --- a/apps/netmonitor/java/src/net/i2p/netmonitor/PeerSummaryReader.java +++ /dev/null @@ -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(); - } - } -} diff --git a/apps/netmonitor/java/src/net/i2p/netmonitor/PeerSummaryWriter.java b/apps/netmonitor/java/src/net/i2p/netmonitor/PeerSummaryWriter.java deleted file mode 100644 index ba848ed948..0000000000 --- a/apps/netmonitor/java/src/net/i2p/netmonitor/PeerSummaryWriter.java +++ /dev/null @@ -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)); - } - } -} \ No newline at end of file diff --git a/apps/netmonitor/java/src/net/i2p/netmonitor/StatGroup.java b/apps/netmonitor/java/src/net/i2p/netmonitor/StatGroup.java deleted file mode 100644 index dcf9e58174..0000000000 --- a/apps/netmonitor/java/src/net/i2p/netmonitor/StatGroup.java +++ /dev/null @@ -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; } - } -} \ No newline at end of file diff --git a/apps/netmonitor/java/src/net/i2p/netmonitor/StatGroupLoader.java b/apps/netmonitor/java/src/net/i2p/netmonitor/StatGroupLoader.java deleted file mode 100644 index 1b777f2097..0000000000 --- a/apps/netmonitor/java/src/net/i2p/netmonitor/StatGroupLoader.java +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/apps/netmonitor/java/src/net/i2p/netmonitor/gui/JFreeChartAdapter.java b/apps/netmonitor/java/src/net/i2p/netmonitor/gui/JFreeChartAdapter.java deleted file mode 100644 index 95a26e4ced..0000000000 --- a/apps/netmonitor/java/src/net/i2p/netmonitor/gui/JFreeChartAdapter.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/apps/netmonitor/java/src/net/i2p/netmonitor/gui/JFreeChartHeartbeatPlotPane.java b/apps/netmonitor/java/src/net/i2p/netmonitor/gui/JFreeChartHeartbeatPlotPane.java deleted file mode 100644 index 9d4a4e7f18..0000000000 --- a/apps/netmonitor/java/src/net/i2p/netmonitor/gui/JFreeChartHeartbeatPlotPane.java +++ /dev/null @@ -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); - - } -} \ No newline at end of file diff --git a/apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewer.java b/apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewer.java deleted file mode 100644 index 174860b3f2..0000000000 --- a/apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewer.java +++ /dev/null @@ -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(); - } -} \ No newline at end of file diff --git a/apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewerCommandBar.java b/apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewerCommandBar.java deleted file mode 100644 index b1b0b923b4..0000000000 --- a/apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewerCommandBar.java +++ /dev/null @@ -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()); - } -} \ No newline at end of file diff --git a/apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewerControlPane.java b/apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewerControlPane.java deleted file mode 100644 index 22aca39d5b..0000000000 --- a/apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewerControlPane.java +++ /dev/null @@ -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(); } -} \ No newline at end of file diff --git a/apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewerGUI.java b/apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewerGUI.java deleted file mode 100644 index c50d45144d..0000000000 --- a/apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewerGUI.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewerPlotPane.java b/apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewerPlotPane.java deleted file mode 100644 index 321698d0c2..0000000000 --- a/apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewerPlotPane.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewerRunner.java b/apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewerRunner.java deleted file mode 100644 index 2917e14eeb..0000000000 --- a/apps/netmonitor/java/src/net/i2p/netmonitor/gui/NetViewerRunner.java +++ /dev/null @@ -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) {} - } - } -} \ No newline at end of file diff --git a/apps/netmonitor/java/src/net/i2p/netmonitor/gui/PeerPlotConfig.java b/apps/netmonitor/java/src/net/i2p/netmonitor/gui/PeerPlotConfig.java deleted file mode 100644 index fafbd22b9d..0000000000 --- a/apps/netmonitor/java/src/net/i2p/netmonitor/gui/PeerPlotConfig.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/apps/netmonitor/java/src/net/i2p/netmonitor/gui/PeerPlotConfigPane.java b/apps/netmonitor/java/src/net/i2p/netmonitor/gui/PeerPlotConfigPane.java deleted file mode 100644 index 864fd1be83..0000000000 --- a/apps/netmonitor/java/src/net/i2p/netmonitor/gui/PeerPlotConfigPane.java +++ /dev/null @@ -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(); - } - } -} \ No newline at end of file diff --git a/apps/phttprelay/doc/readme.license.txt b/apps/phttprelay/doc/readme.license.txt deleted file mode 100644 index 18f90e09bb..0000000000 --- a/apps/phttprelay/doc/readme.license.txt +++ /dev/null @@ -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. diff --git a/apps/phttprelay/java/build.xml b/apps/phttprelay/java/build.xml deleted file mode 100644 index 6d924a2a27..0000000000 --- a/apps/phttprelay/java/build.xml +++ /dev/null @@ -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> diff --git a/apps/phttprelay/java/lib/LICENSE.html b/apps/phttprelay/java/lib/LICENSE.html deleted file mode 100644 index 678cdec531..0000000000 --- a/apps/phttprelay/java/lib/LICENSE.html +++ /dev/null @@ -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> - - diff --git a/apps/phttprelay/java/lib/javax.servlet.jar b/apps/phttprelay/java/lib/javax.servlet.jar deleted file mode 100644 index 6bee0cc32157b81cbae461f3c465dc413d796c46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74543 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�(>a4{=Sn>LB1-ShtZvwSf+8tD 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 CpF_I< diff --git a/apps/phttprelay/java/lib/readme.txt b/apps/phttprelay/java/lib/readme.txt deleted file mode 100644 index d16d9e6791..0000000000 --- a/apps/phttprelay/java/lib/readme.txt +++ /dev/null @@ -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. diff --git a/apps/phttprelay/java/src/net/i2p/phttprelay/CheckSendStatusServlet.java b/apps/phttprelay/java/src/net/i2p/phttprelay/CheckSendStatusServlet.java deleted file mode 100644 index b7c2af1b94..0000000000 --- a/apps/phttprelay/java/src/net/i2p/phttprelay/CheckSendStatusServlet.java +++ /dev/null @@ -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(); - } -} \ No newline at end of file diff --git a/apps/phttprelay/java/src/net/i2p/phttprelay/LockManager.java b/apps/phttprelay/java/src/net/i2p/phttprelay/LockManager.java deleted file mode 100644 index 433203169f..0000000000 --- a/apps/phttprelay/java/src/net/i2p/phttprelay/LockManager.java +++ /dev/null @@ -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(); - } - } -} \ No newline at end of file diff --git a/apps/phttprelay/java/src/net/i2p/phttprelay/PHTTPRelayServlet.java b/apps/phttprelay/java/src/net/i2p/phttprelay/PHTTPRelayServlet.java deleted file mode 100644 index 45744a2d20..0000000000 --- a/apps/phttprelay/java/src/net/i2p/phttprelay/PHTTPRelayServlet.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/apps/phttprelay/java/src/net/i2p/phttprelay/PollServlet.java b/apps/phttprelay/java/src/net/i2p/phttprelay/PollServlet.java deleted file mode 100644 index d1cf4acaf3..0000000000 --- a/apps/phttprelay/java/src/net/i2p/phttprelay/PollServlet.java +++ /dev/null @@ -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; - } - } -} \ No newline at end of file diff --git a/apps/phttprelay/java/src/net/i2p/phttprelay/RegisterServlet.java b/apps/phttprelay/java/src/net/i2p/phttprelay/RegisterServlet.java deleted file mode 100644 index 822228b18b..0000000000 --- a/apps/phttprelay/java/src/net/i2p/phttprelay/RegisterServlet.java +++ /dev/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; - } -} \ No newline at end of file diff --git a/apps/phttprelay/java/src/net/i2p/phttprelay/SendServlet.java b/apps/phttprelay/java/src/net/i2p/phttprelay/SendServlet.java deleted file mode 100644 index a09a4258c4..0000000000 --- a/apps/phttprelay/java/src/net/i2p/phttprelay/SendServlet.java +++ /dev/null @@ -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 + "]"); - } - } -} \ No newline at end of file diff --git a/apps/phttprelay/java/web.xml b/apps/phttprelay/java/web.xml deleted file mode 100644 index 8a82891fed..0000000000 --- a/apps/phttprelay/java/web.xml +++ /dev/null @@ -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> diff --git a/apps/tests/COPYING b/apps/tests/COPYING deleted file mode 100644 index 5ec43ee156..0000000000 --- a/apps/tests/COPYING +++ /dev/null @@ -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. diff --git a/apps/tests/EchoServer.java b/apps/tests/EchoServer.java deleted file mode 100644 index 1ffe8f2e35..0000000000 --- a/apps/tests/EchoServer.java +++ /dev/null @@ -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(); - } - } -} diff --git a/apps/tests/GuaranteedBug.java b/apps/tests/GuaranteedBug.java deleted file mode 100644 index 147b845267..0000000000 --- a/apps/tests/GuaranteedBug.java +++ /dev/null @@ -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(); - } - } - } -} diff --git a/apps/tests/README b/apps/tests/README deleted file mode 100644 index 83f17a4a88..0000000000 --- a/apps/tests/README +++ /dev/null @@ -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. diff --git a/apps/tests/echotester/BasicEchoTestAnalyzer.java b/apps/tests/echotester/BasicEchoTestAnalyzer.java deleted file mode 100644 index 577db8feb0..0000000000 --- a/apps/tests/echotester/BasicEchoTestAnalyzer.java +++ /dev/null @@ -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++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); - } - } -} \ No newline at end of file diff --git a/apps/tests/echotester/EchoTestAnalyzer.java b/apps/tests/echotester/EchoTestAnalyzer.java deleted file mode 100644 index 96c5f715b7..0000000000 --- a/apps/tests/echotester/EchoTestAnalyzer.java +++ /dev/null @@ -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); - -} - diff --git a/apps/tests/echotester/EchoTester.java b/apps/tests/echotester/EchoTester.java deleted file mode 100644 index 750ccb7ba6..0000000000 --- a/apps/tests/echotester/EchoTester.java +++ /dev/null @@ -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; - } - } - } -} \ No newline at end of file diff --git a/apps/tests/readme.license.txt b/apps/tests/readme.license.txt deleted file mode 100644 index 4c14112cb0..0000000000 --- a/apps/tests/readme.license.txt +++ /dev/null @@ -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. -- GitLab