forked from I2P_Developers/i2p.i2p
Compare commits
412 Commits
i2p_0_6_1_
...
i2p-0.6.1.
| Author | SHA1 | Date | |
|---|---|---|---|
| cffcbe5f94 | |||
| c46b06fb81 | |||
| a7397879aa | |||
| 9b86da7ce5 | |||
| c68977ca8c | |||
| bc7bd628db | |||
| bc7ab39131 | |||
| 100163e03b | |||
| 49c02f13b2 | |||
| 40f072e25e | |||
| 918b1acb8f | |||
| bc16078e3f | |||
| 4a8dbd0634 | |||
| 38c0184f95 | |||
| 68829ddb99 | |||
| 19089bd6a7 | |||
| 69cc0afd1b | |||
| d2f3a262db | |||
|
|
c1703b872d | ||
|
|
f7b0e8181b | ||
| 43f2695901 | |||
|
|
ea0d4ffd7f | ||
|
|
0ed29573a7 | ||
| 1365d3e3b9 | |||
| 40befb5a92 | |||
| 9c16eec361 | |||
| 093c69637d | |||
| 134ec7acea | |||
|
|
0802a5ae40 | ||
|
|
14ddfb360f | ||
|
|
78ad831028 | ||
| 22f1684262 | |||
| 83f51b4a66 | |||
| 9c28de0704 | |||
| c69fda2298 | |||
| cabb22331b | |||
|
|
5b3aca29a8 | ||
|
|
f35cbf59d8 | ||
|
|
a96119d09b | ||
|
|
2711294aee | ||
|
|
f838b1828b | ||
|
|
fbf6282c1a | ||
|
|
5195a5c1fc | ||
|
|
62b18b18b5 | ||
|
|
7c8f519b35 | ||
|
|
d6fb979616 | ||
|
|
f568d21969 | ||
|
|
7fe9d590f5 | ||
|
|
0a1240ebfd | ||
|
|
4e68f2a157 | ||
|
|
e9bd6907d1 | ||
|
|
b20495c39f | ||
|
|
0ecbc4c27b | ||
|
|
4d5b1d4c3f | ||
|
|
17b719f3f7 | ||
|
|
979a3e98d8 | ||
|
|
c6a1112f0a | ||
|
|
4ebcc95d9f | ||
|
|
7e59ce27fa | ||
|
|
22345a264e | ||
|
|
03739996da | ||
|
|
819a72d4f6 | ||
|
|
e480931e20 | ||
|
|
3f01d0a69e | ||
|
|
f67f47f0cd | ||
|
|
5ad6ee60eb | ||
|
|
5accba6cdc | ||
|
|
313e1704df | ||
|
|
cf4d2b17c9 | ||
|
|
9145eedc35 | ||
|
|
b772179077 | ||
|
|
9054a196ce | ||
|
|
d28a96ac7d | ||
|
|
9c73f80ac3 | ||
|
|
20c46cff04 | ||
|
|
f332513755 | ||
|
|
cb69a66498 | ||
|
|
1c66543938 | ||
|
|
53ab3c472e | ||
|
|
a4b221fa71 | ||
|
|
e3e1d0842d | ||
|
|
99b5bf9609 | ||
|
|
da10fe0df7 | ||
|
|
9fd5ba7b2d | ||
|
|
05b5df9d76 | ||
|
|
5c1dc79767 | ||
|
|
4acd2996c5 | ||
|
|
16fa6a89bc | ||
|
|
2a72e8574b | ||
|
|
d4a1bcf28f | ||
|
|
409b71def5 | ||
|
|
2dc5fbda02 | ||
|
|
71aaf03d09 | ||
|
|
30c99e630b | ||
|
|
445b39171a | ||
|
|
571c2d6047 | ||
|
|
42ff763933 | ||
|
|
727edc3ff9 | ||
|
|
82a4758a0a | ||
|
|
915914ebb3 | ||
|
|
c438b56378 | ||
|
|
307ccfb1b4 | ||
|
|
34e23259b4 | ||
|
|
036802d66a | ||
|
|
6a7dbc8e3a | ||
|
|
cf4a9ffc27 | ||
|
|
6ef4adf318 | ||
|
|
f84c9bf3b1 | ||
|
|
da0837bd58 | ||
|
|
9094a62273 | ||
|
|
026183a655 | ||
|
|
b033c7945c | ||
|
|
0c2dcf0845 | ||
|
|
b6e597e5bf | ||
|
|
ae402baa71 | ||
|
|
d6c8a4d9eb | ||
|
|
8e2849b7e5 | ||
|
|
0aa0cd330f | ||
|
|
0960cafaf5 | ||
|
|
2088a28053 | ||
|
|
a5c4ba3bff | ||
|
|
1bbd2cf52e | ||
|
|
ce50efa60c | ||
|
|
a3c64a9ba3 | ||
|
|
1447164a8a | ||
|
|
bc0bf8d7ff | ||
|
|
9f346f488e | ||
|
|
760d7d9704 | ||
|
|
77310e17d1 | ||
|
|
e54b964929 | ||
|
|
809f3e847b | ||
|
|
f4beebe60d | ||
|
|
827e427f0b | ||
|
|
c02125511d | ||
|
|
1aa1069b6f | ||
|
|
91d281077d | ||
|
|
f339dec024 | ||
|
|
2aeef44f8d | ||
|
|
0fd41a9490 | ||
|
|
58f10d14b2 | ||
|
|
46ca42ddf8 | ||
|
|
e6e6d6f4ee | ||
|
|
8a87df605b | ||
|
|
8ca085bceb | ||
|
|
df47587db0 | ||
|
|
d705e0ad04 | ||
|
|
40d209dd7c | ||
|
|
7f2a0457bf | ||
|
|
f4749f2483 | ||
|
|
9c42830076 | ||
|
|
53ba6c2a64 | ||
|
|
61b3f21f69 | ||
|
|
506fd5f889 | ||
|
|
d538f888b4 | ||
|
|
976c5fdd47 | ||
|
|
b63f3437f2 | ||
|
|
e760f2e538 | ||
|
|
17c8fca779 | ||
|
|
87fda382c3 | ||
|
|
1e404cd7ac | ||
|
|
098f99d806 | ||
|
|
da93f96035 | ||
|
|
ead39cc87e | ||
|
|
e4e3c44459 | ||
|
|
af151e32e5 | ||
|
|
12819a2a17 | ||
|
|
87eedff254 | ||
|
|
4c59cd7621 | ||
|
|
ef707e7956 | ||
|
|
73cf3fb299 | ||
|
|
80b0c97d72 | ||
|
|
5cf85c1d7b | ||
|
|
c14e52ceb5 | ||
|
|
32a579e480 | ||
|
|
0a240a4436 | ||
|
|
9325b806e4 | ||
|
|
ef2e24ea11 | ||
|
|
373934c6e0 | ||
|
|
e8e8bac694 | ||
|
|
23e8a558c2 | ||
|
|
46f2645834 | ||
|
|
2329439034 | ||
|
|
6d400368b9 | ||
|
|
26c13b40fe | ||
|
|
9fd0e95fe8 | ||
|
|
7e21f2c92b | ||
|
|
c9d8e796c6 | ||
|
|
e7203f5d46 | ||
|
|
22d76a1b64 | ||
|
|
0903dc46c6 | ||
|
|
0f56ec8078 | ||
|
|
70ee1df2bf | ||
|
|
61a6a29bec | ||
|
|
678f7d8f72 | ||
|
|
b92ee364bc | ||
|
|
aef19fcd38 | ||
|
|
3b01df1d2c | ||
|
|
4aed23b198 | ||
|
|
03e8875c27 | ||
|
|
48921a0875 | ||
|
|
633fabb09e | ||
|
|
bc42c26d94 | ||
|
|
3c09ca3359 | ||
|
|
1e9e7dd345 | ||
|
|
034803add7 | ||
|
|
b25bb053bb | ||
|
|
9bd0c79441 | ||
|
|
06b8670410 | ||
|
|
6577ae499f | ||
|
|
54bc5485ec | ||
|
|
84b741ac98 | ||
|
|
c48c419d74 | ||
|
|
fb2e795add | ||
|
|
ec215777ec | ||
|
|
d4e0f27c56 | ||
|
|
e1c686baa6 | ||
|
|
d57af1aef4 | ||
|
|
a52dd57215 | ||
|
|
65138357d3 | ||
|
|
f6320696dd | ||
|
|
900d8a2026 | ||
|
|
ccc9a87e8c | ||
|
|
208634e5de | ||
|
|
3d07205c9d | ||
|
|
f0a424a93f | ||
|
|
f9b59ee07d | ||
|
|
b92b9d2618 | ||
|
|
a3db9429a7 | ||
|
|
291a5c9578 | ||
|
|
0a3281c279 | ||
|
|
23f30ba576 | ||
|
|
f3de85c4de | ||
|
|
a3a4888e0b | ||
|
|
6fd7881f8e | ||
|
|
381f716769 | ||
|
|
f2078e1523 | ||
|
|
f2fb87c88b | ||
|
|
fcbea19478 | ||
|
|
92f25bd4fa | ||
|
|
85c2c11217 | ||
|
|
de1ca4aea4 | ||
|
|
a0f865fb99 | ||
|
|
2c3fea5605 | ||
|
|
ba1d88b5c9 | ||
|
|
2ad715c668 | ||
|
|
5f17557e54 | ||
|
|
2ad5a6f907 | ||
|
|
0920462060 | ||
|
|
870e94e184 | ||
|
|
6b0d507644 | ||
|
|
70cf9e4ca7 | ||
|
|
2a3974c71d | ||
|
|
46ac9292e8 | ||
|
|
4307097472 | ||
|
|
ed3fdaf4f1 | ||
|
|
378a9a8f5c | ||
|
|
4ef6180455 | ||
|
|
d4970e23c0 | ||
|
|
0c9f165016 | ||
|
|
be3a899ecb | ||
|
|
7a6a749004 | ||
|
|
17271ee3f0 | ||
|
|
99bcfa90df | ||
|
|
eb36e993c1 | ||
|
|
e5eca5fa45 | ||
|
|
8cba2f4236 | ||
|
|
40d5ed31ac | ||
|
|
181275fe35 | ||
|
|
23d8c01ce7 | ||
|
|
de83944486 | ||
|
|
90cd7ff23a | ||
|
|
8d0a9b4ccd | ||
|
|
230d4cd23f | ||
|
|
e9b6fcc0a4 | ||
|
|
8fcb871409 | ||
|
|
83bef43fd5 | ||
|
|
b4fc6ca31b | ||
|
|
ab3f1b708d | ||
|
|
c76402a160 | ||
|
|
a50c73aa5e | ||
|
|
5aa66795d2 | ||
|
|
ac3c2d2b15 | ||
|
|
072a45e5ce | ||
|
|
1ab14e52d2 | ||
|
|
9a820961a2 | ||
|
|
764149aef3 | ||
|
|
1b3ad31bff | ||
|
|
15e6c27c04 | ||
|
|
8b707e569f | ||
|
|
e4c4b24c61 | ||
|
|
031636e607 | ||
|
|
b5c0d77c69 | ||
|
|
d489caa88c | ||
|
|
2a24029acf | ||
|
|
c5aab8c750 | ||
|
|
343748111a | ||
|
|
c5ddfabfe9 | ||
|
|
1ef33906ed | ||
|
|
f3849a22ad | ||
|
|
b03ff21d3b | ||
|
|
52094b10c9 | ||
|
|
fc927efaa3 | ||
|
|
65dc803fb7 | ||
|
|
349adf6690 | ||
|
|
2c843fd818 | ||
|
|
863b511cde | ||
|
|
c417e7c237 | ||
|
|
1822c0d7d8 | ||
|
|
94c1c32b51 | ||
|
|
deb35f4af4 | ||
|
|
883150f943 | ||
|
|
717d1b97b2 | ||
|
|
e62135eacc | ||
|
|
2c6d953359 | ||
|
|
2b79e2df3f | ||
|
|
fab6e421b8 | ||
|
|
589cbd675a | ||
|
|
c486f5980a | ||
|
|
eee21aa301 | ||
|
|
a2854cf6f6 | ||
|
|
62b7cf64da | ||
|
|
7b2a435aad | ||
|
|
3d8d21e543 | ||
|
|
8b7958cff2 | ||
|
|
7bb792836d | ||
|
|
03f509ca54 | ||
|
|
5f05631936 | ||
|
|
5cfedd4c8b | ||
|
|
269fec64a5 | ||
|
|
f63c6f4717 | ||
|
|
b4c495531a | ||
|
|
9990126e3e | ||
|
|
ac8436a8eb | ||
|
|
dee79dfb1c | ||
|
|
9b4e6f475d | ||
|
|
7672ba23d1 | ||
|
|
4b77ddedcc | ||
|
|
222af6c090 | ||
|
|
8e879cb646 | ||
|
|
65975df1be | ||
|
|
c94de2fbb5 | ||
|
|
5aa335740a | ||
|
|
1202751359 | ||
|
|
34fcf53d87 | ||
|
|
9ddc632b9f | ||
|
|
941b65eb32 | ||
|
|
8c9167464b | ||
|
|
5b94965983 | ||
|
|
3226ea5bf6 | ||
|
|
84a24784e4 | ||
|
|
71d3fa6b8c | ||
|
|
9e00dbaafd | ||
|
|
2e9e0c64d4 | ||
|
|
fde3f1ce7d | ||
|
|
321c560648 | ||
|
|
d2ddca7d64 | ||
|
|
fb17e70f12 | ||
|
|
79f934fe17 | ||
|
|
3d76df6af3 | ||
|
|
d5c36f7f4e | ||
|
|
41ac628744 | ||
|
|
41e5e1a094 | ||
|
|
74edc3fa7d | ||
|
|
3a26218b5a | ||
|
|
687abd9427 | ||
|
|
113fbc1df3 | ||
|
|
1374ea0ea1 | ||
|
|
424e55d3b2 | ||
|
|
fde4f579f4 | ||
|
|
2d651a41f0 | ||
|
|
1eebd5463f | ||
|
|
f22601b477 | ||
|
|
ab8e11657f | ||
|
|
17eb7fa983 | ||
|
|
d1134f9704 | ||
|
|
13fe45b489 | ||
|
|
cd235e5902 | ||
|
|
b727d868fb | ||
|
|
cd2609b34a | ||
|
|
a12ede096a | ||
|
|
eb3442823e | ||
|
|
211f37c207 | ||
|
|
d60d0923c0 | ||
|
|
0448203ede | ||
|
|
00c97dbf92 | ||
|
|
d07342e3e3 | ||
|
|
205d8de281 | ||
|
|
a638301b5c | ||
|
|
4f51ad492b | ||
|
|
c3a9f72d41 | ||
|
|
79476d3609 | ||
|
|
97a206fcda | ||
|
|
f783e65a4c | ||
|
|
dbd1f65864 | ||
|
|
5c78d8108f | ||
|
|
1b273bdf43 | ||
|
|
934f4082f1 | ||
|
|
002aed145f | ||
|
|
1ca27ffd39 | ||
|
|
66e6dbec33 | ||
|
|
894caaa63c | ||
|
|
97dae94b46 | ||
|
|
c00488afeb | ||
|
|
23723b56ca | ||
|
|
76f89ac93c | ||
|
|
0f8611e465 | ||
|
|
8e87ae08fb | ||
|
|
5b1a6391f3 | ||
|
|
728f177473 | ||
|
|
1d0d0d9c69 | ||
|
|
9b7e5d1817 | ||
|
|
dc0485b526 |
11
Makefile.gcj
11
Makefile.gcj
@@ -21,11 +21,12 @@ NATIVE_DIR=native
|
||||
# router.jar: full I2P router
|
||||
# jbigi.jar: collection of native optimized GMP routines for crypto
|
||||
JAR_BASE=i2p.jar mstreaming.jar streaming.jar
|
||||
JAR_CLIENTS=i2ptunnel.jar sam.jar i2psnark.jar
|
||||
JAR_CLIENTS=i2ptunnel.jar sam.jar
|
||||
JAR_ROUTER=router.jar
|
||||
JAR_JBIGI=jbigi.jar
|
||||
JAR_XML=xml-apis.jar resolver.jar xercesImpl.jar
|
||||
JAR_CONSOLE=\
|
||||
i2psnark.jar \
|
||||
javax.servlet.jar \
|
||||
commons-el.jar \
|
||||
commons-logging.jar \
|
||||
@@ -79,15 +80,15 @@ native_clean:
|
||||
native_shared: libi2p.so
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2p_dsa --main=net.i2p.crypto.DSAEngine
|
||||
@echo "* i2p_dsa is a simple test app with the DSA engine and Fortuna PRNG to make sure crypto is working"
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/prng --main=gnu.crypto.prng.Fortuna
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/prng --main=gnu.crypto.prng.FortunaStandalone
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2ptunnel --main=net.i2p.i2ptunnel.I2PTunnel
|
||||
@echo "* i2ptunnel is mihi's I2PTunnel CLI"
|
||||
@echo " run it as ./i2ptunnel -cli to avoid awt complaints"
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2ptunnelctl --main=net.i2p.i2ptunnel.TunnelControllerGroup
|
||||
@echo "* i2ptunnelctl is a controller for I2PTunnel, reading i2ptunnel.config"
|
||||
@echo " and launching the appropriate proxies"
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2psnark --main=org.klomp.snark.Snark
|
||||
@echo "* i2psnark is an anonymous bittorrent client"
|
||||
#@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2psnark --main=org.klomp.snark.Snark
|
||||
#@echo "* i2psnark is an anonymous bittorrent client"
|
||||
@cd build ; ${GCJ} ${OPTIMIZE} -fjni -L../${NATIVE_DIR} -li2p ${SYSTEM_PROPS} -o ../${NATIVE_DIR}/i2prouter --main=net.i2p.router.Router
|
||||
@echo "* i2prouter is the main I2P router"
|
||||
@echo " it can be used, and while the router console won't load,"
|
||||
@@ -95,6 +96,6 @@ native_shared: libi2p.so
|
||||
|
||||
libi2p.so:
|
||||
@echo "* Building libi2p.so"
|
||||
@(cd build ; ${GCJ} ${OPTIMIZE} -fPIC -fjni -shared -o ../${NATIVE_DIR}/libi2p.so ${LIBI2P_JARS} ; cd .. )
|
||||
@(cd build ; time ${GCJ} ${OPTIMIZE} -fPIC -fjni -shared -o ../${NATIVE_DIR}/libi2p.so ${LIBI2P_JARS} ; cd .. )
|
||||
@ls -l ${NATIVE_DIR}/libi2p.so
|
||||
@echo "* libi2p.so built"
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
# Contains the addresses from your master address book
|
||||
# and your subscribed address books. (../userhosts.txt)
|
||||
#
|
||||
# private_addressbook The path to the private address book used by the router.
|
||||
# This is used only by the router and SusiDNS.
|
||||
# It is not published by addressbook. (../privatehosts.txt)
|
||||
#
|
||||
# published_addressbook The path to the copy of your address book made
|
||||
# available on i2p. (../eepsite/docroot/hosts.txt)
|
||||
#
|
||||
@@ -35,6 +39,7 @@ proxy_host=localhost
|
||||
proxy_port=4444
|
||||
master_addressbook=myhosts.txt
|
||||
router_addressbook=../userhosts.txt
|
||||
private_addressbook=../privatehosts.txt
|
||||
published_addressbook=../eepsite/docroot/hosts.txt
|
||||
log=log.txt
|
||||
subscriptions=subscriptions.txt
|
||||
|
||||
@@ -66,6 +66,7 @@ public class AddressBook {
|
||||
* where key is a human readable name, and value is a base64 i2p
|
||||
* destination.
|
||||
*/
|
||||
/* unused
|
||||
public AddressBook(String url, String proxyHost, int proxyPort) {
|
||||
this.location = url;
|
||||
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true,
|
||||
@@ -79,22 +80,26 @@ public class AddressBook {
|
||||
}
|
||||
new File("addressbook.tmp").delete();
|
||||
}
|
||||
|
||||
*/
|
||||
/**
|
||||
* Construct an AddressBook from the Subscription subscription. If the
|
||||
* address book at subscription has not changed since the last time it was
|
||||
* read or cannot be read, return an empty AddressBook.
|
||||
* Set a maximum size of the remote book to make it a little harder for a malicious book-sender.
|
||||
*
|
||||
* @param subscription
|
||||
* A Subscription instance pointing at a remote address book.
|
||||
*/
|
||||
static final long MAX_SUB_SIZE = 3 * 1024 * 1024l; //about 5,000 hosts
|
||||
public AddressBook(Subscription subscription, String proxyHost, int proxyPort) {
|
||||
this.location = subscription.getLocation();
|
||||
EepGet get = new EepGet(I2PAppContext.getGlobalContext(), true,
|
||||
proxyHost, proxyPort, 0, "addressbook.tmp",
|
||||
subscription.getLocation(), true, subscription.getEtag());
|
||||
get.fetch();
|
||||
subscription.setEtag(get.getETag());
|
||||
proxyHost, proxyPort, 0, -1l, MAX_SUB_SIZE, "addressbook.tmp", null,
|
||||
subscription.getLocation(), true, subscription.getEtag(), subscription.getLastModified(), null);
|
||||
if (get.fetch()) {
|
||||
subscription.setEtag(get.getETag());
|
||||
subscription.setLastModified(get.getLastModified());
|
||||
}
|
||||
try {
|
||||
this.addresses = ConfigParser.parse(new File("addressbook.tmp"));
|
||||
} catch (IOException exp) {
|
||||
@@ -151,6 +156,36 @@ public class AddressBook {
|
||||
return this.addresses.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do basic validation of the hostname and dest
|
||||
* hostname was already converted to lower case by ConfigParser.parse()
|
||||
*/
|
||||
private static boolean valid(String host, String dest) {
|
||||
return
|
||||
host.endsWith(".i2p") &&
|
||||
host.length() > 4 &&
|
||||
host.length() <= 67 && // 63 + ".i2p"
|
||||
(! host.startsWith(".")) &&
|
||||
(! host.startsWith("-")) &&
|
||||
(! host.endsWith("-.i2p")) &&
|
||||
host.indexOf("..") < 0 &&
|
||||
// IDN - basic check, not complete validation
|
||||
(host.indexOf("--") < 0 || host.startsWith("xn--") || host.indexOf(".xn--") > 0) &&
|
||||
host.replaceAll("[a-z0-9.-]", "").length() == 0 &&
|
||||
// some reserved names that may be used for local configuration someday
|
||||
(! host.equals("proxy.i2p")) &&
|
||||
(! host.equals("router.i2p")) &&
|
||||
(! host.equals("console.i2p")) &&
|
||||
(! host.endsWith(".proxy.i2p")) &&
|
||||
(! host.endsWith(".router.i2p")) &&
|
||||
(! host.endsWith(".console.i2p")) &&
|
||||
|
||||
dest.length() == 516 &&
|
||||
dest.endsWith("AAAA") &&
|
||||
dest.replaceAll("[a-zA-Z0-9~-]", "").length() == 0
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this AddressBook with AddressBook other, writing messages about new
|
||||
* addresses or conflicts to log. Addresses in AddressBook other that are
|
||||
@@ -169,7 +204,7 @@ public class AddressBook {
|
||||
String otherKey = (String) otherIter.next();
|
||||
String otherValue = (String) other.addresses.get(otherKey);
|
||||
|
||||
if (otherKey.endsWith(".i2p") && otherValue.length() >= 516) {
|
||||
if (valid(otherKey, otherValue)) {
|
||||
if (this.addresses.containsKey(otherKey) && !overwrite) {
|
||||
if (!this.addresses.get(otherKey).equals(otherValue)
|
||||
&& log != null) {
|
||||
@@ -184,7 +219,7 @@ public class AddressBook {
|
||||
this.modified = true;
|
||||
if (log != null) {
|
||||
log.append("New address " + otherKey
|
||||
+ " added to address book.");
|
||||
+ " added to address book. From: " + other.location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ public class ConfigParser {
|
||||
* a single key, value pair on each line, in the format: key=value. Lines
|
||||
* starting with '#' or ';' are considered comments, and ignored. Lines that
|
||||
* are obviously not in the format key=value are also ignored.
|
||||
* The key is converted to lower case.
|
||||
*
|
||||
* @param input
|
||||
* A BufferedReader with lines in key=value format to parse into
|
||||
@@ -77,7 +78,7 @@ public class ConfigParser {
|
||||
inputLine = ConfigParser.stripComments(inputLine);
|
||||
String[] splitLine = inputLine.split("=");
|
||||
if (splitLine.length == 2) {
|
||||
result.put(splitLine[0].trim(), splitLine[1].trim());
|
||||
result.put(splitLine[0].trim().toLowerCase(), splitLine[1].trim());
|
||||
}
|
||||
inputLine = input.readLine();
|
||||
}
|
||||
@@ -301,4 +302,4 @@ public class ConfigParser {
|
||||
new FileWriter(file, false)));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,8 @@ public class Daemon {
|
||||
AddressBook router = new AddressBook(routerFile);
|
||||
|
||||
List defaultSubs = new LinkedList();
|
||||
defaultSubs.add("http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/hosts.txt");
|
||||
// defaultSubs.add("http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/hosts.txt");
|
||||
defaultSubs.add("http://www.i2p2.i2p/hosts.txt");
|
||||
|
||||
SubscriptionList subscriptions = new SubscriptionList(subscriptionFile,
|
||||
etagsFile, lastModifiedFile, defaultSubs, (String) settings
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
The Heartbeat GUI loads up the stat files generated by the Heartbeat
|
||||
engine and renders them visually, offering a way to drill through different
|
||||
data points and take snapshots as things change (by saving particular stat
|
||||
files for later). The GUI itself doesn't need to be on the same machine
|
||||
as the Heartbeat engine - it pulls the stat files through any URL - even
|
||||
through the EepProxy.
|
||||
|
||||
An example Heartbeat GUI config file follows
|
||||
|
||||
# how often do we want to pull new data to render
|
||||
refreshFrequency=60
|
||||
## for each peer test we may want to include in the GUI:
|
||||
# where to find the current stat file (URL or filename)
|
||||
stat.0.location=http://dev.i2p.net/stats/heartbeatStat_khWY_30s_1kb.txt
|
||||
## optional entries for each peer test describing what we want shown
|
||||
## (and how we want it shown)
|
||||
# do we want to plot the send time (from when the ping was sent until the pong server got it)?
|
||||
stat.0.plot.current.send=true
|
||||
# do we want to plot the receive time (from when the pong was sent until reception)?
|
||||
stat.0.plot.current.receive=true
|
||||
# do we want to plot the lost messages?
|
||||
stat.0.plot.current.lost=true
|
||||
# what color should the current lines be rendered in?
|
||||
stat.0.plot.current.color=BLUE
|
||||
## optional entries for each peer test describing what averages we want
|
||||
## rendered
|
||||
# plot 1 minute send average?
|
||||
stat.0.plot.1m.send=true
|
||||
# plot 1 minute receive average?
|
||||
stat.0.plot.1m.receive=true
|
||||
# plot 1 minute lost message average?
|
||||
stat.0.plot.1m.lost=true
|
||||
# what color should the 1 minute averages be rendered as?
|
||||
stat.0.plot.1m.color=GREEN
|
||||
## repeated for all of the averaged periods, e.g.
|
||||
## stat.0.plot.30m, .60m, 1440m (1 day)
|
||||
|
||||
There may be some other options, such as where to store snapshot files, whether
|
||||
to generate PNG images, etc.
|
||||
@@ -1,122 +0,0 @@
|
||||
Heartbeat
|
||||
|
||||
Application layer tool for monitoring the long term health of the
|
||||
network by periodically testing peers, generating stats, and
|
||||
rendering them visually. The engine (both server and client) should
|
||||
work headless and seperate from the GUI, exposing the data in a simple
|
||||
to parse (and human readable) text file for each peer being tested.
|
||||
The GUI then periodically refreshes itself by loading those files (
|
||||
either locally or from a URL) and renders the current state accordingly,
|
||||
giving users a way to check that the network is alive, devs a tool to
|
||||
both monitor the state of the network and to debug different situations (by
|
||||
accessing the stat file - either live or archived).
|
||||
|
||||
The heartbeat configuration file is organized as a standard properties
|
||||
file (by default located at heartbeat.config, but that can be overridden by
|
||||
passing a filename as the first argument to the Heartbeat command):
|
||||
|
||||
# where the router is located (default is localhost)
|
||||
i2cpHost=localhost
|
||||
# I2CP port for the router (default is 7654)
|
||||
i2cpPort=4001
|
||||
# How many hops we want the router to put in our tunnels (default is 2)
|
||||
numHops=2
|
||||
# where our private destination keys are located - if this doesn't exist,
|
||||
# a new one will be created and saved there (by default, heartbeat.keys)
|
||||
privateDestinationFile=heartbeat_r2.keys
|
||||
|
||||
## peer tests configured below:
|
||||
|
||||
# destination peer for test 0
|
||||
peer.0.peer=[destination in base64]
|
||||
# where will we write out the stat data?
|
||||
peer.0.statFile=heartbeatStat_khWY_30s_1kb.txt
|
||||
# how many minutes will we keep stats for?
|
||||
peer.0.statDuration=30
|
||||
# how often will we write out new stat data (in seconds)?
|
||||
peer.0.statFrequency=60
|
||||
# how often will we send a ping to the peer (in seconds)?
|
||||
peer.0.sendFrequency=30
|
||||
# how many bytes will be included in the ping?
|
||||
peer.0.sendSize=1024
|
||||
# take a guess...
|
||||
peer.0.comment=Test with localhost sending 1KB of data every 30 seconds
|
||||
# we can keep track of a few moving averages - this value includes a whitespace
|
||||
# delimited list of numbers, each specifying a period to calculate the average
|
||||
# over (in minutes)
|
||||
peer.0.averagePeriods=1 5 30
|
||||
## repeat the peer.0.* for as many tests as desired, incrementing as necessary
|
||||
|
||||
If there are no peer.* lines, it will simply run a pong server. If any data is
|
||||
missing, it will use the defaults (though there are no defaults for peer.* lines) -
|
||||
running the Heartbeat app with no heartbeat configuration file whatsoever will create
|
||||
a new pong server (storing its keys at heartbeat.keys) and using the I2P router at
|
||||
localhost:7654.
|
||||
|
||||
The stat file generated for each set of peer.n.* lines contains the current state
|
||||
of the test, its averages, as well as any other interesting data points. An example
|
||||
stat file follows (hopefully it is self explanatory):
|
||||
|
||||
peer khWYqCETu9YtPUvGV92ocsbEW5DezhKlIG7ci8RLX3g=
|
||||
local u-9hlR1ik2hemXf0HvKMfeRgrS86CbNQh25e7XBhaQE=
|
||||
peerDest [base 64 of the full destination]
|
||||
localDest [base 64 of the full destination]
|
||||
numTunnelHops 2
|
||||
comment Test with localhost sending 30KB every 20 seconds
|
||||
sendFrequency 20
|
||||
sendSize 30720
|
||||
sessionStart 20040409.22:51:10.915
|
||||
currentTime 20040409.23:31:39.607
|
||||
numPending 2
|
||||
lifetimeSent 118
|
||||
lifetimeRecv 113
|
||||
#averages minutes sendMs recvMs numLost
|
||||
periodAverage 1 1843 771 0
|
||||
periodAverage 5 786 752 1
|
||||
periodAverage 30 855 735 3
|
||||
#action status date and time sent sendMs replyMs
|
||||
EVENT OK 20040409.23:21:44.742 691 670
|
||||
EVENT OK 20040409.23:22:05.201 671 581
|
||||
EVENT OK 20040409.23:22:26.301 1182 1452
|
||||
EVENT OK 20040409.23:22:47.322 24304 1723
|
||||
EVENT OK 20040409.23:23:08.232 2293 1081
|
||||
EVENT OK 20040409.23:23:29.332 1392 641
|
||||
EVENT OK 20040409.23:23:50.262 641 761
|
||||
EVENT OK 20040409.23:24:11.102 651 701
|
||||
EVENT OK 20040409.23:24:31.401 841 621
|
||||
EVENT OK 20040409.23:24:52.061 651 681
|
||||
EVENT OK 20040409.23:25:12.480 701 1623
|
||||
EVENT OK 20040409.23:25:32.990 1442 1212
|
||||
EVENT OK 20040409.23:25:54.230 591 631
|
||||
EVENT OK 20040409.23:26:14.620 620 691
|
||||
EVENT OK 20040409.23:26:35.199 1793 1432
|
||||
EVENT OK 20040409.23:26:56.570 661 641
|
||||
EVENT OK 20040409.23:27:17.200 641 660
|
||||
EVENT OK 20040409.23:27:38.120 611 921
|
||||
EVENT OK 20040409.23:27:58.699 831 621
|
||||
EVENT OK 20040409.23:28:19.559 801 661
|
||||
EVENT OK 20040409.23:28:40.279 601 611
|
||||
EVENT OK 20040409.23:29:00.648 601 621
|
||||
EVENT OK 20040409.23:29:21.288 701 661
|
||||
EVENT LOST 20040409.23:29:41.828
|
||||
EVENT LOST 20040409.23:30:02.327
|
||||
EVENT LOST 20040409.23:30:22.656
|
||||
EVENT OK 20040409.23:31:24.305 1843 771
|
||||
|
||||
The actual ping and pong messages sent are formatted trivially -
|
||||
ping messages contain
|
||||
$from $series $type $sentOn $size $payload
|
||||
while pong messages contain
|
||||
$from $series $type $sentOn $receivedOn $size $payload
|
||||
|
||||
$series is a number describing the sending client's test (so that you can
|
||||
ping the same peer with different configurations concurrently, varying things
|
||||
like the frequency and size of the message, window, etc).
|
||||
|
||||
They are sent as raw binary messages though, so see I2PAdapter.sendPing(..)
|
||||
and I2PAdapter.sendPong(..) for the details.
|
||||
|
||||
To get valid measurements, of course, you will want to make sure that
|
||||
both the heartbeat client and pong server have synchronized clocks (even
|
||||
more so than I2P requires). It is highly recommended that only NTP
|
||||
synchronized peers be used for heartbeat tests.
|
||||
@@ -1,66 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="heartbeat">
|
||||
<target name="all" depends="clean, buildGUI" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="buildGUI" depends="build, jarGUI" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../../core/java/" target="build" />
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac srcdir="./src" debug="true" deprecation="on" source="1.3" target="1.3" destdir="./build/obj" includes="**/*.java" excludes="net/i2p/heartbeat/gui/**" classpath="../../../core/java/build/i2p.jar" />
|
||||
</target>
|
||||
<target name="compileGUI">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac debug="true" source="1.3" target="1.3" deprecation="on" destdir="./build/obj">
|
||||
<src path="src/" />
|
||||
<classpath path="../../../core/java/build/i2p.jar" />
|
||||
<classpath path="../../jfreechart/jfreechart-0.9.17/lib/jcommon-0.9.2.jar" />
|
||||
<classpath path="../../jfreechart/jfreechart-0.9.17/lib/log4j-1.2.8.jar" />
|
||||
<classpath path="../../jfreechart/jfreechart-0.9.17/jfreechart-0.9.17.jar" />
|
||||
</javac>
|
||||
</target>
|
||||
<target name="jar" depends="compile">
|
||||
<jar destfile="./build/heartbeat.jar" basedir="./build/obj" includes="**/*.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.heartbeat.Heartbeat" />
|
||||
<attribute name="Class-Path" value="i2p.jar heartbeat.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
<target name="jarGUI" depends="compileGUI">
|
||||
<copy file="../../jfreechart/jfreechart-0.9.17/jfreechart-0.9.17.jar" todir="build/" />
|
||||
<copy file="../../jfreechart/jfreechart-0.9.17/lib/log4j-1.2.8.jar" todir="build/" />
|
||||
<copy file="../../jfreechart/jfreechart-0.9.17/lib/jcommon-0.9.2.jar" todir="build/" />
|
||||
<jar destfile="./build/heartbeatGUI.jar" basedir="./build/obj" includes="**">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.heartbeat.gui.HeartbeatMonitor" />
|
||||
<attribute name="Class-Path" value="log4j-1.2.8.jar jcommon-0.9.2.jar jfreechart-0.9.17.jar heartbeatGUI.jar i2p.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
<echo message="You will need to copy the log4j, jcommon, and jfreechart jar files into your lib dir" />
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="./src:../../../core/java/src:../../../core/java/test" destdir="./build/javadoc"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
access="package"
|
||||
splitindex="true"
|
||||
windowtitle="I2P heartbeat monitor" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../../core/java/" target="cleandep" />
|
||||
<ant dir="../../../core/java/" target="cleandep" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
||||
@@ -1,468 +0,0 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Define the configuration for testing against one particular peer as a client
|
||||
*/
|
||||
public class ClientConfig {
|
||||
private static final Log _log = new Log(ClientConfig.class);
|
||||
private Destination _peer;
|
||||
private Destination _us;
|
||||
private String _statFile;
|
||||
private int _statDuration;
|
||||
private int _statFrequency;
|
||||
private int _sendFrequency;
|
||||
private int _sendSize;
|
||||
private int _numHops;
|
||||
private String _comment;
|
||||
private int _averagePeriods[];
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_PREFIX = "peer.";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_PEER = ".peer";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_STATFILE = ".statFile";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_STATDURATION = ".statDuration";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_STATFREQUENCY = ".statFrequency";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_SENDFREQUENCY = ".sendFrequency";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_SENDSIZE = ".sendSize";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_COMMENT = ".comment";
|
||||
|
||||
/**
|
||||
* @seeRoutine ClientConfig#load
|
||||
* @seeRoutine ClientConfig#store
|
||||
*/
|
||||
public static final String PROP_AVERAGEPERIODS = ".averagePeriods";
|
||||
|
||||
/**
|
||||
* Default constructor...
|
||||
*/
|
||||
public ClientConfig() {
|
||||
this(null, null, null, -1, -1, -1, -1, 0, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a dummy client config to be fetched from the specified location
|
||||
* @param location the location to fetch from
|
||||
*/
|
||||
public ClientConfig(String location) {
|
||||
this(null, null, location, -1, -1, -1, -1, 0, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param peer who we will test against
|
||||
* @param us who we are
|
||||
* @param statLocation where the stat data should be stored/fetched
|
||||
* @param duration how many minutes to keep events for
|
||||
* @param statFreq how often to write out stats
|
||||
* @param sendFreq how often to send pings
|
||||
* @param sendSize how large the pings should be
|
||||
* @param numHops how many hops is the current Heartbeat app using
|
||||
* @param comment describe this test
|
||||
* @param averagePeriods list of minutes to summarize over
|
||||
*/
|
||||
public ClientConfig(Destination peer, Destination us, String statLocation, int duration, int statFreq, int sendFreq,
|
||||
int sendSize, int numHops, String comment, int averagePeriods[]) {
|
||||
_peer = peer;
|
||||
_us = us;
|
||||
_statFile = statLocation;
|
||||
_statDuration = duration;
|
||||
_statFrequency = statFreq;
|
||||
_sendFrequency = sendFreq;
|
||||
_sendSize = sendSize;
|
||||
_numHops = numHops;
|
||||
_comment = comment;
|
||||
_averagePeriods = averagePeriods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the peer to test against
|
||||
*
|
||||
* @return the Destination (peer)
|
||||
*/
|
||||
public Destination getPeer() {
|
||||
return _peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the peer to test against
|
||||
*
|
||||
* @param peer the Destination (peer)
|
||||
*/
|
||||
public void setPeer(Destination peer) {
|
||||
_peer = peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves who we are when we test
|
||||
*
|
||||
* @return the Destination (us)
|
||||
*/
|
||||
public Destination getUs() {
|
||||
return _us;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets who we are when we test
|
||||
*
|
||||
* @param us the Destination (us)
|
||||
*/
|
||||
public void setUs(Destination us) {
|
||||
_us = us;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the location to write the current stats to
|
||||
*
|
||||
* @return the name of the file
|
||||
*/
|
||||
public String getStatFile() {
|
||||
return _statFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the location we write the current stats to
|
||||
*
|
||||
* @param statFile the name of the file
|
||||
*/
|
||||
public void setStatFile(String statFile) {
|
||||
_statFile = statFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how many minutes of statistics should be maintained within the window for this client
|
||||
*
|
||||
* @return the number of minutes
|
||||
*/
|
||||
public int getStatDuration() {
|
||||
return _statDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how many minutes of statistics should be maintained within the window for this client
|
||||
*
|
||||
* @param durationMinutes the number of minutes
|
||||
*/
|
||||
public void setStatDuration(int durationMinutes) {
|
||||
_statDuration = durationMinutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how frequently the stats are written out (in seconds)
|
||||
*
|
||||
* @return the frequency in seconds
|
||||
*/
|
||||
public int getStatFrequency() {
|
||||
return _statFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how frequently the stats are written out (in seconds)
|
||||
*
|
||||
* @param freqSeconds the frequency in seconds
|
||||
*/
|
||||
public void setStatFrequency(int freqSeconds) {
|
||||
_statFrequency = freqSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how frequenty we send messages to the peer (in seconds)
|
||||
*
|
||||
* @return the frequency in seconds
|
||||
*/
|
||||
public int getSendFrequency() {
|
||||
return _sendFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how frequenty we send messages to the peer (in seconds)
|
||||
*
|
||||
* @param freqSeconds the frequency in seconds
|
||||
*/
|
||||
public void setSendFrequency(int freqSeconds) {
|
||||
_sendFrequency = freqSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how many bytes the ping messages should be (min values ~700, max ~32KB)
|
||||
*
|
||||
* @return the size in bytes
|
||||
*/
|
||||
public int getSendSize() {
|
||||
return _sendSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how many bytes the ping messages should be (min values ~700, max ~32KB)
|
||||
*
|
||||
* @param numBytes the size in bytes
|
||||
*/
|
||||
public void setSendSize(int numBytes) {
|
||||
_sendSize = numBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the brief, 1 line description of the test. Useful comments are along the lines of "The peer is located on a fast router and connection with 2
|
||||
* hop tunnels".
|
||||
*
|
||||
* @return the brief comment
|
||||
*/
|
||||
public String getComment() {
|
||||
return _comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a brief, 1 line description (comment) of the test.
|
||||
*
|
||||
* @param comment the brief comment
|
||||
*/
|
||||
public void setComment(String comment) {
|
||||
_comment = comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the periods that the client's tests should be averaged over.
|
||||
*
|
||||
* @return list of periods (in minutes) that the data should be averaged over, or null
|
||||
*/
|
||||
public int[] getAveragePeriods() {
|
||||
return _averagePeriods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the periods that the client's tests should be averaged over.
|
||||
*
|
||||
* @param periods the list of periods (in minutes) that the data should be averaged over, or null
|
||||
*/
|
||||
public void setAveragePeriods(int periods[]) {
|
||||
_averagePeriods = periods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure we're keeping track of the average over the given time period.
|
||||
*
|
||||
* @param minutes how many minutes to monitor
|
||||
*/
|
||||
public void addAveragePeriod(int minutes) {
|
||||
if (_averagePeriods != null) {
|
||||
for (int i = 0; i < _averagePeriods.length; i++) {
|
||||
if (_averagePeriods[i] == minutes)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int numPeriods = 1;
|
||||
if (_averagePeriods != null)
|
||||
numPeriods += _averagePeriods.length;
|
||||
int periods[] = new int[numPeriods];
|
||||
if (_averagePeriods != null)
|
||||
System.arraycopy(_averagePeriods, 0, periods, 0, _averagePeriods.length);
|
||||
periods[periods.length-1] = minutes;
|
||||
Arrays.sort(periods);
|
||||
_averagePeriods = periods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves how many hops this test engine is configured to use for its outbound and inbound tunnels
|
||||
*
|
||||
* @return the number of hops
|
||||
*/
|
||||
public int getNumHops() {
|
||||
return _numHops;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how many hops this test engine is configured to use for its outbound and inbound tunnels
|
||||
*
|
||||
* @param numHops the number of hops
|
||||
*/
|
||||
public void setNumHops(int numHops) {
|
||||
_numHops = numHops;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the client config from the properties specified, deriving the current config entry from the peer number.
|
||||
*
|
||||
* @param clientConfig the properties to load from
|
||||
* @param peerNum the number associated with the peer
|
||||
* @return true if it was loaded correctly, false if there were errors
|
||||
*/
|
||||
public boolean load(Properties clientConfig, int peerNum) {
|
||||
if ((clientConfig == null) || (peerNum < 0)) return false;
|
||||
String peerVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_PEER);
|
||||
String statFileVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_STATFILE);
|
||||
String statDurationVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_STATDURATION);
|
||||
String statFrequencyVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_STATFREQUENCY);
|
||||
String sendFrequencyVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_SENDFREQUENCY);
|
||||
String sendSizeVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_SENDSIZE);
|
||||
String commentVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_COMMENT);
|
||||
String periodsVal = clientConfig.getProperty(PROP_PREFIX + peerNum + PROP_AVERAGEPERIODS);
|
||||
|
||||
if ((peerVal == null) || (statFileVal == null) || (statDurationVal == null) || (statFrequencyVal == null)
|
||||
|| (sendFrequencyVal == null) || (sendSizeVal == null)) {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Peer number " + peerNum + " does not exist");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
int duration = getInt(statDurationVal);
|
||||
int statFreq = getInt(statFrequencyVal);
|
||||
int sendFreq = getInt(sendFrequencyVal);
|
||||
int sendSize = getInt(sendSizeVal);
|
||||
|
||||
if ((duration <= 0) || (statFreq <= 0) || (sendFreq < 0) || (sendSize <= 0)) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("Invalid client config: duration [" + statDurationVal + "] stat frequency ["
|
||||
+ statFrequencyVal + "] send frequency [" + sendFrequencyVal + "] send size ["
|
||||
+ sendSizeVal + "]");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
statFileVal = statFileVal.trim();
|
||||
if (statFileVal.length() <= 0) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("Stat file is blank for peer " + peerNum);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(peerVal);
|
||||
|
||||
if (commentVal == null) {
|
||||
commentVal = "";
|
||||
}
|
||||
|
||||
commentVal = commentVal.trim();
|
||||
commentVal = commentVal.replace('\n', '_');
|
||||
|
||||
List periods = new ArrayList(4);
|
||||
if (periodsVal != null) {
|
||||
StringTokenizer tok = new StringTokenizer(periodsVal);
|
||||
while (tok.hasMoreTokens()) {
|
||||
String periodVal = tok.nextToken();
|
||||
int minutes = getInt(periodVal);
|
||||
if (minutes > 0) {
|
||||
periods.add(new Integer(minutes));
|
||||
}
|
||||
}
|
||||
}
|
||||
int avgPeriods[] = new int[periods.size()];
|
||||
for (int i = 0; i < periods.size(); i++) {
|
||||
avgPeriods[i] = ((Integer) periods.get(i)).intValue();
|
||||
}
|
||||
|
||||
_comment = commentVal;
|
||||
_statDuration = duration;
|
||||
_statFrequency = statFreq;
|
||||
_sendFrequency = sendFreq;
|
||||
_sendSize = sendSize;
|
||||
_statFile = statFileVal;
|
||||
_peer = d;
|
||||
_averagePeriods = avgPeriods;
|
||||
return true;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Peer destination for " + peerNum + " was invalid: " + peerVal);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the client config to the properties specified, deriving the current config entry from the peer number.
|
||||
*
|
||||
* @param clientConfig the properties to store to
|
||||
* @param peerNum the number associated with the peer
|
||||
* @return true if it was stored correctly, false if there were errors
|
||||
*/
|
||||
public boolean store(Properties clientConfig, int peerNum) {
|
||||
if ((_peer == null) || (_sendFrequency < 0) || (_sendSize <= 0) || (_statDuration <= 0)
|
||||
|| (_statFrequency <= 0) || (_statFile == null)) { return false; }
|
||||
|
||||
String comment = _comment;
|
||||
if (comment == null) {
|
||||
comment = "";
|
||||
}
|
||||
|
||||
comment = comment.trim();
|
||||
comment = comment.replace('\n', '_');
|
||||
|
||||
StringBuffer buf = new StringBuffer(32);
|
||||
if (_averagePeriods != null) {
|
||||
for (int i = 0; i < _averagePeriods.length; i++) {
|
||||
buf.append(_averagePeriods[i]).append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_PEER, _peer.toBase64());
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_STATFILE, _statFile);
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_STATDURATION, _statDuration + "");
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_STATFREQUENCY, _statFrequency + "");
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_SENDFREQUENCY, _sendFrequency + "");
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_SENDSIZE, _sendSize + "");
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_COMMENT, comment);
|
||||
clientConfig.setProperty(PROP_PREFIX + peerNum + PROP_AVERAGEPERIODS, buf.toString());
|
||||
return true;
|
||||
}
|
||||
|
||||
private static final int getInt(String val) {
|
||||
if (val == null) return -1;
|
||||
try {
|
||||
int i = Integer.parseInt(val);
|
||||
return i;
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug("Value [" + val + "] is not a valid integer");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Responsible for actually conducting the tests, coordinating the storing of the
|
||||
* stats, and the management of the rates. This has its own thread specific for
|
||||
* pumping data around as well.
|
||||
*
|
||||
*/
|
||||
class ClientEngine {
|
||||
private static final Log _log = new Log(ClientEngine.class);
|
||||
/** who can send our pings? */
|
||||
private Heartbeat _heartbeat;
|
||||
/** actual test state */
|
||||
private PeerData _data;
|
||||
/** have we been stopped? */
|
||||
private boolean _active;
|
||||
/** used to generate engine IDs */
|
||||
private static int __id = 0;
|
||||
/** this engine's id, unique to the {test,sendingClient,startTime} */
|
||||
private int _id;
|
||||
private static PeerDataWriter writer = new PeerDataWriter();
|
||||
|
||||
/**
|
||||
* Create a new engine that will send its pings through the given heartbeat
|
||||
* system, and will coordinate the test according to the configuration specified.
|
||||
* @param heartbeat the Heartbeat to send pings through
|
||||
* @param config the Configuration to load configuration from =p
|
||||
*/
|
||||
public ClientEngine(Heartbeat heartbeat, ClientConfig config) {
|
||||
_heartbeat = heartbeat;
|
||||
_data = new PeerData(config);
|
||||
_active = false;
|
||||
_id = ++__id;
|
||||
}
|
||||
|
||||
/** stop sending any more pings or writing any more state */
|
||||
public void stopEngine() {
|
||||
_active = false;
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Stopping engine talking to peer " + _data.getConfig().getPeer().calculateHash().toBase64());
|
||||
}
|
||||
|
||||
/** start up the test (this does not block, as it fires up the test thread) */
|
||||
public void startEngine() {
|
||||
_active = true;
|
||||
I2PThread t = new I2PThread(new ClientRunner());
|
||||
t.setName("HeartbeatClient " + _id);
|
||||
t.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Who are we testing?
|
||||
* @return the Destination (peer) we're testing
|
||||
*/
|
||||
public Destination getPeer() {
|
||||
return _data.getConfig().getPeer();
|
||||
}
|
||||
|
||||
/**
|
||||
* What is our series identifier (used to locally identify a test)
|
||||
* @return the series identifier
|
||||
*/
|
||||
public int getSeriesNum() {
|
||||
return _id;
|
||||
}
|
||||
|
||||
/**
|
||||
* receive notification from the heartbeat system that a pong was received in
|
||||
* reply to a ping we have sent.
|
||||
*
|
||||
* @param sentOn when did we send the ping?
|
||||
* @param replyOn when did the peer send the pong?
|
||||
*/
|
||||
public void receivePong(long sentOn, long replyOn) {
|
||||
_data.pongReceived(sentOn, replyOn);
|
||||
}
|
||||
|
||||
/** fire off a new ping */
|
||||
private void doSend() {
|
||||
long now = Clock.getInstance().now();
|
||||
_data.addPing(now);
|
||||
_heartbeat.sendPing(_data.getConfig().getPeer(), _id, now, _data.getConfig().getSendSize());
|
||||
}
|
||||
|
||||
/** our actual heartbeat pumper - this drives the test */
|
||||
private class ClientRunner implements Runnable {
|
||||
|
||||
/**
|
||||
* @see java.lang.Runnable#run()
|
||||
*/
|
||||
public void run() {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Starting engine talking to peer " + _data.getConfig().getPeer().calculateHash().toBase64());
|
||||
|
||||
// when do we need to send the next PING?
|
||||
long nextSend = Clock.getInstance().now();
|
||||
// when do we need to write out the next state data?
|
||||
long nextWrite = Clock.getInstance().now();
|
||||
|
||||
while (_active) {
|
||||
|
||||
if (Clock.getInstance().now() >= nextSend) {
|
||||
doSend();
|
||||
nextSend = Clock.getInstance().now() + _data.getConfig().getSendFrequency() * 1000;
|
||||
}
|
||||
|
||||
if (Clock.getInstance().now() >= nextWrite) {
|
||||
boolean written = writer.persist(_data);
|
||||
if (!written) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error("Unable to write the client state data");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Client state data written");
|
||||
}
|
||||
}
|
||||
|
||||
_data.cleanup();
|
||||
|
||||
long timeToWait = nextSend - Clock.getInstance().now();
|
||||
if (timeToWait > 0) {
|
||||
try {
|
||||
Thread.sleep(timeToWait);
|
||||
} catch (InterruptedException ie) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Main driver for the heartbeat engine, loading 0 or more tests, firing
|
||||
* up a ClientEngine for each, and serving as a pong server. If there isn't
|
||||
* a configuration file, or if the configuration file doesn't specify any tests,
|
||||
* it simply sits around as a pong server, passively responding to whatever is
|
||||
* sent its way. <p />
|
||||
*
|
||||
* The config file format is examplified below:
|
||||
* <pre>
|
||||
* # where the router is located (default is localhost)
|
||||
* i2cpHost=localhost
|
||||
* # I2CP port for the router (default is 7654)
|
||||
* i2cpPort=4001
|
||||
* # How many hops we want the router to put in our tunnels (default is 2)
|
||||
* numHops=2
|
||||
* # where our private destination keys are located - if this doesn't exist,
|
||||
* # a new one will be created and saved there (by default, heartbeat.keys)
|
||||
* privateDestinationFile=heartbeat_r2.keys
|
||||
* # where do we want to export the plain base64 of our destination?
|
||||
* publicDestinationFile=heartbeat_r2.txt
|
||||
*
|
||||
* ## peer tests configured below:
|
||||
*
|
||||
* # destination peer for test 0
|
||||
* peer.0.peer=[destination in base64]
|
||||
* # where will we write out the stat data?
|
||||
* peer.0.statFile=heartbeatStat_khWY_30s_1kb.txt
|
||||
* # how many minutes will we keep stats for?
|
||||
* peer.0.statDuration=30
|
||||
* # how often will we write out new stat data (in seconds)?
|
||||
* peer.0.statFrequency=60
|
||||
* # how often will we send a ping to the peer (in seconds)?
|
||||
* peer.0.sendFrequency=30
|
||||
* # how many bytes will be included in the ping?
|
||||
* peer.0.sendSize=1024
|
||||
* # take a guess...
|
||||
* peer.0.comment=Test with localhost sending 1KB of data every 30 seconds
|
||||
* # we can keep track of a few moving averages - this value includes a whitespace
|
||||
* # delimited list of numbers, each specifying a period to calculate the average
|
||||
* # over (in minutes)
|
||||
* peer.0.averagePeriods=1 5 30
|
||||
* ## repeat the peer.0.* for as many tests as desired, incrementing as necessary
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public class Heartbeat {
|
||||
private static final Log _log = new Log(Heartbeat.class);
|
||||
/** location containing this heartbeat's config */
|
||||
private String _configFile;
|
||||
/** clientNum (Integer) to ClientConfig mapping */
|
||||
private Map _clientConfigs;
|
||||
/** series num (Integer) to ClientEngine mapping */
|
||||
private Map _clientEngines;
|
||||
/** helper class for managing our I2P send/receive and message formatting */
|
||||
private I2PAdapter _adapter;
|
||||
/** our own callback that the I2PAdapter notifies on ping or pong messages */
|
||||
private PingPongAdapter _eventAdapter;
|
||||
|
||||
/** if there are no command line arguments, load the config from "heartbeat.config" */
|
||||
public static final String CONFIG_FILE_DEFAULT = "heartbeat.config";
|
||||
|
||||
/**
|
||||
* build up a new heartbeat manager, but don't actually do anything
|
||||
* @param configFile the name of the configuration file
|
||||
*/
|
||||
public Heartbeat(String configFile) {
|
||||
_configFile = configFile;
|
||||
_clientConfigs = new HashMap();
|
||||
_clientEngines = new HashMap();
|
||||
_eventAdapter = new PingPongAdapter();
|
||||
_adapter = new I2PAdapter();
|
||||
_adapter.setListener(_eventAdapter);
|
||||
}
|
||||
|
||||
private Heartbeat() {
|
||||
}
|
||||
|
||||
/** load up the config data (but don't build any engines or start them up) */
|
||||
public void loadConfig() {
|
||||
Properties props = new Properties();
|
||||
FileInputStream fin = null;
|
||||
File configFile = new File(_configFile);
|
||||
if (configFile.exists()) {
|
||||
try {
|
||||
fin = new FileInputStream(_configFile);
|
||||
props.load(fin);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error reading the config data", ioe);
|
||||
}
|
||||
} finally {
|
||||
if (fin != null) try {
|
||||
fin.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadBaseConfig(props);
|
||||
loadClientConfigs(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* send a ping message to the peer
|
||||
*
|
||||
* @param peer peer to ping
|
||||
* @param seriesNum id used to keep track of multiple pings (of different size/frequency) to a peer
|
||||
* @param now current time to be sent in the ping (so we can watch for it in the pong)
|
||||
* @param size total message size to send
|
||||
*/
|
||||
void sendPing(Destination peer, int seriesNum, long now, int size) {
|
||||
if (_adapter.getIsConnected()) _adapter.sendPing(peer, seriesNum, now, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* load up the base data (I2CP config, etc)
|
||||
* @param props the properties to load from
|
||||
*/
|
||||
private void loadBaseConfig(Properties props) {
|
||||
_adapter.loadConfig(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* load up all of the test config data
|
||||
* @param props the properties to load from
|
||||
* */
|
||||
private void loadClientConfigs(Properties props) {
|
||||
int i = 0;
|
||||
while (true) {
|
||||
ClientConfig config = new ClientConfig();
|
||||
if (!config.load(props, i)) {
|
||||
break;
|
||||
}
|
||||
_clientConfigs.put(new Integer(i), config);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/** connect to the network */
|
||||
private void connect() {
|
||||
boolean connected = _adapter.connect();
|
||||
if (!connected) _log.error("Unable to connect to the router");
|
||||
}
|
||||
|
||||
/** disconnect from the network */
|
||||
private void disconnect() { /* UNUSED */
|
||||
_adapter.disconnect();
|
||||
}
|
||||
|
||||
/** start up all of the tests */
|
||||
public void startEngines() {
|
||||
for (Iterator iter = _clientConfigs.values().iterator(); iter.hasNext();) {
|
||||
ClientConfig config = (ClientConfig) iter.next();
|
||||
ClientEngine engine = new ClientEngine(this, config);
|
||||
config.setUs(_adapter.getLocalDestination());
|
||||
config.setNumHops(_adapter.getNumHops());
|
||||
_clientEngines.put(new Integer(engine.getSeriesNum()), engine);
|
||||
engine.startEngine();
|
||||
}
|
||||
}
|
||||
|
||||
/** stop all of the tests */
|
||||
public void stopEngines() {
|
||||
for (Iterator iter = _clientEngines.values().iterator(); iter.hasNext();) {
|
||||
ClientEngine engine = (ClientEngine) iter.next();
|
||||
engine.stopEngine();
|
||||
}
|
||||
_clientEngines.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire up a new heartbeat system, waiting until, well, forever. Builds
|
||||
* a new heartbeat system, loads the config, connects to the network, starts
|
||||
* the engines, and then sits back and relaxes, responding to any pings and
|
||||
* running any tests. <p />
|
||||
*
|
||||
* <code> <b>Usage: </b> Heartbeat [<i>configFileName</i>]</code> <p />
|
||||
* @param args the list of args passed to the program from the command-line
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
String configFile = CONFIG_FILE_DEFAULT;
|
||||
if (args.length == 1) {
|
||||
configFile = args[0];
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Starting up with config file " + configFile);
|
||||
}
|
||||
Heartbeat heartbeat = new Heartbeat(configFile);
|
||||
heartbeat.loadConfig();
|
||||
heartbeat.connect();
|
||||
heartbeat.startEngines();
|
||||
Object o = new Object();
|
||||
while (true) {
|
||||
try {
|
||||
synchronized (o) {
|
||||
o.wait();
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive event notification from the I2PAdapter
|
||||
*
|
||||
*/
|
||||
private class PingPongAdapter implements I2PAdapter.PingPongEventListener {
|
||||
/**
|
||||
* We were pinged, so always just send a pong back.
|
||||
*
|
||||
* @param from who sent us the ping?
|
||||
* @param seriesNum what series did the sender specify?
|
||||
* @param sentOn when did the sender say they sent their ping?
|
||||
* @param data arbitrary payload data
|
||||
*/
|
||||
public void receivePing(Destination from, int seriesNum, Date sentOn, byte[] data) {
|
||||
if (_adapter.getIsConnected()) {
|
||||
_adapter.sendPong(from, seriesNum, sentOn, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We received a pong, so find the right client engine and tell it about the pong.
|
||||
*
|
||||
* @param from who sent us the pong
|
||||
* @param seriesNum our client ID
|
||||
* @param sentOn when did we send the ping?
|
||||
* @param replyOn when did they send their pong?
|
||||
* @param data the arbitrary data we sent in the ping (that they sent back in the pong)
|
||||
*/
|
||||
public void receivePong(Destination from, int seriesNum, Date sentOn, Date replyOn, byte[] data) {
|
||||
ClientEngine engine = (ClientEngine) _clientEngines.get(new Integer(seriesNum));
|
||||
if (engine.getPeer().equals(from)) {
|
||||
engine.receivePong(sentOn.getTime(), replyOn.getTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,604 +0,0 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Tie-in to the I2P SDK for the Heartbeat system, talking to the I2PSession and
|
||||
* dealing with the raw ping and pong messages.
|
||||
*
|
||||
*/
|
||||
class I2PAdapter {
|
||||
private final static Log _log = new Log(I2PAdapter.class);
|
||||
/** I2CP host */
|
||||
private String _i2cpHost;
|
||||
/** I2CP port */
|
||||
private int _i2cpPort;
|
||||
/** how long do we want our tunnels to be? */
|
||||
private int _numHops;
|
||||
/** filename containing the heartbeat engine's private destination info */
|
||||
private String _privateDestFile;
|
||||
/** filename to store the heartbeat engine's public destination in base64*/
|
||||
private String _publicDestFile;
|
||||
/** our destination */
|
||||
private Destination _localDest;
|
||||
/** who do we tell? */
|
||||
private PingPongEventListener _listener;
|
||||
/** how do we talk to the router */
|
||||
private I2PSession _session;
|
||||
/** object that receives our i2cp notifications from the session and tells us */
|
||||
private I2PListener _i2pListener; /* UNUSED */
|
||||
|
||||
/**
|
||||
* This config property tells us where the private destination data for our
|
||||
* connection (or if it doesn't exist, where will we save it)
|
||||
*/
|
||||
private static final String DEST_FILE_PROP = "privateDestinationFile";
|
||||
/** by default, the private destination data is in "heartbeat.keys" */
|
||||
private static final String DEST_FILE_DEFAULT = "heartbeat.keys";
|
||||
/** where will we export the public destination in base 64? */
|
||||
private static final String PUBLIC_DEST_FILE_PROP = "publicDestinationFile";
|
||||
/** where will we export the public destination in base 64? */
|
||||
private static final String PUBLIC_DEST_FILE_DEFAULT = "heartbeat.txt";
|
||||
/** This config property defines where the I2P router is */
|
||||
private static final String I2CP_HOST_PROP = "i2cpHost";
|
||||
/** by default, the I2P host is "localhost" */
|
||||
private static final String I2CP_HOST_DEFAULT = "localhost";
|
||||
/** This config property defines the I2CP port on the router */
|
||||
private static final String I2CP_PORT_PROP = "i2cpPort";
|
||||
/** by default, the I2CP port is 7654 */
|
||||
private static final int I2CP_PORT_DEFAULT = 7654;
|
||||
|
||||
/** This property defines how many hops we want in our tunnels. */
|
||||
public static final String NUMHOPS_PROP = "numHops";
|
||||
/** by default, use 2 hop tunnels */
|
||||
public static final int NUMHOPS_DEFAULT = 2;
|
||||
|
||||
/**
|
||||
* Constructs an I2PAdapter . . .
|
||||
*/
|
||||
public I2PAdapter() {
|
||||
_privateDestFile = null;
|
||||
_publicDestFile = null;
|
||||
_i2cpHost = null;
|
||||
_i2cpPort = -1;
|
||||
_localDest = null;
|
||||
_listener = null;
|
||||
_session = null;
|
||||
_numHops = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* who are we?
|
||||
* @return the destination (us)
|
||||
*/
|
||||
public Destination getLocalDestination() {
|
||||
return _localDest;
|
||||
}
|
||||
|
||||
/**
|
||||
* who gets notified when we receive a ping or a pong?
|
||||
* @return the event listener who gets notified
|
||||
*/
|
||||
public PingPongEventListener getListener() {
|
||||
return _listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets who gets notified when we receive a ping or a pong
|
||||
* @param listener the event listener to get notified
|
||||
*/
|
||||
public void setListener(PingPongEventListener listener) {
|
||||
_listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* how many hops do we want in our tunnels?
|
||||
* @return the number of hops
|
||||
*/
|
||||
public int getNumHops() {
|
||||
return _numHops;
|
||||
}
|
||||
|
||||
/**
|
||||
* are we connected?
|
||||
* @return true or false . . .
|
||||
*/
|
||||
public boolean getIsConnected() {
|
||||
return _session != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read in all of the config data
|
||||
* @param props the properties to load from
|
||||
*/
|
||||
void loadConfig(Properties props) {
|
||||
String privDestFile = props.getProperty(DEST_FILE_PROP, DEST_FILE_DEFAULT);
|
||||
String pubDestFile = props.getProperty(PUBLIC_DEST_FILE_PROP, PUBLIC_DEST_FILE_DEFAULT);
|
||||
String host = props.getProperty(I2CP_HOST_PROP, I2CP_HOST_DEFAULT);
|
||||
String port = props.getProperty(I2CP_PORT_PROP, "" + I2CP_PORT_DEFAULT);
|
||||
String numHops = props.getProperty(NUMHOPS_PROP, "" + NUMHOPS_DEFAULT);
|
||||
|
||||
int portNum = -1;
|
||||
try {
|
||||
portNum = Integer.parseInt(port);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("Invalid I2CP port specified [" + port + "]");
|
||||
}
|
||||
portNum = I2CP_PORT_DEFAULT;
|
||||
}
|
||||
int hops = -1;
|
||||
try {
|
||||
hops = Integer.parseInt(numHops);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN)) {
|
||||
_log.warn("Invalid # hops specified [" + numHops + "]");
|
||||
}
|
||||
hops = NUMHOPS_DEFAULT;
|
||||
}
|
||||
|
||||
_numHops = hops;
|
||||
_privateDestFile = privDestFile;
|
||||
_publicDestFile = pubDestFile;
|
||||
_i2cpHost = host;
|
||||
_i2cpPort = portNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* write out the config to the props
|
||||
* @param props the properties to write to
|
||||
*/
|
||||
void storeConfig(Properties props) {
|
||||
if (_privateDestFile != null) {
|
||||
props.setProperty(DEST_FILE_PROP, _privateDestFile);
|
||||
} else {
|
||||
props.setProperty(DEST_FILE_PROP, DEST_FILE_DEFAULT);
|
||||
}
|
||||
|
||||
if (_publicDestFile != null) {
|
||||
props.setProperty(PUBLIC_DEST_FILE_PROP, _publicDestFile);
|
||||
} else {
|
||||
props.setProperty(PUBLIC_DEST_FILE_PROP, PUBLIC_DEST_FILE_DEFAULT);
|
||||
}
|
||||
|
||||
if (_i2cpHost != null) {
|
||||
props.setProperty(I2CP_HOST_PROP, _i2cpHost);
|
||||
} else {
|
||||
props.setProperty(I2CP_HOST_PROP, I2CP_HOST_DEFAULT);
|
||||
}
|
||||
|
||||
if (_i2cpPort > 0) {
|
||||
props.setProperty(I2CP_PORT_PROP, "" + _i2cpPort);
|
||||
} else {
|
||||
props.setProperty(I2CP_PORT_PROP, "" + I2CP_PORT_DEFAULT);
|
||||
}
|
||||
|
||||
props.setProperty(NUMHOPS_PROP, "" + _numHops);
|
||||
}
|
||||
|
||||
private static final int TYPE_PING = 0;
|
||||
private static final int TYPE_PONG = 1;
|
||||
|
||||
/**
|
||||
* send a ping message to the peer
|
||||
*
|
||||
* @param peer peer to ping
|
||||
* @param seriesNum id used to keep track of multiple pings (of different size/frequency) to a peer
|
||||
* @param now current time to be sent in the ping (so we can watch for it in the pong)
|
||||
* @param size total message size to send
|
||||
*
|
||||
* @throws IllegalStateException if we are not connected to the router
|
||||
*/
|
||||
public void sendPing(Destination peer, int seriesNum, long now, int size) {
|
||||
if (_session == null) throw new IllegalStateException("Not connected to the router");
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(size);
|
||||
try {
|
||||
_localDest.writeBytes(baos);
|
||||
DataHelper.writeLong(baos, 2, seriesNum);
|
||||
DataHelper.writeLong(baos, 1, TYPE_PING);
|
||||
DataHelper.writeDate(baos, new Date(now));
|
||||
int padding = size - baos.size();
|
||||
byte paddingData[] = new byte[padding];
|
||||
I2PAppContext.getGlobalContext().random().nextBytes(paddingData);
|
||||
//Arrays.fill(paddingData, (byte) 0x2A);
|
||||
DataHelper.writeLong(baos, 2, padding);
|
||||
baos.write(paddingData);
|
||||
boolean sent = _session.sendMessage(peer, baos.toByteArray());
|
||||
if (!sent) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error sending the ping to " + peer.calculateHash().toBase64() + " for series "
|
||||
+ seriesNum);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Ping sent to " + peer.calculateHash().toBase64() + " for series " + seriesNum);
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error sending the ping", ioe);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error writing out the ping message", dfe);
|
||||
}
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error writing out the ping message", ise);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* send a pong message to the peer
|
||||
*
|
||||
* @param peer peer to pong
|
||||
* @param seriesNum id given to us in the ping
|
||||
* @param sentOn date the peer said they sent us the message
|
||||
* @param data payload the peer sent us in the ping
|
||||
*
|
||||
* @throws IllegalStateException if we are not connected to the router
|
||||
*/
|
||||
public void sendPong(Destination peer, int seriesNum, Date sentOn, byte data[]) {
|
||||
if (_session == null) throw new IllegalStateException("Not connected to the router");
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(data.length + 768);
|
||||
try {
|
||||
_localDest.writeBytes(baos);
|
||||
DataHelper.writeLong(baos, 2, seriesNum);
|
||||
DataHelper.writeLong(baos, 1, TYPE_PONG);
|
||||
DataHelper.writeDate(baos, sentOn);
|
||||
DataHelper.writeDate(baos, new Date(Clock.getInstance().now()));
|
||||
DataHelper.writeLong(baos, 2, data.length);
|
||||
baos.write(data);
|
||||
boolean sent = _session.sendMessage(peer, baos.toByteArray());
|
||||
if (!sent) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error sending the pong to " + peer.calculateHash().toBase64() + " for series "
|
||||
+ seriesNum + " which was sent on " + sentOn);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Pong sent to " + peer.calculateHash().toBase64() + " for series " + seriesNum
|
||||
+ " which was sent on " + sentOn);
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error sending the ping", ioe);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error writing out the pong message", dfe);
|
||||
}
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error writing out the pong message", ise);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We've received this data from I2P - parse it into a ping or a pong
|
||||
* and notify accordingly
|
||||
* @param data the data to handle
|
||||
*/
|
||||
private void handleMessage(byte data[]) {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(data);
|
||||
try {
|
||||
Destination from = new Destination();
|
||||
from.readBytes(bais);
|
||||
int series = (int) DataHelper.readLong(bais, 2);
|
||||
long type = DataHelper.readLong(bais, 1);
|
||||
Date sentOn = DataHelper.readDate(bais);
|
||||
Date receivedOn = null;
|
||||
if (type == TYPE_PONG) {
|
||||
receivedOn = DataHelper.readDate(bais);
|
||||
}
|
||||
int size = (int) DataHelper.readLong(bais, 2);
|
||||
byte payload[] = new byte[size];
|
||||
int read = DataHelper.read(bais, payload);
|
||||
if (read != size) { throw new IOException("Malformed payload - read " + read + " instead of " + size); }
|
||||
|
||||
if (_listener == null) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Listener isn't set, but we received a valid message of type " + type + " sent from "
|
||||
+ from.calculateHash().toBase64());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == TYPE_PING) {
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Ping received from " + from.calculateHash().toBase64() + " on series " + series
|
||||
+ " sent on " + sentOn + " containing " + size + " bytes");
|
||||
}
|
||||
_listener.receivePing(from, series, sentOn, payload);
|
||||
} else if (type == TYPE_PONG) {
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Pong received from " + from.calculateHash().toBase64() + " on series " + series
|
||||
+ " sent on " + sentOn + " with pong sent on " + receivedOn + " containing " + size
|
||||
+ " bytes");
|
||||
}
|
||||
_listener.receivePong(from, series, sentOn, receivedOn, payload);
|
||||
} else {
|
||||
throw new IOException("Invalid message type " + type);
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error handling the message", ioe);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error parsing the message", dfe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* connect to the I2P router and either authenticate ourselves with the
|
||||
* destination we're given, or create a new one and write that to the
|
||||
* destination file.
|
||||
*
|
||||
* @return true if we connect successfully, false otherwise
|
||||
*/
|
||||
boolean connect() {
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
Destination us = null;
|
||||
File destFile = new File(_privateDestFile);
|
||||
us = verifyDestination(client, destFile);
|
||||
if (us == null) return false;
|
||||
|
||||
// if we're here, we got a destination. lets connect
|
||||
FileInputStream fin = null;
|
||||
try {
|
||||
fin = new FileInputStream(destFile);
|
||||
Properties options = getOptions();
|
||||
I2PSession session = client.createSession(fin, options);
|
||||
I2PListener lsnr = new I2PListener();
|
||||
session.setSessionListener(lsnr);
|
||||
session.connect();
|
||||
_localDest = session.getMyDestination();
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("I2CP Session created and connected as " + _localDest.calculateHash().toBase64());
|
||||
}
|
||||
_session = session;
|
||||
_i2pListener = lsnr;
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error connecting", ise);
|
||||
}
|
||||
return false;
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error loading the destionation", ioe);
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
if (fin != null) try {
|
||||
fin.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* load, verify, or create a destination
|
||||
*
|
||||
* @param client the client
|
||||
* @param destFile the file holding the destination
|
||||
* @return the destination loaded, or null if there was an error
|
||||
*/
|
||||
private Destination verifyDestination(I2PClient client, File destFile) {
|
||||
Destination us = null;
|
||||
FileInputStream fin = null;
|
||||
if (destFile.exists()) {
|
||||
try {
|
||||
fin = new FileInputStream(destFile);
|
||||
us = new Destination();
|
||||
us.readBytes(fin);
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("Existing destination loaded: [" + us.toBase64() + "]");
|
||||
}
|
||||
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(_publicDestFile);
|
||||
fos.write(us.toBase64().getBytes());
|
||||
fos.flush();
|
||||
} catch (IOException fioe) {
|
||||
_log.error("Error writing out the plain destination to [" + _publicDestFile + "]", fioe);
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException fioe) {}
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
if (fin != null) try {
|
||||
fin.close();
|
||||
} catch (IOException ioe2) {
|
||||
}
|
||||
fin = null;
|
||||
destFile.delete();
|
||||
us = null;
|
||||
} catch (DataFormatException dfe) {
|
||||
if (fin != null) try {
|
||||
fin.close();
|
||||
} catch (IOException ioe2) {
|
||||
}
|
||||
fin = null;
|
||||
destFile.delete();
|
||||
us = null;
|
||||
} finally {
|
||||
if (fin != null) try {
|
||||
fin.close();
|
||||
} catch (IOException ioe2) {
|
||||
}
|
||||
fin = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (us == null) {
|
||||
// need to create a new one
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(destFile);
|
||||
us = client.createDestination(fos);
|
||||
if (_log.shouldLog(Log.INFO)) {
|
||||
_log.info("New destination created: [" + us.toBase64() + "]");
|
||||
}
|
||||
fos.close();
|
||||
|
||||
try {
|
||||
fos = new FileOutputStream(_publicDestFile);
|
||||
fos.write(us.toBase64().getBytes());
|
||||
fos.flush();
|
||||
} catch (IOException fioe) {
|
||||
_log.error("Error writing out the plain destination to [" + _publicDestFile + "]", fioe);
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException fioe) {}
|
||||
fos = null;
|
||||
}
|
||||
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error writing out the destination keys being created", ioe);
|
||||
}
|
||||
return null;
|
||||
} catch (I2PException ie) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error creating the destination", ie);
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
if (fos != null) try {
|
||||
fos.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return us;
|
||||
}
|
||||
|
||||
/**
|
||||
* I2PSession connect options
|
||||
* @return the options as Properties
|
||||
*/
|
||||
private Properties getOptions() {
|
||||
Properties props = new Properties();
|
||||
// this should be BEST_EFFORT, but i'm too lazy to update the code to handle tracking
|
||||
// sessionTags and sessionKeys, marking them as delivered on pong.
|
||||
props.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
|
||||
props.setProperty(I2PClient.PROP_TCP_HOST, _i2cpHost);
|
||||
props.setProperty(I2PClient.PROP_TCP_PORT, _i2cpPort + "");
|
||||
props.setProperty("tunnels.depthInbound", "" + _numHops);
|
||||
props.setProperty("tunnels.depthOutbound", "" + _numHops);
|
||||
return props;
|
||||
}
|
||||
|
||||
/** disconnect from the I2P router */
|
||||
void disconnect() {
|
||||
if (_session != null) {
|
||||
try {
|
||||
_session.destroySession();
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Error destroying the session", ise);
|
||||
}
|
||||
}
|
||||
_session = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an event notification system for receiving pings and pongs
|
||||
*
|
||||
*/
|
||||
public interface PingPongEventListener {
|
||||
/**
|
||||
* receive a ping message from the peer
|
||||
*
|
||||
* @param from peer that sent us the ping
|
||||
* @param seriesNum id the peer sent us in the ping
|
||||
* @param sentOn date the peer said they sent us the message
|
||||
* @param data payload from the ping
|
||||
*/
|
||||
void receivePing(Destination from, int seriesNum, Date sentOn, byte data[]);
|
||||
|
||||
/**
|
||||
* receive a pong message from the peer
|
||||
*
|
||||
* @param from peer that sent us the pong
|
||||
* @param seriesNum id the peer sent us in the pong (that we sent them in the ping)
|
||||
* @param sentOn when we sent out the ping
|
||||
* @param replyOn when they sent out the pong
|
||||
* @param data payload from the ping/pong
|
||||
*/
|
||||
void receivePong(Destination from, int seriesNum, Date sentOn, Date replyOn, byte data[]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive data from the session and pass it along to handleMessage for parsing/dispersal
|
||||
*
|
||||
*/
|
||||
private class I2PListener implements I2PSessionListener {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.client.I2PSessionListener#disconnected(net.i2p.client.I2PSession)
|
||||
*/
|
||||
public void disconnected(I2PSession session) {
|
||||
if (_log.shouldLog(Log.ERROR)) {
|
||||
_log.error("Session disconnected");
|
||||
}
|
||||
disconnect();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.client.I2PSessionListener#errorOccurred(net.i2p.client.I2PSession, java.lang.String, java.lang.Throwable)
|
||||
*/
|
||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error("Error occurred: " + message, error);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.client.I2PSessionListener#reportAbuse(net.i2p.client.I2PSession, int)
|
||||
*/
|
||||
public void reportAbuse(I2PSession session, int severity) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error("Abuse reported with severity " + String.valueOf(severity));
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.client.I2PSessionListener#messageAvailable(net.i2p.client.I2PSession, int, long)
|
||||
*/
|
||||
public void messageAvailable(I2PSession session, int msgId, long size) {
|
||||
try {
|
||||
byte data[] = session.receiveMessage(msgId);
|
||||
handleMessage(data);
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error("Error receiving the message", ise);
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,412 +0,0 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Contain the current window of data for a particular series of ping/pong stats
|
||||
* sent to a peer. This should be periodically kept clean by calling cleanup()
|
||||
* to timeout expired pings and to drop data outside the window.
|
||||
*
|
||||
*/
|
||||
public class PeerData {
|
||||
private final static Log _log = new Log(PeerData.class);
|
||||
/** peer / sequence / config in this data series */
|
||||
private ClientConfig _peer;
|
||||
/** date sent (Long) to EventDataPoint containing the datapoints sent in the current period */
|
||||
private Map _dataPoints;
|
||||
/** date sent (Long) to EventDataPoint containing pings that haven't yet timed out or been ponged */
|
||||
private TreeMap _pendingPings;
|
||||
private long _sessionStart;
|
||||
private long _lifetimeSent;
|
||||
private long _lifetimeReceived;
|
||||
/** rate averaging the time to send over a variety of periods */
|
||||
private RateStat _sendRate;
|
||||
/** rate averaging the time to receive over a variety of periods */
|
||||
private RateStat _receiveRate;
|
||||
/** rate averaging the frequency of lost messages over a variety of periods */
|
||||
private RateStat _lostRate;
|
||||
|
||||
/** how long we wait before timing out pending pings (30 seconds) */
|
||||
private static final long TIMEOUT_PERIOD = 60 * 1000;
|
||||
|
||||
/** synchronize on this when updating _dataPoints or _pendingPings */
|
||||
private Object _updateLock = new Object();
|
||||
|
||||
/**
|
||||
* Creates a PeerData . . .
|
||||
* @param config configuration to load from
|
||||
*/
|
||||
public PeerData(ClientConfig config) {
|
||||
_peer = config;
|
||||
_dataPoints = new TreeMap();
|
||||
_pendingPings = new TreeMap();
|
||||
_sessionStart = Clock.getInstance().now();
|
||||
_lifetimeSent = 0;
|
||||
_lifetimeReceived = 0;
|
||||
_sendRate = new RateStat("sendRate", "How long it takes to send", "peer",
|
||||
getPeriods(config.getAveragePeriods()));
|
||||
_receiveRate = new RateStat("receiveRate", "How long it takes to receive", "peer",
|
||||
getPeriods(config.getAveragePeriods()));
|
||||
_lostRate = new RateStat("lostRate", "How frequently we lose messages", "peer",
|
||||
getPeriods(config.getAveragePeriods()));
|
||||
}
|
||||
|
||||
/**
|
||||
* turn the periods (# minutes) into rate periods (# milliseconds)
|
||||
* @param periods (in minutes)
|
||||
* @return an array of periods (in milliseconds)
|
||||
*/
|
||||
private static long[] getPeriods(int periods[]) {
|
||||
long rv[] = null;
|
||||
if (periods == null) periods = new int[0];
|
||||
rv = new long[periods.length];
|
||||
for (int i = 0; i < periods.length; i++)
|
||||
rv[i] = (long) periods[i] * 60 * 1000; // they're in minutes
|
||||
Arrays.sort(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* how many pings are still outstanding?
|
||||
* @return the number of pings outstanding
|
||||
*/
|
||||
public int getPendingCount() {
|
||||
synchronized (_updateLock) {
|
||||
return _pendingPings.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* how many data points are available in the current window?
|
||||
* @return the number of datapoints available
|
||||
*/
|
||||
public int getDataPointCount() {
|
||||
synchronized (_updateLock) {
|
||||
return _dataPoints.size();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* when did this test begin?
|
||||
* @return when the test began
|
||||
*/
|
||||
public long getSessionStart() { return _sessionStart; }
|
||||
|
||||
/**
|
||||
* sets when the test began
|
||||
* @param when when it began
|
||||
*/
|
||||
public void setSessionStart(long when) { _sessionStart = when; }
|
||||
|
||||
/**
|
||||
* how many pings have we sent for this test?
|
||||
* @return the number of pings sent
|
||||
*/
|
||||
public long getLifetimeSent() { return _lifetimeSent; }
|
||||
|
||||
/**
|
||||
* how many pongs have we received for this test?
|
||||
* @return the number of pings received
|
||||
*/
|
||||
public long getLifetimeReceived() { return _lifetimeReceived; }
|
||||
|
||||
/**
|
||||
* @return the client configuration
|
||||
*/
|
||||
public ClientConfig getConfig() {
|
||||
return _peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* What periods are we averaging the data over (in minutes)?
|
||||
* @return the periods as an array of ints (in minutes)
|
||||
*/
|
||||
public int[] getAveragePeriods() {
|
||||
return (_peer.getAveragePeriods() != null ? _peer.getAveragePeriods() : new int[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* average time to send over the given period.
|
||||
*
|
||||
* @param period number of minutes to retrieve the average for
|
||||
* @return milliseconds average, or -1 if we dont track that period
|
||||
*/
|
||||
public double getAverageSendTime(int period) {
|
||||
return getAverage(_sendRate, period);
|
||||
}
|
||||
|
||||
/**
|
||||
* average time to receive over the given period.
|
||||
*
|
||||
* @param period number of minutes to retrieve the average for
|
||||
* @return milliseconds average, or -1 if we dont track that period
|
||||
*/
|
||||
public double getAverageReceiveTime(int period) {
|
||||
return getAverage(_receiveRate, period);
|
||||
}
|
||||
|
||||
/**
|
||||
* number of lost messages over the given period.
|
||||
*
|
||||
* @param period number of minutes to retrieve the average for
|
||||
* @return number of lost messages in the period, or -1 if we dont track that period
|
||||
*/
|
||||
public double getLostMessages(int period) {
|
||||
Rate rate = _lostRate.getRate(period * 60 * 1000);
|
||||
if (rate == null) return -1;
|
||||
return rate.getCurrentTotalValue();
|
||||
}
|
||||
|
||||
private double getAverage(RateStat stat, int period) {
|
||||
Rate rate = stat.getRate(period * 60 * 1000);
|
||||
if (rate == null) return -1;
|
||||
return rate.getAverageValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an ordered list of data points in the current window (after doing a cleanup)
|
||||
*
|
||||
* @return list of EventDataPoint objects
|
||||
*/
|
||||
public List getDataPoints() {
|
||||
cleanup();
|
||||
synchronized (_updateLock) {
|
||||
return new ArrayList(_dataPoints.values());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We have sent the peer a ping on this series (using the send time as given)
|
||||
* @param dateSent when the ping was sent
|
||||
*/
|
||||
public void addPing(long dateSent) {
|
||||
EventDataPoint sent = new EventDataPoint(dateSent);
|
||||
synchronized (_updateLock) {
|
||||
_pendingPings.put(new Long(dateSent), sent);
|
||||
}
|
||||
_lifetimeSent++;
|
||||
}
|
||||
|
||||
/**
|
||||
* we have received a pong from the peer on this series
|
||||
*
|
||||
* @param dateSent when we sent the ping
|
||||
* @param pongSent when the peer received the ping and sent the pong
|
||||
*/
|
||||
public void pongReceived(long dateSent, long pongSent) {
|
||||
long now = Clock.getInstance().now();
|
||||
synchronized (_updateLock) {
|
||||
if (_pendingPings.size() <= 0) {
|
||||
_log.warn("Pong received (sent at " + dateSent + ", " + (now-dateSent)
|
||||
+ "ms ago, pong delay " + (pongSent-dateSent) + "ms, pong receive delay "
|
||||
+ (now-pongSent) + "ms)");
|
||||
return;
|
||||
}
|
||||
Long first = (Long)_pendingPings.firstKey();
|
||||
EventDataPoint data = (EventDataPoint)_pendingPings.remove(new Long(dateSent));
|
||||
|
||||
if (data != null) {
|
||||
data.setPongReceived(now);
|
||||
data.setPongSent(pongSent);
|
||||
data.setWasPonged(true);
|
||||
locked_addDataPoint(data);
|
||||
|
||||
if (dateSent != first.longValue()) {
|
||||
_log.error("Out of order delivery: received " + dateSent
|
||||
+ " but the first pending is " + first.longValue()
|
||||
+ " (delta " + (dateSent - first.longValue()) + ")");
|
||||
} else {
|
||||
_log.info("In order delivery for " + dateSent + " in ping "
|
||||
+ _peer.getComment());
|
||||
}
|
||||
} else {
|
||||
_log.warn("Pong received, but no matching ping? ping sent at = " + dateSent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_sendRate.addData(pongSent - dateSent, 0);
|
||||
_receiveRate.addData(now - pongSent, 0);
|
||||
_lifetimeReceived++;
|
||||
}
|
||||
|
||||
protected void addDataPoint(EventDataPoint data) {
|
||||
synchronized (_updateLock) {
|
||||
locked_addDataPoint(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void locked_addDataPoint(EventDataPoint data) {
|
||||
Object val = _dataPoints.put(new Long(data.getPingSent()), data);
|
||||
if (val != null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Duplicate data point received: " + data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* drop all datapoints outside the window we're watching, and timeout all
|
||||
* pending pings not ponged in the TIMEOUT_PERIOD, both updating the lost message
|
||||
* rate and coallescing all of the rates.
|
||||
*
|
||||
*/
|
||||
public void cleanup() {
|
||||
long dropBefore = Clock.getInstance().now() - _peer.getStatDuration() * 60 * 1000;
|
||||
long timeoutBefore = Clock.getInstance().now() - TIMEOUT_PERIOD;
|
||||
long numDropped = 0;
|
||||
long numTimedOut = 0;
|
||||
|
||||
synchronized (_updateLock) {
|
||||
numDropped = locked_dropExpired(dropBefore);
|
||||
numTimedOut = locked_timeoutPending(timeoutBefore);
|
||||
}
|
||||
|
||||
_lostRate.addData(numTimedOut, 0);
|
||||
|
||||
_receiveRate.coalesceStats();
|
||||
_sendRate.coalesceStats();
|
||||
_lostRate.coalesceStats();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Peer data cleaned up " + numTimedOut + " timed out pings and removed " + numDropped
|
||||
+ " old entries");
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop all data points that are already too old for us to be interested in
|
||||
*
|
||||
* @param when the earliest ping send time we care about
|
||||
* @return number of data points dropped
|
||||
*/
|
||||
private int locked_dropExpired(long when) {
|
||||
Set toDrop = new HashSet(4);
|
||||
// drop the failed and really old
|
||||
for (Iterator iter = _dataPoints.keySet().iterator(); iter.hasNext(); ) {
|
||||
Long pingTime = (Long)iter.next();
|
||||
if (pingTime.longValue() < when)
|
||||
toDrop.add(pingTime);
|
||||
}
|
||||
for (Iterator iter = toDrop.iterator(); iter.hasNext(); ) {
|
||||
_dataPoints.remove(iter.next());
|
||||
}
|
||||
return toDrop.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* timeout and remove all pings that were sent before the given time,
|
||||
* moving them from the set of pending pings to the set of data points
|
||||
*
|
||||
* @param when the earliest ping send time we care about
|
||||
* @return number of pings timed out
|
||||
*/
|
||||
private int locked_timeoutPending(long when) {
|
||||
Set toDrop = new HashSet(4);
|
||||
for (Iterator iter = _pendingPings.keySet().iterator(); iter.hasNext(); ) {
|
||||
Long pingTime = (Long)iter.next();
|
||||
if (pingTime.longValue() < when) {
|
||||
toDrop.add(pingTime);
|
||||
EventDataPoint point = (EventDataPoint)_pendingPings.get(pingTime);
|
||||
point.setWasPonged(false);
|
||||
locked_addDataPoint(point);
|
||||
}
|
||||
}
|
||||
for (Iterator iter = toDrop.iterator(); iter.hasNext(); ) {
|
||||
_pendingPings.remove(iter.next());
|
||||
}
|
||||
return toDrop.size();
|
||||
}
|
||||
|
||||
/** actual data point for the peer */
|
||||
public class EventDataPoint {
|
||||
private boolean _wasPonged;
|
||||
private long _pingSent;
|
||||
private long _pongSent;
|
||||
private long _pongReceived;
|
||||
|
||||
/**
|
||||
* Creates an EventDataPoint
|
||||
*/
|
||||
public EventDataPoint() { this(-1); }
|
||||
|
||||
/**
|
||||
* Creates an EventDataPoint with pingtime associated with it =)
|
||||
* @param pingSentOn the time a ping was sent
|
||||
*/
|
||||
public EventDataPoint(long pingSentOn) {
|
||||
_wasPonged = false;
|
||||
_pingSent = pingSentOn;
|
||||
_pongSent = -1;
|
||||
_pongReceived = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* when did we send this ping?
|
||||
* @return the time the ping was sent
|
||||
*/
|
||||
public long getPingSent() { return _pingSent; }
|
||||
|
||||
/**
|
||||
* sets when we sent this ping
|
||||
* @param when when we sent the ping
|
||||
*/
|
||||
public void setPingSent(long when) { _pingSent = when; }
|
||||
|
||||
/**
|
||||
* when did the peer receive the ping?
|
||||
* @return the time the ping was receieved
|
||||
*/
|
||||
public long getPongSent() {
|
||||
return _pongSent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time the peer received the ping
|
||||
* @param when the time to set
|
||||
*/
|
||||
public void setPongSent(long when) {
|
||||
_pongSent = when;
|
||||
}
|
||||
|
||||
/**
|
||||
* when did we receive the peer's pong?
|
||||
* @return the time we receieved the pong
|
||||
*/
|
||||
public long getPongReceived() {
|
||||
return _pongReceived;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time the peer's pong was receieved
|
||||
* @param when the time to set
|
||||
*/
|
||||
public void setPongReceived(long when) {
|
||||
_pongReceived = when;
|
||||
}
|
||||
|
||||
/**
|
||||
* did the peer reply in time?
|
||||
* @return true or false, whether we got a reply in time */
|
||||
public boolean getWasPonged() {
|
||||
return _wasPonged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether we receieved the peer's reply in time
|
||||
* @param pong true or false
|
||||
*/
|
||||
public void setWasPonged(boolean pong) {
|
||||
_wasPonged = pong;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
package net.i2p.heartbeat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Actually write out the stats for peer test
|
||||
*
|
||||
*/
|
||||
public class PeerDataWriter {
|
||||
private final static Log _log = new Log(PeerDataWriter.class);
|
||||
|
||||
/**
|
||||
* persist the peer state to the location specified in the peer config
|
||||
*
|
||||
* @param data the peer data to persist
|
||||
* @return true if it was persisted correctly, false on error
|
||||
*/
|
||||
public boolean persist(PeerData data) {
|
||||
String filename = data.getConfig().getStatFile();
|
||||
File statFile = new File(filename);
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(statFile);
|
||||
persist(data, fos);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error persisting the peer data for "
|
||||
+ data.getConfig().getPeer().calculateHash().toBase64(), ioe);
|
||||
return false;
|
||||
} finally {
|
||||
if (fos != null) try {
|
||||
fos.close();
|
||||
} catch (IOException ioe) {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* persists the peer state to the output stream
|
||||
* @param data the peer data to persist
|
||||
* @param out where to persist the data
|
||||
* @return true if it was persisted correctly [always (as implemented)], false on error
|
||||
* @throws IOException
|
||||
*/
|
||||
public boolean persist(PeerData data, OutputStream out) throws IOException {
|
||||
String header = getHeader(data);
|
||||
|
||||
out.write(header.getBytes());
|
||||
out.write("#action\tstatus\tdate and time sent \tsendMs\treplyMs\troundTrip\n".getBytes());
|
||||
for (Iterator iter = data.getDataPoints().iterator(); iter.hasNext();) {
|
||||
PeerData.EventDataPoint point = (PeerData.EventDataPoint) iter.next();
|
||||
String line = getEvent(point);
|
||||
out.write(line.getBytes());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private String getHeader(PeerData data) {
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
buf.append("peer \t").append(data.getConfig().getPeer().calculateHash().toBase64()).append('\n');
|
||||
buf.append("local \t").append(data.getConfig().getUs().calculateHash().toBase64()).append('\n');
|
||||
buf.append("peerDest \t").append(data.getConfig().getPeer().toBase64()).append('\n');
|
||||
buf.append("localDest \t").append(data.getConfig().getUs().toBase64()).append('\n');
|
||||
buf.append("numTunnelHops\t").append(data.getConfig().getNumHops()).append('\n');
|
||||
buf.append("comment \t").append(data.getConfig().getComment()).append('\n');
|
||||
buf.append("sendFrequency\t").append(data.getConfig().getSendFrequency()).append('\n');
|
||||
buf.append("sendSize \t").append(data.getConfig().getSendSize()).append('\n');
|
||||
buf.append("sessionStart \t").append(getTime(data.getSessionStart())).append('\n');
|
||||
buf.append("currentTime \t").append(getTime(Clock.getInstance().now())).append('\n');
|
||||
buf.append("numPending \t").append(data.getPendingCount()).append('\n');
|
||||
buf.append("lifetimeSent \t").append(data.getLifetimeSent()).append('\n');
|
||||
buf.append("lifetimeRecv \t").append(data.getLifetimeReceived()).append('\n');
|
||||
int periods[] = data.getAveragePeriods();
|
||||
buf.append("#averages\tminutes\tsendMs\trecvMs\tnumLost\troundTrip\n");
|
||||
for (int i = 0; i < periods.length; i++) {
|
||||
buf.append("periodAverage\t").append(periods[i]).append('\t');
|
||||
buf.append(getNum(data.getAverageSendTime(periods[i]))).append('\t');
|
||||
buf.append(getNum(data.getAverageReceiveTime(periods[i]))).append('\t');
|
||||
buf.append(getNum(data.getLostMessages(periods[i]))).append('\t');
|
||||
double rtt = data.getAverageSendTime(periods[i])
|
||||
+ data.getAverageReceiveTime(periods[i]);
|
||||
buf.append(getNum(rtt)).append('\n');
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private String getEvent(PeerData.EventDataPoint point) {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
buf.append("EVENT\t");
|
||||
if (point.getWasPonged())
|
||||
buf.append("OK\t");
|
||||
else
|
||||
buf.append("LOST\t");
|
||||
buf.append(getTime(point.getPingSent())).append('\t');
|
||||
if (point.getWasPonged()) {
|
||||
buf.append(point.getPongSent() - point.getPingSent()).append('\t');
|
||||
buf.append(point.getPongReceived() - point.getPongSent()).append('\t');
|
||||
buf.append(point.getPongReceived() - point.getPingSent()).append('\t');
|
||||
}
|
||||
buf.append('\n');
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private final SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd.HH:mm:ss.SSS", Locale.UK);
|
||||
|
||||
/**
|
||||
* Converts a time (long) to text
|
||||
* @param when the time to convert
|
||||
* @return the textual representation
|
||||
*/
|
||||
public String getTime(long when) {
|
||||
synchronized (_fmt) {
|
||||
return _fmt.format(new Date(when));
|
||||
}
|
||||
}
|
||||
|
||||
private final DecimalFormat _numFmt = new DecimalFormat("#0", new DecimalFormatSymbols(Locale.UK));
|
||||
|
||||
/**
|
||||
* Converts a number (double) to text
|
||||
* @param val the number to convert
|
||||
* @return the textual representation
|
||||
*/
|
||||
public String getNum(double val) {
|
||||
synchronized (_numFmt) {
|
||||
return _numFmt.format(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTabbedPane;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Render the control widgets (refresh/load/snapshot and the
|
||||
* tabbed panel with the plot config data)
|
||||
*
|
||||
*/
|
||||
class HeartbeatControlPane extends JPanel {
|
||||
private final static Log _log = new Log(HeartbeatControlPane.class);
|
||||
private HeartbeatMonitorGUI _gui;
|
||||
private JTabbedPane _configPane;
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
private final static Color LIGHT_BLUE = new Color(180, 180, 255); /* UNUSED */
|
||||
private final static Color BLACK = new Color(0, 0, 0);
|
||||
private Color _background = WHITE;
|
||||
private Color _foreground = BLACK;
|
||||
|
||||
/**
|
||||
* Constructs a control panel onto the gui
|
||||
* @param gui the gui the panel is associated with
|
||||
*/
|
||||
public HeartbeatControlPane(HeartbeatMonitorGUI gui) {
|
||||
_gui = gui;
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a test to the panel
|
||||
* @param config the configuration for the test
|
||||
*/
|
||||
public void addTest(PeerPlotConfig config) {
|
||||
_configPane.addTab(config.getTitle(), null, new JScrollPane(new PeerPlotConfigPane(config, this)), config.getSummary());
|
||||
_configPane.setBackgroundAt(_configPane.getTabCount()-1, _background);
|
||||
_configPane.setForegroundAt(_configPane.getTabCount()-1, _foreground);
|
||||
_gui.pack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a test from the panel
|
||||
* @param config the configuration for the test
|
||||
*/
|
||||
public void removeTest(PeerPlotConfig config) {
|
||||
_gui.getMonitor().getState().removeTest(config);
|
||||
int index = _configPane.indexOfTab(config.getTitle());
|
||||
if (index >= 0)
|
||||
_configPane.removeTabAt(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback: when tests have changed
|
||||
*/
|
||||
public void testsUpdated() {
|
||||
List knownNames = new ArrayList(8);
|
||||
for (int i = 0; i < _gui.getMonitor().getState().getTestCount(); i++) {
|
||||
PeerPlotState state = _gui.getMonitor().getState().getTest(i);
|
||||
String title = state.getPlotConfig().getTitle();
|
||||
knownNames.add(state.getPlotConfig().getTitle());
|
||||
if (_configPane.indexOfTab(title) >= 0) {
|
||||
_log.debug("We already know about [" + title + "]");
|
||||
} else {
|
||||
_log.info("The test [" + title + "] is new to us");
|
||||
PeerPlotConfigPane pane = new PeerPlotConfigPane(state.getPlotConfig(), this);
|
||||
_configPane.addTab(state.getPlotConfig().getTitle(), null, new JScrollPane(pane), state.getPlotConfig().getSummary());
|
||||
_configPane.setBackgroundAt(_configPane.getTabCount()-1, _background);
|
||||
_configPane.setForegroundAt(_configPane.getTabCount()-1, _foreground);
|
||||
}
|
||||
}
|
||||
List toRemove = new ArrayList(4);
|
||||
for (int i = 0; i < _configPane.getTabCount(); i++) {
|
||||
if (knownNames.contains(_configPane.getTitleAt(i))) {
|
||||
// noop
|
||||
} else {
|
||||
toRemove.add(_configPane.getTitleAt(i));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < toRemove.size(); i++) {
|
||||
String title = (String)toRemove.get(i);
|
||||
_log.info("Removing test [" + title + "]");
|
||||
_configPane.removeTabAt(_configPane.indexOfTab(title));
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeComponents() {
|
||||
if (_gui != null)
|
||||
setBackground(_gui.getBackground());
|
||||
else
|
||||
setBackground(_background);
|
||||
setLayout(new BorderLayout());
|
||||
HeartbeatMonitorCommandBar bar = new HeartbeatMonitorCommandBar(_gui);
|
||||
bar.setBackground(getBackground());
|
||||
add(bar, BorderLayout.NORTH);
|
||||
_configPane = new JTabbedPane(JTabbedPane.LEFT);
|
||||
_configPane.setBackground(_background);
|
||||
//add(_configPane, BorderLayout.CENTER);
|
||||
add(_configPane, BorderLayout.SOUTH);
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* The HeartbeatMonitor, complete with main()! Act now, and it's only 5 easy
|
||||
* payments of $19.95 (plus shipping and handling)! You heard me, only _5_
|
||||
* easy payments of $19.95 (plus shipping and handling)! <p />
|
||||
*
|
||||
* (fine print: something about some states in the US requiring the addition
|
||||
* of sales tax... or something) <p />
|
||||
*
|
||||
* (finer print: Satan owns you. Deal with it.) <p />
|
||||
*
|
||||
* (even finer print: usage: <code>HeartbeatMonitor [configFilename]</code>)
|
||||
*/
|
||||
public class HeartbeatMonitor implements PeerPlotStateFetcher.FetchStateReceptor, PeerPlotConfig.UpdateListener {
|
||||
private final static Log _log = new Log(HeartbeatMonitor.class);
|
||||
private HeartbeatMonitorState _state;
|
||||
private HeartbeatMonitorGUI _gui;
|
||||
|
||||
/**
|
||||
* Delegating constructor.
|
||||
* @see HeartbeatMonitor#HeartbeatMonitor(String)
|
||||
*/
|
||||
public HeartbeatMonitor() { this(null); }
|
||||
|
||||
/**
|
||||
* Creates a HeartbeatMonitor . . .
|
||||
* @param configFilename the configuration file to read from
|
||||
*/
|
||||
public HeartbeatMonitor(String configFilename) {
|
||||
_state = new HeartbeatMonitorState(configFilename);
|
||||
_gui = new HeartbeatMonitorGUI(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the game rollin'
|
||||
*/
|
||||
public void runMonitor() {
|
||||
loadConfig();
|
||||
I2PThread t = new I2PThread(new HeartbeatMonitorRunner(this));
|
||||
t.setName("HeartbeatMonitor");
|
||||
t.setDaemon(false);
|
||||
t.start();
|
||||
_log.debug("Monitor started");
|
||||
}
|
||||
|
||||
/**
|
||||
* give us all the data/config available
|
||||
* @return the current state (data/config)
|
||||
*/
|
||||
HeartbeatMonitorState getState() {
|
||||
return _state;
|
||||
}
|
||||
|
||||
/** for all of the peer tests being monitored, refetch the data and rerender */
|
||||
void refetchData() {
|
||||
_log.debug("Refetching data");
|
||||
for (int i = 0; i < _state.getTestCount(); i++)
|
||||
PeerPlotStateFetcher.fetchPeerPlotState(this, _state.getTest(i));
|
||||
}
|
||||
|
||||
/** (re)load the config defining what peer tests we are monitoring (and how to render) */
|
||||
void loadConfig() {
|
||||
//for (int i = 0; i < 10; i++) {
|
||||
// load("fake" + i);
|
||||
//}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads config data
|
||||
* @param location the name of the location to load data from
|
||||
*/
|
||||
public void load(String location) {
|
||||
PeerPlotConfig cfg = new PeerPlotConfig(location);
|
||||
cfg.addListener(this);
|
||||
PeerPlotState state = new PeerPlotState(cfg);
|
||||
PeerPlotStateFetcher.fetchPeerPlotState(this, state);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see PeerPlotStateFetcher.FetchStateReceptor#peerPlotStateFetched
|
||||
*/
|
||||
public synchronized void peerPlotStateFetched(PeerPlotState state) {
|
||||
_state.addTest(state);
|
||||
_gui.stateUpdated();
|
||||
}
|
||||
|
||||
/**
|
||||
* store the config defining what peer tests we are monitoring (and how to render)
|
||||
*/
|
||||
void storeConfig() {}
|
||||
|
||||
/**
|
||||
* And now, the main function, the one you've all been waiting for! . . .
|
||||
* @param args da args. Should take 1, which is the location to load config data from
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
Thread.currentThread().setName("HeartbeatMonitor.main");
|
||||
if (args.length == 1)
|
||||
new HeartbeatMonitor(args[0]).runMonitor();
|
||||
else
|
||||
new HeartbeatMonitor().runMonitor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the config is updated
|
||||
* @param config the updated config
|
||||
*/
|
||||
public void configUpdated(PeerPlotConfig config) {
|
||||
_log.debug("Config updated, revamping the gui");
|
||||
_gui.stateUpdated();
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
class HeartbeatMonitorCommandBar extends JPanel {
|
||||
private HeartbeatMonitorGUI _gui;
|
||||
private JComboBox _refreshRate;
|
||||
private JTextField _location;
|
||||
|
||||
/**
|
||||
* Constructs a command bar onto the gui
|
||||
* @param gui the gui the command bar is associated with
|
||||
*/
|
||||
public HeartbeatMonitorCommandBar(HeartbeatMonitorGUI gui) {
|
||||
_gui = gui;
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
private void refreshChanged(ItemEvent evt) {}
|
||||
private void loadCalled() {
|
||||
_gui.getMonitor().load(_location.getText());
|
||||
}
|
||||
|
||||
private void browseCalled() {
|
||||
JFileChooser chooser = new JFileChooser(_location.getText());
|
||||
chooser.setBackground(_gui.getBackground());
|
||||
chooser.setMultiSelectionEnabled(false);
|
||||
int rv = chooser.showDialog(this, "Load");
|
||||
if (rv == JFileChooser.APPROVE_OPTION)
|
||||
_gui.getMonitor().load(chooser.getSelectedFile().getAbsolutePath());
|
||||
}
|
||||
|
||||
private void initializeComponents() {
|
||||
_refreshRate = new JComboBox(new DefaultComboBoxModel(new Object[] {"10 second refresh", "30 second refresh", "1 minute refresh", "5 minute refresh"}));
|
||||
_refreshRate.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent evt) { refreshChanged(evt); } });
|
||||
_refreshRate.setEnabled(false);
|
||||
_refreshRate.setBackground(_gui.getBackground());
|
||||
//add(_refreshRate);
|
||||
JLabel loadLabel = new JLabel("Load from: ");
|
||||
loadLabel.setBackground(_gui.getBackground());
|
||||
add(loadLabel);
|
||||
_location = new JTextField(20);
|
||||
_location.setToolTipText("Either specify a local filename or a fully qualified URL");
|
||||
_location.setBackground(_gui.getBackground());
|
||||
add(_location);
|
||||
JButton browse = new JButton("Browse...");
|
||||
browse.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { browseCalled(); } });
|
||||
browse.setBackground(_gui.getBackground());
|
||||
add(browse);
|
||||
JButton load = new JButton("Load");
|
||||
load.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { loadCalled(); } });
|
||||
load.setBackground(_gui.getBackground());
|
||||
add(load);
|
||||
setBackground(_gui.getBackground());
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JScrollPane;
|
||||
|
||||
class HeartbeatMonitorGUI extends JFrame {
|
||||
private HeartbeatMonitor _monitor;
|
||||
private HeartbeatPlotPane _plotPane;
|
||||
private HeartbeatControlPane _controlPane;
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
private Color _background = WHITE;
|
||||
|
||||
/**
|
||||
* Creates the GUI for all youz who be too shoopid for text based shitz
|
||||
* @param monitor the monitor the gui operates over
|
||||
*/
|
||||
public HeartbeatMonitorGUI(HeartbeatMonitor monitor) {
|
||||
super("Heartbeat Monitor");
|
||||
_monitor = monitor;
|
||||
initializeComponents();
|
||||
pack();
|
||||
//setResizable(false);
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
HeartbeatMonitor getMonitor() { return _monitor; }
|
||||
|
||||
/** build up all our widgets */
|
||||
private void initializeComponents() {
|
||||
getContentPane().setLayout(new BorderLayout());
|
||||
|
||||
setBackground(_background);
|
||||
|
||||
_plotPane = new JFreeChartHeartbeatPlotPane(this); // new HeartbeatPlotPane(this);
|
||||
_plotPane.setBackground(_background);
|
||||
//JScrollPane pane = new JScrollPane(_plotPane);
|
||||
//pane.setBackground(_background);
|
||||
getContentPane().add(new JScrollPane(_plotPane), BorderLayout.CENTER);
|
||||
|
||||
_controlPane = new HeartbeatControlPane(this);
|
||||
_controlPane.setBackground(_background);
|
||||
getContentPane().add(_controlPane, BorderLayout.SOUTH);
|
||||
|
||||
//JSplitPane pane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, new JScrollPane(_plotPane), new JScrollPane(_controlPane));
|
||||
//getContentPane().add(pane, BorderLayout.CENTER);
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
initializeMenus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback: when the state of the world changes . . .
|
||||
*/
|
||||
public void stateUpdated() {
|
||||
_controlPane.testsUpdated();
|
||||
_plotPane.stateUpdated();
|
||||
}
|
||||
|
||||
private void exitCalled() {
|
||||
_monitor.getState().setWasKilled(true);
|
||||
setVisible(false);
|
||||
System.exit(0);
|
||||
}
|
||||
private void loadConfigCalled() {}
|
||||
private void saveConfigCalled() {}
|
||||
private void loadSnapshotCalled() {}
|
||||
private void saveSnapshotCalled() {}
|
||||
|
||||
private void initializeMenus() {
|
||||
JMenuBar bar = new JMenuBar();
|
||||
JMenu fileMenu = new JMenu("File");
|
||||
JMenuItem loadConfig = new JMenuItem("Load config");
|
||||
loadConfig.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { loadConfigCalled(); } });
|
||||
JMenuItem saveConfig = new JMenuItem("Save config");
|
||||
saveConfig.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { saveConfigCalled(); } });
|
||||
JMenuItem saveSnapshot = new JMenuItem("Save snapshot");
|
||||
saveSnapshot.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { saveSnapshotCalled(); } });
|
||||
JMenuItem loadSnapshot = new JMenuItem("Load snapshot");
|
||||
loadSnapshot.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { loadSnapshotCalled(); } });
|
||||
JMenuItem exit = new JMenuItem("Exit");
|
||||
exit.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { exitCalled(); } });
|
||||
|
||||
fileMenu.add(loadConfig);
|
||||
fileMenu.add(saveConfig);
|
||||
fileMenu.add(loadSnapshot);
|
||||
fileMenu.add(saveSnapshot);
|
||||
fileMenu.add(exit);
|
||||
bar.add(fileMenu);
|
||||
setJMenuBar(bar);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Periodically fire off necessary events (instructing the heartbeat monitor when
|
||||
* to refetch the data, etc). This is the only active thread in the heartbeat
|
||||
* monitor (outside the swing/jvm threads)
|
||||
*/
|
||||
class HeartbeatMonitorRunner implements Runnable {
|
||||
private final static Log _log = new Log(HeartbeatMonitorRunner.class);
|
||||
private HeartbeatMonitor _monitor;
|
||||
|
||||
/**
|
||||
* Creates the thread . . .
|
||||
* @param monitor the monitor the thread runs over
|
||||
*/
|
||||
public HeartbeatMonitorRunner(HeartbeatMonitor monitor) {
|
||||
_monitor = monitor;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Runnable#run()
|
||||
*/
|
||||
public void run() {
|
||||
while (!_monitor.getState().getWasKilled()) {
|
||||
_monitor.refetchData();
|
||||
try { Thread.sleep(_monitor.getState().getRefreshRateMs()); } catch (InterruptedException ie) {}
|
||||
}
|
||||
_log.info("Stopping the heartbeat monitor runner");
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* manage the current state of the GUI - all data points, as well as any
|
||||
* rendering or configuration options.
|
||||
*/
|
||||
class HeartbeatMonitorState {
|
||||
private String _configFile;
|
||||
private List _peerPlotState;
|
||||
private int _currentPeerPlotConfig;
|
||||
private int _refreshRateMs;
|
||||
private boolean _killed;
|
||||
|
||||
/** by default, refresh every 30 seconds */
|
||||
private final static int DEFAULT_REFRESH_RATE = 30*1000;
|
||||
/** where do we load/store config info from? */
|
||||
private final static String DEFAULT_CONFIG_FILE = "heartbeatMonitor.config";
|
||||
|
||||
/**
|
||||
* A delegating constructor.
|
||||
* @see HeartbeatMonitorState#HeartbeatMonitorState(String)
|
||||
*/
|
||||
public HeartbeatMonitorState() { this(DEFAULT_CONFIG_FILE); }
|
||||
|
||||
/**
|
||||
* Constructs the state, loading from the specified location
|
||||
* @param configFile the name of the file to load info from
|
||||
*/
|
||||
public HeartbeatMonitorState(String configFile) {
|
||||
_peerPlotState = Collections.synchronizedList(new ArrayList());
|
||||
_refreshRateMs = DEFAULT_REFRESH_RATE;
|
||||
_configFile = configFile;
|
||||
_killed = false;
|
||||
_currentPeerPlotConfig = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* how many tests are we monitoring?
|
||||
* @return the number of tests
|
||||
*/
|
||||
public int getTestCount() { return _peerPlotState.size(); }
|
||||
|
||||
/**
|
||||
* Retrieves the current info of a test for a certain peer . . .
|
||||
* @param peer a number associated with a certain peer
|
||||
* @return the test data
|
||||
*/
|
||||
public PeerPlotState getTest(int peer) { return (PeerPlotState)_peerPlotState.get(peer); }
|
||||
|
||||
/**
|
||||
* Adds a test . . .
|
||||
* @param peerState the test (by state) to add . . .
|
||||
*/
|
||||
public void addTest(PeerPlotState peerState) {
|
||||
if (!_peerPlotState.contains(peerState))
|
||||
_peerPlotState.add(peerState);
|
||||
}
|
||||
/**
|
||||
* Removes a test . . .
|
||||
* @param peerState the test (by state) to remove . . .
|
||||
*/
|
||||
public void removeTest(PeerPlotState peerState) { _peerPlotState.remove(peerState); }
|
||||
|
||||
/**
|
||||
* Removes a test . . .
|
||||
* @param peerConfig the test (by config) to remove . . .
|
||||
*/
|
||||
public void removeTest(PeerPlotConfig peerConfig) {
|
||||
for (int i = 0; i < getTestCount(); i++) {
|
||||
PeerPlotState state = getTest(i);
|
||||
if (state.getPlotConfig() == peerConfig) {
|
||||
removeTest(state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* which of the tests are we currently editing/viewing?
|
||||
* @return the number associated with the test
|
||||
*/
|
||||
public int getPeerPlotConfig() { return _currentPeerPlotConfig; }
|
||||
|
||||
/**
|
||||
* Sets the test we are currently editting/viewing
|
||||
* @param whichTest the number associated with the test
|
||||
*/
|
||||
public void setPeerPlotConfig(int whichTest) { _currentPeerPlotConfig = whichTest; }
|
||||
|
||||
/**
|
||||
* how frequently should we update the data?
|
||||
* @return the current frequency (in milliseconds)
|
||||
*/
|
||||
public int getRefreshRateMs() { return _refreshRateMs; }
|
||||
|
||||
/**
|
||||
* Sets how frequently we should update data
|
||||
* @param ms the frequency (in milliseconds)
|
||||
*/
|
||||
public void setRefreshRateMs(int ms) { _refreshRateMs = ms; }
|
||||
|
||||
/**
|
||||
* where is our config stored?
|
||||
* @return the name of the config file
|
||||
*/
|
||||
public String getConfigFile() { return _configFile; }
|
||||
|
||||
/**
|
||||
* Sets where our config is stored
|
||||
* @param filename the name of the config file
|
||||
*/
|
||||
public void setConfigFile(String filename) { _configFile = filename; }
|
||||
|
||||
/**
|
||||
* have we been shut down?
|
||||
* @return true if we have, false otherwise
|
||||
*/
|
||||
public boolean getWasKilled() { return _killed; }
|
||||
|
||||
/**
|
||||
* Sets if we have been shutdown or not
|
||||
* @param killed true if we've been shutdown, false otherwise
|
||||
*/
|
||||
public void setWasKilled(boolean killed) { _killed = killed; }
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextArea;
|
||||
|
||||
import net.i2p.heartbeat.PeerDataWriter;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Render the graph and legend
|
||||
*/
|
||||
class HeartbeatPlotPane extends JPanel {
|
||||
private final static Log _log = new Log(HeartbeatPlotPane.class);
|
||||
protected HeartbeatMonitorGUI _gui;
|
||||
private JTextArea _text;
|
||||
|
||||
/**
|
||||
* Constructs the plot pane
|
||||
* @param gui the gui the pane is attached to
|
||||
*/
|
||||
public HeartbeatPlotPane(HeartbeatMonitorGUI gui) {
|
||||
_gui = gui;
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback: when things change . . .
|
||||
*/
|
||||
public void stateUpdated() {
|
||||
StringBuffer buf = new StringBuffer(32*1024);
|
||||
PeerDataWriter writer = new PeerDataWriter();
|
||||
|
||||
for (int i = 0; i < _gui.getMonitor().getState().getTestCount(); i++) {
|
||||
StaticPeerData data = _gui.getMonitor().getState().getTest(i).getCurrentData();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
|
||||
try {
|
||||
writer.persist(data, baos);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("wtf, error writing to a byte array?", ioe);
|
||||
}
|
||||
buf.append(new String(baos.toByteArray())).append("\n\n\n");
|
||||
}
|
||||
|
||||
_text.setText(buf.toString());
|
||||
}
|
||||
|
||||
protected void initializeComponents() {
|
||||
setBackground(_gui.getBackground());
|
||||
//Dimension size = new Dimension(800, 600);
|
||||
_text = new JTextArea("",30,80); // 16, 60);
|
||||
_text.setAutoscrolls(true);
|
||||
_text.setEditable(false);
|
||||
// _text.setLineWrap(true);
|
||||
// add(new JScrollPane(_text));
|
||||
add(_text);
|
||||
// add(new JScrollPane(_text, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS));
|
||||
// setPreferredSize(size);
|
||||
}
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.util.List;
|
||||
|
||||
import org.jfree.chart.ChartPanel;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.jfree.chart.axis.DateAxis;
|
||||
import org.jfree.chart.axis.NumberAxis;
|
||||
import org.jfree.chart.plot.Plot;
|
||||
import org.jfree.chart.plot.XYPlot;
|
||||
import org.jfree.chart.renderer.XYItemRenderer;
|
||||
import org.jfree.chart.renderer.XYLineAndShapeRenderer;
|
||||
import org.jfree.data.XYSeries;
|
||||
import org.jfree.data.XYSeriesCollection;
|
||||
|
||||
import net.i2p.heartbeat.PeerData;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
class JFreeChartAdapter {
|
||||
private final static Log _log = new Log(JFreeChartAdapter.class); /* UNUSED */
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
|
||||
ChartPanel createPanel(HeartbeatMonitorState state) {
|
||||
ChartPanel panel = new ChartPanel(createChart(state));
|
||||
panel.setDisplayToolTips(true);
|
||||
panel.setEnforceFileExtensions(true);
|
||||
panel.setHorizontalZoom(true);
|
||||
panel.setVerticalZoom(true);
|
||||
panel.setMouseZoomable(true, true);
|
||||
panel.getChart().setBackgroundPaint(WHITE);
|
||||
return panel;
|
||||
}
|
||||
|
||||
JFreeChart createChart(HeartbeatMonitorState state) {
|
||||
Plot plot = createPlot(state);
|
||||
JFreeChart chart = new JFreeChart("I2P Heartbeat performance", Font.getFont("arial"), plot, true);
|
||||
return chart;
|
||||
}
|
||||
|
||||
void updateChart(ChartPanel panel, HeartbeatMonitorState state) {
|
||||
XYPlot plot = (XYPlot)panel.getChart().getPlot();
|
||||
plot.setDataset(getCollection(state));
|
||||
updateLines(plot, state);
|
||||
}
|
||||
|
||||
private long getFirst(HeartbeatMonitorState state) { /* UNUSED */
|
||||
long first = -1;
|
||||
for (int i = 0; i < state.getTestCount(); i++) {
|
||||
List dataPoints = state.getTest(i).getCurrentData().getDataPoints();
|
||||
if ( (dataPoints != null) && (dataPoints.size() > 0) ) {
|
||||
PeerData.EventDataPoint data = (PeerData.EventDataPoint)dataPoints.get(0);
|
||||
if ( (first < 0) || (first > data.getPingSent()) )
|
||||
first = data.getPingSent();
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
Plot createPlot(HeartbeatMonitorState state) {
|
||||
XYItemRenderer renderer = new XYLineAndShapeRenderer(); // new XYDotRenderer(); //
|
||||
XYPlot plot = new XYPlot(getCollection(state), new DateAxis(), new NumberAxis("ms"), renderer);
|
||||
updateLines(plot, state);
|
||||
return plot;
|
||||
}
|
||||
|
||||
private void updateLines(XYPlot plot, HeartbeatMonitorState state) {
|
||||
if (true) return;
|
||||
if (state == null) return;
|
||||
for (int i = 0; i < state.getTestCount(); i++) {
|
||||
PeerPlotConfig config = state.getTest(i).getPlotConfig();
|
||||
PeerPlotConfig.PlotSeriesConfig curConfig = config.getCurrentSeriesConfig();
|
||||
XYSeriesCollection col = ((XYSeriesCollection)plot.getDataset());
|
||||
for (int j = 0; j < col.getSeriesCount(); j++) {
|
||||
//XYItemRenderer renderer = plot.getRendererForDataset(col.getSeries(j));
|
||||
XYItemRenderer renderer = plot.getRendererForDataset(col);
|
||||
if (col.getSeriesName(j).startsWith(config.getTitle() + " send")) {
|
||||
if (curConfig.getPlotSendTime()) {
|
||||
//renderer.setPaint(curConfig.getPlotLineColor());
|
||||
}
|
||||
}
|
||||
if (col.getSeriesName(j).startsWith(config.getTitle() + " receive")) {
|
||||
if (curConfig.getPlotReceiveTime()) {
|
||||
//renderer.setPaint(curConfig.getPlotLineColor());
|
||||
}
|
||||
}
|
||||
if (col.getSeriesName(j).startsWith(config.getTitle() + " lost")) {
|
||||
if (curConfig.getPlotLostMessages()) {
|
||||
//renderer.setPaint(curConfig.getPlotLineColor());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XYSeriesCollection getCollection(HeartbeatMonitorState state) {
|
||||
XYSeriesCollection col = new XYSeriesCollection();
|
||||
if (state != null) {
|
||||
for (int i = 0; i < state.getTestCount(); i++) {
|
||||
addTest(col, state.getTest(i));
|
||||
}
|
||||
} else {
|
||||
XYSeries series = new XYSeries("latency", false, false);
|
||||
series.add(System.currentTimeMillis(), 0);
|
||||
col.addSeries(series);
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
void addTest(XYSeriesCollection col, PeerPlotState state) {
|
||||
PeerPlotConfig config = state.getPlotConfig();
|
||||
PeerPlotConfig.PlotSeriesConfig curConfig = config.getCurrentSeriesConfig();
|
||||
addLines(col, curConfig, config.getTitle(), state.getCurrentData().getDataPoints());
|
||||
addAverageLines(col, config, config.getTitle(), state.getCurrentData());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param col the collection of xy series to add to
|
||||
* @param config preferences for how to display this test
|
||||
* @param lineName minimal name of the test (e.g. "jxHa.32KB.60s")
|
||||
* @param data List of PeerData.EventDataPoint describing all of the events in the test
|
||||
*/
|
||||
void addLines(XYSeriesCollection col, PeerPlotConfig.PlotSeriesConfig config, String lineName, List data) {
|
||||
if (config.getPlotSendTime()) {
|
||||
XYSeries sendSeries = getSendSeries(data);
|
||||
sendSeries.setName(lineName + " send");
|
||||
sendSeries.setDescription("milliseconds for the ping to reach the peer");
|
||||
col.addSeries(sendSeries);
|
||||
}
|
||||
if (config.getPlotReceiveTime()) {
|
||||
XYSeries recvSeries = getReceiveSeries(data);
|
||||
recvSeries.setName(lineName + " receive");
|
||||
recvSeries.setDescription("milliseconds for the peer's pong to reach the sender");
|
||||
col.addSeries(recvSeries);
|
||||
}
|
||||
if (config.getPlotLostMessages()) {
|
||||
XYSeries lostSeries = getLostSeries(data);
|
||||
lostSeries.setName(lineName + " lost");
|
||||
lostSeries.setDescription("number of ping/pong messages lost");
|
||||
col.addSeries(lostSeries);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a data series for each average that we're configured to render
|
||||
*
|
||||
* @param col the collection of xy series to add to
|
||||
* @param config preferences for how to display this test
|
||||
* @param lineName minimal name of the test (e.g. "jxHa.32KB.60s")
|
||||
* @param data List of PeerData.EventDataPoint describing all of the events in the test
|
||||
*/
|
||||
void addAverageLines(XYSeriesCollection col, PeerPlotConfig config, String lineName, StaticPeerData data) {
|
||||
if (data.getDataPointCount() <= 0) return;
|
||||
PeerData.EventDataPoint start = (PeerData.EventDataPoint)data.getDataPoints().get(0);
|
||||
PeerData.EventDataPoint finish = (PeerData.EventDataPoint)data.getDataPoints().get(data.getDataPointCount()-1);
|
||||
|
||||
List configs = config.getAverageSeriesConfigs();
|
||||
|
||||
for (int i = 0; i < configs.size(); i++) {
|
||||
PeerPlotConfig.PlotSeriesConfig cfg = (PeerPlotConfig.PlotSeriesConfig)configs.get(i);
|
||||
int minutes = (int)cfg.getPeriod()/(60*1000);
|
||||
if (cfg.getPlotSendTime()) {
|
||||
double time = data.getAverageSendTime(minutes);
|
||||
if (time > 0) {
|
||||
XYSeries series = new XYSeries(lineName + " send " + minutes + "m avg [" + time + "]", false, false);
|
||||
series.add(start.getPingSent(), time);
|
||||
series.add(finish.getPingSent(), time);
|
||||
series.setDescription("send time, averaged over the last " + minutes + " minutes");
|
||||
col.addSeries(series);
|
||||
}
|
||||
}
|
||||
if (cfg.getPlotReceiveTime()) {
|
||||
double time = data.getAverageReceiveTime(minutes);
|
||||
if (time > 0) {
|
||||
XYSeries series = new XYSeries(lineName + " receive " + minutes + "m avg[" + time + "]", false, false);
|
||||
series.add(start.getPingSent(), time);
|
||||
series.add(finish.getPingSent(), time);
|
||||
series.setDescription("receive time, averaged over the last " + minutes + " minutes");
|
||||
col.addSeries(series);
|
||||
}
|
||||
}
|
||||
if (cfg.getPlotLostMessages()) {
|
||||
double num = data.getLostMessages(minutes);
|
||||
if (num > 0) {
|
||||
XYSeries series = new XYSeries(lineName + " lost messages (" + num + " in " + minutes + "m)", false, false);
|
||||
series.add(start.getPingSent(), num);
|
||||
series.add(finish.getPingSent(), num);
|
||||
series.setDescription("number of messages lost in the last " + minutes + " minutes");
|
||||
col.addSeries(series);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XYSeries getSendSeries(List data) {
|
||||
XYSeries series = new XYSeries("sent", false, false);
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
PeerData.EventDataPoint point = (PeerData.EventDataPoint)data.get(i);
|
||||
if (point.getWasPonged()) {
|
||||
series.add(point.getPingSent(), point.getPongSent()-point.getPingSent());
|
||||
} else {
|
||||
// series.add(data.getPingSent(), 0);
|
||||
}
|
||||
}
|
||||
return series;
|
||||
}
|
||||
|
||||
XYSeries getReceiveSeries(List data) {
|
||||
XYSeries series = new XYSeries("receive", false, false);
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
PeerData.EventDataPoint point = (PeerData.EventDataPoint)data.get(i);
|
||||
if (point.getWasPonged()) {
|
||||
series.add(point.getPingSent(), point.getPongReceived()-point.getPongSent());
|
||||
} else {
|
||||
// series.add(data.getPingSent(), 0);
|
||||
}
|
||||
}
|
||||
return series;
|
||||
}
|
||||
XYSeries getLostSeries(List data) {
|
||||
XYSeries series = new XYSeries("lost", false, false);
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
PeerData.EventDataPoint point = (PeerData.EventDataPoint)data.get(i);
|
||||
if (point.getWasPonged()) {
|
||||
//series.add(point.getPingSent(), 0);
|
||||
} else {
|
||||
series.add(point.getPingSent(), 1);
|
||||
}
|
||||
}
|
||||
return series;
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JScrollPane;
|
||||
|
||||
import org.jfree.chart.ChartPanel;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Render the graph and legend
|
||||
*
|
||||
*/
|
||||
class JFreeChartHeartbeatPlotPane extends HeartbeatPlotPane {
|
||||
private final static Log _log = new Log(JFreeChartHeartbeatPlotPane.class); /* UNUSED */
|
||||
private ChartPanel _panel;
|
||||
private JFreeChartAdapter _adapter;
|
||||
|
||||
/**
|
||||
* Creates a JFreeChart plot pane for the given gui
|
||||
* @param gui the heartbeat monitor gui
|
||||
*/
|
||||
public JFreeChartHeartbeatPlotPane(HeartbeatMonitorGUI gui) {
|
||||
super(gui);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the state is updated
|
||||
*/
|
||||
public void stateUpdated() {
|
||||
if (_panel == null) {
|
||||
remove(0); // remove the dummy
|
||||
|
||||
_adapter = new JFreeChartAdapter();
|
||||
_panel = _adapter.createPanel(_gui.getMonitor().getState());
|
||||
_panel.setBackground(_gui.getBackground());
|
||||
add(new JScrollPane(_panel), BorderLayout.CENTER);
|
||||
_gui.pack();
|
||||
} else {
|
||||
_adapter.updateChart(_panel, _gui.getMonitor().getState());
|
||||
//_gui.pack();
|
||||
}
|
||||
}
|
||||
|
||||
protected void initializeComponents() {
|
||||
// noop
|
||||
setLayout(new BorderLayout());
|
||||
add(new JLabel(), BorderLayout.CENTER);
|
||||
//dummy.setBackground(_gui.getBackground());
|
||||
//dummy.setPreferredSize(new Dimension(800,600));
|
||||
//add(dummy);
|
||||
|
||||
//add(_panel);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,367 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.heartbeat.ClientConfig;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Configure how we want to render a particular clientConfig in the GUI
|
||||
*/
|
||||
class PeerPlotConfig {
|
||||
private final static Log _log = new Log(PeerPlotConfig.class); /* UNUSED */
|
||||
/** where can we find the current state/data (either as a filename or a URL)? */
|
||||
private String _location;
|
||||
/** what test are we defining the plot data for? */
|
||||
private ClientConfig _config;
|
||||
/** how should we render the current data set? */
|
||||
private PlotSeriesConfig _currentSeriesConfig;
|
||||
/** how should we render the various averages available? */
|
||||
private List _averageSeriesConfigs;
|
||||
private Set _listeners;
|
||||
private boolean _disabled;
|
||||
|
||||
/**
|
||||
* Delegating constructor . . .
|
||||
* @param location the name of the file/URL to get the data from
|
||||
*/
|
||||
public PeerPlotConfig(String location) {
|
||||
this(location, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a config =)
|
||||
* @param location the location of the file/URL to get the data from
|
||||
* @param config the client's configuration
|
||||
* @param currentSeriesConfig the series config
|
||||
* @param averageSeriesConfigs the average
|
||||
*/
|
||||
public PeerPlotConfig(String location, ClientConfig config, PlotSeriesConfig currentSeriesConfig, List averageSeriesConfigs) {
|
||||
_location = location;
|
||||
if (config == null)
|
||||
config = new ClientConfig(location);
|
||||
_config = config;
|
||||
if (currentSeriesConfig != null)
|
||||
_currentSeriesConfig = currentSeriesConfig;
|
||||
else
|
||||
_currentSeriesConfig = new PlotSeriesConfig(0);
|
||||
|
||||
if (averageSeriesConfigs != null) {
|
||||
_averageSeriesConfigs = averageSeriesConfigs;
|
||||
} else {
|
||||
rebuildAverageSeriesConfigs();
|
||||
}
|
||||
_listeners = Collections.synchronizedSet(new HashSet(2));
|
||||
_disabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 'Rebuilds' the average series stuff from the client configuration
|
||||
*/
|
||||
public void rebuildAverageSeriesConfigs() {
|
||||
int periods[] = _config.getAveragePeriods();
|
||||
if (periods == null) {
|
||||
_averageSeriesConfigs = Collections.synchronizedList(new ArrayList(0));
|
||||
} else {
|
||||
Arrays.sort(periods);
|
||||
_averageSeriesConfigs = Collections.synchronizedList(new ArrayList(periods.length));
|
||||
for (int i = 0; i < periods.length; i++) {
|
||||
_averageSeriesConfigs.add(new PlotSeriesConfig(periods[i]*60*1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an average period
|
||||
* @param minutes the number of minutes averaged over
|
||||
*/
|
||||
public void addAverage(int minutes) {
|
||||
_config.addAveragePeriod(minutes);
|
||||
|
||||
TreeMap ordered = new TreeMap();
|
||||
for (int i = 0; i < _averageSeriesConfigs.size(); i++) {
|
||||
PlotSeriesConfig cfg = (PlotSeriesConfig)_averageSeriesConfigs.get(i);
|
||||
ordered.put(new Long(cfg.getPeriod()), cfg);
|
||||
}
|
||||
Long period = new Long(minutes*60*1000);
|
||||
if (!ordered.containsKey(period))
|
||||
ordered.put(period, new PlotSeriesConfig(minutes*60*1000));
|
||||
|
||||
List cfgs = Collections.synchronizedList(new ArrayList(ordered.size()));
|
||||
for (Iterator iter = ordered.values().iterator(); iter.hasNext(); )
|
||||
cfgs.add(iter.next());
|
||||
|
||||
_averageSeriesConfigs = cfgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Where is the current state data supposed to be found? This must either be a
|
||||
* local file path or a URL
|
||||
* @return the current location
|
||||
*/
|
||||
public String getLocation() { return _location; }
|
||||
|
||||
/**
|
||||
* The location the current state data is supposed to be found. This must either be
|
||||
* a local file path or a URL
|
||||
* @param location the location
|
||||
*/
|
||||
public void setLocation(String location) {
|
||||
_location = location;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* What are we configuring?
|
||||
* @return the client configuration
|
||||
*/
|
||||
public ClientConfig getClientConfig() { return _config; }
|
||||
|
||||
/**
|
||||
* Sets what we are currently configuring
|
||||
* @param config the new config
|
||||
*/
|
||||
public void setClientConfig(ClientConfig config) {
|
||||
_config = config;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* How do we want to render the current data set?
|
||||
* @return the way we currently render the data
|
||||
*/
|
||||
public PlotSeriesConfig getCurrentSeriesConfig() { return _currentSeriesConfig; }
|
||||
|
||||
/**
|
||||
* Sets how we want to render the current data set.
|
||||
* @param config the new config
|
||||
*/
|
||||
public void setCurrentSeriesConfig(PlotSeriesConfig config) {
|
||||
_currentSeriesConfig = config;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* How do we want to render the averages?
|
||||
* @return the way we currently render the averages
|
||||
*/
|
||||
public List getAverageSeriesConfigs() { return _averageSeriesConfigs; }
|
||||
|
||||
/**
|
||||
* Sets how we want to render the averages
|
||||
* @param configs the new configs
|
||||
*/
|
||||
public void setAverageSeriesConfigs(List configs) { _averageSeriesConfigs = configs; }
|
||||
|
||||
/**
|
||||
* four char description of the peer
|
||||
* @return the name
|
||||
*/
|
||||
public String getPeerName() {
|
||||
Destination peer = getClientConfig().getPeer();
|
||||
if (peer == null)
|
||||
return "????";
|
||||
|
||||
return peer.calculateHash().toBase64().substring(0, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* title: name.packetsize.sendfrequency
|
||||
* @return the title
|
||||
*/
|
||||
public String getTitle() {
|
||||
return getPeerName() + '.' + getSize() + '.' + getClientConfig().getSendFrequency();
|
||||
}
|
||||
|
||||
/**
|
||||
* summary. includes:name, size, sendfrequency, and # of hops
|
||||
* @return the summary
|
||||
*/
|
||||
public String getSummary() {
|
||||
return "Send peer " + getPeerName() + ' ' + getSize() + " every " +
|
||||
getClientConfig().getSendFrequency() + " seconds through " +
|
||||
getClientConfig().getNumHops() + "-hop tunnels";
|
||||
}
|
||||
|
||||
private String getSize() {
|
||||
int bytes = getClientConfig().getSendSize();
|
||||
if (bytes < 1024)
|
||||
return bytes + "b";
|
||||
|
||||
return bytes/1024 + "kb";
|
||||
}
|
||||
|
||||
/**
|
||||
* we've got someone who wants to be notified of changes to the plot config
|
||||
* @param lsnr the listener to be added
|
||||
*/
|
||||
public void addListener(UpdateListener lsnr) { _listeners.add(lsnr); }
|
||||
|
||||
/**
|
||||
* remove a listener
|
||||
* @param lsnr the listener to remove
|
||||
*/
|
||||
public void removeListener(UpdateListener lsnr) { _listeners.remove(lsnr); }
|
||||
|
||||
void fireUpdate() {
|
||||
if (_disabled) return;
|
||||
for (Iterator iter = _listeners.iterator(); iter.hasNext(); ) {
|
||||
((UpdateListener)iter.next()).configUpdated(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables notification of events listeners
|
||||
* @see PeerPlotConfig#fireUpdate()
|
||||
*/
|
||||
public void disableEvents() { _disabled = true; }
|
||||
|
||||
/**
|
||||
* Enables notification of events listeners
|
||||
* @see PeerPlotConfig#fireUpdate()
|
||||
*/
|
||||
public void enableEvents() { _disabled = false; }
|
||||
|
||||
/**
|
||||
* How do we want to render a particular dataset (either the current or the averaged values)?
|
||||
*/
|
||||
public class PlotSeriesConfig {
|
||||
private long _period;
|
||||
private boolean _plotSendTime;
|
||||
private boolean _plotReceiveTime;
|
||||
private boolean _plotLostMessages;
|
||||
private Color _plotLineColor;
|
||||
|
||||
/**
|
||||
* Delegating constructor . . .
|
||||
* @param period the period for the config
|
||||
* (0 for current, otherwise # of milliseconds being averaged over)
|
||||
*/
|
||||
public PlotSeriesConfig(long period) {
|
||||
this(period, false, false, false, null);
|
||||
if (period <= 0) {
|
||||
_plotSendTime = true;
|
||||
_plotReceiveTime = true;
|
||||
_plotLostMessages = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a config for the rendering of a particular dataset)
|
||||
* @param period the period for the config
|
||||
* (0 for current, otherwise # of milliseconds being averaged over)
|
||||
* @param plotSend do we plot send times?
|
||||
* @param plotReceive do we plot receive times?
|
||||
* @param plotLost do we plot lost packets?
|
||||
* @param plotColor in what color?
|
||||
*/
|
||||
public PlotSeriesConfig(long period, boolean plotSend, boolean plotReceive, boolean plotLost, Color plotColor) {
|
||||
_period = period;
|
||||
_plotSendTime = plotSend;
|
||||
_plotReceiveTime = plotReceive;
|
||||
_plotLostMessages = plotLost;
|
||||
_plotLineColor = plotColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the plot config this plot series config is a part of
|
||||
* @return the plot config
|
||||
*/
|
||||
public PeerPlotConfig getPlotConfig() { return PeerPlotConfig.this; }
|
||||
|
||||
/**
|
||||
* What period is this series config describing?
|
||||
* @return 0 for current, otherwise # milliseconds that are being averaged over
|
||||
*/
|
||||
public long getPeriod() { return _period; }
|
||||
|
||||
/**
|
||||
* Sets the period this series config is describing
|
||||
* @param period the period
|
||||
* (0 for current, otherwise # milliseconds that are being averaged over)
|
||||
*/
|
||||
public void setPeriod(long period) {
|
||||
_period = period;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we render the time to send (ping to peer)?
|
||||
* @return true or false . . .
|
||||
*/
|
||||
public boolean getPlotSendTime() { return _plotSendTime; }
|
||||
|
||||
/**
|
||||
* Sets whether we render the time to send (ping to peer) or not
|
||||
* @param shouldPlot true or false
|
||||
*/
|
||||
public void setPlotSendTime(boolean shouldPlot) {
|
||||
_plotSendTime = shouldPlot;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we render the time to receive (peer pong to us)?
|
||||
* @return true or false . . .
|
||||
*/
|
||||
public boolean getPlotReceiveTime() { return _plotReceiveTime; }
|
||||
|
||||
/**
|
||||
* Sets whether we render the time to receive (peer pong to us)
|
||||
* @param shouldPlot true or false
|
||||
*/
|
||||
public void setPlotReceiveTime(boolean shouldPlot) {
|
||||
_plotReceiveTime = shouldPlot;
|
||||
fireUpdate();
|
||||
}
|
||||
/**
|
||||
* Should we render the number of messages lost (ping sent, no pong received in time)?
|
||||
* @return true or false . . .
|
||||
*/
|
||||
public boolean getPlotLostMessages() { return _plotLostMessages; }
|
||||
|
||||
/**
|
||||
* Sets whether we render the number of messages lost (ping sent, no pong received in time) or not
|
||||
* @param shouldPlot true or false
|
||||
*/
|
||||
public void setPlotLostMessages(boolean shouldPlot) {
|
||||
_plotLostMessages = shouldPlot;
|
||||
fireUpdate();
|
||||
}
|
||||
/**
|
||||
* What color should we plot the data with?
|
||||
* @return the color
|
||||
*/
|
||||
public Color getPlotLineColor() { return _plotLineColor; }
|
||||
|
||||
/**
|
||||
* Sets the color we should plot the data with
|
||||
* @param color the color to use
|
||||
*/
|
||||
public void setPlotLineColor(Color color) {
|
||||
_plotLineColor = color;
|
||||
fireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for listening to updates . . .
|
||||
*/
|
||||
public interface UpdateListener {
|
||||
/**
|
||||
* @param config the peer plot config that changes
|
||||
* @see PeerPlotConfig#fireUpdate()
|
||||
*/
|
||||
void configUpdated(PeerPlotConfig config);
|
||||
}
|
||||
}
|
||||
@@ -1,371 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JColorChooser;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
class PeerPlotConfigPane extends JPanel implements PeerPlotConfig.UpdateListener {
|
||||
private final static Log _log = new Log(PeerPlotConfigPane.class);
|
||||
private PeerPlotConfig _config;
|
||||
private HeartbeatControlPane _parent;
|
||||
private JLabel _title;
|
||||
private JButton _delete;
|
||||
private JLabel _fromLabel;
|
||||
private JTextField _from;
|
||||
private JTextArea _comments;
|
||||
private JLabel _peerLabel;
|
||||
private JTextField _peerKey;
|
||||
private JLabel _localLabel;
|
||||
private JTextField _localKey;
|
||||
private OptionLine _options[];
|
||||
private Random _rnd = new Random();
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
private Color _background = WHITE;
|
||||
|
||||
/**
|
||||
* Constructs a pane
|
||||
* @param config the plot config it represents
|
||||
* @param pane the pane this one is attached to
|
||||
*/
|
||||
public PeerPlotConfigPane(PeerPlotConfig config, HeartbeatControlPane pane) {
|
||||
_config = config;
|
||||
_parent = pane;
|
||||
if (_parent != null)
|
||||
_background = _parent.getBackground();
|
||||
_config.addListener(this);
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
/** called when the user wants to stop monitoring this test */
|
||||
private void delete() {
|
||||
_parent.removeTest(_config);
|
||||
}
|
||||
|
||||
private void initializeComponents() {
|
||||
buildComponents();
|
||||
placeComponents(this);
|
||||
refreshView();
|
||||
//setBorder(new BevelBorder(BevelBorder.RAISED));
|
||||
setBackground(_background);
|
||||
}
|
||||
|
||||
/**
|
||||
* place all the gui components onto the given panel
|
||||
* @param body the panel to place the components on
|
||||
*/
|
||||
private void placeComponents(JPanel body) {
|
||||
body.setLayout(new GridBagLayout());
|
||||
GridBagConstraints cts = new GridBagConstraints();
|
||||
|
||||
// row 0: title + delete
|
||||
cts.gridx = 0;
|
||||
cts.gridy = 0;
|
||||
cts.gridwidth = 5;
|
||||
cts.anchor = GridBagConstraints.WEST;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
body.add(_title, cts);
|
||||
cts.gridx = 5;
|
||||
cts.gridwidth = 1;
|
||||
cts.anchor = GridBagConstraints.NORTHWEST;
|
||||
cts.fill = GridBagConstraints.BOTH;
|
||||
body.add(_delete, cts);
|
||||
|
||||
// row 1: from + location
|
||||
cts.gridx = 0;
|
||||
cts.gridy = 1;
|
||||
cts.gridwidth = 1;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
body.add(_fromLabel, cts);
|
||||
cts.gridx = 1;
|
||||
cts.gridwidth = 5;
|
||||
cts.fill = GridBagConstraints.BOTH;
|
||||
body.add(_from, cts);
|
||||
|
||||
// row 2: comment
|
||||
cts.gridx = 0;
|
||||
cts.gridy = 2;
|
||||
cts.gridwidth = 6;
|
||||
cts.fill = GridBagConstraints.BOTH;
|
||||
body.add(_comments, cts);
|
||||
|
||||
// row 3: peer + peerKey
|
||||
cts.gridx = 0;
|
||||
cts.gridy = 3;
|
||||
cts.gridwidth = 1;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
body.add(_peerLabel, cts);
|
||||
cts.gridx = 1;
|
||||
cts.gridwidth = 5;
|
||||
cts.fill = GridBagConstraints.BOTH;
|
||||
body.add(_peerKey, cts);
|
||||
|
||||
// row 4: local + localKey
|
||||
cts.gridx = 0;
|
||||
cts.gridy = 4;
|
||||
cts.gridwidth = 1;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
body.add(_localLabel, cts);
|
||||
cts.gridx = 1;
|
||||
cts.gridwidth = 5;
|
||||
cts.fill = GridBagConstraints.BOTH;
|
||||
body.add(_localKey, cts);
|
||||
|
||||
// row 5-N: data row
|
||||
for (int i = 0; i < _options.length; i++) {
|
||||
cts.gridx = 0;
|
||||
cts.gridy = 5 + i;
|
||||
cts.gridwidth = 1;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
cts.anchor = GridBagConstraints.WEST;
|
||||
if (_options[i]._durationMinutes <= 0)
|
||||
body.add(new JLabel("Data: "), cts);
|
||||
else
|
||||
body.add(new JLabel(_options[i]._durationMinutes + "m avg: "), cts);
|
||||
|
||||
cts.gridx = 1;
|
||||
body.add(_options[i]._send, cts);
|
||||
cts.gridx = 2;
|
||||
body.add(_options[i]._recv, cts);
|
||||
cts.gridx = 3;
|
||||
body.add(_options[i]._lost, cts);
|
||||
cts.gridx = 4;
|
||||
body.add(_options[i]._all, cts);
|
||||
cts.gridx = 5;
|
||||
body.add(_options[i]._color, cts);
|
||||
}
|
||||
}
|
||||
|
||||
/** build all of the gui components */
|
||||
private void buildComponents() {
|
||||
_title = new JLabel(_config.getSummary());
|
||||
_title.setBackground(_background);
|
||||
_delete = new JButton("Delete");
|
||||
_delete.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { delete(); } });
|
||||
_delete.setEnabled(false);
|
||||
_delete.setBackground(_background);
|
||||
_fromLabel = new JLabel("Location: ");
|
||||
_fromLabel.setBackground(_background);
|
||||
_from = new JTextField(_config.getLocation());
|
||||
_from.setEditable(false);
|
||||
_from.setBackground(_background);
|
||||
_comments = new JTextArea(_config.getClientConfig().getComment(), 2, 20);
|
||||
// _comments = new JTextArea(_config.getClientConfig().getComment(), 2, 40);
|
||||
_comments.setEditable(false);
|
||||
_comments.setBackground(_background);
|
||||
_peerLabel = new JLabel("Peer: ");
|
||||
_peerLabel.setBackground(_background);
|
||||
_peerKey = new JTextField(_config.getClientConfig().getPeer().toBase64(), 8);
|
||||
_peerKey.setBackground(_background);
|
||||
_localLabel = new JLabel("Local: ");
|
||||
_localLabel.setBackground(_background);
|
||||
_localKey = new JTextField(_config.getClientConfig().getUs().toBase64(), 8);
|
||||
_localKey.setBackground(_background);
|
||||
|
||||
int averagedPeriods[] = _config.getClientConfig().getAveragePeriods();
|
||||
if (averagedPeriods == null)
|
||||
averagedPeriods = new int[0];
|
||||
|
||||
_options = new OptionLine[1 + averagedPeriods.length];
|
||||
_options[0] = new OptionLine(0);
|
||||
for (int i = 0; i < averagedPeriods.length; i++) {
|
||||
_options[1+i] = new OptionLine(averagedPeriods[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/** the settings have changed - revise */
|
||||
private void refreshView() {
|
||||
for (int i = 0; i < _options.length; i++) {
|
||||
PeerPlotConfig.PlotSeriesConfig cfg = getConfig(_options[i]._durationMinutes);
|
||||
if (cfg == null) {
|
||||
_log.warn("Config for minutes " + _options[i]._durationMinutes + " was not found?");
|
||||
continue;
|
||||
}
|
||||
//_log.debug("Refreshing view for minutes ["+ _options[i]._durationMinutes + "]: send [" +
|
||||
// _options[i]._send.isSelected() + "/" + cfg.getPlotSendTime() + "] recv [" +
|
||||
// _options[i]._recv.isSelected() + "/" + cfg.getPlotReceiveTime() + "] lost [" +
|
||||
// _options[i]._lost.isSelected() + "/" + cfg.getPlotLostMessages() + "]");
|
||||
_options[i]._send.setSelected(cfg.getPlotSendTime());
|
||||
_options[i]._recv.setSelected(cfg.getPlotReceiveTime());
|
||||
_options[i]._lost.setSelected(cfg.getPlotLostMessages());
|
||||
if (cfg.getPlotLineColor() != null)
|
||||
_options[i]._color.setBackground(cfg.getPlotLineColor());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* find the right config for the given period
|
||||
* @param minutes the minutes to locate the config by
|
||||
* @return the config for the given period, or null
|
||||
*/
|
||||
private PeerPlotConfig.PlotSeriesConfig getConfig(int minutes) {
|
||||
if (minutes <= 0)
|
||||
return _config.getCurrentSeriesConfig();
|
||||
|
||||
List configs = _config.getAverageSeriesConfigs();
|
||||
for (int i = 0; i < configs.size(); i++) {
|
||||
PeerPlotConfig.PlotSeriesConfig cfg = (PeerPlotConfig.PlotSeriesConfig)configs.get(i);
|
||||
if (cfg.getPeriod() == minutes * 60*1000)
|
||||
return cfg;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* notified that the config has been updated
|
||||
* @param config the config that was been updated
|
||||
*/
|
||||
public void configUpdated(PeerPlotConfig config) { refreshView(); }
|
||||
|
||||
private class ChooseColor implements ActionListener {
|
||||
private int _minutes;
|
||||
private JButton _button;
|
||||
|
||||
/**
|
||||
* @param minutes the minutes (line) to change the color of...
|
||||
* @param button the associated button
|
||||
*/
|
||||
public ChooseColor(int minutes, JButton button) {
|
||||
_minutes = minutes;
|
||||
_button = button;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
|
||||
*/
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
PeerPlotConfig.PlotSeriesConfig cfg = getConfig(_minutes);
|
||||
Color origColor = null;
|
||||
if (cfg != null)
|
||||
origColor = cfg.getPlotLineColor();
|
||||
Color color = JColorChooser.showDialog(PeerPlotConfigPane.this, "What color should this line be?", origColor);
|
||||
if (color != null) {
|
||||
if (cfg != null)
|
||||
cfg.setPlotLineColor(color);
|
||||
_button.setBackground(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class OptionLine {
|
||||
int _durationMinutes;
|
||||
JCheckBox _send;
|
||||
JCheckBox _recv;
|
||||
JCheckBox _lost;
|
||||
JCheckBox _all;
|
||||
JButton _color;
|
||||
|
||||
/**
|
||||
* Creates an OptionLine.
|
||||
* @param durationMinutes the minutes =)
|
||||
*/
|
||||
public OptionLine(int durationMinutes) {
|
||||
_durationMinutes = durationMinutes;
|
||||
_send = new JCheckBox("send time");
|
||||
_send.setBackground(_background);
|
||||
_recv = new JCheckBox("receive time");
|
||||
_recv.setBackground(_background);
|
||||
_lost = new JCheckBox("lost messages");
|
||||
_lost.setBackground(_background);
|
||||
_all = new JCheckBox("all");
|
||||
_all.setBackground(_background);
|
||||
_color = new JButton("color");
|
||||
int r = _rnd.nextInt(255);
|
||||
if (r < 0) r = -r;
|
||||
int g = _rnd.nextInt(255);
|
||||
if (g < 0) g = -g;
|
||||
int b = _rnd.nextInt(255);
|
||||
if (b < 0) b = -b;
|
||||
//_color.setBackground(new Color(r, g, b));
|
||||
_color.setBackground(_background);
|
||||
|
||||
_send.addActionListener(new UpdateListener(OptionLine.this, _durationMinutes));
|
||||
_recv.addActionListener(new UpdateListener(OptionLine.this, _durationMinutes));
|
||||
_lost.addActionListener(new UpdateListener(OptionLine.this, _durationMinutes));
|
||||
_all.addActionListener(new UpdateListener(OptionLine.this, _durationMinutes));
|
||||
_color.addActionListener(new ChooseColor(durationMinutes, _color));
|
||||
_color.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateListener implements ActionListener {
|
||||
private OptionLine _line;
|
||||
private int _minutes;
|
||||
|
||||
/**
|
||||
* Update Listener constructor . . .
|
||||
* @param line the line
|
||||
* @param minutes the minutes
|
||||
*/
|
||||
public UpdateListener(OptionLine line, int minutes) {
|
||||
_line = line;
|
||||
_minutes = minutes;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
|
||||
*/
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
PeerPlotConfig.PlotSeriesConfig cfg = getConfig(_minutes);
|
||||
|
||||
cfg.getPlotConfig().disableEvents();
|
||||
_log.debug("Updating data for minutes ["+ _line._durationMinutes + "]: send [" +
|
||||
_line._send.isSelected() + "/" + cfg.getPlotSendTime() + "] recv [" +
|
||||
_line._recv.isSelected() + "/" + cfg.getPlotReceiveTime() + "] lost [" +
|
||||
_line._lost.isSelected() + "/" + cfg.getPlotLostMessages() + "]: config = " + cfg);
|
||||
|
||||
boolean force = _line._all.isSelected();
|
||||
cfg.setPlotSendTime(_line._send.isSelected() || force);
|
||||
cfg.setPlotReceiveTime(_line._recv.isSelected() || force);
|
||||
cfg.setPlotLostMessages(_line._lost.isSelected() || force);
|
||||
cfg.getPlotConfig().enableEvents();
|
||||
cfg.getPlotConfig().fireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unit test stuff
|
||||
* @param args da arsg
|
||||
*/
|
||||
public final static void main(String args[]) {
|
||||
Test t = new Test();
|
||||
t.runTest();
|
||||
}
|
||||
|
||||
private final static class Test implements PeerPlotStateFetcher.FetchStateReceptor {
|
||||
/**
|
||||
* Runs da test
|
||||
*/
|
||||
public void runTest() {
|
||||
PeerPlotConfig cfg = new PeerPlotConfig("C:\\testnet\\r2\\heartbeatStat_10s_30kb.txt");
|
||||
PeerPlotState state = new PeerPlotState(cfg);
|
||||
PeerPlotStateFetcher.fetchPeerPlotState(this, state);
|
||||
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.heartbeat.gui.PeerPlotStateFetcher.FetchStateReceptor#peerPlotStateFetched(net.i2p.heartbeat.gui.PeerPlotState)
|
||||
*/
|
||||
public void peerPlotStateFetched(PeerPlotState state) {
|
||||
javax.swing.JFrame f = new javax.swing.JFrame("Test");
|
||||
f.getContentPane().add(new JScrollPane(new PeerPlotConfigPane(state.getPlotConfig(), null)));
|
||||
f.pack();
|
||||
f.setVisible(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
|
||||
/**
|
||||
* Current data + plot config for a particular test
|
||||
*
|
||||
*/
|
||||
class PeerPlotState {
|
||||
private StaticPeerData _currentData;
|
||||
private PeerPlotConfig _plotConfig;
|
||||
|
||||
/**
|
||||
* Delegating constructor . . .
|
||||
* @see PeerPlotState#PeerPlotState(PeerPlotConfig, StaticPeerData)
|
||||
*/
|
||||
public PeerPlotState() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegating constructor . . .
|
||||
* @param config plot config
|
||||
* @see PeerPlotState#PeerPlotState(PeerPlotConfig, StaticPeerData)
|
||||
*/
|
||||
public PeerPlotState(PeerPlotConfig config) {
|
||||
this(config, new StaticPeerData(config.getClientConfig()));
|
||||
}
|
||||
/**
|
||||
* Creates a PeerPlotState
|
||||
* @param config plot config
|
||||
* @param data peer data
|
||||
*/
|
||||
public PeerPlotState(PeerPlotConfig config, StaticPeerData data) {
|
||||
_plotConfig = config;
|
||||
_currentData = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an average
|
||||
* @param minutes mins averaged over
|
||||
* @param sendMs how much later did the peer receieve
|
||||
* @param recvMs how much later did we receieve
|
||||
* @param lost how many were lost
|
||||
*/
|
||||
public void addAverage(int minutes, int sendMs, int recvMs, int lost) {
|
||||
// make sure we've got the config entry for the average
|
||||
_plotConfig.addAverage(minutes);
|
||||
// add the data point...
|
||||
_currentData.addAverage(minutes, sendMs, recvMs, lost);
|
||||
}
|
||||
|
||||
/**
|
||||
* we successfully got a ping/pong through
|
||||
*
|
||||
* @param sendTime when did the ping get sent?
|
||||
* @param sendMs how much later did the peer receive the ping?
|
||||
* @param recvMs how much later than that did we receive the pong?
|
||||
*/
|
||||
public void addSuccess(long sendTime, int sendMs, int recvMs) {
|
||||
_currentData.addData(sendTime, sendMs, recvMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* we lost a ping/pong
|
||||
*
|
||||
* @param sendTime when did we send the ping?
|
||||
*/
|
||||
public void addLost(long sendTime) {
|
||||
_currentData.addData(sendTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* data set to render
|
||||
* @return the data set
|
||||
*/
|
||||
public StaticPeerData getCurrentData() { return _currentData; }
|
||||
|
||||
/**
|
||||
* Sets the data set to render
|
||||
* @param data the data set
|
||||
*/
|
||||
public void setCurrentData(StaticPeerData data) { _currentData = data; }
|
||||
|
||||
/**
|
||||
* configuration options on how to render the data set
|
||||
* @return the config options
|
||||
*/
|
||||
public PeerPlotConfig getPlotConfig() { return _plotConfig; }
|
||||
|
||||
/**
|
||||
* Sets the configuration options on how to render the data
|
||||
* @param config the config options
|
||||
*/
|
||||
public void setPlotConfig(PeerPlotConfig config) { _plotConfig = config; }
|
||||
}
|
||||
@@ -1,363 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Locale;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
class PeerPlotStateFetcher {
|
||||
private final static Log _log = new Log(PeerPlotStateFetcher.class);
|
||||
|
||||
/**
|
||||
* Fetch and fill the specified state structure
|
||||
* @param receptor the 'receptor' (callbacks)
|
||||
* @param state the state
|
||||
*/
|
||||
public static void fetchPeerPlotState(FetchStateReceptor receptor, PeerPlotState state) {
|
||||
I2PThread t = new I2PThread(new Fetcher(receptor, state));
|
||||
t.setDaemon(true);
|
||||
t.setName("Fetch state from " + state.getPlotConfig().getLocation());
|
||||
t.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback stuff . . .
|
||||
*/
|
||||
public interface FetchStateReceptor {
|
||||
/**
|
||||
* Called when a peer plot state is fetched
|
||||
* @param state state that was fetched
|
||||
*/
|
||||
void peerPlotStateFetched(PeerPlotState state);
|
||||
}
|
||||
|
||||
private static class Fetcher implements Runnable {
|
||||
private PeerPlotState _state;
|
||||
private FetchStateReceptor _receptor;
|
||||
|
||||
/**
|
||||
* Creates a Fetcher thread
|
||||
* @param receptor the 'receptor' (callbacks)
|
||||
* @param state the state
|
||||
*/
|
||||
public Fetcher(FetchStateReceptor receptor, PeerPlotState state) {
|
||||
_state = state;
|
||||
_receptor = receptor;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Runnable#run()
|
||||
*/
|
||||
public void run() {
|
||||
String loc = _state.getPlotConfig().getLocation();
|
||||
_log.debug("Load called [" + loc + "]");
|
||||
InputStream in = null;
|
||||
try {
|
||||
try {
|
||||
URL location = new URL(loc);
|
||||
in = location.openStream();
|
||||
} catch (MalformedURLException mue) {
|
||||
_log.debug("Not a url [" + loc + "]");
|
||||
in = null;
|
||||
}
|
||||
|
||||
if (in == null)
|
||||
in = new FileInputStream(loc);
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
String line = null;
|
||||
while ( (line = reader.readLine()) != null) {
|
||||
handleLine(line);
|
||||
}
|
||||
|
||||
if (valid())
|
||||
_receptor.peerPlotStateFetched(_state);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error retrieving from the location [" + loc + "]", ioe);
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check to make sure we've got everything we need
|
||||
* @return true [always]
|
||||
*/
|
||||
boolean valid() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* handle a line from the data set - these can be formatted in one of the
|
||||
* following ways. <p />
|
||||
*
|
||||
* <pre>
|
||||
* peer khWYqCETu9YtPUvGV92ocsbEW5DezhKlIG7ci8RLX3g=
|
||||
* local u-9hlR1ik2hemXf0HvKMfeRgrS86CbNQh25e7XBhaQE=
|
||||
* peerDest [base 64 of the full destination]
|
||||
* localDest [base 64 of the full destination]
|
||||
* numTunnelHops 2
|
||||
* comment Test with localhost sending 30KB every 20 seconds
|
||||
* sendFrequency 20
|
||||
* sendSize 30720
|
||||
* sessionStart 20040409.22:51:10.915
|
||||
* currentTime 20040409.23:31:39.607
|
||||
* numPending 2
|
||||
* lifetimeSent 118
|
||||
* lifetimeRecv 113
|
||||
* #averages minutes sendMs recvMs numLost
|
||||
* periodAverage 1 1843 771 0
|
||||
* periodAverage 5 786 752 1
|
||||
* periodAverage 30 855 735 3
|
||||
* #action status date and time sent sendMs replyMs
|
||||
* EVENT OK 20040409.23:21:44.742 691 670
|
||||
* EVENT OK 20040409.23:22:05.201 671 581
|
||||
* EVENT OK 20040409.23:22:26.301 1182 1452
|
||||
* EVENT OK 20040409.23:22:47.322 24304 1723
|
||||
* EVENT OK 20040409.23:23:08.232 2293 1081
|
||||
* EVENT OK 20040409.23:23:29.332 1392 641
|
||||
* EVENT OK 20040409.23:23:50.262 641 761
|
||||
* EVENT OK 20040409.23:24:11.102 651 701
|
||||
* EVENT OK 20040409.23:24:31.401 841 621
|
||||
* EVENT OK 20040409.23:24:52.061 651 681
|
||||
* EVENT OK 20040409.23:25:12.480 701 1623
|
||||
* EVENT OK 20040409.23:25:32.990 1442 1212
|
||||
* EVENT OK 20040409.23:25:54.230 591 631
|
||||
* EVENT OK 20040409.23:26:14.620 620 691
|
||||
* EVENT OK 20040409.23:26:35.199 1793 1432
|
||||
* EVENT OK 20040409.23:26:56.570 661 641
|
||||
* EVENT OK 20040409.23:27:17.200 641 660
|
||||
* EVENT OK 20040409.23:27:38.120 611 921
|
||||
* EVENT OK 20040409.23:27:58.699 831 621
|
||||
* EVENT OK 20040409.23:28:19.559 801 661
|
||||
* EVENT OK 20040409.23:28:40.279 601 611
|
||||
* EVENT OK 20040409.23:29:00.648 601 621
|
||||
* EVENT OK 20040409.23:29:21.288 701 661
|
||||
* EVENT LOST 20040409.23:29:41.828
|
||||
* EVENT LOST 20040409.23:30:02.327
|
||||
* EVENT LOST 20040409.23:30:22.656
|
||||
* EVENT OK 20040409.23:31:24.305 1843 771
|
||||
* </pre>
|
||||
*
|
||||
* @param line (see above)
|
||||
*/
|
||||
private void handleLine(String line) {
|
||||
if (line.startsWith("peerDest"))
|
||||
handlePeerDest(line);
|
||||
else if (line.startsWith("localDest"))
|
||||
handleLocalDest(line);
|
||||
else if (line.startsWith("numTunnelHops"))
|
||||
handleNumTunnelHops(line);
|
||||
else if (line.startsWith("comment"))
|
||||
handleComment(line);
|
||||
else if (line.startsWith("sendFrequency"))
|
||||
handleSendFrequency(line);
|
||||
else if (line.startsWith("sendSize"))
|
||||
handleSendSize(line);
|
||||
else if (line.startsWith("periodAverage"))
|
||||
handlePeriodAverage(line);
|
||||
else if (line.startsWith("EVENT"))
|
||||
handleEvent(line);
|
||||
else if (line.startsWith("numPending"))
|
||||
handleNumPending(line);
|
||||
else if (line.startsWith("sessionStart"))
|
||||
handleSessionStart(line);
|
||||
else
|
||||
_log.debug("Not handled: " + line);
|
||||
}
|
||||
|
||||
private void handlePeerDest(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String destKey = tok.nextToken();
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(destKey);
|
||||
_state.getPlotConfig().getClientConfig().setPeer(d);
|
||||
_log.debug("Setting the peer to " + d.calculateHash().toBase64());
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Unable to parse the peerDest line: [" + line + "]", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLocalDest(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String destKey = tok.nextToken();
|
||||
try {
|
||||
Destination d = new Destination();
|
||||
d.fromBase64(destKey);
|
||||
_state.getPlotConfig().getClientConfig().setUs(d);
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Unable to parse the localDest line: [" + line + "]", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleComment(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
StringBuffer buf = new StringBuffer(line.length()-32);
|
||||
while (tok.hasMoreTokens())
|
||||
buf.append(tok.nextToken()).append(' ');
|
||||
_state.getPlotConfig().getClientConfig().setComment(buf.toString());
|
||||
}
|
||||
|
||||
private void handleNumTunnelHops(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String num = tok.nextToken();
|
||||
try {
|
||||
int val = Integer.parseInt(num);
|
||||
_state.getPlotConfig().getClientConfig().setNumHops(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the numTunnelHops line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleNumPending(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String num = tok.nextToken();
|
||||
try {
|
||||
int val = Integer.parseInt(num);
|
||||
_state.getCurrentData().setPendingCount(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the numPending line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSendFrequency(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String num = tok.nextToken();
|
||||
try {
|
||||
int val = Integer.parseInt(num);
|
||||
_state.getPlotConfig().getClientConfig().setSendFrequency(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the sendFrequency line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSendSize(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String num = tok.nextToken();
|
||||
try {
|
||||
int val = Integer.parseInt(num);
|
||||
_state.getPlotConfig().getClientConfig().setSendSize(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the sendSize line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSessionStart(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
String date = tok.nextToken();
|
||||
try {
|
||||
long when = getDate(date);
|
||||
_state.getCurrentData().setSessionStart(when);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the sessionStart line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePeriodAverage(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
tok.nextToken(); // ignore;
|
||||
try {
|
||||
// periodAverage minutes sendMs recvMs numLost
|
||||
int min = Integer.parseInt(tok.nextToken());
|
||||
int send = Integer.parseInt(tok.nextToken());
|
||||
int recv = Integer.parseInt(tok.nextToken());
|
||||
int lost = Integer.parseInt(tok.nextToken());
|
||||
_state.addAverage(min, send, recv, lost);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the sendSize line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEvent(String line) {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
|
||||
// * EVENT OK 20040409.23:29:21.288 701 661
|
||||
// * EVENT LOST 20040409.23:29:41.828
|
||||
tok.nextToken(); // ignore first two
|
||||
tok.nextToken();
|
||||
try {
|
||||
long when = getDate(tok.nextToken());
|
||||
if (when < 0) {
|
||||
_log.error("Invalid EVENT line: [" + line + "]");
|
||||
return;
|
||||
}
|
||||
if (tok.hasMoreTokens()) {
|
||||
int sendMs = Integer.parseInt(tok.nextToken());
|
||||
int recvMs = Integer.parseInt(tok.nextToken());
|
||||
_state.addSuccess(when, sendMs, recvMs);
|
||||
} else {
|
||||
_state.addLost(when);
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Unable to parse the EVENT line: [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
|
||||
private static final SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd.HH:mm:ss.SSS", Locale.UK);
|
||||
private long getDate(String date) {
|
||||
synchronized (_fmt) {
|
||||
try {
|
||||
return _fmt.parse(date).getTime();
|
||||
} catch (ParseException pe) {
|
||||
_log.error("Unable to parse the date [" + date + "]", pe);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fakeRun() { /* UNUSED */
|
||||
try {
|
||||
Destination peer = new Destination();
|
||||
Destination us = new Destination();
|
||||
peer.fromBase64("3RPLOkQGlq8anNyNWhjbMyHxpAvUyUJKbiUejI80DnPR59T3blc7-XrBhQ2iPbf-BRAR~v1j34Kpba1eDyhPk2gevsE6ULO1irarJ3~C9WcQH2wAbNiVwfWqbh6onQ~YmkSpGNwGHD6ytwbvTyXeBJ" +
|
||||
"cS8e6gmfNN-sYLn1aQu8UqWB3D6BmTfLtyS3eqWVk66Nrzmwy8E1Hvq5z~1lukYb~cyiDO1oZHAOLyUQtd9eN16yJY~2SRG8LiscpPMl9nSJUr6fmXMUubW-M7QGFH82Om-735PJUk6WMy1Hi9Vgh4Pxhdl7g" +
|
||||
"fqGRWioFABdhcypb7p1Ca77p73uabLDFK-SjIYmdj7TwSdbNa6PCmzEvCEW~IZeZmnZC5B6pK30AdmD9vc641wUGce9xTJVfNRupf5L7pSsVIISix6FkKQk-FTW2RsZKLbuMCYMaPzLEx5gzODEqtI6Jf2teM" +
|
||||
"d5xCz51RPayDJl~lJ-W0IWYfosnjM~KxYaqc4agviBuF5ZWeAAAA");
|
||||
us.fromBase64("W~JFpqSH8uopylox2V5hMbpcHSsb-dJkSKvdJ1vj~KQcUFJWXFyfbetBAukcGH5S559aK9oslU0qbVoMDlJITVC4OXfXSnVbJBP1IhsK8SvjSYicjmIi2fA~k4HvSh9Wxu~bg8yo~jgfHA8tjYpp" +
|
||||
"K9QKc56BpkJb~hx0nNGy4Ny9eW~6A5AwAmHvwdt5NqcREYRMjRd63dMGm8BcEe-6FbOyMo3dnIFcETWAe8TCeoMxm~S1n~6Jlinw3ETxv-L6lQkhFFWnC5zyzQ~4JhVxxT3taTMYXg8td4CBGmrS078jcjW63" +
|
||||
"rlSiQgZBlYfN3iEYmurhuIEV9NXRcmnMrBOQUAoXPpVuRIxJbaQNDL71FO2iv424n4YjKs84suAho34GGQKq7WoL5V5KQgihfcl0f~xne-qP3FtpoPFeyA9x-sA2JWDAsxoZlfvgkiP5eyOn23prT9TJK47HC" +
|
||||
"VilHSV11uTVaC4Jc5YsjoBCZadWbgQnMCKlZ4jk-bLE1PSWLg7AAAA");
|
||||
_state.getPlotConfig().getClientConfig().setPeer(peer);
|
||||
_state.getPlotConfig().getClientConfig().setUs(us);
|
||||
_state.getPlotConfig().getClientConfig().setNumHops(2);
|
||||
_state.getPlotConfig().getClientConfig().setComment("we do stuff\nreally nifty stuff. really");
|
||||
_state.getPlotConfig().getClientConfig().setAveragePeriods(new int[] { 1, 5, 30, 60 });
|
||||
int rnd = new java.util.Random().nextInt();
|
||||
if (rnd > 0)
|
||||
rnd = rnd % 10;
|
||||
else
|
||||
rnd = (-rnd) % 10;
|
||||
_state.getPlotConfig().getClientConfig().setSendFrequency(rnd);
|
||||
_state.getPlotConfig().getClientConfig().setSendSize(16*1024);
|
||||
_state.getPlotConfig().getClientConfig().setStatDuration(10);
|
||||
_state.getPlotConfig().rebuildAverageSeriesConfigs();
|
||||
_state.setCurrentData(new StaticPeerData(_state.getPlotConfig().getClientConfig()));
|
||||
|
||||
_receptor.peerPlotStateFetched(_state);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
package net.i2p.heartbeat.gui;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.heartbeat.ClientConfig;
|
||||
import net.i2p.heartbeat.PeerData;
|
||||
|
||||
/**
|
||||
* Raw data points for a test
|
||||
*/
|
||||
class StaticPeerData extends PeerData {
|
||||
private int _pending;
|
||||
/** Integer (period, in minutes) to Integer (milliseconds) for sending a ping */
|
||||
private Map _averageSendTimes;
|
||||
/** Integer (period, in minutes) to Integer (milliseconds) for receiving a pong */
|
||||
private Map _averageReceiveTimes;
|
||||
/** Integer (period, in minutes) to Integer (num messages) of how many messages were lost on average */
|
||||
private Map _lostMessages;
|
||||
|
||||
/**
|
||||
* Creates a static peer data with a specified client config ... duh
|
||||
* @param config the client config
|
||||
*/
|
||||
public StaticPeerData(ClientConfig config) {
|
||||
super(config);
|
||||
_averageSendTimes = new HashMap(4);
|
||||
_averageReceiveTimes = new HashMap(4);
|
||||
_lostMessages = new HashMap(4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds averaged data
|
||||
* @param minutes the minutes (averaged over)
|
||||
* @param sendMs the send time (ping) in milliseconds
|
||||
* @param recvMs the receive time (pong) in milliseconds
|
||||
* @param lost the number lost
|
||||
*/
|
||||
public void addAverage(int minutes, int sendMs, int recvMs, int lost) {
|
||||
_averageSendTimes.put(new Integer(minutes), new Integer(sendMs));
|
||||
_averageReceiveTimes.put(new Integer(minutes), new Integer(recvMs));
|
||||
_lostMessages.put(new Integer(minutes), new Integer(lost));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number pending
|
||||
* @param numPending the number pending
|
||||
*/
|
||||
public void setPendingCount(int numPending) { _pending = numPending; }
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.heartbeat.PeerData#setSessionStart(long)
|
||||
*/
|
||||
public void setSessionStart(long when) { super.setSessionStart(when); }
|
||||
|
||||
/**
|
||||
* Adds data
|
||||
* @param sendTime the time it was sent
|
||||
* @param sendMs the send time (ping) in milliseconds
|
||||
* @param recvMs the receive time (pong) in milliseconds
|
||||
*/
|
||||
public void addData(long sendTime, int sendMs, int recvMs) {
|
||||
PeerData.EventDataPoint dataPoint = new PeerData.EventDataPoint(sendTime);
|
||||
dataPoint.setPongSent(sendTime + sendMs);
|
||||
dataPoint.setPongReceived(sendTime + sendMs + recvMs);
|
||||
dataPoint.setWasPonged(true);
|
||||
addDataPoint(dataPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds data
|
||||
* @param sendTime the time it was sent
|
||||
*/
|
||||
public void addData(long sendTime) {
|
||||
PeerData.EventDataPoint dataPoint = new PeerData.EventDataPoint(sendTime);
|
||||
dataPoint.setWasPonged(false);
|
||||
addDataPoint(dataPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* how many pings are still outstanding?
|
||||
* @return the number of pings outstanding
|
||||
*/
|
||||
public int getPendingCount() { return _pending; }
|
||||
|
||||
|
||||
/**
|
||||
* average time to send over the given period.
|
||||
*
|
||||
* @param period number of minutes to retrieve the average for
|
||||
* @return milliseconds average, or -1 if we dont track that period
|
||||
*/
|
||||
public double getAverageSendTime(int period) {
|
||||
Integer i = (Integer)_averageSendTimes.get(new Integer(period));
|
||||
if (i == null)
|
||||
return -1;
|
||||
|
||||
return i.doubleValue();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* average time to receive over the given period.
|
||||
*
|
||||
* @param period number of minutes to retrieve the average for
|
||||
* @return milliseconds average, or -1 if we dont track that period
|
||||
*/
|
||||
public double getAverageReceiveTime(int period) {
|
||||
Integer i = (Integer)_averageReceiveTimes.get(new Integer(period));
|
||||
if (i == null)
|
||||
return -1;
|
||||
|
||||
return i.doubleValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* number of lost messages over the given period.
|
||||
*
|
||||
* @param period number of minutes to retrieve the average for
|
||||
* @return number of lost messages in the period, or -1 if we dont track that period
|
||||
*/
|
||||
public double getLostMessages(int period) {
|
||||
Integer i = (Integer)_lostMessages.get(new Integer(period));
|
||||
if (i == null)
|
||||
return -1;
|
||||
|
||||
return i.doubleValue();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.heartbeat.PeerData#cleanup()
|
||||
*/
|
||||
public void cleanup() {}
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
@@ -1,11 +0,0 @@
|
||||
$Id$
|
||||
|
||||
the i2p/apps/httptunnel module is the root of the
|
||||
HTTPTunnel application, and everything within it
|
||||
is released according to the terms of the I2P
|
||||
license policy. That means everything contained
|
||||
within the i2p/apps/httptunnel module is released
|
||||
under the GPL plus the java exception unless
|
||||
otherwise marked. Alternate licenses that may be
|
||||
used include BSD, Cryptix, and MIT, as well as
|
||||
code granted into the public domain.
|
||||
@@ -1,47 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="httptunnel">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../ministreaming/java/" target="build" />
|
||||
<!-- ministreaming will build core -->
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac
|
||||
srcdir="./src"
|
||||
debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="./build/obj"
|
||||
classpath="../../../core/java/build/i2p.jar:../../ministreaming/java/build/mstreaming.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="compile">
|
||||
<jar destfile="./build/httptunnel.jar" basedir="./build/obj" includes="**/*.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.httptunnel.HTTPTunnel" />
|
||||
<attribute name="Class-Path" value="i2p.jar mstreaming.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="./src:../../../core/java/src:../../ministreaming/java/src" destdir="./build/javadoc"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
splitindex="true"
|
||||
windowtitle="HTTPTunnel" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<!-- ministreaming will clean core -->
|
||||
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<!-- ministreaming will clean core -->
|
||||
<ant dir="../../ministreaming/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
||||
@@ -1,87 +0,0 @@
|
||||
package net.i2p.httptunnel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Listens on a port for HTTP connections.
|
||||
*/
|
||||
public class HTTPListener extends Thread {
|
||||
|
||||
private static final Log _log = new Log(HTTPListener.class);
|
||||
|
||||
private int port;
|
||||
private String listenHost;
|
||||
private SocketManagerProducer smp;
|
||||
|
||||
/**
|
||||
* A public constructor. It contstructs things. In this case,
|
||||
* it constructs a nice HTTPListener, for all your listening on
|
||||
* HTTP needs. Yep. That's right.
|
||||
* @param smp A SocketManagerProducer, producing Sockets, no doubt
|
||||
* @param port A port, to connect to.
|
||||
* @param listenHost A host, to connect to.
|
||||
*/
|
||||
|
||||
public HTTPListener(SocketManagerProducer smp, int port, String listenHost) {
|
||||
this.smp = smp;
|
||||
this.port = port;
|
||||
start();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Thread#run()
|
||||
*/
|
||||
public void run() {
|
||||
try {
|
||||
InetAddress lh = listenHost == null ? null : InetAddress.getByName(listenHost);
|
||||
ServerSocket ss = new ServerSocket(port, 0, lh);
|
||||
while (true) {
|
||||
Socket s = ss.accept();
|
||||
new HTTPSocketHandler(this, s);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while accepting connections", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean proxyUsed = false;
|
||||
|
||||
/**
|
||||
* Query whether this is the first use of the proxy or not
|
||||
* @return Whether this is the first proxy use, no doubt.
|
||||
*/
|
||||
public boolean firstProxyUse() {
|
||||
if (true) return false; // FIXME: check a config option here
|
||||
|
||||
if (proxyUsed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
proxyUsed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The SocketManagerProducer being used.
|
||||
*/
|
||||
public SocketManagerProducer getSMP() {
|
||||
return smp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs with HTTP 1.1 flair that a feature isn't implemented.
|
||||
* @param out The stream the text goes to.
|
||||
* @deprecated
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handleNotImplemented(OutputStream out) throws IOException {
|
||||
out.write(("HTTP/1.1 200 Document following\n\n" + "<h1>Feature not implemented</h1>").getBytes("ISO-8859-1"));
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package net.i2p.httptunnel;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import net.i2p.httptunnel.handler.RootHandler;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Handles a single HTTP socket connection.
|
||||
*/
|
||||
public class HTTPSocketHandler extends Thread {
|
||||
|
||||
private static final Log _log = new Log(HTTPSocketHandler.class);
|
||||
|
||||
private Socket s;
|
||||
private HTTPListener httpl;
|
||||
private RootHandler h;
|
||||
|
||||
/**
|
||||
* A public constructor.
|
||||
* @param httpl An HTTPListener, to listen for HTTP, no doubt
|
||||
* @param s A socket.
|
||||
*/
|
||||
public HTTPSocketHandler(HTTPListener httpl, Socket s) {
|
||||
this.httpl = httpl;
|
||||
this.s = s;
|
||||
h = RootHandler.getInstance();
|
||||
start();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Thread#run()
|
||||
*/
|
||||
public void run() {
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
in = new BufferedInputStream(s.getInputStream());
|
||||
out = new BufferedOutputStream(s.getOutputStream());
|
||||
Request req = new Request(in);
|
||||
h.handle(req, httpl, out);
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while handling data", ex);
|
||||
} finally {
|
||||
try {
|
||||
if (in != null) in.close();
|
||||
if (out != null) {
|
||||
out.flush();
|
||||
out.close();
|
||||
}
|
||||
s.close();
|
||||
} catch (IOException ex) {
|
||||
_log.error("IOException in finalizer", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/*
|
||||
* HTTPTunnel
|
||||
* (c) 2003 - 2004 mihi
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* In addition, as a special exception, mihi gives permission to link
|
||||
* the code of this program with the proprietary Java implementation
|
||||
* provided by Sun (or other vendors as well), and distribute linked
|
||||
* combinations including the two. You must obey the GNU General
|
||||
* Public License in all respects for all of the code used other than
|
||||
* the proprietary Java implementation. If you modify this file, you
|
||||
* may extend this exception to your version of the file, but you are
|
||||
* not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
package net.i2p.httptunnel;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
|
||||
/**
|
||||
* HTTPTunnel main class.
|
||||
*/
|
||||
public class HTTPTunnel {
|
||||
|
||||
/**
|
||||
* Create a HTTPTunnel instance.
|
||||
*
|
||||
* @param initialManagers a list of socket managers to use
|
||||
* @param maxManagers how many managers to have in the cache
|
||||
* @param shouldThrowAwayManagers whether to throw away a manager after use
|
||||
* @param listenPort which port to listen on
|
||||
*/
|
||||
public HTTPTunnel(I2PSocketManager[] initialManagers, int maxManagers, boolean shouldThrowAwayManagers,
|
||||
int listenPort) {
|
||||
this(initialManagers, maxManagers, shouldThrowAwayManagers, listenPort, "127.0.0.1", 7654);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a HTTPTunnel instance.
|
||||
*
|
||||
* @param initialManagers a list of socket managers to use
|
||||
* @param maxManagers how many managers to have in the cache
|
||||
* @param shouldThrowAwayManagers whether to throw away a manager after use
|
||||
* @param listenPort which port to listen on
|
||||
* @param i2cpAddress the I2CP address
|
||||
* @param i2cpPort the I2CP port
|
||||
*/
|
||||
public HTTPTunnel(I2PSocketManager[] initialManagers, int maxManagers, boolean shouldThrowAwayManagers,
|
||||
int listenPort, String i2cpAddress, int i2cpPort) {
|
||||
SocketManagerProducer smp = new SocketManagerProducer(initialManagers, maxManagers, shouldThrowAwayManagers,
|
||||
i2cpAddress, i2cpPort);
|
||||
new HTTPListener(smp, listenPort, "127.0.0.1");
|
||||
}
|
||||
|
||||
/**
|
||||
* The all important main function, allowing HTTPTunnel to be
|
||||
* stand-alone, a program in it's own right, and all that jazz.
|
||||
* @param args A list of String passed to the program
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
String host = "127.0.0.1";
|
||||
int port = 7654, max = 1;
|
||||
boolean throwAwayManagers = false;
|
||||
if (args.length > 1) {
|
||||
if (args.length == 4) {
|
||||
host = args[2];
|
||||
port = Integer.parseInt(args[3]);
|
||||
} else if (args.length != 2) {
|
||||
showInfo();
|
||||
return;
|
||||
}
|
||||
max = Integer.parseInt(args[1]);
|
||||
} else if (args.length != 1) {
|
||||
showInfo();
|
||||
return;
|
||||
}
|
||||
if (max == 0) {
|
||||
max = 1;
|
||||
} else if (max < 0) {
|
||||
max = -max;
|
||||
throwAwayManagers = true;
|
||||
}
|
||||
new HTTPTunnel(null, max, throwAwayManagers, Integer.parseInt(args[0]), host, port);
|
||||
}
|
||||
|
||||
private static void showInfo() {
|
||||
System.out.println("Usage: java HTTPTunnel <listenPort> [<max> " + "[<i2cphost> <i2cpport>]]\n"
|
||||
+ " <listenPort> port to listen for browsers\n"
|
||||
+ " <max> max number of SocketMangers in pool, " + "use neg. number\n"
|
||||
+ " to use each SocketManager only once " + "(default: 1)\n"
|
||||
+ " <i2cphost> host to connect to the router " + "(default: 127.0.0.1)\n"
|
||||
+ " <i2cpport> port to connect to the router " + "(default: 7654)");
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
package net.i2p.httptunnel;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.StringReader;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* A HTTP request (GET or POST). This will be passed to a hander for
|
||||
* handling it.
|
||||
*/
|
||||
public class Request {
|
||||
|
||||
private static final Log _log = new Log(Request.class);
|
||||
|
||||
// all strings are forced to be ISO-8859-1 encoding
|
||||
private String method;
|
||||
private String url;
|
||||
private String proto;
|
||||
private String params;
|
||||
private String postData;
|
||||
|
||||
/**
|
||||
* A constructor, creating a request from an InputStream
|
||||
* @param in InputStream from which we "read-in" a Request
|
||||
* @throws IOException
|
||||
*/
|
||||
public Request(InputStream in) throws IOException {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(in, "ISO-8859-1"));
|
||||
String line = br.readLine();
|
||||
if (line == null) { // no data at all
|
||||
method = null;
|
||||
_log.error("Connection but no data");
|
||||
return;
|
||||
}
|
||||
int pos = line.indexOf(" ");
|
||||
if (pos == -1) {
|
||||
method = line;
|
||||
url = "";
|
||||
_log.error("Malformed HTTP request: " + line);
|
||||
} else {
|
||||
method = line.substring(0, pos);
|
||||
url = line.substring(pos + 1);
|
||||
}
|
||||
proto = "";
|
||||
pos = url.indexOf(" ");
|
||||
if (pos != -1) {
|
||||
proto = url.substring(pos); // leading space intended
|
||||
url = url.substring(0, pos);
|
||||
}
|
||||
StringBuffer sb = new StringBuffer(512);
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (line.length() == 0) break;
|
||||
sb.append(line).append("\r\n");
|
||||
}
|
||||
params = sb.toString(); // no leading empty line!
|
||||
sb = new StringBuffer();
|
||||
// hack for POST requests, ripped from HttpClient
|
||||
// this won't work for large POSTDATA
|
||||
// FIXME: do this better, please.
|
||||
if (!method.equals("GET")) {
|
||||
while (br.ready()) { // empty the buffer (POST requests)
|
||||
int i = br.read();
|
||||
if (i != -1) {
|
||||
sb.append((char) i);
|
||||
}
|
||||
}
|
||||
postData = sb.toString();
|
||||
} else {
|
||||
postData = "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A Request as an array of bytes of a String in ISO-8859-1 format
|
||||
* @throws IOException
|
||||
*/
|
||||
public byte[] toByteArray() throws IOException {
|
||||
if (method == null) return null;
|
||||
return toISO8859_1String().getBytes("ISO-8859-1");
|
||||
|
||||
}
|
||||
|
||||
private String toISO8859_1String() throws IOException {
|
||||
if (method == null) return null;
|
||||
return method + " " + url + proto + "\r\n" + params + "\r\n" + postData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the URL of the request
|
||||
*/
|
||||
public String getURL() {
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL of the Request
|
||||
* @param newURL the new URL
|
||||
*/
|
||||
public void setURL(String newURL) {
|
||||
url = newURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of a param.
|
||||
* @param name The name of the param
|
||||
* @return The value of the param, or null
|
||||
*/
|
||||
public String getParam(String name) {
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(new StringReader(params));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (line.startsWith(name)) { return line.substring(name.length()); }
|
||||
}
|
||||
return null;
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error getting parameter", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a param.
|
||||
* @param name the name of the param
|
||||
* @param value the value to be set
|
||||
*/
|
||||
public void setParam(String name, String value) {
|
||||
try {
|
||||
StringBuffer sb = new StringBuffer(params.length() + value.length());
|
||||
BufferedReader br = new BufferedReader(new StringReader(params));
|
||||
String line;
|
||||
boolean replaced = false;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (line.startsWith(name)) {
|
||||
replaced = true;
|
||||
if (value == null) continue; // kill param
|
||||
line = name + value;
|
||||
}
|
||||
sb.append(line).append("\r\n");
|
||||
}
|
||||
if (!replaced && value != null) {
|
||||
sb.append(name).append(value).append("\r\n");
|
||||
}
|
||||
params = sb.toString();
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error getting parameter", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
package net.i2p.httptunnel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
|
||||
/**
|
||||
* Produces SocketManagers in a thread and gives them to those who
|
||||
* need them.
|
||||
*/
|
||||
public class SocketManagerProducer extends Thread {
|
||||
|
||||
private ArrayList myManagers = new ArrayList();
|
||||
private HashMap usedManagers = new HashMap();
|
||||
private int port;
|
||||
private String host;
|
||||
private int maxManagers;
|
||||
private boolean shouldThrowAwayManagers;
|
||||
|
||||
/**
|
||||
* Public constructor creating a SocketManagerProducer
|
||||
* @param initialManagers a list of socket managers to use
|
||||
* @param maxManagers how many managers to have in the cache
|
||||
* @param shouldThrowAwayManagers whether to throw away a manager after use
|
||||
* @param host which host to listen on
|
||||
* @param port which port to listen on
|
||||
*/
|
||||
public SocketManagerProducer(I2PSocketManager[] initialManagers, int maxManagers, boolean shouldThrowAwayManagers,
|
||||
String host, int port) {
|
||||
if (maxManagers < 1) { throw new IllegalArgumentException("maxManagers < 1"); }
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.shouldThrowAwayManagers = shouldThrowAwayManagers;
|
||||
if (initialManagers != null) {
|
||||
myManagers.addAll(Arrays.asList(initialManagers));
|
||||
}
|
||||
this.maxManagers = maxManagers;
|
||||
this.shouldThrowAwayManagers = shouldThrowAwayManagers;
|
||||
setDaemon(true);
|
||||
start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread producing new SocketManagers.
|
||||
*/
|
||||
public void run() {
|
||||
while (true) {
|
||||
synchronized (this) {
|
||||
// without mcDonalds mode, we most probably need no
|
||||
// new managers.
|
||||
while (!shouldThrowAwayManagers && myManagers.size() == maxManagers) {
|
||||
myWait();
|
||||
}
|
||||
}
|
||||
// produce a new manager, regardless whether it is needed
|
||||
// or not. Do not synchronized this part, since it can be
|
||||
// quite time-consuming.
|
||||
I2PSocketManager newManager = I2PSocketManagerFactory.createManager(host, port, new Properties());
|
||||
// when done, check if it is needed.
|
||||
synchronized (this) {
|
||||
while (myManagers.size() == maxManagers) {
|
||||
myWait();
|
||||
}
|
||||
myManagers.add(newManager);
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a manager for connecting to a given destination. Each
|
||||
* destination will always get the same manager.
|
||||
*
|
||||
* @param dest the destination to connect to
|
||||
* @return the SocketManager to use
|
||||
*/
|
||||
public synchronized I2PSocketManager getManager(String dest) {
|
||||
I2PSocketManager result = (I2PSocketManager) usedManagers.get(dest);
|
||||
if (result == null) {
|
||||
result = getManager();
|
||||
usedManagers.put(dest, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a "new" SocketManager. Depending on the anonymity settings,
|
||||
* this can be a completely new one or one randomly selected from
|
||||
* a pool.
|
||||
*
|
||||
* @return the SocketManager to use
|
||||
*/
|
||||
public synchronized I2PSocketManager getManager() {
|
||||
while (myManagers.size() == 0) {
|
||||
myWait(); // no manager here, so wait until one is produced
|
||||
}
|
||||
int which = (int) (Math.random() * myManagers.size());
|
||||
I2PSocketManager result = (I2PSocketManager) myManagers.get(which);
|
||||
if (shouldThrowAwayManagers) {
|
||||
myManagers.remove(which);
|
||||
notifyAll();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until InterruptedException
|
||||
*/
|
||||
public void myWait() {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package net.i2p.httptunnel.filter;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Chain multiple filters. Decorator pattern...
|
||||
*/
|
||||
public class ChainFilter implements Filter {
|
||||
|
||||
private static final Log _log = new Log(ChainFilter.class);
|
||||
|
||||
private Collection filters; // perhaps protected?
|
||||
|
||||
/**
|
||||
* @param filters A collection (list) of filters to chain to
|
||||
*/
|
||||
public ChainFilter(Collection filters) {
|
||||
this.filters = filters;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.httptunnel.filter.Filter#filter(byte[])
|
||||
*/
|
||||
public byte[] filter(byte[] toFilter) {
|
||||
byte[] buf = toFilter;
|
||||
for (Iterator it = filters.iterator(); it.hasNext();) {
|
||||
Filter f = (Filter) it.next();
|
||||
buf = f.filter(buf);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.httptunnel.filter.Filter#finish()
|
||||
*/
|
||||
public byte[] finish() {
|
||||
// this is a bit complicated. Think about it...
|
||||
try {
|
||||
byte[] buf = EMPTY;
|
||||
for (Iterator it = filters.iterator(); it.hasNext();) {
|
||||
Filter f = (Filter) it.next();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
if (buf.length != 0) {
|
||||
baos.write(f.filter(buf));
|
||||
}
|
||||
baos.write(f.finish());
|
||||
buf = baos.toByteArray();
|
||||
}
|
||||
return buf;
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error chaining filters", ex);
|
||||
return EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package net.i2p.httptunnel.filter;
|
||||
|
||||
/**
|
||||
* A generic filtering interface.
|
||||
*/
|
||||
public interface Filter {
|
||||
|
||||
/**
|
||||
* An empty byte array.
|
||||
*/
|
||||
public static final byte[] EMPTY = new byte[0];
|
||||
|
||||
/**
|
||||
* Filter some data. Not all filtered data need to be returned.
|
||||
* @param toFilter the bytes that are to be filtered.
|
||||
* @return the filtered data
|
||||
*/
|
||||
public byte[] filter(byte[] toFilter);
|
||||
|
||||
/**
|
||||
* Data stream has finished. Return all of the rest data.
|
||||
* @return the rest of the data
|
||||
*/
|
||||
public byte[] finish();
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package net.i2p.httptunnel.filter;
|
||||
|
||||
/**
|
||||
* A filter letting everything pass as is.
|
||||
*/
|
||||
public class NullFilter implements Filter {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.httptunnel.filter.Filter#filter(byte[])
|
||||
*/
|
||||
public byte[] filter(byte[] toFilter) {
|
||||
return toFilter;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see net.i2p.httptunnel.filter.Filter#finish()
|
||||
*/
|
||||
public byte[] finish() {
|
||||
return EMPTY;
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
package net.i2p.httptunnel.handler;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.SocketException;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.httptunnel.HTTPListener;
|
||||
import net.i2p.httptunnel.Request;
|
||||
import net.i2p.httptunnel.SocketManagerProducer;
|
||||
import net.i2p.httptunnel.filter.Filter;
|
||||
import net.i2p.httptunnel.filter.NullFilter;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Handler for browsing Eepsites.
|
||||
*/
|
||||
public class EepHandler {
|
||||
|
||||
private static final Log _log = new Log(EepHandler.class);
|
||||
private static I2PAppContext _context = new I2PAppContext();
|
||||
|
||||
protected ErrorHandler errorHandler;
|
||||
|
||||
/* package private */EepHandler(ErrorHandler eh) {
|
||||
errorHandler = eh;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param req the Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @param destination destination as a string, (subject to naming
|
||||
* service lookup)
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out,
|
||||
/* boolean fromProxy, */String destination) throws IOException {
|
||||
SocketManagerProducer smp = httpl.getSMP();
|
||||
Destination dest = _context.namingService().lookup(destination);
|
||||
if (dest == null) {
|
||||
errorHandler.handle(req, httpl, out, "Could not lookup host: " + destination);
|
||||
return;
|
||||
}
|
||||
I2PSocketManager sm = smp.getManager(destination);
|
||||
Filter f = new NullFilter(); //FIXME: use other filter
|
||||
req.setParam("Host: ", dest.toBase64());
|
||||
if (!handle(req, f, out, dest, sm)) {
|
||||
errorHandler.handle(req, httpl, out, "Unable to reach peer");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param req the Request to send out
|
||||
* @param f a Filter to apply to the bytes retrieved from the Destination
|
||||
* @param out where to write the results
|
||||
* @param dest the Destination of the Request
|
||||
* @param sm an I2PSocketManager, to get a socket for the Destination
|
||||
* @return boolean, true if something was written, false otherwise.
|
||||
* @throws IOException
|
||||
*/
|
||||
public boolean handle(Request req, Filter f, OutputStream out, Destination dest,
|
||||
I2PSocketManager sm) throws IOException {
|
||||
I2PSocket s = null;
|
||||
boolean written = false;
|
||||
try {
|
||||
synchronized (sm) {
|
||||
s = sm.connect(dest, new I2PSocketOptions());
|
||||
}
|
||||
InputStream in = new BufferedInputStream(s.getInputStream());
|
||||
OutputStream sout = new BufferedOutputStream(s.getOutputStream());
|
||||
sout.write(req.toByteArray());
|
||||
sout.flush();
|
||||
byte[] buffer = new byte[16384], filtered;
|
||||
int len;
|
||||
while ((len = in.read(buffer)) != -1) {
|
||||
if (len != buffer.length) {
|
||||
byte[] b2 = new byte[len];
|
||||
System.arraycopy(buffer, 0, b2, 0, len);
|
||||
filtered = f.filter(b2);
|
||||
} else {
|
||||
filtered = f.filter(buffer);
|
||||
}
|
||||
written = true;
|
||||
out.write(filtered);
|
||||
}
|
||||
filtered = f.finish();
|
||||
written = true;
|
||||
out.write(filtered);
|
||||
out.flush();
|
||||
} catch (SocketException ex) {
|
||||
_log.error("Error while handling eepsite request");
|
||||
return written;
|
||||
} catch (IOException ex) {
|
||||
_log.error("Error while handling eepsite request");
|
||||
return written;
|
||||
} catch (I2PException ex) {
|
||||
_log.error("Error while handling eepsite request");
|
||||
return written;
|
||||
} finally {
|
||||
if (s != null) s.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package net.i2p.httptunnel.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.httptunnel.HTTPListener;
|
||||
import net.i2p.httptunnel.Request;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Handler for general error messages.
|
||||
*/
|
||||
public class ErrorHandler {
|
||||
|
||||
private static final Log _log = new Log(ErrorHandler.class); /* UNUSED */
|
||||
|
||||
/* package private */ErrorHandler() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param req the Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @param error the error that happened
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out, String error) throws IOException {
|
||||
// FIXME: Make nicer messages for more likely errors.
|
||||
out
|
||||
.write(("HTTP/1.1 500 Internal Server Error\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n\r\n")
|
||||
.getBytes("ISO-8859-1"));
|
||||
out
|
||||
.write(("<html><head><title>" + error + "</title></head><body><h1>" + error
|
||||
+ "</h1>An internal error occurred while " + "handling a request by HTTPTunnel:<br><b>" + error + "</b><h2>Complete request:</h2><b>---</b><br><i><pre>\r\n")
|
||||
.getBytes("ISO-8859-1"));
|
||||
out.write(req.toByteArray());
|
||||
out.write(("</pre></i><br><b>---</b></body></html>").getBytes("ISO-8859-1"));
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package net.i2p.httptunnel.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.httptunnel.HTTPListener;
|
||||
import net.i2p.httptunnel.Request;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Handler for requests that do not require any connection to anyone
|
||||
* (except errors).
|
||||
*/
|
||||
public class LocalHandler {
|
||||
|
||||
private static final Log _log = new Log(LocalHandler.class); /* UNUSED */
|
||||
|
||||
/* package private */LocalHandler() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param req the Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out
|
||||
/*, boolean fromProxy */) throws IOException {
|
||||
//FIXME: separate multiple pages, not only a start page
|
||||
//FIXME: provide some info on this page
|
||||
out
|
||||
.write(("HTTP/1.1 200 Document following\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n\r\n"
|
||||
+ "<html><head><title>Welcome to I2P HTTPTunnel</title>"
|
||||
+ "</head><body><h1>Welcome to I2P HTTPTunnel</h1>You can "
|
||||
+ "browse Eepsites by adding an eepsite name to the request." + "</body></html>")
|
||||
.getBytes("ISO-8859-1"));
|
||||
out.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently always throws an IO Exception
|
||||
* @param req the Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handleProxyConfWarning(Request req, HTTPListener httpl, OutputStream out) throws IOException {
|
||||
//FIXME
|
||||
throw new IOException("jrandom ate the deprecated method. mooo");
|
||||
//httpl.handleNotImplemented(out);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently always throws an IO Exception
|
||||
* @param req the Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handleHTTPWarning(Request req, HTTPListener httpl, OutputStream out /*, boolean fromProxy */)
|
||||
throws IOException {
|
||||
// FIXME
|
||||
throw new IOException("jrandom ate the deprecated method. mooo");
|
||||
//httpl.handleNotImplemented(out);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package net.i2p.httptunnel.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.httptunnel.HTTPListener;
|
||||
import net.i2p.httptunnel.Request;
|
||||
import net.i2p.httptunnel.SocketManagerProducer;
|
||||
import net.i2p.httptunnel.filter.Filter;
|
||||
import net.i2p.httptunnel.filter.NullFilter;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Handler for proxying "normal" HTTP requests.
|
||||
*/
|
||||
public class ProxyHandler extends EepHandler {
|
||||
|
||||
private static final Log _log = new Log(ErrorHandler.class); /* UNUSED */
|
||||
private static I2PAppContext _context = new I2PAppContext();
|
||||
|
||||
/* package private */ProxyHandler(ErrorHandler eh) {
|
||||
super(eh);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param req a Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out
|
||||
/*, boolean fromProxy */) throws IOException {
|
||||
SocketManagerProducer smp = httpl.getSMP();
|
||||
Destination dest = findProxy();
|
||||
if (dest == null) {
|
||||
errorHandler.handle(req, httpl, out, "Could not find proxy");
|
||||
return;
|
||||
}
|
||||
// one manager for all proxy requests
|
||||
I2PSocketManager sm = smp.getManager("--proxy--");
|
||||
Filter f = new NullFilter(); //FIXME: use other filter
|
||||
if (!handle(req, f, out, dest, sm)) {
|
||||
errorHandler.handle(req, httpl, out, "Unable to reach peer");
|
||||
}
|
||||
}
|
||||
|
||||
private Destination findProxy() {
|
||||
//FIXME!
|
||||
return _context.namingService().lookup("squid.i2p");
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package net.i2p.httptunnel.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import net.i2p.httptunnel.HTTPListener;
|
||||
import net.i2p.httptunnel.Request;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Main handler for all requests. Dispatches requests to other handlers.
|
||||
*/
|
||||
public class RootHandler {
|
||||
|
||||
private static final Log _log = new Log(RootHandler.class); /* UNUSED */
|
||||
|
||||
private RootHandler() {
|
||||
errorHandler = new ErrorHandler();
|
||||
localHandler = new LocalHandler();
|
||||
proxyHandler = new ProxyHandler(errorHandler);
|
||||
eepHandler = new EepHandler(errorHandler);
|
||||
}
|
||||
|
||||
private ErrorHandler errorHandler;
|
||||
private ProxyHandler proxyHandler;
|
||||
private LocalHandler localHandler;
|
||||
private EepHandler eepHandler;
|
||||
|
||||
private static RootHandler instance;
|
||||
|
||||
/**
|
||||
* Singleton stuff
|
||||
* @return the one and only instance, yay!
|
||||
*/
|
||||
public static synchronized RootHandler getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new RootHandler();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* The _ROOT_ handler: it passes its workload off to the other handlers.
|
||||
* @param req a Request
|
||||
* @param httpl an HTTPListener
|
||||
* @param out where to write the results
|
||||
* @throws IOException
|
||||
*/
|
||||
public void handle(Request req, HTTPListener httpl, OutputStream out) throws IOException {
|
||||
String url = req.getURL();
|
||||
System.out.println(url);
|
||||
/* boolean byProxy = false; */
|
||||
int pos;
|
||||
if (url.startsWith("http://")) { // access via proxy
|
||||
/* byProxy=true; */
|
||||
if (httpl.firstProxyUse()) {
|
||||
localHandler.handleProxyConfWarning(req, httpl, out);
|
||||
return;
|
||||
}
|
||||
url = url.substring(7);
|
||||
pos = url.indexOf("/");
|
||||
String host;
|
||||
|
||||
if (pos == -1) {
|
||||
errorHandler.handle(req, httpl, out, "No host end in URL");
|
||||
return;
|
||||
}
|
||||
|
||||
host = url.substring(0, pos);
|
||||
url = url.substring(pos);
|
||||
if ("i2p".equals(host) || "i2p.i2p".equals(host)) {
|
||||
// normal request; go on below...
|
||||
} else if (host.endsWith(".i2p")) {
|
||||
// "old" service request, send a redirect...
|
||||
out.write(("HTTP/1.1 302 Moved\r\nLocation: " + "http://i2p.i2p/" + host + url + "\r\n\r\n").getBytes("ISO-8859-1"));
|
||||
return;
|
||||
} else {
|
||||
// this is for proxying to the real web
|
||||
proxyHandler.handle(req, httpl, out /*, true */);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (url.equals("/")) { // main page
|
||||
url = "/_/local/index";
|
||||
} else if (!url.startsWith("/")) {
|
||||
errorHandler.handle(req, httpl, out, "No leading slash in URL: " + url);
|
||||
return;
|
||||
}
|
||||
String dest;
|
||||
url = url.substring(1);
|
||||
pos = url.indexOf("/");
|
||||
if (pos == -1) {
|
||||
dest = url;
|
||||
url = "/";
|
||||
} else {
|
||||
dest = url.substring(0, pos);
|
||||
url = url.substring(pos);
|
||||
}
|
||||
req.setURL(url);
|
||||
if (dest.equals("_")) { // no eepsite
|
||||
if (url.startsWith("/local/")) { // local request
|
||||
req.setURL(url.substring(6));
|
||||
localHandler.handle(req, httpl, out /*, byProxy */);
|
||||
} else if (url.startsWith("/http/")) { // http warning
|
||||
localHandler.handleHTTPWarning(req, httpl, out /*, byProxy */);
|
||||
} else if (url.startsWith("/proxy/")) { // http proxying
|
||||
req.setURL("http://" + url.substring(7));
|
||||
proxyHandler.handle(req, httpl, out /*, byProxy */);
|
||||
} else {
|
||||
errorHandler.handle(req, httpl, out, "No local handler for this URL: " + url);
|
||||
}
|
||||
} else {
|
||||
eepHandler.handle(req, httpl, out, /* byProxy, */dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ public class BitField
|
||||
|
||||
private final byte[] bitfield;
|
||||
private final int size;
|
||||
private int count;
|
||||
|
||||
/**
|
||||
* Creates a new BitField that represents <code>size</code> unset bits.
|
||||
@@ -41,6 +42,7 @@ public class BitField
|
||||
this.size = size;
|
||||
int arraysize = ((size-1)/8)+1;
|
||||
bitfield = new byte[arraysize];
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,6 +62,11 @@ public class BitField
|
||||
// XXX - More correct would be to check that unused bits are
|
||||
// cleared or clear them explicitly ourselves.
|
||||
System.arraycopy(bitfield, 0, this.bitfield, 0, arraysize);
|
||||
|
||||
this.count = 0;
|
||||
for (int i = 0; i < size; i++)
|
||||
if (get(i))
|
||||
this.count++;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,7 +102,10 @@ public class BitField
|
||||
throw new IndexOutOfBoundsException(Integer.toString(bit));
|
||||
int index = bit/8;
|
||||
int mask = 128 >> (bit % 8);
|
||||
bitfield[index] |= mask;
|
||||
if ((bitfield[index] & mask) == 0) {
|
||||
count++;
|
||||
bitfield[index] |= mask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,10 +124,27 @@ public class BitField
|
||||
return (bitfield[index] & mask) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of set bits.
|
||||
*/
|
||||
public int count()
|
||||
{
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if all bits are set.
|
||||
*/
|
||||
public boolean complete()
|
||||
{
|
||||
return count >= size;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
// Not very efficient
|
||||
StringBuffer sb = new StringBuffer("BitField[");
|
||||
StringBuffer sb = new StringBuffer("BitField(");
|
||||
sb.append(size).append(")[");
|
||||
for (int i = 0; i < size; i++)
|
||||
if (get(i))
|
||||
{
|
||||
@@ -128,4 +155,5 @@ public class BitField
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -113,10 +113,10 @@ public class ConnectionAcceptor implements Runnable
|
||||
// ok, already updated
|
||||
socketChanged = false;
|
||||
}
|
||||
if (serverSocket == null) {
|
||||
Snark.debug("Server socket went away.. boo hiss", Snark.ERROR);
|
||||
stop = true;
|
||||
return;
|
||||
while ( (serverSocket == null) && (!stop)) {
|
||||
serverSocket = I2PSnarkUtil.instance().getServerSocket();
|
||||
if (serverSocket == null)
|
||||
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
try
|
||||
{
|
||||
@@ -125,7 +125,11 @@ public class ConnectionAcceptor implements Runnable
|
||||
if (socketChanged) {
|
||||
continue;
|
||||
} else {
|
||||
Snark.debug("Null socket accepted, but socket wasn't changed?", Snark.ERROR);
|
||||
I2PServerSocket ss = I2PSnarkUtil.instance().getServerSocket();
|
||||
if (ss != serverSocket) {
|
||||
serverSocket = ss;
|
||||
socketChanged = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Thread t = new I2PThread(new Handler(socket), "Connection-" + socket);
|
||||
@@ -148,7 +152,8 @@ public class ConnectionAcceptor implements Runnable
|
||||
|
||||
try
|
||||
{
|
||||
serverSocket.close();
|
||||
if (serverSocket != null)
|
||||
serverSocket.close();
|
||||
}
|
||||
catch (I2PException ignored) { }
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
@@ -31,6 +32,8 @@ public class I2PSnarkUtil {
|
||||
private Map _opts;
|
||||
private I2PSocketManager _manager;
|
||||
private boolean _configured;
|
||||
private Set _shitlist;
|
||||
private int _maxUploaders;
|
||||
|
||||
private I2PSnarkUtil() {
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
@@ -38,7 +41,9 @@ public class I2PSnarkUtil {
|
||||
_opts = new HashMap();
|
||||
setProxy("127.0.0.1", 4444);
|
||||
setI2CPConfig("127.0.0.1", 7654, null);
|
||||
_shitlist = new HashSet(64);
|
||||
_configured = false;
|
||||
_maxUploaders = Snark.MAX_TOTAL_UPLOADERS;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,12 +74,18 @@ public class I2PSnarkUtil {
|
||||
_configured = true;
|
||||
}
|
||||
|
||||
public void setMaxUploaders(int limit) {
|
||||
_maxUploaders = limit;
|
||||
_configured = true;
|
||||
}
|
||||
|
||||
public String getI2CPHost() { return _i2cpHost; }
|
||||
public int getI2CPPort() { return _i2cpPort; }
|
||||
public Map getI2CPOptions() { return _opts; }
|
||||
public String getEepProxyHost() { return _proxyHost; }
|
||||
public int getEepProxyPort() { return _proxyPort; }
|
||||
public boolean getEepProxySet() { return _shouldProxy; }
|
||||
public int getMaxUploaders() { return _maxUploaders; }
|
||||
|
||||
/**
|
||||
* Connect to the router, if we aren't already
|
||||
@@ -90,14 +101,20 @@ public class I2PSnarkUtil {
|
||||
}
|
||||
if (opts.getProperty("inbound.nickname") == null)
|
||||
opts.setProperty("inbound.nickname", "I2PSnark");
|
||||
if (opts.getProperty("outbound.nickname") == null)
|
||||
opts.setProperty("outbound.nickname", "I2PSnark");
|
||||
if (opts.getProperty("i2p.streaming.inactivityTimeout") == null)
|
||||
opts.setProperty("i2p.streaming.inactivityTimeout", "90000");
|
||||
opts.setProperty("i2p.streaming.inactivityTimeout", "240000");
|
||||
if (opts.getProperty("i2p.streaming.inactivityAction") == null)
|
||||
opts.setProperty("i2p.streaming.inactivityAction", "1");
|
||||
if (opts.getProperty("i2p.streaming.writeTimeout") == null)
|
||||
opts.setProperty("i2p.streaming.writeTimeout", "90000");
|
||||
if (opts.getProperty("i2p.streaming.readTimeout") == null)
|
||||
opts.setProperty("i2p.streaming.readTimeout", "90000");
|
||||
opts.setProperty("i2p.streaming.inactivityAction", "1"); // 1 == disconnect, 2 == ping
|
||||
if (opts.getProperty("i2p.streaming.initialWindowSize") == null)
|
||||
opts.setProperty("i2p.streaming.initialWindowSize", "1");
|
||||
if (opts.getProperty("i2p.streaming.slowStartGrowthRateFactor") == null)
|
||||
opts.setProperty("i2p.streaming.slowStartGrowthRateFactor", "1");
|
||||
//if (opts.getProperty("i2p.streaming.writeTimeout") == null)
|
||||
// opts.setProperty("i2p.streaming.writeTimeout", "90000");
|
||||
//if (opts.getProperty("i2p.streaming.readTimeout") == null)
|
||||
// opts.setProperty("i2p.streaming.readTimeout", "120000");
|
||||
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts);
|
||||
}
|
||||
return (_manager != null);
|
||||
@@ -110,18 +127,36 @@ public class I2PSnarkUtil {
|
||||
public void disconnect() {
|
||||
I2PSocketManager mgr = _manager;
|
||||
_manager = null;
|
||||
_shitlist.clear();
|
||||
mgr.destroySocketManager();
|
||||
}
|
||||
|
||||
/** connect to the given destination */
|
||||
I2PSocket connect(PeerID peer) throws IOException {
|
||||
Hash dest = peer.getAddress().calculateHash();
|
||||
synchronized (_shitlist) {
|
||||
if (_shitlist.contains(dest))
|
||||
throw new IOException("Not trying to contact " + dest.toBase64() + ", as they are shitlisted");
|
||||
}
|
||||
try {
|
||||
return _manager.connect(peer.getAddress());
|
||||
I2PSocket rv = _manager.connect(peer.getAddress());
|
||||
if (rv != null) synchronized (_shitlist) { _shitlist.remove(dest); }
|
||||
return rv;
|
||||
} catch (I2PException ie) {
|
||||
synchronized (_shitlist) {
|
||||
_shitlist.add(dest);
|
||||
}
|
||||
SimpleTimer.getInstance().addEvent(new Unshitlist(dest), 10*60*1000);
|
||||
throw new IOException("Unable to reach the peer " + peer + ": " + ie.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private class Unshitlist implements SimpleTimer.TimedEvent {
|
||||
private Hash _dest;
|
||||
public Unshitlist(Hash dest) { _dest = dest; }
|
||||
public void timeReached() { synchronized (_shitlist) { _shitlist.remove(_dest); } }
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch the given URL, returning the file it is stored in, or null on error
|
||||
*/
|
||||
@@ -130,7 +165,7 @@ public class I2PSnarkUtil {
|
||||
_log.debug("Fetching [" + url + "] proxy=" + _proxyHost + ":" + _proxyPort + ": " + _shouldProxy);
|
||||
File out = null;
|
||||
try {
|
||||
out = File.createTempFile("i2psnark", "url");
|
||||
out = File.createTempFile("i2psnark", "url", new File("."));
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
out.delete();
|
||||
@@ -152,7 +187,11 @@ public class I2PSnarkUtil {
|
||||
}
|
||||
|
||||
public I2PServerSocket getServerSocket() {
|
||||
return _manager.getServerSocket();
|
||||
I2PSocketManager mgr = _manager;
|
||||
if (mgr != null)
|
||||
return mgr.getServerSocket();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
String getOurIPString() {
|
||||
|
||||
@@ -54,6 +54,10 @@ public class Peer implements Comparable
|
||||
private boolean deregister = true;
|
||||
private static long __id;
|
||||
private long _id;
|
||||
final static long CHECK_PERIOD = PeerCoordinator.CHECK_PERIOD; // 40 seconds
|
||||
final static int RATE_DEPTH = PeerCoordinator.RATE_DEPTH; // make following arrays RATE_DEPTH long
|
||||
private long uploaded_old[] = {-1,-1,-1,-1,-1,-1};
|
||||
private long downloaded_old[] = {-1,-1,-1,-1,-1,-1};
|
||||
|
||||
/**
|
||||
* Creates a disconnected peer given a PeerID, your own id and the
|
||||
@@ -318,6 +322,14 @@ public class Peer implements Comparable
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
{
|
||||
// try to save partial piece
|
||||
if (this.deregister) {
|
||||
PeerListener p = state.listener;
|
||||
if (p != null) {
|
||||
p.savePeerPartial(state);
|
||||
p.markUnrequested(this);
|
||||
}
|
||||
}
|
||||
state = null;
|
||||
|
||||
PeerConnectionIn in = s.in;
|
||||
@@ -449,13 +461,105 @@ public class Peer implements Comparable
|
||||
public long getInactiveTime() {
|
||||
PeerState s = state;
|
||||
if (s != null) {
|
||||
PeerConnectionIn in = s.in;
|
||||
PeerConnectionOut out = s.out;
|
||||
if (out != null)
|
||||
return System.currentTimeMillis() - out.lastSent;
|
||||
else
|
||||
if (in != null && out != null) {
|
||||
long now = System.currentTimeMillis();
|
||||
return Math.max(now - out.lastSent, now - in.lastRcvd);
|
||||
} else
|
||||
return -1; //"state, no out";
|
||||
} else {
|
||||
return -1; //"no state";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send keepalive
|
||||
*/
|
||||
public void keepAlive()
|
||||
{
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
s.keepAlive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retransmit outstanding requests if necessary
|
||||
*/
|
||||
public void retransmitRequests()
|
||||
{
|
||||
PeerState s = state;
|
||||
if (s != null)
|
||||
s.retransmitRequests();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return how much the peer has
|
||||
*/
|
||||
public int completed()
|
||||
{
|
||||
PeerState s = state;
|
||||
if (s == null || s.bitfield == null)
|
||||
return 0;
|
||||
return s.bitfield.count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if a peer is a seeder
|
||||
*/
|
||||
public boolean isCompleted()
|
||||
{
|
||||
PeerState s = state;
|
||||
if (s == null || s.bitfield == null)
|
||||
return false;
|
||||
return s.bitfield.complete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Push the total uploaded/downloaded onto a RATE_DEPTH deep stack
|
||||
*/
|
||||
public void setRateHistory(long up, long down)
|
||||
{
|
||||
setRate(up, uploaded_old);
|
||||
setRate(down, downloaded_old);
|
||||
}
|
||||
|
||||
private void setRate(long val, long array[])
|
||||
{
|
||||
synchronized(array) {
|
||||
for (int i = RATE_DEPTH-1; i > 0; i--)
|
||||
array[i] = array[i-1];
|
||||
array[0] = val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the 4-minute-average rate in Bps
|
||||
*/
|
||||
public long getUploadRate()
|
||||
{
|
||||
return getRate(uploaded_old);
|
||||
}
|
||||
|
||||
public long getDownloadRate()
|
||||
{
|
||||
return getRate(downloaded_old);
|
||||
}
|
||||
|
||||
private long getRate(long array[])
|
||||
{
|
||||
long rate = 0;
|
||||
int i = 0;
|
||||
synchronized(array) {
|
||||
for ( ; i < RATE_DEPTH; i++){
|
||||
if (array[i] < 0)
|
||||
break;
|
||||
rate += array[i];
|
||||
}
|
||||
}
|
||||
if (i == 0)
|
||||
return 0;
|
||||
return rate / (i * CHECK_PERIOD / 1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -41,6 +41,17 @@ class PeerCheckerTask extends TimerTask
|
||||
{
|
||||
synchronized(coordinator.peers)
|
||||
{
|
||||
Iterator it = coordinator.peers.iterator();
|
||||
if ((!it.hasNext()) || coordinator.halted()) {
|
||||
coordinator.peerCount = 0;
|
||||
coordinator.interestedAndChoking = 0;
|
||||
coordinator.setRateHistory(0, 0);
|
||||
coordinator.uploaders = 0;
|
||||
if (coordinator.halted())
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate total uploading and worst downloader.
|
||||
long worstdownload = Long.MAX_VALUE;
|
||||
Peer worstDownloader = null;
|
||||
@@ -48,10 +59,7 @@ class PeerCheckerTask extends TimerTask
|
||||
int peers = 0;
|
||||
int uploaders = 0;
|
||||
int downloaders = 0;
|
||||
int interested = 0;
|
||||
int interesting = 0;
|
||||
int choking = 0;
|
||||
int choked = 0;
|
||||
int removedCount = 0;
|
||||
|
||||
long uploaded = 0;
|
||||
long downloaded = 0;
|
||||
@@ -59,8 +67,7 @@ class PeerCheckerTask extends TimerTask
|
||||
// Keep track of peers we remove now,
|
||||
// we will add them back to the end of the list.
|
||||
List removed = new ArrayList();
|
||||
|
||||
Iterator it = coordinator.peers.iterator();
|
||||
int uploadLimit = coordinator.allowedUploaders();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer peer = (Peer)it.next();
|
||||
@@ -80,48 +87,36 @@ class PeerCheckerTask extends TimerTask
|
||||
uploaders++;
|
||||
if (!peer.isChoked() && peer.isInteresting())
|
||||
downloaders++;
|
||||
if (peer.isInterested())
|
||||
interested++;
|
||||
if (peer.isInteresting())
|
||||
interesting++;
|
||||
if (peer.isChoking())
|
||||
choking++;
|
||||
if (peer.isChoked())
|
||||
choked++;
|
||||
|
||||
// XXX - We should calculate the up/download rate a bit
|
||||
// more intelligently
|
||||
long upload = peer.getUploaded();
|
||||
uploaded += upload;
|
||||
long download = peer.getDownloaded();
|
||||
downloaded += download;
|
||||
peer.setRateHistory(upload, download);
|
||||
peer.resetCounters();
|
||||
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
{
|
||||
Snark.debug(peer + ":", Snark.DEBUG);
|
||||
Snark.debug(" ul: " + upload/KILOPERSECOND
|
||||
+ " dl: " + download/KILOPERSECOND
|
||||
+ " i: " + peer.isInterested()
|
||||
+ " I: " + peer.isInteresting()
|
||||
+ " c: " + peer.isChoking()
|
||||
+ " C: " + peer.isChoked(),
|
||||
Snark.DEBUG);
|
||||
}
|
||||
Snark.debug(peer + ":", Snark.DEBUG);
|
||||
Snark.debug(" ul: " + upload/KILOPERSECOND
|
||||
+ " dl: " + download/KILOPERSECOND
|
||||
+ " i: " + peer.isInterested()
|
||||
+ " I: " + peer.isInteresting()
|
||||
+ " c: " + peer.isChoking()
|
||||
+ " C: " + peer.isChoked(),
|
||||
Snark.DEBUG);
|
||||
|
||||
// If we are at our max uploaders and we have lots of other
|
||||
// interested peers try to make some room.
|
||||
// (Note use of coordinator.uploaders)
|
||||
if (coordinator.uploaders >= PeerCoordinator.MAX_UPLOADERS
|
||||
&& interested > PeerCoordinator.MAX_UPLOADERS
|
||||
if (((coordinator.uploaders == uploadLimit
|
||||
&& coordinator.interestedAndChoking > 0)
|
||||
|| coordinator.uploaders > uploadLimit)
|
||||
&& !peer.isChoking())
|
||||
{
|
||||
// Check if it still wants pieces from us.
|
||||
if (!peer.isInterested())
|
||||
{
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Choke uninterested peer: " + peer,
|
||||
Snark.INFO);
|
||||
Snark.debug("Choke uninterested peer: " + peer,
|
||||
Snark.INFO);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
coordinator.uploaders--;
|
||||
@@ -130,14 +125,27 @@ class PeerCheckerTask extends TimerTask
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (peer.isChoked())
|
||||
else if (peer.isInteresting() && peer.isChoked())
|
||||
{
|
||||
// If they are choking us make someone else a downloader
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Choke choking peer: " + peer, Snark.DEBUG);
|
||||
Snark.debug("Choke choking peer: " + peer, Snark.DEBUG);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
coordinator.uploaders--;
|
||||
removedCount++;
|
||||
|
||||
// Put it at the back of the list
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (!peer.isInteresting() && !coordinator.completed())
|
||||
{
|
||||
// If they aren't interesting make someone else a downloader
|
||||
Snark.debug("Choke uninteresting peer: " + peer, Snark.DEBUG);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
coordinator.uploaders--;
|
||||
removedCount++;
|
||||
|
||||
// Put it at the back of the list
|
||||
it.remove();
|
||||
@@ -148,41 +156,51 @@ class PeerCheckerTask extends TimerTask
|
||||
&& download == 0)
|
||||
{
|
||||
// We are downloading but didn't receive anything...
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Choke downloader that doesn't deliver:"
|
||||
+ peer, Snark.DEBUG);
|
||||
Snark.debug("Choke downloader that doesn't deliver:"
|
||||
+ peer, Snark.DEBUG);
|
||||
peer.setChoking(true);
|
||||
uploaders--;
|
||||
coordinator.uploaders--;
|
||||
removedCount++;
|
||||
|
||||
// Put it at the back of the list
|
||||
it.remove();
|
||||
removed.add(peer);
|
||||
}
|
||||
else if (!peer.isChoking() && download < worstdownload)
|
||||
else if (peer.isInteresting() && !peer.isChoked() &&
|
||||
download < worstdownload)
|
||||
{
|
||||
// Make sure download is good if we are uploading
|
||||
worstdownload = download;
|
||||
worstDownloader = peer;
|
||||
}
|
||||
else if (upload < worstdownload && coordinator.completed())
|
||||
{
|
||||
// Make sure upload is good if we are seeding
|
||||
worstdownload = upload;
|
||||
worstDownloader = peer;
|
||||
}
|
||||
}
|
||||
peer.retransmitRequests();
|
||||
peer.keepAlive();
|
||||
}
|
||||
|
||||
// Resync actual uploaders value
|
||||
// (can shift a bit by disconnecting peers)
|
||||
coordinator.uploaders = uploaders;
|
||||
|
||||
// Remove the worst downloader if needed.
|
||||
if (uploaders >= PeerCoordinator.MAX_UPLOADERS
|
||||
&& interested > PeerCoordinator.MAX_UPLOADERS
|
||||
// Remove the worst downloader if needed. (uploader if seeding)
|
||||
if (((uploaders == uploadLimit
|
||||
&& coordinator.interestedAndChoking > 0)
|
||||
|| uploaders > uploadLimit)
|
||||
&& worstDownloader != null)
|
||||
{
|
||||
if (Snark.debug >= Snark.DEBUG)
|
||||
Snark.debug("Choke worst downloader: " + worstDownloader,
|
||||
Snark.DEBUG);
|
||||
Snark.debug("Choke worst downloader: " + worstDownloader,
|
||||
Snark.DEBUG);
|
||||
|
||||
worstDownloader.setChoking(true);
|
||||
coordinator.uploaders--;
|
||||
removedCount++;
|
||||
|
||||
// Put it at the back of the list
|
||||
coordinator.peers.remove(worstDownloader);
|
||||
@@ -196,9 +214,11 @@ class PeerCheckerTask extends TimerTask
|
||||
// Put peers back at the end of the list that we removed earlier.
|
||||
coordinator.peers.addAll(removed);
|
||||
coordinator.peerCount = coordinator.peers.size();
|
||||
coordinator.interestedAndChoking += removedCount;
|
||||
|
||||
// store the rates
|
||||
coordinator.setRateHistory(uploaded, downloaded);
|
||||
|
||||
}
|
||||
if (coordinator.halted()) {
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,12 +33,15 @@ class PeerConnectionIn implements Runnable
|
||||
private final DataInputStream din;
|
||||
|
||||
private Thread thread;
|
||||
private boolean quit;
|
||||
private volatile boolean quit;
|
||||
|
||||
long lastRcvd;
|
||||
|
||||
public PeerConnectionIn(Peer peer, DataInputStream din)
|
||||
{
|
||||
this.peer = peer;
|
||||
this.din = din;
|
||||
lastRcvd = System.currentTimeMillis();
|
||||
quit = false;
|
||||
}
|
||||
|
||||
@@ -51,6 +54,13 @@ class PeerConnectionIn implements Runnable
|
||||
Thread t = thread;
|
||||
if (t != null)
|
||||
t.interrupt();
|
||||
if (din != null) {
|
||||
try {
|
||||
din.close();
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Error closing the stream from " + peer, ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void run()
|
||||
@@ -69,6 +79,7 @@ class PeerConnectionIn implements Runnable
|
||||
// Wait till we hear something...
|
||||
// The length of a complete message in bytes.
|
||||
int i = din.readInt();
|
||||
lastRcvd = System.currentTimeMillis();
|
||||
if (i < 0)
|
||||
throw new IOException("Unexpected length prefix: " + i);
|
||||
|
||||
@@ -116,7 +127,7 @@ class PeerConnectionIn implements Runnable
|
||||
din.readFully(bitmap);
|
||||
ps.bitfieldMessage(bitmap);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Received bitmap from " + peer + " on " + peer.metainfo.getName());
|
||||
_log.debug("Received bitmap from " + peer + " on " + peer.metainfo.getName() + ": size=" + (i-1) + ": " + ps.bitfield);
|
||||
break;
|
||||
case 6:
|
||||
piece = din.readInt();
|
||||
|
||||
@@ -72,6 +72,16 @@ class PeerConnectionOut implements Runnable
|
||||
{
|
||||
Message m = null;
|
||||
PeerState state = null;
|
||||
boolean shouldFlush;
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
shouldFlush = !quit && peer.isConnected() && sendQueue.isEmpty();
|
||||
}
|
||||
if (shouldFlush)
|
||||
// Make sure everything will reach the other side.
|
||||
// flush while not holding lock, could take a long time
|
||||
dout.flush();
|
||||
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
while (!quit && peer.isConnected() && sendQueue.isEmpty())
|
||||
@@ -79,7 +89,8 @@ class PeerConnectionOut implements Runnable
|
||||
try
|
||||
{
|
||||
// Make sure everything will reach the other side.
|
||||
dout.flush();
|
||||
// don't flush while holding lock, could take a long time
|
||||
// dout.flush();
|
||||
|
||||
// Wait till more data arrives.
|
||||
sendQueue.wait(60*1000);
|
||||
@@ -197,10 +208,12 @@ class PeerConnectionOut implements Runnable
|
||||
/**
|
||||
* Adds a message to the sendQueue and notifies the method waiting
|
||||
* on the sendQueue to change.
|
||||
* If a PIECE message only, add a timeout.
|
||||
*/
|
||||
private void addMessage(Message m)
|
||||
{
|
||||
SimpleTimer.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
|
||||
if (m.type == Message.PIECE)
|
||||
SimpleTimer.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
sendQueue.add(m);
|
||||
@@ -259,7 +272,13 @@ class PeerConnectionOut implements Runnable
|
||||
{
|
||||
Message m = new Message();
|
||||
m.type = Message.KEEP_ALIVE;
|
||||
addMessage(m);
|
||||
// addMessage(m);
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
if(sendQueue.isEmpty())
|
||||
sendQueue.add(m);
|
||||
sendQueue.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
void sendChoke(boolean choke)
|
||||
@@ -318,6 +337,23 @@ class PeerConnectionOut implements Runnable
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
/** reransmit requests not received in 7m */
|
||||
private static final int REQ_TIMEOUT = (2 * SEND_TIMEOUT) + (60 * 1000);
|
||||
void retransmitRequests(List requests)
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
Iterator it = requests.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Request req = (Request)it.next();
|
||||
if(now > req.sendTime + REQ_TIMEOUT) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Retransmit request " + req + " to peer " + peer);
|
||||
sendRequest(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sendRequests(List requests)
|
||||
{
|
||||
Iterator it = requests.iterator();
|
||||
@@ -330,12 +366,30 @@ class PeerConnectionOut implements Runnable
|
||||
|
||||
void sendRequest(Request req)
|
||||
{
|
||||
// Check for duplicate requests to deal with fibrillating i2p-bt
|
||||
// (multiple choke/unchokes received cause duplicate requests in the queue)
|
||||
synchronized(sendQueue)
|
||||
{
|
||||
Iterator it = sendQueue.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Message m = (Message)it.next();
|
||||
if (m.type == Message.REQUEST && m.piece == req.piece &&
|
||||
m.begin == req.off && m.length == req.len)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Discarding duplicate request " + req + " to peer " + peer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Message m = new Message();
|
||||
m.type = Message.REQUEST;
|
||||
m.piece = req.piece;
|
||||
m.begin = req.off;
|
||||
m.length = req.len;
|
||||
addMessage(m);
|
||||
req.sendTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
void sendPiece(int piece, int begin, int length, byte[] bytes)
|
||||
@@ -346,7 +400,7 @@ class PeerConnectionOut implements Runnable
|
||||
m.begin = begin;
|
||||
m.length = length;
|
||||
m.data = bytes;
|
||||
m.off = begin;
|
||||
m.off = 0;
|
||||
m.len = length;
|
||||
addMessage(m);
|
||||
}
|
||||
|
||||
@@ -37,19 +37,23 @@ public class PeerCoordinator implements PeerListener
|
||||
final Snark snark;
|
||||
|
||||
// package local for access by CheckDownLoadersTask
|
||||
final static long CHECK_PERIOD = 20*1000; // 20 seconds
|
||||
final static long CHECK_PERIOD = 40*1000; // 40 seconds
|
||||
final static int MAX_CONNECTIONS = 24;
|
||||
final static int MAX_UPLOADERS = 12; // i2p: might as well balance it out
|
||||
final static int MAX_UPLOADERS = 4;
|
||||
|
||||
// Approximation of the number of current uploaders.
|
||||
// Resynced by PeerChecker once in a while.
|
||||
int uploaders = 0;
|
||||
int interestedAndChoking = 0;
|
||||
|
||||
// final static int MAX_DOWNLOADERS = MAX_CONNECTIONS;
|
||||
// int downloaders = 0;
|
||||
|
||||
private long uploaded;
|
||||
private long downloaded;
|
||||
final static int RATE_DEPTH = 6; // make following arrays RATE_DEPTH long
|
||||
private long uploaded_old[] = {0,0,0,0,0,0};
|
||||
private long downloaded_old[] = {0,0,0,0,0,0};
|
||||
|
||||
// synchronize on this when changing peers or downloaders
|
||||
final List peers = new ArrayList();
|
||||
@@ -62,7 +66,7 @@ public class PeerCoordinator implements PeerListener
|
||||
private final byte[] id;
|
||||
|
||||
// Some random wanted pieces
|
||||
private final List wantedPieces;
|
||||
private List wantedPieces;
|
||||
|
||||
private boolean halted = false;
|
||||
|
||||
@@ -80,6 +84,15 @@ public class PeerCoordinator implements PeerListener
|
||||
this.listener = listener;
|
||||
this.snark = torrent;
|
||||
|
||||
setWantedPieces();
|
||||
|
||||
// Install a timer to check the uploaders.
|
||||
timer.schedule(new PeerCheckerTask(this), CHECK_PERIOD, CHECK_PERIOD);
|
||||
}
|
||||
|
||||
// only called externally from Storage after the double-check fails
|
||||
public void setWantedPieces()
|
||||
{
|
||||
// Make a list of pieces
|
||||
wantedPieces = new ArrayList();
|
||||
BitField bitfield = storage.getBitField();
|
||||
@@ -87,14 +100,20 @@ public class PeerCoordinator implements PeerListener
|
||||
if (!bitfield.get(i))
|
||||
wantedPieces.add(new Piece(i));
|
||||
Collections.shuffle(wantedPieces);
|
||||
|
||||
// Install a timer to check the uploaders.
|
||||
timer.schedule(new PeerCheckerTask(this), CHECK_PERIOD, CHECK_PERIOD);
|
||||
}
|
||||
|
||||
|
||||
public Storage getStorage() { return storage; }
|
||||
public CoordinatorListener getListener() { return listener; }
|
||||
|
||||
// for web page detailed stats
|
||||
public List peerList()
|
||||
{
|
||||
synchronized(peers)
|
||||
{
|
||||
return new ArrayList(peers);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getID()
|
||||
{
|
||||
return id;
|
||||
@@ -123,7 +142,7 @@ public class PeerCoordinator implements PeerListener
|
||||
public long getLeft()
|
||||
{
|
||||
// XXX - Only an approximation.
|
||||
return storage.needed() * metainfo.getPieceLength(0);
|
||||
return ((long) storage.needed()) * metainfo.getPieceLength(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,6 +161,47 @@ public class PeerCoordinator implements PeerListener
|
||||
return downloaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push the total uploaded/downloaded onto a RATE_DEPTH deep stack
|
||||
*/
|
||||
public void setRateHistory(long up, long down)
|
||||
{
|
||||
setRate(up, uploaded_old);
|
||||
setRate(down, downloaded_old);
|
||||
}
|
||||
|
||||
private static void setRate(long val, long array[])
|
||||
{
|
||||
synchronized(array) {
|
||||
for (int i = RATE_DEPTH-1; i > 0; i--)
|
||||
array[i] = array[i-1];
|
||||
array[0] = val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the 4-minute-average rate in Bps
|
||||
*/
|
||||
public long getDownloadRate()
|
||||
{
|
||||
return getRate(downloaded_old);
|
||||
}
|
||||
|
||||
public long getUploadRate()
|
||||
{
|
||||
return getRate(uploaded_old);
|
||||
}
|
||||
|
||||
private long getRate(long array[])
|
||||
{
|
||||
long rate = 0;
|
||||
synchronized(array) {
|
||||
for (int i = 0; i < RATE_DEPTH; i++)
|
||||
rate += array[i];
|
||||
}
|
||||
return rate / (RATE_DEPTH * CHECK_PERIOD / 1000);
|
||||
}
|
||||
|
||||
public MetaInfo getMetaInfo()
|
||||
{
|
||||
return metainfo;
|
||||
@@ -177,6 +237,8 @@ public class PeerCoordinator implements PeerListener
|
||||
peer.disconnect();
|
||||
removePeerFromPieces(peer);
|
||||
}
|
||||
// delete any saved orphan partial piece
|
||||
savedRequest = null;
|
||||
}
|
||||
|
||||
public void connected(Peer peer)
|
||||
@@ -191,8 +253,10 @@ public class PeerCoordinator implements PeerListener
|
||||
synchronized(peers)
|
||||
{
|
||||
Peer old = peerIDInList(peer.getPeerID(), peers);
|
||||
if ( (old != null) && (old.getInactiveTime() > 2*60*1000) ) {
|
||||
// idle for 2 minutes, kill the old con
|
||||
if ( (old != null) && (old.getInactiveTime() > 8*60*1000) ) {
|
||||
// idle for 8 minutes, kill the old con (32KB/8min = 68B/sec minimum for one block)
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Remomving old peer: " + peer + ": " + old + ", inactive for " + old.getInactiveTime());
|
||||
peers.remove(old);
|
||||
toDisconnect = old;
|
||||
old = null;
|
||||
@@ -201,6 +265,7 @@ public class PeerCoordinator implements PeerListener
|
||||
{
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Already connected to: " + peer + ": " + old + ", inactive for " + old.getInactiveTime());
|
||||
// toDisconnect = peer to get out of synchronized(peers)
|
||||
peer.disconnect(false); // Don't deregister this connection/peer.
|
||||
}
|
||||
else
|
||||
@@ -235,18 +300,22 @@ public class PeerCoordinator implements PeerListener
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addPeer(final Peer peer)
|
||||
// returns true if actual attempt to add peer occurs
|
||||
public boolean addPeer(final Peer peer)
|
||||
{
|
||||
if (halted)
|
||||
{
|
||||
peer.disconnect(false);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean need_more;
|
||||
synchronized(peers)
|
||||
{
|
||||
need_more = !peer.isConnected() && peers.size() < MAX_CONNECTIONS;
|
||||
// Check if we already have this peer before we build the connection
|
||||
Peer old = peerIDInList(peer.getPeerID(), peers);
|
||||
need_more = need_more && ((old == null) || (old.getInactiveTime() > 8*60*1000));
|
||||
}
|
||||
|
||||
if (need_more)
|
||||
@@ -265,6 +334,7 @@ public class PeerCoordinator implements PeerListener
|
||||
};
|
||||
String threadName = peer.toString();
|
||||
new I2PThread(r, threadName).start();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
@@ -274,6 +344,7 @@ public class PeerCoordinator implements PeerListener
|
||||
_log.info("MAX_CONNECTIONS = " + MAX_CONNECTIONS
|
||||
+ " not accepting extra peer: " + peer);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -285,34 +356,40 @@ public class PeerCoordinator implements PeerListener
|
||||
// other peer that are interested, but are choking us.
|
||||
List interested = new LinkedList();
|
||||
synchronized (peers) {
|
||||
int count = 0;
|
||||
int unchokedCount = 0;
|
||||
int maxUploaders = allowedUploaders();
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer peer = (Peer)it.next();
|
||||
boolean remove = false;
|
||||
if (uploaders < MAX_UPLOADERS
|
||||
&& peer.isChoking()
|
||||
&& peer.isInterested())
|
||||
if (peer.isChoking() && peer.isInterested())
|
||||
{
|
||||
if (!peer.isChoked())
|
||||
interested.add(0, peer);
|
||||
else
|
||||
interested.add(peer);
|
||||
count++;
|
||||
if (uploaders < maxUploaders)
|
||||
{
|
||||
if (!peer.isChoked())
|
||||
interested.add(unchokedCount++, peer);
|
||||
else
|
||||
interested.add(peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (uploaders < MAX_UPLOADERS && interested.size() > 0)
|
||||
while (uploaders < maxUploaders && interested.size() > 0)
|
||||
{
|
||||
Peer peer = (Peer)interested.remove(0);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Unchoke: " + peer);
|
||||
peer.setChoking(false);
|
||||
uploaders++;
|
||||
count--;
|
||||
// Put peer back at the end of the list.
|
||||
peers.remove(peer);
|
||||
peers.add(peer);
|
||||
peerCount = peers.size();
|
||||
}
|
||||
interestedAndChoking = count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,9 +428,10 @@ public class PeerCoordinator implements PeerListener
|
||||
{
|
||||
Piece p = (Piece)it.next();
|
||||
int i = p.getId();
|
||||
if (bitfield.get(i))
|
||||
if (bitfield.get(i)) {
|
||||
p.addPeer(peer);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -365,8 +443,11 @@ public class PeerCoordinator implements PeerListener
|
||||
*/
|
||||
public int wantPiece(Peer peer, BitField havePieces)
|
||||
{
|
||||
if (halted)
|
||||
if (halted) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("We don't want anything from the peer, as we are halted! peer=" + peer);
|
||||
return -1;
|
||||
}
|
||||
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
@@ -389,6 +470,8 @@ public class PeerCoordinator implements PeerListener
|
||||
|
||||
//Only request a piece we've requested before if there's no other choice.
|
||||
if (piece == null) {
|
||||
// let's not all get on the same piece
|
||||
Collections.shuffle(requested);
|
||||
Iterator it2 = requested.iterator();
|
||||
while (piece == null && it2.hasNext())
|
||||
{
|
||||
@@ -398,7 +481,20 @@ public class PeerCoordinator implements PeerListener
|
||||
piece = p;
|
||||
}
|
||||
}
|
||||
if (piece == null) return -1; //If we still can't find a piece we want, so be it.
|
||||
if (piece == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("nothing to even rerequest from " + peer + ": requested = " + requested);
|
||||
// _log.warn("nothing to even rerequest from " + peer + ": requested = " + requested
|
||||
// + " wanted = " + wantedPieces + " peerHas = " + havePieces);
|
||||
return -1; //If we still can't find a piece we want, so be it.
|
||||
} else {
|
||||
// Should be a lot smarter here - limit # of parallel attempts and
|
||||
// share blocks rather than starting from 0 with each peer.
|
||||
// This is where the flaws of the snark data model are really exposed.
|
||||
// Could also randomize within the duplicate set rather than strict rarest-first
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("parallel request (end game?) for " + peer + ": piece = " + piece);
|
||||
}
|
||||
}
|
||||
piece.setRequested(true);
|
||||
return piece.getId();
|
||||
@@ -409,14 +505,14 @@ public class PeerCoordinator implements PeerListener
|
||||
* Returns a byte array containing the requested piece or null of
|
||||
* the piece is unknown.
|
||||
*/
|
||||
public byte[] gotRequest(Peer peer, int piece)
|
||||
public byte[] gotRequest(Peer peer, int piece, int off, int len)
|
||||
{
|
||||
if (halted)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return storage.getPiece(piece);
|
||||
return storage.getPiece(piece, off, len);
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
@@ -496,14 +592,27 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
|
||||
// Announce to the world we have it!
|
||||
// Disconnect from other seeders when we get the last piece
|
||||
synchronized(peers)
|
||||
{
|
||||
List toDisconnect = new ArrayList();
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer p = (Peer)it.next();
|
||||
if (p.isConnected())
|
||||
p.have(piece);
|
||||
{
|
||||
if (completed() && p.isCompleted())
|
||||
toDisconnect.add(p);
|
||||
else
|
||||
p.have(piece);
|
||||
}
|
||||
}
|
||||
it = toDisconnect.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
Peer p = (Peer)it.next();
|
||||
p.disconnect(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,7 +634,7 @@ public class PeerCoordinator implements PeerListener
|
||||
{
|
||||
synchronized(peers)
|
||||
{
|
||||
if (uploaders < MAX_UPLOADERS)
|
||||
if (uploaders < allowedUploaders())
|
||||
{
|
||||
if(peer.isChoking())
|
||||
{
|
||||
@@ -574,4 +683,141 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Simple method to save a partial piece on peer disconnection
|
||||
* and hopefully restart it later.
|
||||
* Only one partial piece is saved at a time.
|
||||
* Replace it if a new one is bigger or the old one is too old.
|
||||
* Storage method is private so we can expand to save multiple partials
|
||||
* if we wish.
|
||||
*/
|
||||
private Request savedRequest = null;
|
||||
private long savedRequestTime = 0;
|
||||
public void savePeerPartial(PeerState state)
|
||||
{
|
||||
if (halted)
|
||||
return;
|
||||
Request req = state.getPartialRequest();
|
||||
if (req == null)
|
||||
return;
|
||||
if (savedRequest == null ||
|
||||
req.off > savedRequest.off ||
|
||||
System.currentTimeMillis() > savedRequestTime + (15 * 60 * 1000)) {
|
||||
if (savedRequest == null || (req.piece != savedRequest.piece && req.off != savedRequest.off)) {
|
||||
if (_log.shouldLog(Log.DEBUG)) {
|
||||
_log.debug(" Saving orphaned partial piece " + req);
|
||||
if (savedRequest != null)
|
||||
_log.debug(" (Discarding previously saved orphan) " + savedRequest);
|
||||
}
|
||||
}
|
||||
savedRequest = req;
|
||||
savedRequestTime = System.currentTimeMillis();
|
||||
} else {
|
||||
if (req.piece != savedRequest.piece)
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(" Discarding orphaned partial piece " + req);
|
||||
}
|
||||
}
|
||||
|
||||
/** Return partial piece if it's still wanted and peer has it.
|
||||
*/
|
||||
public Request getPeerPartial(BitField havePieces) {
|
||||
if (savedRequest == null)
|
||||
return null;
|
||||
if (! havePieces.get(savedRequest.piece)) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Peer doesn't have orphaned piece " + savedRequest);
|
||||
return null;
|
||||
}
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
for(Iterator iter = wantedPieces.iterator(); iter.hasNext(); ) {
|
||||
Piece piece = (Piece)iter.next();
|
||||
if (piece.getId() == savedRequest.piece) {
|
||||
Request req = savedRequest;
|
||||
piece.setRequested(true);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Restoring orphaned partial piece " + req);
|
||||
savedRequest = null;
|
||||
return req;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("We no longer want orphaned piece " + savedRequest);
|
||||
savedRequest = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Clear the requested flag for a piece if the peer
|
||||
** is the only one requesting it
|
||||
*/
|
||||
private void markUnrequestedIfOnlyOne(Peer peer, int piece)
|
||||
{
|
||||
// see if anybody else is requesting
|
||||
synchronized (peers)
|
||||
{
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext()) {
|
||||
Peer p = (Peer)it.next();
|
||||
if (p.equals(peer))
|
||||
continue;
|
||||
if (p.state == null)
|
||||
continue;
|
||||
int[] arr = p.state.getRequestedPieces();
|
||||
for (int i = 0; arr[i] >= 0; i++)
|
||||
if(arr[i] == piece) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Another peer is requesting piece " + piece);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nobody is, so mark unrequested
|
||||
synchronized(wantedPieces)
|
||||
{
|
||||
Iterator it = wantedPieces.iterator();
|
||||
while (it.hasNext()) {
|
||||
Piece p = (Piece)it.next();
|
||||
if (p.getId() == piece) {
|
||||
p.setRequested(false);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Removing from request list piece " + piece);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Mark a peer's requested pieces unrequested when it is disconnected
|
||||
** Once for each piece
|
||||
** This is enough trouble, maybe would be easier just to regenerate
|
||||
** the requested list from scratch instead.
|
||||
*/
|
||||
public void markUnrequested(Peer peer)
|
||||
{
|
||||
if (halted || peer.state == null)
|
||||
return;
|
||||
int[] arr = peer.state.getRequestedPieces();
|
||||
for (int i = 0; arr[i] >= 0; i++)
|
||||
markUnrequestedIfOnlyOne(peer, arr[i]);
|
||||
}
|
||||
|
||||
/** Return number of allowed uploaders for this torrent.
|
||||
** Check with Snark to see if we are over the total upload limit.
|
||||
*/
|
||||
public int allowedUploaders()
|
||||
{
|
||||
if (Snark.overUploadLimit(uploaders)) {
|
||||
// if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Over limit, uploaders was: " + uploaders);
|
||||
return uploaders - 1;
|
||||
} else if (uploaders < MAX_UPLOADERS)
|
||||
return uploaders + 1;
|
||||
else
|
||||
return MAX_UPLOADERS;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,11 +107,13 @@ public interface PeerListener
|
||||
*
|
||||
* @param peer the Peer that wants the piece.
|
||||
* @param piece the piece number requested.
|
||||
* @param off byte offset into the piece.
|
||||
* @param len length of the chunk requested.
|
||||
*
|
||||
* @return a byte array containing the piece or null when the piece
|
||||
* is not available (which is a protocol error).
|
||||
*/
|
||||
byte[] gotRequest(Peer peer, int piece);
|
||||
byte[] gotRequest(Peer peer, int piece, int off, int len);
|
||||
|
||||
/**
|
||||
* Called when a (partial) piece has been downloaded from the peer.
|
||||
@@ -142,4 +144,29 @@ public interface PeerListener
|
||||
* we are no longer interested in the peer.
|
||||
*/
|
||||
int wantPiece(Peer peer, BitField bitfield);
|
||||
|
||||
/**
|
||||
* Called when the peer has disconnected and the peer task may have a partially
|
||||
* downloaded piece that the PeerCoordinator can save
|
||||
*
|
||||
* @param state the PeerState for the peer
|
||||
*/
|
||||
void savePeerPartial(PeerState state);
|
||||
|
||||
/**
|
||||
* Called when a peer has connected and there may be a partially
|
||||
* downloaded piece that the coordinatorator can give the peer task
|
||||
*
|
||||
* @param havePieces the have-pieces bitmask for the peer
|
||||
*
|
||||
* @return request (contains the partial data and valid length)
|
||||
*/
|
||||
Request getPeerPartial(BitField havePieces);
|
||||
|
||||
/** Mark a peer's requested pieces unrequested when it is disconnected
|
||||
* This prevents premature end game
|
||||
*
|
||||
* @param peer the peer that is disconnecting
|
||||
*/
|
||||
void markUnrequested(Peer peer);
|
||||
}
|
||||
|
||||
@@ -62,8 +62,9 @@ class PeerState
|
||||
// If we have te resend outstanding requests (true after we got choked).
|
||||
private boolean resend = false;
|
||||
|
||||
private final static int MAX_PIPELINE = 1;
|
||||
private final static int PARTSIZE = 64*1024; // default was 16K, i2p-bt uses 64KB
|
||||
private final static int MAX_PIPELINE = 2;
|
||||
private final static int PARTSIZE = 32*1024; // Snark was 16K, i2p-bt uses 64KB
|
||||
private final static int MAX_PARTSIZE = 64*1024; // Don't let anybody request more than this
|
||||
|
||||
PeerState(Peer peer, PeerListener listener, MetaInfo metainfo,
|
||||
PeerConnectionIn in, PeerConnectionOut out)
|
||||
@@ -173,7 +174,7 @@ class PeerState
|
||||
|| begin < 0
|
||||
|| begin > metainfo.getPieceLength(piece)
|
||||
|| length <= 0
|
||||
|| length > 4*PARTSIZE)
|
||||
|| length > MAX_PARTSIZE)
|
||||
{
|
||||
// XXX - Protocol error -> disconnect?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -184,7 +185,7 @@ class PeerState
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] pieceBytes = listener.gotRequest(peer, piece);
|
||||
byte[] pieceBytes = listener.gotRequest(peer, piece, begin, length);
|
||||
if (pieceBytes == null)
|
||||
{
|
||||
// XXX - Protocol error-> diconnect?
|
||||
@@ -194,7 +195,7 @@ class PeerState
|
||||
}
|
||||
|
||||
// More sanity checks
|
||||
if (begin >= pieceBytes.length || begin + length > pieceBytes.length)
|
||||
if (length != pieceBytes.length)
|
||||
{
|
||||
// XXX - Protocol error-> disconnect?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
@@ -221,6 +222,10 @@ class PeerState
|
||||
listener.uploaded(peer, size);
|
||||
}
|
||||
|
||||
// This is used to flag that we have to back up from the firstOutstandingRequest
|
||||
// when calculating how far we've gotten
|
||||
Request pendingRequest = null;
|
||||
|
||||
/**
|
||||
* Called when a partial piece request has been handled by
|
||||
* PeerConnectionIn.
|
||||
@@ -231,6 +236,8 @@ class PeerState
|
||||
downloaded += size;
|
||||
listener.downloaded(peer, size);
|
||||
|
||||
pendingRequest = null;
|
||||
|
||||
// Last chunk needed for this piece?
|
||||
if (getFirstOutstandingRequest(req.piece) == -1)
|
||||
{
|
||||
@@ -318,14 +325,8 @@ class PeerState
|
||||
{
|
||||
Request dropReq = (Request)outstandingRequests.remove(0);
|
||||
outstandingRequests.add(dropReq);
|
||||
// We used to rerequest the missing chunks but that mostly
|
||||
// just confuses the other side. So now we just keep
|
||||
// waiting for them. They will be rerequested when we get
|
||||
// choked/unchoked again.
|
||||
/*
|
||||
if (!choked)
|
||||
if (!choked)
|
||||
out.sendRequest(dropReq);
|
||||
*/
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("dropped " + dropReq + " with peer " + peer);
|
||||
}
|
||||
@@ -336,10 +337,58 @@ class PeerState
|
||||
// Request more if necessary to keep the pipeline filled.
|
||||
addRequest();
|
||||
|
||||
pendingRequest = req;
|
||||
return req;
|
||||
|
||||
}
|
||||
|
||||
// get longest partial piece
|
||||
Request getPartialRequest()
|
||||
{
|
||||
Request req = null;
|
||||
for (int i = 0; i < outstandingRequests.size(); i++) {
|
||||
Request r1 = (Request)outstandingRequests.get(i);
|
||||
int j = getFirstOutstandingRequest(r1.piece);
|
||||
if (j == -1)
|
||||
continue;
|
||||
Request r2 = (Request)outstandingRequests.get(j);
|
||||
if (r2.off > 0 && ((req == null) || (r2.off > req.off)))
|
||||
req = r2;
|
||||
}
|
||||
if (pendingRequest != null && req != null && pendingRequest.off < req.off) {
|
||||
if (pendingRequest.off != 0)
|
||||
req = pendingRequest;
|
||||
else
|
||||
req = null;
|
||||
}
|
||||
return req;
|
||||
}
|
||||
|
||||
// return array of pieces terminated by -1
|
||||
// remove most duplicates
|
||||
// but still could be some duplicates, not guaranteed
|
||||
int[] getRequestedPieces()
|
||||
{
|
||||
int size = outstandingRequests.size();
|
||||
int[] arr = new int[size+2];
|
||||
int pc = -1;
|
||||
int pos = 0;
|
||||
if (pendingRequest != null) {
|
||||
pc = pendingRequest.piece;
|
||||
arr[pos++] = pc;
|
||||
}
|
||||
Request req = null;
|
||||
for (int i = 0; i < size; i++) {
|
||||
Request r1 = (Request)outstandingRequests.get(i);
|
||||
if (pc != r1.piece) {
|
||||
pc = r1.piece;
|
||||
arr[pos++] = pc;
|
||||
}
|
||||
}
|
||||
arr[pos] = -1;
|
||||
return(arr);
|
||||
}
|
||||
|
||||
void cancelMessage(int piece, int begin, int length)
|
||||
{
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@@ -401,7 +450,9 @@ class PeerState
|
||||
// Are there outstanding requests that have to be resend?
|
||||
if (resend)
|
||||
{
|
||||
out.sendRequests(outstandingRequests);
|
||||
synchronized (this) {
|
||||
out.sendRequests(outstandingRequests);
|
||||
}
|
||||
resend = false;
|
||||
}
|
||||
|
||||
@@ -412,16 +463,12 @@ class PeerState
|
||||
/**
|
||||
* Adds a new request to the outstanding requests list.
|
||||
*/
|
||||
private void addRequest()
|
||||
synchronized private void addRequest()
|
||||
{
|
||||
boolean more_pieces = true;
|
||||
while (more_pieces)
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
more_pieces = outstandingRequests.size() < MAX_PIPELINE;
|
||||
}
|
||||
|
||||
more_pieces = outstandingRequests.size() < MAX_PIPELINE;
|
||||
// We want something and we don't have outstanding requests?
|
||||
if (more_pieces && lastRequest == null)
|
||||
more_pieces = requestNextPiece();
|
||||
@@ -429,19 +476,14 @@ class PeerState
|
||||
{
|
||||
int pieceLength;
|
||||
boolean isLastChunk;
|
||||
synchronized(this)
|
||||
{
|
||||
pieceLength = metainfo.getPieceLength(lastRequest.piece);
|
||||
isLastChunk = lastRequest.off + lastRequest.len == pieceLength;
|
||||
}
|
||||
pieceLength = metainfo.getPieceLength(lastRequest.piece);
|
||||
isLastChunk = lastRequest.off + lastRequest.len == pieceLength;
|
||||
|
||||
// Last part of a piece?
|
||||
if (isLastChunk)
|
||||
more_pieces = requestNextPiece();
|
||||
else
|
||||
{
|
||||
synchronized(this)
|
||||
{
|
||||
int nextPiece = lastRequest.piece;
|
||||
int nextBegin = lastRequest.off + PARTSIZE;
|
||||
byte[] bs = lastRequest.bs;
|
||||
@@ -454,7 +496,6 @@ class PeerState
|
||||
if (!choked)
|
||||
out.sendRequest(req);
|
||||
lastRequest = req;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -470,16 +511,41 @@ class PeerState
|
||||
// Check that we already know what the other side has.
|
||||
if (bitfield != null)
|
||||
{
|
||||
// Check for adopting an orphaned partial piece
|
||||
Request r = listener.getPeerPartial(bitfield);
|
||||
if (r != null) {
|
||||
// Check that r not already in outstandingRequests
|
||||
int[] arr = getRequestedPieces();
|
||||
boolean found = false;
|
||||
for (int i = 0; arr[i] >= 0; i++) {
|
||||
if (arr[i] == r.piece) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
outstandingRequests.add(r);
|
||||
if (!choked)
|
||||
out.sendRequest(r);
|
||||
lastRequest = r;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
int nextPiece = listener.wantPiece(peer, bitfield);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(peer + " want piece " + nextPiece);
|
||||
synchronized(this)
|
||||
{
|
||||
if (nextPiece != -1
|
||||
&& (lastRequest == null || lastRequest.piece != nextPiece))
|
||||
{
|
||||
int piece_length = metainfo.getPieceLength(nextPiece);
|
||||
byte[] bs = new byte[piece_length];
|
||||
//Catch a common place for OOMs esp. on 1MB pieces
|
||||
byte[] bs;
|
||||
try {
|
||||
bs = new byte[piece_length];
|
||||
} catch (OutOfMemoryError oom) {
|
||||
_log.warn("Out of memory, can't request piece " + nextPiece, oom);
|
||||
return false;
|
||||
}
|
||||
|
||||
int length = Math.min(piece_length, PARTSIZE);
|
||||
Request req = new Request(nextPiece, bs, 0, length);
|
||||
@@ -489,7 +555,6 @@ class PeerState
|
||||
lastRequest = req;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -521,4 +586,15 @@ class PeerState
|
||||
out.sendChoke(choke);
|
||||
}
|
||||
}
|
||||
|
||||
void keepAlive()
|
||||
{
|
||||
out.sendAlive();
|
||||
}
|
||||
|
||||
synchronized void retransmitRequests()
|
||||
{
|
||||
if (interesting && !choked)
|
||||
out.retransmitRequests(outstandingRequests);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,4 +35,8 @@ public class Piece implements Comparable {
|
||||
public boolean removePeer(Peer peer) { return this.peers.remove(peer.getPeerID()); }
|
||||
public boolean isRequested() { return this.requested; }
|
||||
public void setRequested(boolean requested) { this.requested = requested; }
|
||||
|
||||
public String toString() {
|
||||
return String.valueOf(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ class Request
|
||||
final byte[] bs;
|
||||
final int off;
|
||||
final int len;
|
||||
long sendTime;
|
||||
|
||||
/**
|
||||
* Creates a new Request.
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.*;
|
||||
|
||||
import org.klomp.snark.bencode.*;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.util.I2PThread;
|
||||
@@ -62,7 +63,7 @@ public class Snark
|
||||
/**
|
||||
* What level of debug info to show.
|
||||
*/
|
||||
public static int debug = NOTICE;
|
||||
//public static int debug = NOTICE;
|
||||
|
||||
// Whether or not to ask the user for commands while sharing
|
||||
private static boolean command_interpreter = true;
|
||||
@@ -233,6 +234,7 @@ public class Snark
|
||||
public String rootDataDir = ".";
|
||||
public CompleteListener completeListener;
|
||||
public boolean stopped;
|
||||
byte[] id;
|
||||
|
||||
Snark(String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener) {
|
||||
@@ -267,7 +269,7 @@ public class Snark
|
||||
// zeros bytes, then three bytes filled with snark and then
|
||||
// sixteen random bytes.
|
||||
byte snark = (((3 + 7 + 10) * (1000 - 8)) / 992) - 17;
|
||||
byte[] id = new byte[20];
|
||||
id = new byte[20];
|
||||
Random random = new Random();
|
||||
int i;
|
||||
for (i = 0; i < 9; i++)
|
||||
@@ -282,13 +284,21 @@ public class Snark
|
||||
|
||||
int port;
|
||||
IOException lastException = null;
|
||||
/*
|
||||
* Don't start a tunnel if the torrent isn't going to be started.
|
||||
* If we are starting,
|
||||
* startTorrent() will force a connect.
|
||||
*
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
if (!ok) fatal("Unable to connect to I2P");
|
||||
I2PServerSocket serversocket = I2PSnarkUtil.instance().getServerSocket();
|
||||
if (serversocket == null)
|
||||
fatal("Unable to listen for I2P connections");
|
||||
else
|
||||
debug("Listening on I2P destination " + serversocket.getManager().getSession().getMyDestination().toBase64(), NOTICE);
|
||||
else {
|
||||
Destination d = serversocket.getManager().getSession().getMyDestination();
|
||||
debug("Listening on I2P destination " + d.toBase64() + " / " + d.calculateHash().toBase64(), NOTICE);
|
||||
}
|
||||
*/
|
||||
|
||||
// Figure out what the torrent argument represents.
|
||||
meta = null;
|
||||
@@ -358,6 +368,9 @@ public class Snark
|
||||
activity = "Checking storage";
|
||||
storage = new Storage(meta, slistener);
|
||||
storage.check(rootDataDir);
|
||||
// have to figure out when to reopen
|
||||
// if (!start)
|
||||
// storage.close();
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
@@ -368,14 +381,19 @@ public class Snark
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* see comment above
|
||||
*
|
||||
activity = "Collecting pieces";
|
||||
coordinator = new PeerCoordinator(id, meta, storage, clistener, this);
|
||||
PeerCoordinatorSet set = PeerCoordinatorSet.instance();
|
||||
set.add(coordinator);
|
||||
ConnectionAcceptor acceptor = ConnectionAcceptor.instance();
|
||||
acceptor.startAccepting(set, serversocket);
|
||||
|
||||
trackerclient = new TrackerClient(meta, coordinator);
|
||||
*/
|
||||
|
||||
if (start)
|
||||
startTorrent();
|
||||
}
|
||||
@@ -383,6 +401,26 @@ public class Snark
|
||||
* Start up contacting peers and querying the tracker
|
||||
*/
|
||||
public void startTorrent() {
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
if (!ok) fatal("Unable to connect to I2P");
|
||||
if (coordinator == null) {
|
||||
I2PServerSocket serversocket = I2PSnarkUtil.instance().getServerSocket();
|
||||
if (serversocket == null)
|
||||
fatal("Unable to listen for I2P connections");
|
||||
else {
|
||||
Destination d = serversocket.getManager().getSession().getMyDestination();
|
||||
debug("Listening on I2P destination " + d.toBase64() + " / " + d.calculateHash().toBase64(), NOTICE);
|
||||
}
|
||||
debug("Starting PeerCoordinator, ConnectionAcceptor, and TrackerClient", NOTICE);
|
||||
activity = "Collecting pieces";
|
||||
coordinator = new PeerCoordinator(id, meta, storage, this, this);
|
||||
PeerCoordinatorSet set = PeerCoordinatorSet.instance();
|
||||
set.add(coordinator);
|
||||
ConnectionAcceptor acceptor = ConnectionAcceptor.instance();
|
||||
acceptor.startAccepting(set, serversocket);
|
||||
trackerclient = new TrackerClient(meta, coordinator);
|
||||
}
|
||||
|
||||
stopped = false;
|
||||
boolean coordinatorChanged = false;
|
||||
if (coordinator.halted()) {
|
||||
@@ -399,6 +437,17 @@ public class Snark
|
||||
if (!trackerclient.started() && !coordinatorChanged) {
|
||||
trackerclient.start();
|
||||
} else if (trackerclient.halted() || coordinatorChanged) {
|
||||
try
|
||||
{
|
||||
storage.reopen(rootDataDir);
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
try { storage.close(); } catch (IOException ioee) {
|
||||
ioee.printStackTrace();
|
||||
}
|
||||
fatal("Could not reopen storage", ioe);
|
||||
}
|
||||
TrackerClient newClient = new TrackerClient(coordinator.getMetaInfo(), coordinator);
|
||||
if (!trackerclient.halted())
|
||||
trackerclient.halt();
|
||||
@@ -411,15 +460,25 @@ public class Snark
|
||||
*/
|
||||
public void stopTorrent() {
|
||||
stopped = true;
|
||||
trackerclient.halt();
|
||||
coordinator.halt();
|
||||
try {
|
||||
storage.close();
|
||||
} catch (IOException ioe) {
|
||||
System.out.println("Error closing " + torrent);
|
||||
ioe.printStackTrace();
|
||||
TrackerClient tc = trackerclient;
|
||||
if (tc != null)
|
||||
tc.halt();
|
||||
PeerCoordinator pc = coordinator;
|
||||
if (pc != null)
|
||||
pc.halt();
|
||||
Storage st = storage;
|
||||
if (st != null) {
|
||||
if (storage.changed)
|
||||
SnarkManager.instance().saveTorrentStatus(storage.getMetaInfo(), storage.getBitField());
|
||||
try {
|
||||
storage.close();
|
||||
} catch (IOException ioe) {
|
||||
System.out.println("Error closing " + torrent);
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
PeerCoordinatorSet.instance().remove(coordinator);
|
||||
if (pc != null)
|
||||
PeerCoordinatorSet.instance().remove(pc);
|
||||
}
|
||||
|
||||
static Snark parseArguments(String[] args)
|
||||
@@ -446,6 +505,7 @@ public class Snark
|
||||
int i = 0;
|
||||
while (i < args.length)
|
||||
{
|
||||
/*
|
||||
if (args[i].equals("--debug"))
|
||||
{
|
||||
debug = INFO;
|
||||
@@ -466,7 +526,7 @@ public class Snark
|
||||
catch (NumberFormatException nfe) { }
|
||||
}
|
||||
}
|
||||
else if (args[i].equals("--port"))
|
||||
else */ if (args[i].equals("--port"))
|
||||
{
|
||||
if (args.length - 1 < i + 1)
|
||||
usage("--port needs port number to listen on");
|
||||
@@ -622,11 +682,11 @@ public class Snark
|
||||
boolean allocating = false;
|
||||
public void storageCreateFile(Storage storage, String name, long length)
|
||||
{
|
||||
if (allocating)
|
||||
System.out.println(); // Done with last file.
|
||||
//if (allocating)
|
||||
// System.out.println(); // Done with last file.
|
||||
|
||||
System.out.print("Creating file '" + name
|
||||
+ "' of length " + length + ": ");
|
||||
//System.out.print("Creating file '" + name
|
||||
// + "' of length " + length + ": ");
|
||||
allocating = true;
|
||||
}
|
||||
|
||||
@@ -636,10 +696,10 @@ public class Snark
|
||||
public void storageAllocated(Storage storage, long length)
|
||||
{
|
||||
allocating = true;
|
||||
System.out.print(".");
|
||||
//System.out.print(".");
|
||||
allocated += length;
|
||||
if (allocated == meta.getTotalLength())
|
||||
System.out.println(); // We have all the disk space we need.
|
||||
//if (allocated == meta.getTotalLength())
|
||||
// System.out.println(); // We have all the disk space we need.
|
||||
}
|
||||
|
||||
boolean allChecked = false;
|
||||
@@ -653,26 +713,21 @@ public class Snark
|
||||
// Use the MetaInfo from the storage since our own might not
|
||||
// yet be setup correctly.
|
||||
MetaInfo meta = storage.getMetaInfo();
|
||||
if (meta != null)
|
||||
System.out.print("Checking existing "
|
||||
+ meta.getPieces()
|
||||
+ " pieces: ");
|
||||
//if (meta != null)
|
||||
// System.out.print("Checking existing "
|
||||
// + meta.getPieces()
|
||||
// + " pieces: ");
|
||||
checking = true;
|
||||
}
|
||||
if (checking)
|
||||
if (checked)
|
||||
System.out.print("+");
|
||||
else
|
||||
System.out.print("-");
|
||||
else
|
||||
if (!checking)
|
||||
Snark.debug("Got " + (checked ? "" : "BAD ") + "piece: " + num,
|
||||
Snark.INFO);
|
||||
}
|
||||
|
||||
public void storageAllChecked(Storage storage)
|
||||
{
|
||||
if (checking)
|
||||
System.out.println();
|
||||
//if (checking)
|
||||
// System.out.println();
|
||||
|
||||
allChecked = true;
|
||||
checking = false;
|
||||
@@ -682,11 +737,16 @@ public class Snark
|
||||
{
|
||||
Snark.debug("Completely received " + torrent, Snark.INFO);
|
||||
//storage.close();
|
||||
System.out.println("Completely received: " + torrent);
|
||||
//System.out.println("Completely received: " + torrent);
|
||||
if (completeListener != null)
|
||||
completeListener.torrentComplete(this);
|
||||
}
|
||||
|
||||
public void setWantedPieces(Storage storage)
|
||||
{
|
||||
coordinator.setWantedPieces();
|
||||
}
|
||||
|
||||
public void shutdown()
|
||||
{
|
||||
// Should not be necessary since all non-deamon threads should
|
||||
@@ -697,4 +757,23 @@ public class Snark
|
||||
public interface CompleteListener {
|
||||
public void torrentComplete(Snark snark);
|
||||
}
|
||||
|
||||
/** Maintain a configurable total uploader cap
|
||||
*/
|
||||
final static int MIN_TOTAL_UPLOADERS = 4;
|
||||
final static int MAX_TOTAL_UPLOADERS = 10;
|
||||
public static boolean overUploadLimit(int uploaders) {
|
||||
PeerCoordinatorSet coordinators = PeerCoordinatorSet.instance();
|
||||
if (coordinators == null || uploaders <= 0)
|
||||
return false;
|
||||
int totalUploaders = 0;
|
||||
for (Iterator iter = coordinators.iterator(); iter.hasNext(); ) {
|
||||
PeerCoordinator c = (PeerCoordinator)iter.next();
|
||||
if (!c.halted())
|
||||
totalUploaders += c.uploaders;
|
||||
}
|
||||
int limit = I2PSnarkUtil.instance().getMaxUploaders();
|
||||
// Snark.debug("Total uploaders: " + totalUploaders + " Limit: " + limit, Snark.DEBUG);
|
||||
return totalUploaders > limit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.klomp.snark;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
@@ -16,6 +17,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
|
||||
/** map of (canonical) filename to Snark instance (unsynchronized) */
|
||||
private Map _snarks;
|
||||
private Object _addSnarkLock;
|
||||
private String _configFile;
|
||||
private Properties _config;
|
||||
private I2PAppContext _context;
|
||||
@@ -27,19 +29,23 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
public static final String PROP_I2CP_OPTS = "i2psnark.i2cpOptions";
|
||||
public static final String PROP_EEP_HOST = "i2psnark.eepHost";
|
||||
public static final String PROP_EEP_PORT = "i2psnark.eepPort";
|
||||
public static final String PROP_UPLOADERS_TOTAL = "i2psnark.uploaders.total";
|
||||
public static final String PROP_DIR = "i2psnark.dir";
|
||||
public static final String PROP_META_PREFIX = "i2psnark.zmeta.";
|
||||
public static final String PROP_META_BITFIELD_SUFFIX = ".bitfield";
|
||||
|
||||
public static final String PROP_AUTO_START = "i2snark.autoStart";
|
||||
public static final String DEFAULT_AUTO_START = "false";
|
||||
|
||||
private SnarkManager() {
|
||||
_snarks = new HashMap();
|
||||
_addSnarkLock = new Object();
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
_log = _context.logManager().getLog(SnarkManager.class);
|
||||
_messages = new ArrayList(16);
|
||||
loadConfig("i2psnark.config");
|
||||
int minutes = getStartupDelayMinutes();
|
||||
_messages.add("Starting up torrents in " + minutes + (minutes == 1 ? " minute" : " minutes"));
|
||||
_messages.add("Adding torrents in " + minutes + (minutes == 1 ? " minute" : " minutes"));
|
||||
I2PThread monitor = new I2PThread(new DirMonitor(), "Snark DirMonitor");
|
||||
monitor.setDaemon(true);
|
||||
monitor.start();
|
||||
@@ -52,7 +58,8 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
while (_messages.size() > MAX_MESSAGES)
|
||||
_messages.remove(0);
|
||||
}
|
||||
_log.info("MSG: " + message);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("MSG: " + message);
|
||||
}
|
||||
|
||||
/** newest last */
|
||||
@@ -90,10 +97,14 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
_config.setProperty(PROP_I2CP_HOST, "localhost");
|
||||
if (!_config.containsKey(PROP_I2CP_PORT))
|
||||
_config.setProperty(PROP_I2CP_PORT, "7654");
|
||||
if (!_config.containsKey(PROP_I2CP_OPTS))
|
||||
_config.setProperty(PROP_I2CP_OPTS, "inbound.length=1 inbound.lengthVariance=1 outbound.length=1 outbound.lengthVariance=1");
|
||||
if (!_config.containsKey(PROP_EEP_HOST))
|
||||
_config.setProperty(PROP_EEP_HOST, "localhost");
|
||||
if (!_config.containsKey(PROP_EEP_PORT))
|
||||
_config.setProperty(PROP_EEP_PORT, "4444");
|
||||
if (!_config.containsKey(PROP_UPLOADERS_TOTAL))
|
||||
_config.setProperty(PROP_UPLOADERS_TOTAL, "" + Snark.MAX_TOTAL_UPLOADERS);
|
||||
if (!_config.containsKey(PROP_DIR))
|
||||
_config.setProperty(PROP_DIR, "i2psnark");
|
||||
if (!_config.containsKey(PROP_AUTO_START))
|
||||
@@ -124,6 +135,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
int eepPort = getInt(PROP_EEP_PORT, 4444);
|
||||
if (eepHost != null)
|
||||
I2PSnarkUtil.instance().setProxy(eepHost, eepPort);
|
||||
I2PSnarkUtil.instance().setMaxUploaders(getInt(PROP_UPLOADERS_TOTAL, Snark.MAX_TOTAL_UPLOADERS));
|
||||
getDataDir().mkdirs();
|
||||
}
|
||||
|
||||
@@ -139,7 +151,8 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
}
|
||||
|
||||
public void updateConfig(String dataDir, boolean autoStart, String seedPct, String eepHost,
|
||||
String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts) {
|
||||
String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts,
|
||||
String upLimit) {
|
||||
boolean changed = false;
|
||||
if (eepHost != null) {
|
||||
int port = I2PSnarkUtil.instance().getEepProxyPort();
|
||||
@@ -154,6 +167,20 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
addMessage("EepProxy location changed to " + eepHost + ":" + port);
|
||||
}
|
||||
}
|
||||
if (upLimit != null) {
|
||||
int limit = I2PSnarkUtil.instance().getMaxUploaders();
|
||||
try { limit = Integer.parseInt(upLimit); } catch (NumberFormatException nfe) {}
|
||||
if ( limit != I2PSnarkUtil.instance().getEepProxyPort()) {
|
||||
if ( limit >= Snark.MIN_TOTAL_UPLOADERS ) {
|
||||
I2PSnarkUtil.instance().setMaxUploaders(limit);
|
||||
changed = true;
|
||||
_config.setProperty(PROP_UPLOADERS_TOTAL, "" + limit);
|
||||
addMessage("Total uploaders limit changed to " + limit);
|
||||
} else {
|
||||
addMessage("Minimum total uploaders limit is " + Snark.MIN_TOTAL_UPLOADERS);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i2cpHost != null) {
|
||||
int oldI2CPPort = I2PSnarkUtil.instance().getI2CPPort();
|
||||
String oldI2CPHost = I2PSnarkUtil.instance().getI2CPHost();
|
||||
@@ -247,7 +274,9 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
|
||||
public void saveConfig() {
|
||||
try {
|
||||
DataHelper.storeProps(_config, new File(_configFile));
|
||||
synchronized (_configFile) {
|
||||
DataHelper.storeProps(_config, new File(_configFile));
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
addMessage("Unable to save the config to '" + _configFile + "'");
|
||||
}
|
||||
@@ -264,8 +293,9 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
* Grab the torrent given the (canonical) filename
|
||||
*/
|
||||
public Snark getTorrent(String filename) { synchronized (_snarks) { return (Snark)_snarks.get(filename); } }
|
||||
public void addTorrent(String filename) {
|
||||
if (!I2PSnarkUtil.instance().connected()) {
|
||||
public void addTorrent(String filename) { addTorrent(filename, false); }
|
||||
public void addTorrent(String filename, boolean dontAutoStart) {
|
||||
if ((!dontAutoStart) && !I2PSnarkUtil.instance().connected()) {
|
||||
addMessage("Connecting to I2P");
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
if (!ok) {
|
||||
@@ -285,7 +315,16 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
Snark torrent = null;
|
||||
synchronized (_snarks) {
|
||||
torrent = (Snark)_snarks.get(filename);
|
||||
if (torrent == null) {
|
||||
}
|
||||
// don't hold the _snarks lock while verifying the torrent
|
||||
if (torrent == null) {
|
||||
synchronized (_addSnarkLock) {
|
||||
// double-check
|
||||
synchronized (_snarks) {
|
||||
if(_snarks.get(filename) != null)
|
||||
return;
|
||||
}
|
||||
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(sfile);
|
||||
@@ -301,7 +340,9 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
} else {
|
||||
torrent = new Snark(filename, null, -1, null, null, false, dataDir.getPath());
|
||||
torrent.completeListener = this;
|
||||
_snarks.put(filename, torrent);
|
||||
synchronized (_snarks) {
|
||||
_snarks.put(filename, torrent);
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
addMessage("Torrent in " + sfile.getName() + " is invalid: " + ioe.getMessage());
|
||||
@@ -311,13 +352,13 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
// ok, snark created, now lets start it up or configure it further
|
||||
File f = new File(filename);
|
||||
if (shouldAutoStart()) {
|
||||
if (!dontAutoStart && shouldAutoStart()) {
|
||||
torrent.startTorrent();
|
||||
addMessage("Torrent added and started: '" + f.getName() + "'");
|
||||
} else {
|
||||
@@ -325,13 +366,103 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp for a torrent from the config file
|
||||
*/
|
||||
public long getSavedTorrentTime(MetaInfo metainfo) {
|
||||
byte[] ih = metainfo.getInfoHash();
|
||||
String infohash = Base64.encode(ih);
|
||||
infohash = infohash.replace('=', '$');
|
||||
String time = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
|
||||
if (time == null)
|
||||
return 0;
|
||||
int comma = time.indexOf(',');
|
||||
if (comma <= 0)
|
||||
return 0;
|
||||
time = time.substring(0, comma);
|
||||
try { return Long.parseLong(time); } catch (NumberFormatException nfe) {}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the saved bitfield for a torrent from the config file.
|
||||
* Convert "." to a full bitfield.
|
||||
*/
|
||||
public BitField getSavedTorrentBitField(MetaInfo metainfo) {
|
||||
byte[] ih = metainfo.getInfoHash();
|
||||
String infohash = Base64.encode(ih);
|
||||
infohash = infohash.replace('=', '$');
|
||||
String bf = _config.getProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
|
||||
if (bf == null)
|
||||
return null;
|
||||
int comma = bf.indexOf(',');
|
||||
if (comma <= 0)
|
||||
return null;
|
||||
bf = bf.substring(comma + 1).trim();
|
||||
int len = metainfo.getPieces();
|
||||
if (bf.equals(".")) {
|
||||
BitField bitfield = new BitField(len);
|
||||
for (int i = 0; i < len; i++)
|
||||
bitfield.set(i);
|
||||
return bitfield;
|
||||
}
|
||||
byte[] bitfield = Base64.decode(bf);
|
||||
if (bitfield == null)
|
||||
return null;
|
||||
if (bitfield.length * 8 < len)
|
||||
return null;
|
||||
return new BitField(bitfield, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the completion status of a torrent and the current time in the config file
|
||||
* in the form "i2psnark.zmeta.$base64infohash=$time,$base64bitfield".
|
||||
* The config file property key is appended with the Base64 of the infohash,
|
||||
* with the '=' changed to '$' since a key can't contain '='.
|
||||
* The time is a standard long converted to string.
|
||||
* The status is either a bitfield converted to Base64 or "." for a completed
|
||||
* torrent to save space in the config file and in memory.
|
||||
*/
|
||||
public void saveTorrentStatus(MetaInfo metainfo, BitField bitfield) {
|
||||
byte[] ih = metainfo.getInfoHash();
|
||||
String infohash = Base64.encode(ih);
|
||||
infohash = infohash.replace('=', '$');
|
||||
String now = "" + System.currentTimeMillis();
|
||||
String bfs;
|
||||
if (bitfield.complete()) {
|
||||
bfs = ".";
|
||||
} else {
|
||||
byte[] bf = bitfield.getFieldBytes();
|
||||
bfs = Base64.encode(bf);
|
||||
}
|
||||
_config.setProperty(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX, now + "," + bfs);
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the status of a torrent from the config file.
|
||||
* This may help the config file from growing too big.
|
||||
*/
|
||||
public void removeTorrentStatus(MetaInfo metainfo) {
|
||||
byte[] ih = metainfo.getInfoHash();
|
||||
String infohash = Base64.encode(ih);
|
||||
infohash = infohash.replace('=', '$');
|
||||
_config.remove(PROP_META_PREFIX + infohash + PROP_META_BITFIELD_SUFFIX);
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
private String locked_validateTorrent(MetaInfo info) throws IOException {
|
||||
String announce = info.getAnnounce();
|
||||
// basic validation of url
|
||||
if ((!announce.startsWith("http://")) ||
|
||||
(announce.indexOf(".i2p/") < 0))
|
||||
return "Non-i2p tracker in " + info.getName() + ", deleting it";
|
||||
List files = info.getFiles();
|
||||
if ( (files != null) && (files.size() > MAX_FILES_PER_TORRENT) ) {
|
||||
return "Too many files in " + info.getName() + " (" + files.size() + "), deleting it";
|
||||
} else if (info.getPieces() <= 0) {
|
||||
return "No pieces in " + info.getName() + "? deleting it";
|
||||
} else if (info.getPieceLength(0) > 10*1024*1024) {
|
||||
} else if (info.getPieceLength(0) > 1*1024*1024) {
|
||||
return "Pieces are too large in " + info.getName() + " (" + info.getPieceLength(0)/1024 + "KB, deleting it";
|
||||
} else if (info.getTotalLength() > 10*1024*1024*1024l) {
|
||||
System.out.println("torrent info: " + info.toString());
|
||||
@@ -390,6 +521,8 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
if (torrent != null) {
|
||||
File torrentFile = new File(filename);
|
||||
torrentFile.delete();
|
||||
if (torrent.storage != null)
|
||||
removeTorrentStatus(torrent.storage.getMetaInfo());
|
||||
addMessage("Torrent removed: '" + torrentFile.getName() + "'");
|
||||
}
|
||||
}
|
||||
@@ -442,10 +575,9 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
if (existingNames.contains(foundNames.get(i))) {
|
||||
// already known. noop
|
||||
} else {
|
||||
if (I2PSnarkUtil.instance().connect())
|
||||
addTorrent((String)foundNames.get(i));
|
||||
else
|
||||
if (shouldAutoStart() && !I2PSnarkUtil.instance().connect())
|
||||
addMessage("Unable to connect to I2P");
|
||||
addTorrent((String)foundNames.get(i), !shouldAutoStart());
|
||||
}
|
||||
}
|
||||
// now lets see which ones have been removed...
|
||||
@@ -459,6 +591,48 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final String DEFAULT_TRACKERS[] = {
|
||||
"Postman", "http://YRgrgTLGnbTq2aZOZDJQ~o6Uk5k6TK-OZtx0St9pb0G-5EGYURZioxqYG8AQt~LgyyI~NCj6aYWpPO-150RcEvsfgXLR~CxkkZcVpgt6pns8SRc3Bi-QSAkXpJtloapRGcQfzTtwllokbdC-aMGpeDOjYLd8b5V9Im8wdCHYy7LRFxhEtGb~RL55DA8aYOgEXcTpr6RPPywbV~Qf3q5UK55el6Kex-6VCxreUnPEe4hmTAbqZNR7Fm0hpCiHKGoToRcygafpFqDw5frLXToYiqs9d4liyVB-BcOb0ihORbo0nS3CLmAwZGvdAP8BZ7cIYE3Z9IU9D1G8JCMxWarfKX1pix~6pIA-sp1gKlL1HhYhPMxwyxvuSqx34o3BqU7vdTYwWiLpGM~zU1~j9rHL7x60pVuYaXcFQDR4-QVy26b6Pt6BlAZoFmHhPcAuWfu-SFhjyZYsqzmEmHeYdAwa~HojSbofg0TMUgESRXMw6YThK1KXWeeJVeztGTz25sL8AAAA.i2p/announce.php=http://tracker.postman.i2p/"
|
||||
, "eBook", "http://E71FRom6PZNEqTN2Lr8P-sr23b7HJVC32KoGnVQjaX6zJiXwhJy2HsXob36Qmj81TYFZdewFZa9mSJ533UZgGyQkXo2ahctg82JKYZfDe5uDxAn1E9YPjxZCWJaFJh0S~UwSs~9AZ7UcauSJIoNtpxrtbmRNVFLqnkEDdLZi26TeucfOmiFmIWnVblLniWv3tG1boE9Abd-6j3FmYVrRucYuepAILYt6katmVNOk6sXmno1Eynrp~~MBuFq0Ko6~jsc2E2CRVYXDhGHEMdt-j6JUz5D7S2RIVzDRqQyAZLKJ7OdQDmI31przzmne1vOqqqLC~1xUumZVIvF~yOeJUGNjJ1Vx0J8i2BQIusn1pQJ6UCB~ZtZZLQtEb8EPVCfpeRi2ri1M5CyOuxN0V5ekmPHrYIBNevuTCRC26NP7ZS5VDgx1~NaC3A-CzJAE6f1QXi0wMI9aywNG5KGzOPifcsih8eyGyytvgLtrZtV7ykzYpPCS-rDfITncpn5hliPUAAAA.i2p/pub/bt/announce.php=http://de-ebook-archiv.i2p/pub/bt/"
|
||||
// , "Gaytorrents", "http://uxPWHbK1OIj9HxquaXuhMiIvi21iK0~ZiG9d8G0840ZXIg0r6CbiV71xlsqmdnU6wm0T2LySriM0doW2gUigo-5BNkUquHwOjLROiETnB3ZR0Ml4IGa6QBPn1aAq2d9~g1r1nVjLE~pcFnXB~cNNS7kIhX1d6nLgYVZf0C2cZopEow2iWVUggGGnAA9mHjE86zLEnTvAyhbAMTqDQJhEuLa0ZYSORqzJDMkQt90MV4YMjX1ICY6RfUSFmxEqu0yWTrkHsTtRw48l~dz9wpIgc0a0T9C~eeWvmBFTqlJPtQZwntpNeH~jF7nlYzB58olgV2HHFYpVYD87DYNzTnmNWxCJ5AfDorm6AIUCV2qaE7tZtI1h6fbmGpGlPyW~Kw5GXrRfJwNvr6ajwAVi~bPVnrBwDZezHkfW4slOO8FACPR28EQvaTu9nwhAbqESxV2hCTq6vQSGjuxHeOuzBOEvRWkLKOHWTC09t2DbJ94FSqETmZopTB1ukEmaxRWbKSIaAAAA.i2p/announce.php=http://gaytorrents.i2p/"
|
||||
, "NickyB", "http://9On6d3cZ27JjwYCtyJJbowe054d5tFnfMjv4PHsYs-EQn4Y4mk2zRixatvuAyXz2MmRfXG-NAUfhKr0KCxRNZbvHmlckYfT-WBzwwpiMAl0wDFY~Pl8cqXuhfikSG5WrqdPfDNNIBuuznS0dqaczf~OyVaoEOpvuP3qV6wKqbSSLpjOwwAaQPHjlRtNIW8-EtUZp-I0LT45HSoowp~6b7zYmpIyoATvIP~sT0g0MTrczWhbVTUZnEkZeLhOR0Duw1-IRXI2KHPbA24wLO9LdpKKUXed05RTz0QklW5ROgR6TYv7aXFufX8kC0-DaKvQ5JKG~h8lcoHvm1RCzNqVE-2aiZnO2xH08H-iCWoLNJE-Td2kT-Tsc~3QdQcnEUcL5BF-VT~QYRld2--9r0gfGl-yDrJZrlrihHGr5J7ImahelNn9PpkVp6eIyABRmJHf2iicrk3CtjeG1j9OgTSwaNmEpUpn4aN7Kx0zNLdH7z6uTgCGD9Kmh1MFYrsoNlTp4AAAA.i2p/bittorrent/announce.php=http://nickyb.i2p/bittorrent/"
|
||||
// , "Orion", "http://gKik1lMlRmuroXVGTZ~7v4Vez3L3ZSpddrGZBrxVriosCQf7iHu6CIk8t15BKsj~P0JJpxrofeuxtm7SCUAJEr0AIYSYw8XOmp35UfcRPQWyb1LsxUkMT4WqxAT3s1ClIICWlBu5An~q-Mm0VFlrYLIPBWlUFnfPR7jZ9uP5ZMSzTKSMYUWao3ejiykr~mtEmyls6g-ZbgKZawa9II4zjOy-hdxHgP-eXMDseFsrym4Gpxvy~3Fv9TuiSqhpgm~UeTo5YBfxn6~TahKtE~~sdCiSydqmKBhxAQ7uT9lda7xt96SS09OYMsIWxLeQUWhns-C~FjJPp1D~IuTrUpAFcVEGVL-BRMmdWbfOJEcWPZ~CBCQSO~VkuN1ebvIOr9JBerFMZSxZtFl8JwcrjCIBxeKPBmfh~xYh16BJm1BBBmN1fp2DKmZ2jBNkAmnUbjQOqWvUcehrykWk5lZbE7bjJMDFH48v3SXwRuDBiHZmSbsTY6zhGY~GkMQHNGxPMMSIAAAA.i2p/bt/announce.php=http://orion.i2p/bt/"
|
||||
// , "anonymity", "http://8EoJZIKrWgGuDrxA3nRJs1jsPfiGwmFWL91hBrf0HA7oKhEvAna4Ocx47VLUR9retVEYBAyWFK-eZTPcvhnz9XffBEiJQQ~kFSCqb1fV6IfPiV3HySqi9U5Caf6~hC46fRd~vYnxmaBLICT3N160cxBETqH3v2rdxdJpvYt8q4nMk9LUeVXq7zqCTFLLG5ig1uKgNzBGe58iNcsvTEYlnbYcE930ABmrzj8G1qQSgSwJ6wx3tUQNl1z~4wSOUMan~raZQD60lRK70GISjoX0-D0Po9WmPveN3ES3g72TIET3zc3WPdK2~lgmKGIs8GgNLES1cXTolvbPhdZK1gxddRMbJl6Y6IPFyQ9o4-6Rt3Lp-RMRWZ2TG7j2OMcNSiOmATUhKEFBDfv-~SODDyopGBmfeLw16F4NnYednvn4qP10dyMHcUASU6Zag4mfc2-WivrOqeWhD16fVAh8MoDpIIT~0r9XmwdaVFyLcjbXObabJczxCAW3fodQUnvuSkwzAAAA.i2p/anonymityTracker/announce.php=http://anonymityweb.i2p/anonymityTracker/"
|
||||
// , "The freak's tracker", "http://mHKva9x24E5Ygfey2llR1KyQHv5f8hhMpDMwJDg1U-hABpJ2NrQJd6azirdfaR0OKt4jDlmP2o4Qx0H598~AteyD~RJU~xcWYdcOE0dmJ2e9Y8-HY51ie0B1yD9FtIV72ZI-V3TzFDcs6nkdX9b81DwrAwwFzx0EfNvK1GLVWl59Ow85muoRTBA1q8SsZImxdyZ-TApTVlMYIQbdI4iQRwU9OmmtefrCe~ZOf4UBS9-KvNIqUL0XeBSqm0OU1jq-D10Ykg6KfqvuPnBYT1BYHFDQJXW5DdPKwcaQE4MtAdSGmj1epDoaEBUa9btQlFsM2l9Cyn1hzxqNWXELmx8dRlomQLlV4b586dRzW~fLlOPIGC13ntPXogvYvHVyEyptXkv890jC7DZNHyxZd5cyrKC36r9huKvhQAmNABT2Y~pOGwVrb~RpPwT0tBuPZ3lHYhBFYmD8y~AOhhNHKMLzea1rfwTvovBMByDdFps54gMN1mX4MbCGT4w70vIopS9yAAAA.i2p/bytemonsoon/announce.php"
|
||||
};
|
||||
|
||||
/** comma delimited list of name=announceURL=baseURL for the trackers to be displayed */
|
||||
public static final String PROP_TRACKERS = "i2psnark.trackers";
|
||||
private static Map trackerMap = null;
|
||||
/** sorted map of name to announceURL=baseURL */
|
||||
public Map getTrackers() {
|
||||
if (trackerMap != null) // only do this once, can't be updated while running
|
||||
return trackerMap;
|
||||
Map rv = new TreeMap();
|
||||
String trackers = _config.getProperty(PROP_TRACKERS);
|
||||
if ( (trackers == null) || (trackers.trim().length() <= 0) )
|
||||
trackers = _context.getProperty(PROP_TRACKERS);
|
||||
if ( (trackers == null) || (trackers.trim().length() <= 0) ) {
|
||||
for (int i = 0; i < DEFAULT_TRACKERS.length; i += 2)
|
||||
rv.put(DEFAULT_TRACKERS[i], DEFAULT_TRACKERS[i+1]);
|
||||
} else {
|
||||
StringTokenizer tok = new StringTokenizer(trackers, ",");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String pair = tok.nextToken();
|
||||
int split = pair.indexOf('=');
|
||||
if (split <= 0)
|
||||
continue;
|
||||
String name = pair.substring(0, split).trim();
|
||||
String url = pair.substring(split+1).trim();
|
||||
if ( (name.length() > 0) && (url.length() > 0) )
|
||||
rv.put(name, url);
|
||||
}
|
||||
}
|
||||
|
||||
trackerMap = rv;
|
||||
return trackerMap;
|
||||
}
|
||||
|
||||
private static class TorrentFilenameFilter implements FilenameFilter {
|
||||
private static final TorrentFilenameFilter _filter = new TorrentFilenameFilter();
|
||||
|
||||
@@ -39,15 +39,17 @@ public class Storage
|
||||
|
||||
private final StorageListener listener;
|
||||
|
||||
private final BitField bitfield;
|
||||
private int needed;
|
||||
private BitField bitfield; // BitField to represent the pieces
|
||||
private int needed; // Number of pieces needed
|
||||
|
||||
// XXX - Not always set correctly
|
||||
int piece_size;
|
||||
int pieces;
|
||||
boolean changed;
|
||||
|
||||
/** The default piece size. */
|
||||
private static int MIN_PIECE_SIZE = 256*1024;
|
||||
private static int MAX_PIECE_SIZE = 1024*1024;
|
||||
/** The maximum number of pieces in a torrent. */
|
||||
private static long MAX_PIECES = 100*1024/20;
|
||||
|
||||
@@ -75,7 +77,6 @@ public class Storage
|
||||
throws IOException
|
||||
{
|
||||
this.listener = listener;
|
||||
|
||||
// Create names, rafs and lengths arrays.
|
||||
getFiles(baseFile);
|
||||
|
||||
@@ -90,7 +91,7 @@ public class Storage
|
||||
|
||||
piece_size = MIN_PIECE_SIZE;
|
||||
pieces = (int) ((total - 1)/piece_size) + 1;
|
||||
while (pieces > MAX_PIECES)
|
||||
while (pieces > MAX_PIECES && piece_size < MAX_PIECE_SIZE)
|
||||
{
|
||||
piece_size = piece_size*2;
|
||||
pieces = (int) ((total - 1)/piece_size) +1;
|
||||
@@ -116,7 +117,8 @@ public class Storage
|
||||
}
|
||||
|
||||
String name = baseFile.getName();
|
||||
if (files.size() == 1)
|
||||
if (files.size() == 1) // FIXME: ...and if base file not a directory or should this be the only check?
|
||||
// this makes a bad metainfo if the directory has only one file in it
|
||||
{
|
||||
files = null;
|
||||
lengthsList = null;
|
||||
@@ -131,13 +133,14 @@ public class Storage
|
||||
// Creates piece hases for a new storage.
|
||||
public void create() throws IOException
|
||||
{
|
||||
if (true) {
|
||||
// if (true) {
|
||||
fast_digestCreate();
|
||||
} else {
|
||||
orig_digestCreate();
|
||||
}
|
||||
// } else {
|
||||
// orig_digestCreate();
|
||||
// }
|
||||
}
|
||||
|
||||
/*
|
||||
private void orig_digestCreate() throws IOException {
|
||||
// Calculate piece_hashes
|
||||
MessageDigest digest = null;
|
||||
@@ -155,7 +158,7 @@ public class Storage
|
||||
byte[] piece = new byte[piece_size];
|
||||
for (int i = 0; i < pieces; i++)
|
||||
{
|
||||
int length = getUncheckedPiece(i, piece, 0);
|
||||
int length = getUncheckedPiece(i, piece);
|
||||
digest.update(piece, 0, length);
|
||||
byte[] hash = digest.digest();
|
||||
for (int j = 0; j < 20; j++)
|
||||
@@ -173,6 +176,7 @@ public class Storage
|
||||
// Reannounce to force recalculating the info_hash.
|
||||
metainfo = metainfo.reannounce(metainfo.getAnnounce());
|
||||
}
|
||||
*/
|
||||
|
||||
private void fast_digestCreate() throws IOException {
|
||||
// Calculate piece_hashes
|
||||
@@ -183,7 +187,7 @@ public class Storage
|
||||
byte[] piece = new byte[piece_size];
|
||||
for (int i = 0; i < pieces; i++)
|
||||
{
|
||||
int length = getUncheckedPiece(i, piece, 0);
|
||||
int length = getUncheckedPiece(i, piece);
|
||||
digest.update(piece, 0, length);
|
||||
byte[] hash = digest.digest();
|
||||
for (int j = 0; j < 20; j++)
|
||||
@@ -218,6 +222,8 @@ public class Storage
|
||||
{
|
||||
File f = (File)it.next();
|
||||
names[i] = f.getPath();
|
||||
if (base.isDirectory() && names[i].startsWith(base.getPath()))
|
||||
names[i] = names[i].substring(base.getPath().length() + 1);
|
||||
lengths[i] = f.length();
|
||||
rafs[i] = new RandomAccessFile(f, "r");
|
||||
i++;
|
||||
@@ -281,6 +287,10 @@ public class Storage
|
||||
public void check(String rootDir) throws IOException
|
||||
{
|
||||
File base = new File(rootDir, filterName(metainfo.getName()));
|
||||
// look for saved bitfield and timestamp in the config file
|
||||
long savedTime = SnarkManager.instance().getSavedTorrentTime(metainfo);
|
||||
BitField savedBitField = SnarkManager.instance().getSavedTorrentBitField(metainfo);
|
||||
boolean useSavedBitField = savedTime > 0 && savedBitField != null;
|
||||
|
||||
List files = metainfo.getFiles();
|
||||
if (files == null)
|
||||
@@ -294,6 +304,11 @@ public class Storage
|
||||
rafs = new RandomAccessFile[1];
|
||||
names = new String[1];
|
||||
lengths[0] = metainfo.getTotalLength();
|
||||
if (useSavedBitField) {
|
||||
long lm = base.lastModified();
|
||||
if (lm <= 0 || lm > savedTime)
|
||||
useSavedBitField = false;
|
||||
}
|
||||
if (base.exists() && !base.canWrite()) // hope we can get away with this, if we are only seeding...
|
||||
rafs[0] = new RandomAccessFile(base, "r");
|
||||
else
|
||||
@@ -318,6 +333,11 @@ public class Storage
|
||||
File f = createFileFromNames(base, (List)files.get(i));
|
||||
lengths[i] = ((Long)ls.get(i)).longValue();
|
||||
total += lengths[i];
|
||||
if (useSavedBitField) {
|
||||
long lm = base.lastModified();
|
||||
if (lm <= 0 || lm > savedTime)
|
||||
useSavedBitField = false;
|
||||
}
|
||||
if (f.exists() && !f.canWrite()) // see above re: only seeding
|
||||
rafs[i] = new RandomAccessFile(f, "r");
|
||||
else
|
||||
@@ -331,13 +351,63 @@ public class Storage
|
||||
throw new IOException("File lengths do not add up "
|
||||
+ total + " != " + metalength);
|
||||
}
|
||||
checkCreateFiles();
|
||||
if (useSavedBitField) {
|
||||
bitfield = savedBitField;
|
||||
needed = metainfo.getPieces() - bitfield.count();
|
||||
Snark.debug("Found saved state and files unchanged, skipping check", Snark.NOTICE);
|
||||
} else {
|
||||
checkCreateFiles();
|
||||
SnarkManager.instance().saveTorrentStatus(metainfo, bitfield);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reopen the file descriptors for a restart
|
||||
* Do existence check but no length check or data reverification
|
||||
*/
|
||||
public void reopen(String rootDir) throws IOException
|
||||
{
|
||||
File base = new File(rootDir, filterName(metainfo.getName()));
|
||||
|
||||
List files = metainfo.getFiles();
|
||||
if (files == null)
|
||||
{
|
||||
// Reopen base as file.
|
||||
Snark.debug("Reopening file: " + base, Snark.NOTICE);
|
||||
if (!base.exists())
|
||||
throw new IOException("Could not reopen file " + base);
|
||||
|
||||
if (complete() || !base.canWrite()) // hope we can get away with this, if we are only seeding...
|
||||
rafs[0] = new RandomAccessFile(base, "r");
|
||||
else
|
||||
rafs[0] = new RandomAccessFile(base, "rw");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reopen base as dir.
|
||||
Snark.debug("Reopening directory: " + base, Snark.NOTICE);
|
||||
if (!base.isDirectory())
|
||||
throw new IOException("Could not reopen directory " + base);
|
||||
|
||||
int size = files.size();
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
File f = getFileFromNames(base, (List)files.get(i));
|
||||
if (!f.exists())
|
||||
throw new IOException("Could not reopen file " + f);
|
||||
if (complete() || !f.canWrite()) // see above re: only seeding
|
||||
rafs[i] = new RandomAccessFile(f, "r");
|
||||
else
|
||||
rafs[i] = new RandomAccessFile(f, "rw");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes 'suspicious' characters from the give file name.
|
||||
*/
|
||||
private String filterName(String name)
|
||||
private static String filterName(String name)
|
||||
{
|
||||
// XXX - Is this enough?
|
||||
return name.replace(File.separatorChar, '_');
|
||||
@@ -369,6 +439,17 @@ public class Storage
|
||||
return f;
|
||||
}
|
||||
|
||||
public static File getFileFromNames(File base, List names)
|
||||
{
|
||||
Iterator it = names.iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
String name = filterName((String)it.next());
|
||||
base = new File(base, name);
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
private void checkCreateFiles() throws IOException
|
||||
{
|
||||
// Whether we are resuming or not,
|
||||
@@ -399,7 +480,7 @@ public class Storage
|
||||
byte[] piece = new byte[metainfo.getPieceLength(0)];
|
||||
for (int i = 0; i < pieces; i++)
|
||||
{
|
||||
int length = getUncheckedPiece(i, piece, 0);
|
||||
int length = getUncheckedPiece(i, piece);
|
||||
boolean correctHash = metainfo.checkPiece(i, piece, 0, length);
|
||||
if (correctHash)
|
||||
{
|
||||
@@ -459,19 +540,27 @@ public class Storage
|
||||
// gobble gobble
|
||||
}
|
||||
}
|
||||
changed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a byte array containing the requested piece or null if
|
||||
* Returns a byte array containing a portion of the requested piece or null if
|
||||
* the storage doesn't contain the piece yet.
|
||||
*/
|
||||
public byte[] getPiece(int piece) throws IOException
|
||||
public byte[] getPiece(int piece, int off, int len) throws IOException
|
||||
{
|
||||
if (!bitfield.get(piece))
|
||||
return null;
|
||||
|
||||
byte[] bs = new byte[metainfo.getPieceLength(piece)];
|
||||
getUncheckedPiece(piece, bs, 0);
|
||||
//Catch a common place for OOMs esp. on 1MB pieces
|
||||
byte[] bs;
|
||||
try {
|
||||
bs = new byte[len];
|
||||
} catch (OutOfMemoryError oom) {
|
||||
I2PSnarkUtil.instance().debug("Out of memory, can't honor request for piece " + piece, Snark.WARNING, oom);
|
||||
return null;
|
||||
}
|
||||
getUncheckedPiece(piece, bs, off, len);
|
||||
return bs;
|
||||
}
|
||||
|
||||
@@ -482,10 +571,11 @@ public class Storage
|
||||
* matches), otherwise false.
|
||||
* @exception IOException when some storage related error occurs.
|
||||
*/
|
||||
public boolean putPiece(int piece, byte[] bs) throws IOException
|
||||
public boolean putPiece(int piece, byte[] ba) throws IOException
|
||||
{
|
||||
// First check if the piece is correct.
|
||||
// If we were paranoia we could copy the array first.
|
||||
// Copy the array first to be paranoid.
|
||||
byte[] bs = (byte[]) ba.clone();
|
||||
int length = bs.length;
|
||||
boolean correctHash = metainfo.checkPiece(piece, bs, 0, length);
|
||||
if (listener != null)
|
||||
@@ -504,9 +594,11 @@ public class Storage
|
||||
needed--;
|
||||
complete = needed == 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
long start = piece * metainfo.getPieceLength(0);
|
||||
// Early typecast, avoid possibly overflowing a temp integer
|
||||
long start = (long) piece * (long) metainfo.getPieceLength(0);
|
||||
int i = 0;
|
||||
long raflen = lengths[i];
|
||||
while (start > raflen)
|
||||
@@ -536,21 +628,48 @@ public class Storage
|
||||
}
|
||||
}
|
||||
|
||||
changed = true;
|
||||
if (complete) {
|
||||
listener.storageCompleted(this);
|
||||
// listener.storageCompleted(this);
|
||||
// do we also need to close all of the files and reopen
|
||||
// them readonly?
|
||||
|
||||
// Do a complete check to be sure.
|
||||
// Temporarily resets the 'needed' variable and 'bitfield', then call
|
||||
// checkCreateFiles() which will set 'needed' and 'bitfield'
|
||||
// and also call listener.storageCompleted() if the double-check
|
||||
// was successful.
|
||||
// Todo: set a listener variable so the web shows "checking" and don't
|
||||
// have the user panic when completed amount goes to zero temporarily?
|
||||
needed = metainfo.getPieces();
|
||||
bitfield = new BitField(needed);
|
||||
checkCreateFiles();
|
||||
if (needed > 0) {
|
||||
listener.setWantedPieces(this);
|
||||
Snark.debug("WARNING: Not really done, missing " + needed
|
||||
+ " pieces", Snark.WARNING);
|
||||
} else {
|
||||
SnarkManager.instance().saveTorrentStatus(metainfo, bitfield);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private int getUncheckedPiece(int piece, byte[] bs, int off)
|
||||
private int getUncheckedPiece(int piece, byte[] bs)
|
||||
throws IOException
|
||||
{
|
||||
return getUncheckedPiece(piece, bs, 0, metainfo.getPieceLength(piece));
|
||||
}
|
||||
|
||||
private int getUncheckedPiece(int piece, byte[] bs, int off, int length)
|
||||
throws IOException
|
||||
{
|
||||
// XXX - copy/paste code from putPiece().
|
||||
long start = piece * metainfo.getPieceLength(0);
|
||||
int length = metainfo.getPieceLength(piece);
|
||||
|
||||
// Early typecast, avoid possibly overflowing a temp integer
|
||||
long start = ((long) piece * (long) metainfo.getPieceLength(0)) + off;
|
||||
|
||||
int i = 0;
|
||||
long raflen = lengths[i];
|
||||
while (start > raflen)
|
||||
@@ -568,7 +687,7 @@ public class Storage
|
||||
synchronized(rafs[i])
|
||||
{
|
||||
rafs[i].seek(start);
|
||||
rafs[i].readFully(bs, off + read, len);
|
||||
rafs[i].readFully(bs, read, len);
|
||||
}
|
||||
read += len;
|
||||
if (need - len > 0)
|
||||
|
||||
@@ -55,4 +55,11 @@ public interface StorageListener
|
||||
*
|
||||
*/
|
||||
void storageCompleted(Storage storage);
|
||||
|
||||
/** Reset the peer's wanted pieces table
|
||||
* Call after the storage double-check fails
|
||||
*
|
||||
* @param peer the peer
|
||||
*/
|
||||
void setWantedPieces(Storage storage);
|
||||
}
|
||||
|
||||
@@ -41,8 +41,11 @@ public class TrackerClient extends I2PThread
|
||||
private static final String STARTED_EVENT = "started";
|
||||
private static final String COMPLETED_EVENT = "completed";
|
||||
private static final String STOPPED_EVENT = "stopped";
|
||||
private static final String NOT_REGISTERED = "torrent not registered"; //bytemonsoon
|
||||
|
||||
private final static int SLEEP = 5; // 5 minutes.
|
||||
private final static int DELAY_MIN = 2000; // 2 secs.
|
||||
private final static int DELAY_MUL = 1500; // 1.5 secs.
|
||||
|
||||
private final MetaInfo meta;
|
||||
private final PeerCoordinator coordinator;
|
||||
@@ -110,6 +113,7 @@ public class TrackerClient extends I2PThread
|
||||
long left = coordinator.getLeft();
|
||||
|
||||
boolean completed = (left == 0);
|
||||
int sleptTime = 0;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -117,6 +121,7 @@ public class TrackerClient extends I2PThread
|
||||
boolean started = false;
|
||||
while (!started)
|
||||
{
|
||||
sleptTime = 0;
|
||||
try
|
||||
{
|
||||
// Send start.
|
||||
@@ -125,18 +130,20 @@ public class TrackerClient extends I2PThread
|
||||
STARTED_EVENT);
|
||||
Set peers = info.getPeers();
|
||||
coordinator.trackerSeenPeers = peers.size();
|
||||
coordinator.trackerProblems = null;
|
||||
if (!completed) {
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext()) {
|
||||
Peer cur = (Peer)it.next();
|
||||
coordinator.addPeer(cur);
|
||||
int delay = 3000;
|
||||
int c = ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
|
||||
try { Thread.sleep(delay * c); } catch (InterruptedException ie) {}
|
||||
int delay = DELAY_MUL;
|
||||
delay *= ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
|
||||
delay += DELAY_MIN;
|
||||
sleptTime += delay;
|
||||
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
started = true;
|
||||
coordinator.trackerProblems = null;
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
@@ -145,9 +152,16 @@ public class TrackerClient extends I2PThread
|
||||
("WARNING: Could not contact tracker at '"
|
||||
+ announce + "': " + ioe, Snark.WARNING);
|
||||
coordinator.trackerProblems = ioe.getMessage();
|
||||
if (coordinator.trackerProblems.toLowerCase().startsWith(NOT_REGISTERED)) {
|
||||
stop = true;
|
||||
coordinator.snark.stopTorrent();
|
||||
}
|
||||
}
|
||||
|
||||
if (!started && !stop)
|
||||
if (stop)
|
||||
break;
|
||||
|
||||
if (!started)
|
||||
{
|
||||
Snark.debug(" Retrying in one minute...", Snark.DEBUG);
|
||||
try
|
||||
@@ -168,8 +182,17 @@ public class TrackerClient extends I2PThread
|
||||
try
|
||||
{
|
||||
// Sleep some minutes...
|
||||
int delay = SLEEP*60*1000 + r.nextInt(120*1000);
|
||||
Thread.sleep(delay);
|
||||
int delay;
|
||||
if(coordinator.trackerProblems != null && !completed) {
|
||||
delay = 60*1000;
|
||||
} else if(completed) {
|
||||
delay = 3*SLEEP*60*1000 + r.nextInt(120*1000);
|
||||
} else {
|
||||
delay = SLEEP*60*1000 + r.nextInt(120*1000);
|
||||
delay -= sleptTime;
|
||||
}
|
||||
if (delay > 0)
|
||||
Thread.sleep(delay);
|
||||
}
|
||||
catch(InterruptedException interrupt)
|
||||
{
|
||||
@@ -196,6 +219,7 @@ public class TrackerClient extends I2PThread
|
||||
event = NO_EVENT;
|
||||
|
||||
// Only do a request when necessary.
|
||||
sleptTime = 0;
|
||||
if (event == COMPLETED_EVENT
|
||||
|| coordinator.needPeers()
|
||||
|| System.currentTimeMillis() > lastRequestTime + interval)
|
||||
@@ -206,6 +230,7 @@ public class TrackerClient extends I2PThread
|
||||
uploaded, downloaded, left,
|
||||
event);
|
||||
|
||||
coordinator.trackerProblems = null;
|
||||
Set peers = info.getPeers();
|
||||
coordinator.trackerSeenPeers = peers.size();
|
||||
if ( (left > 0) && (!completed) ) {
|
||||
@@ -216,10 +241,14 @@ public class TrackerClient extends I2PThread
|
||||
Iterator it = ordered.iterator();
|
||||
while (it.hasNext()) {
|
||||
Peer cur = (Peer)it.next();
|
||||
coordinator.addPeer(cur);
|
||||
int delay = 3000;
|
||||
int c = ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
|
||||
try { Thread.sleep(delay * c); } catch (InterruptedException ie) {}
|
||||
// only delay if we actually make an attempt to add peer
|
||||
if(coordinator.addPeer(cur)) {
|
||||
int delay = DELAY_MUL;
|
||||
delay *= ((int)cur.getPeerID().getAddress().calculateHash().toBase64().charAt(0)) % 10;
|
||||
delay += DELAY_MIN;
|
||||
sleptTime += delay;
|
||||
try { Thread.sleep(delay); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -229,6 +258,11 @@ public class TrackerClient extends I2PThread
|
||||
Snark.debug
|
||||
("WARNING: Could not contact tracker at '"
|
||||
+ announce + "': " + ioe, Snark.WARNING);
|
||||
coordinator.trackerProblems = ioe.getMessage();
|
||||
if (coordinator.trackerProblems.toLowerCase().startsWith(NOT_REGISTERED)) {
|
||||
stop = true;
|
||||
coordinator.snark.stopTorrent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,13 +300,13 @@ public class TrackerClient extends I2PThread
|
||||
+ "&downloaded=" + downloaded
|
||||
+ "&left=" + left
|
||||
+ ((event != NO_EVENT) ? ("&event=" + event) : "");
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("Sending TrackerClient request: " + s, Snark.INFO);
|
||||
Snark.debug("Sending TrackerClient request: " + s, Snark.INFO);
|
||||
|
||||
File fetched = I2PSnarkUtil.instance().get(s);
|
||||
if (fetched == null) {
|
||||
throw new IOException("Error fetching " + s);
|
||||
}
|
||||
fetched.deleteOnExit();
|
||||
|
||||
InputStream in = null;
|
||||
try {
|
||||
@@ -280,8 +314,7 @@ public class TrackerClient extends I2PThread
|
||||
|
||||
TrackerInfo info = new TrackerInfo(in, coordinator.getID(),
|
||||
coordinator.getMetaInfo());
|
||||
if (Snark.debug >= Snark.INFO)
|
||||
Snark.debug("TrackerClient response: " + info, Snark.INFO);
|
||||
Snark.debug("TrackerClient response: " + info, Snark.INFO);
|
||||
lastRequestTime = System.currentTimeMillis();
|
||||
|
||||
String failure = info.getFailureReason();
|
||||
@@ -300,7 +333,7 @@ public class TrackerClient extends I2PThread
|
||||
* Very lazy byte[] to URL encoder. Just encodes everything, even
|
||||
* "normal" chars.
|
||||
*/
|
||||
static String urlencode(byte[] bs)
|
||||
public static String urlencode(byte[] bs)
|
||||
{
|
||||
StringBuffer sb = new StringBuffer(bs.length*3);
|
||||
for (int i = 0; i < bs.length; i++)
|
||||
|
||||
@@ -43,22 +43,49 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
req.setCharacterEncoding("UTF-8");
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("text/html; charset=UTF-8");
|
||||
long stats[] = {0,0,0,0};
|
||||
|
||||
String nonce = req.getParameter("nonce");
|
||||
if ( (nonce != null) && (nonce.equals(String.valueOf(_nonce))) )
|
||||
processRequest(req);
|
||||
|
||||
String peerParam = req.getParameter("p");
|
||||
String peerString;
|
||||
if (peerParam == null) {
|
||||
peerString = "";
|
||||
} else {
|
||||
peerString = "?p=" + peerParam;
|
||||
}
|
||||
|
||||
PrintWriter out = resp.getWriter();
|
||||
out.write(HEADER_BEGIN);
|
||||
// we want it to go to the base URI so we don't refresh with some funky action= value
|
||||
out.write("<meta http-equiv=\"refresh\" content=\"60;" + req.getRequestURI() + "\">\n");
|
||||
out.write("<meta http-equiv=\"refresh\" content=\"60;" + req.getRequestURI() + peerString + "\">\n");
|
||||
out.write(HEADER);
|
||||
|
||||
out.write("<table border=\"0\" width=\"100%\">\n");
|
||||
out.write("<tr><td width=\"5%\" class=\"snarkTitle\" valign=\"top\" align=\"left\">");
|
||||
out.write("<tr><td width=\"20%\" class=\"snarkTitle\" valign=\"top\" align=\"left\">");
|
||||
out.write("I2PSnark<br />\n");
|
||||
out.write("<a href=\"" + req.getRequestURI() + "\" class=\"snarkRefresh\">Refresh</a>\n");
|
||||
out.write("</td><td width=\"95%\" class=\"snarkMessages\" valign=\"top\" align=\"left\"><pre>");
|
||||
out.write("<table border=\"0\" width=\"100%\">\n");
|
||||
out.write("<tr><td><a href=\"" + req.getRequestURI() + peerString + "\" class=\"snarkRefresh\">Refresh</a>\n");
|
||||
out.write("<td><a href=\"http://forum.i2p/viewforum.php?f=21\" class=\"snarkRefresh\">Forum</a>\n");
|
||||
int count = 0;
|
||||
Map trackers = _manager.getTrackers();
|
||||
for (Iterator iter = trackers.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
String baseURL = (String)trackers.get(name);
|
||||
int e = baseURL.indexOf('=');
|
||||
if (e < 0)
|
||||
continue;
|
||||
baseURL = baseURL.substring(e + 1);
|
||||
if (count++ % 2 == 0)
|
||||
out.write("<tr>");
|
||||
out.write("<td><a href=\"" + baseURL + "\" class=\"snarkRefresh\">" + name + "</a>\n");
|
||||
}
|
||||
if (count % 2 == 1)
|
||||
out.write("<td> \n");
|
||||
out.write("</table>\n");
|
||||
out.write("</td><td width=\"80%\" class=\"snarkMessages\" valign=\"top\" align=\"left\"><pre>");
|
||||
List msgs = _manager.getMessages();
|
||||
for (int i = msgs.size()-1; i >= 0; i--) {
|
||||
String msg = (String)msgs.get(i);
|
||||
@@ -66,21 +93,44 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
}
|
||||
out.write("</pre></td></tr></table>\n");
|
||||
|
||||
out.write(TABLE_HEADER);
|
||||
|
||||
List snarks = getSortedSnarks(req);
|
||||
String uri = req.getRequestURI();
|
||||
out.write(TABLE_HEADER);
|
||||
if (I2PSnarkUtil.instance().connected() && snarks.size() > 0) {
|
||||
if (peerParam != null)
|
||||
out.write("(<a href=\"" + req.getRequestURI() + "\">Hide Peers</a>)<br />\n");
|
||||
else
|
||||
out.write("(<a href=\"" + req.getRequestURI() + "?p=1" + "\">Show Peers</a>)<br />\n");
|
||||
}
|
||||
out.write(TABLE_HEADER2);
|
||||
out.write("<th align=\"left\" valign=\"top\">");
|
||||
if (I2PSnarkUtil.instance().connected())
|
||||
out.write("<a href=\"" + uri + "?action=StopAll&nonce=" + _nonce +
|
||||
"\" title=\"Stop all torrents and the i2p tunnel\">Stop All</a>");
|
||||
else
|
||||
out.write(" ");
|
||||
out.write("</th></tr></thead>\n");
|
||||
for (int i = 0; i < snarks.size(); i++) {
|
||||
Snark snark = (Snark)snarks.get(i);
|
||||
displaySnark(out, snark, uri, i);
|
||||
boolean showPeers = "1".equals(peerParam) || Base64.encode(snark.meta.getInfoHash()).equals(peerParam);
|
||||
displaySnark(out, snark, uri, i, stats, showPeers);
|
||||
}
|
||||
if (snarks.size() <= 0) {
|
||||
out.write(TABLE_EMPTY);
|
||||
} else if (snarks.size() > 1) {
|
||||
out.write(TABLE_TOTAL);
|
||||
out.write(" <th align=\"right\" valign=\"top\">" + formatSize(stats[0]) + "</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">" + formatSize(stats[1]) + "</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">" + formatSize(stats[2]) + "ps</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">" + formatSize(stats[3]) + "ps</th>\n" +
|
||||
" <th> </th></tr>\n" +
|
||||
"</tfoot>\n");
|
||||
}
|
||||
|
||||
out.write(TABLE_FOOTER);
|
||||
|
||||
writeAddForm(out, req);
|
||||
if (false) // seeding needs to register the torrent first (boo, hiss)
|
||||
if (true) // seeding needs to register the torrent first, so we can't start it automatically (boo, hiss)
|
||||
writeSeedForm(out, req);
|
||||
writeConfigForm(out, req);
|
||||
out.write(FOOTER);
|
||||
@@ -127,7 +177,7 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
}
|
||||
} else if ( (newURL != null) && (newURL.trim().length() > "http://.i2p/".length()) ) {
|
||||
_manager.addMessage("Fetching " + newURL);
|
||||
I2PThread fetch = new I2PThread(new FetchAndAdd(newURL), "Fetch and add");
|
||||
I2PThread fetch = new I2PThread(new FetchAndAdd(_manager, newURL), "Fetch and add");
|
||||
fetch.start();
|
||||
} else {
|
||||
// no file or URL specified
|
||||
@@ -198,21 +248,31 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
_manager.addMessage("Torrent file deleted: " + f.getAbsolutePath());
|
||||
List files = snark.meta.getFiles();
|
||||
String dataFile = snark.meta.getName();
|
||||
for (int i = 0; files != null && i < files.size(); i++) {
|
||||
File df = new File(_manager.getDataDir(), (String)files.get(i));
|
||||
boolean deleted = FileUtil.rmdir(df, false);
|
||||
if (deleted)
|
||||
_manager.addMessage("Data dir deleted: " + df.getAbsolutePath());
|
||||
else
|
||||
_manager.addMessage("Data dir could not be deleted: " + df.getAbsolutePath());
|
||||
}
|
||||
if (dataFile != null) {
|
||||
f = new File(_manager.getDataDir(), dataFile);
|
||||
boolean deleted = f.delete();
|
||||
if (deleted)
|
||||
f = new File(_manager.getDataDir(), dataFile);
|
||||
if (files == null) { // single file torrent
|
||||
if (f.delete())
|
||||
_manager.addMessage("Data file deleted: " + f.getAbsolutePath());
|
||||
else
|
||||
_manager.addMessage("Data file could not be deleted: " + f.getAbsolutePath());
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < files.size(); i++) { // pass 1 delete files
|
||||
// multifile torrents have the getFiles() return lists of lists of filenames, but
|
||||
// each of those lists just contain a single file afaict...
|
||||
File df = Storage.getFileFromNames(f, (List) files.get(i));
|
||||
if (df.delete())
|
||||
_manager.addMessage("Data file deleted: " + df.getAbsolutePath());
|
||||
else
|
||||
_manager.addMessage("Data file could not be deleted: " + df.getAbsolutePath());
|
||||
}
|
||||
for (int i = files.size() - 1; i >= 0; i--) { // pass 2 delete dirs - not foolproof,
|
||||
// we could sort and do a strict bottom-up
|
||||
File df = Storage.getFileFromNames(f, (List) files.get(i));
|
||||
df = df.getParentFile();
|
||||
if (df == null || !df.exists())
|
||||
continue;
|
||||
if(df.delete())
|
||||
_manager.addMessage("Data dir deleted: " + df.getAbsolutePath());
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -228,7 +288,8 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
String i2cpHost = req.getParameter("i2cpHost");
|
||||
String i2cpPort = req.getParameter("i2cpPort");
|
||||
String i2cpOpts = req.getParameter("i2cpOpts");
|
||||
_manager.updateConfig(dataDir, autoStart, seedPct, eepHost, eepPort, i2cpHost, i2cpPort, i2cpOpts);
|
||||
String upLimit = req.getParameter("upLimit");
|
||||
_manager.updateConfig(dataDir, autoStart, seedPct, eepHost, eepPort, i2cpHost, i2cpPort, i2cpOpts, upLimit);
|
||||
} else if ("Create torrent".equals(action)) {
|
||||
String baseData = req.getParameter("baseFile");
|
||||
if (baseData != null) {
|
||||
@@ -238,76 +299,44 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
if ( (announceURLOther != null) && (announceURLOther.trim().length() > "http://.i2p/announce".length()) )
|
||||
announceURL = announceURLOther;
|
||||
|
||||
if (baseFile.exists()) {
|
||||
if (announceURL == null || announceURL.length() <= 0)
|
||||
_manager.addMessage("Error creating torrent - you must select a tracker");
|
||||
else if (baseFile.exists()) {
|
||||
try {
|
||||
Storage s = new Storage(baseFile, announceURL, null);
|
||||
s.create();
|
||||
s.close(); // close the files... maybe need a way to pass this Storage to addTorrent rather than starting over
|
||||
MetaInfo info = s.getMetaInfo();
|
||||
File torrentFile = new File(baseFile.getParent(), baseFile.getName() + ".torrent");
|
||||
if (torrentFile.exists())
|
||||
throw new IOException("Cannot overwrite an existing .torrent file: " + torrentFile.getPath());
|
||||
_manager.saveTorrentStatus(info, s.getBitField()); // so addTorrent won't recheck
|
||||
// DirMonitor could grab this first, maybe hold _snarks lock?
|
||||
FileOutputStream out = new FileOutputStream(torrentFile);
|
||||
out.write(info.getTorrentData());
|
||||
out.close();
|
||||
_manager.addMessage("Torrent created for " + baseFile.getName() + ": " + torrentFile.getAbsolutePath());
|
||||
// now fire it up and seed away!
|
||||
_manager.addTorrent(torrentFile.getCanonicalPath());
|
||||
// now fire it up, but don't automatically seed it
|
||||
_manager.addTorrent(torrentFile.getCanonicalPath(), true);
|
||||
_manager.addMessage("Many I2P trackers require you to register new torrents before seeding - please do so before starting " + baseFile.getName());
|
||||
} catch (IOException ioe) {
|
||||
_manager.addMessage("Error creating a torrent for " + baseFile.getAbsolutePath() + ": " + ioe.getMessage());
|
||||
}
|
||||
} else {
|
||||
_manager.addMessage("Cannot create a torrent for the nonexistant data: " + baseFile.getAbsolutePath());
|
||||
_manager.addMessage("Cannot create a torrent for the nonexistent data: " + baseFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class FetchAndAdd implements Runnable {
|
||||
private String _url;
|
||||
public FetchAndAdd(String url) {
|
||||
_url = url;
|
||||
}
|
||||
public void run() {
|
||||
_url = _url.trim();
|
||||
File file = I2PSnarkUtil.instance().get(_url, false);
|
||||
try {
|
||||
if ( (file != null) && (file.exists()) && (file.length() > 0) ) {
|
||||
_manager.addMessage("Torrent fetched from " + _url);
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(file);
|
||||
MetaInfo info = new MetaInfo(in);
|
||||
String name = info.getName();
|
||||
name = name.replace('/', '_');
|
||||
name = name.replace('\\', '_');
|
||||
name = name.replace('&', '+');
|
||||
name = name.replace('\'', '_');
|
||||
name = name.replace('"', '_');
|
||||
name = name.replace('`', '_');
|
||||
name = name + ".torrent";
|
||||
File torrentFile = new File(_manager.getDataDir(), name);
|
||||
|
||||
String canonical = torrentFile.getCanonicalPath();
|
||||
|
||||
if (torrentFile.exists()) {
|
||||
if (_manager.getTorrent(canonical) != null)
|
||||
_manager.addMessage("Torrent already running: " + name);
|
||||
else
|
||||
_manager.addMessage("Torrent already in the queue: " + name);
|
||||
} else {
|
||||
FileUtil.copy(file.getAbsolutePath(), canonical, true);
|
||||
_manager.addTorrent(canonical);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_manager.addMessage("Torrent at " + _url + " was not valid: " + ioe.getMessage());
|
||||
} finally {
|
||||
try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
} else {
|
||||
_manager.addMessage("Torrent was not retrieved from " + _url);
|
||||
}
|
||||
} finally {
|
||||
if (file != null) file.delete();
|
||||
} else if ("StopAll".equals(action)) {
|
||||
_manager.addMessage("Stopping all torrents and closing the I2P tunnel");
|
||||
List snarks = getSortedSnarks(req);
|
||||
for (int i = 0; i < snarks.size(); i++) {
|
||||
Snark snark = (Snark)snarks.get(i);
|
||||
if (!snark.stopped)
|
||||
_manager.stopTorrent(snark.torrent, false);
|
||||
}
|
||||
if (I2PSnarkUtil.instance().connected()) {
|
||||
I2PSnarkUtil.instance().disconnect();
|
||||
_manager.addMessage("I2P tunnel closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -324,44 +353,95 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
private static final int MAX_DISPLAYED_FILENAME_LENGTH = 60;
|
||||
private void displaySnark(PrintWriter out, Snark snark, String uri, int row) throws IOException {
|
||||
private static final int MAX_DISPLAYED_ERROR_LENGTH = 40;
|
||||
private void displaySnark(PrintWriter out, Snark snark, String uri, int row, long stats[], boolean showPeers) throws IOException {
|
||||
String filename = snark.torrent;
|
||||
File f = new File(filename);
|
||||
filename = f.getName(); // the torrent may be the canonical name, so lets just grab the local name
|
||||
int i = filename.lastIndexOf(".torrent");
|
||||
if (i > 0)
|
||||
filename = filename.substring(0, i);
|
||||
if (filename.length() > MAX_DISPLAYED_FILENAME_LENGTH)
|
||||
filename = filename.substring(0, MAX_DISPLAYED_FILENAME_LENGTH) + "...";
|
||||
long total = snark.meta.getTotalLength();
|
||||
long remaining = snark.storage.needed() * snark.meta.getPieceLength(0);
|
||||
// Early typecast, avoid possibly overflowing a temp integer
|
||||
long remaining = (long) snark.storage.needed() * (long) snark.meta.getPieceLength(0);
|
||||
if (remaining > total)
|
||||
remaining = total;
|
||||
int totalBps = 4096; // should probably grab this from the snark...
|
||||
long remainingSeconds = remaining / totalBps;
|
||||
long uploaded = snark.coordinator.getUploaded();
|
||||
|
||||
long downBps = 0;
|
||||
long upBps = 0;
|
||||
if (snark.coordinator != null) {
|
||||
downBps = snark.coordinator.getDownloadRate();
|
||||
upBps = snark.coordinator.getUploadRate();
|
||||
}
|
||||
long remainingSeconds;
|
||||
if (downBps > 0)
|
||||
remainingSeconds = remaining / downBps;
|
||||
else
|
||||
remainingSeconds = -1;
|
||||
boolean isRunning = !snark.stopped;
|
||||
long uploaded = 0;
|
||||
if (snark.coordinator != null) {
|
||||
uploaded = snark.coordinator.getUploaded();
|
||||
stats[0] += snark.coordinator.getDownloaded();
|
||||
}
|
||||
stats[1] += uploaded;
|
||||
if (isRunning) {
|
||||
stats[2] += downBps;
|
||||
stats[3] += upBps;
|
||||
}
|
||||
|
||||
boolean isValid = snark.meta != null;
|
||||
boolean singleFile = snark.meta.getFiles() == null;
|
||||
|
||||
String err = snark.coordinator.trackerProblems;
|
||||
int curPeers = snark.coordinator.getPeerCount();
|
||||
int knownPeers = snark.coordinator.trackerSeenPeers;
|
||||
String err = null;
|
||||
int curPeers = 0;
|
||||
int knownPeers = 0;
|
||||
if (snark.coordinator != null) {
|
||||
err = snark.coordinator.trackerProblems;
|
||||
curPeers = snark.coordinator.getPeerCount();
|
||||
knownPeers = snark.coordinator.trackerSeenPeers;
|
||||
}
|
||||
|
||||
String statusString = "Unknown";
|
||||
if (err != null) {
|
||||
if (isRunning)
|
||||
statusString = "TrackerErr (" + curPeers + "/" + knownPeers + " peers)";
|
||||
else
|
||||
statusString = "TrackerErr (" + err + ")";
|
||||
if (isRunning && curPeers > 0 && !showPeers)
|
||||
statusString = "<a title=\"" + err + "\">TrackerErr</a> (" +
|
||||
curPeers + "/" + knownPeers +
|
||||
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">peers</a>)";
|
||||
else if (isRunning)
|
||||
statusString = "<a title=\"" + err + "\">TrackerErr (" + curPeers + "/" + knownPeers + " peers)";
|
||||
else {
|
||||
if (err.length() > MAX_DISPLAYED_ERROR_LENGTH)
|
||||
err = err.substring(0, MAX_DISPLAYED_ERROR_LENGTH) + "...";
|
||||
statusString = "TrackerErr<br />(" + err + ")";
|
||||
}
|
||||
} else if (remaining <= 0) {
|
||||
if (isRunning)
|
||||
if (isRunning && curPeers > 0 && !showPeers)
|
||||
statusString = "Seeding (" +
|
||||
curPeers + "/" + knownPeers +
|
||||
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">peers</a>)";
|
||||
else if (isRunning)
|
||||
statusString = "Seeding (" + curPeers + "/" + knownPeers + " peers)";
|
||||
else
|
||||
statusString = "Complete";
|
||||
} else {
|
||||
if (isRunning)
|
||||
if (isRunning && curPeers > 0 && downBps > 0 && !showPeers)
|
||||
statusString = "OK (" +
|
||||
curPeers + "/" + knownPeers +
|
||||
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">peers</a>)";
|
||||
else if (isRunning && curPeers > 0 && downBps > 0)
|
||||
statusString = "OK (" + curPeers + "/" + knownPeers + " peers)";
|
||||
else if (isRunning && curPeers > 0 && !showPeers)
|
||||
statusString = "Stalled (" +
|
||||
curPeers + "/" + knownPeers +
|
||||
" <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">peers</a>)";
|
||||
else if (isRunning && curPeers > 0)
|
||||
statusString = "Stalled (" + curPeers + "/" + knownPeers + " peers)";
|
||||
else if (isRunning)
|
||||
statusString = "No Peers (0/" + knownPeers + ")";
|
||||
else
|
||||
statusString = "Stopped";
|
||||
}
|
||||
@@ -378,40 +458,138 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
out.write(filename);
|
||||
if (remaining == 0)
|
||||
out.write("</a>");
|
||||
out.write("</td>\n\t");
|
||||
|
||||
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentDownloaded " + rowClass + "\">");
|
||||
if (remaining > 0) {
|
||||
out.write(formatSize(total-remaining) + "/" + formatSize(total)); // 18MB/3GB
|
||||
// lets hold off on the ETA until we have rates sorted...
|
||||
//out.write(" (eta " + DataHelper.formatDuration(remainingSeconds*1000) + ")"); // (eta 6h)
|
||||
} else {
|
||||
out.write(formatSize(total)); // 3GB
|
||||
// temporarily hardcoded for postman and anonymity, requires bytemonsoon patch for lookup by info_hash
|
||||
String announce = snark.meta.getAnnounce();
|
||||
if (announce.startsWith("http://YRgrgTLG") || announce.startsWith("http://8EoJZIKr")) {
|
||||
Map trackers = _manager.getTrackers();
|
||||
for (Iterator iter = trackers.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
String baseURL = (String)trackers.get(name);
|
||||
if (!baseURL.startsWith(announce))
|
||||
continue;
|
||||
int e = baseURL.indexOf('=');
|
||||
if (e < 0)
|
||||
continue;
|
||||
baseURL = baseURL.substring(e + 1);
|
||||
out.write(" (<a href=\"" + baseURL + "details.php?dllist=1&filelist=1&info_hash=");
|
||||
out.write(TrackerClient.urlencode(snark.meta.getInfoHash()));
|
||||
out.write("\" title=\"" + name + " Tracker\">Details</a>)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentUploaded " + rowClass
|
||||
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentETA " + rowClass + "\">");
|
||||
if(isRunning && remainingSeconds > 0)
|
||||
out.write(DataHelper.formatDuration(remainingSeconds*1000)); // (eta 6h)
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentDownloaded " + rowClass + "\">");
|
||||
if (remaining > 0)
|
||||
out.write(formatSize(total-remaining) + "/" + formatSize(total)); // 18MB/3GB
|
||||
else
|
||||
out.write(formatSize(total)); // 3GB
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentUploaded " + rowClass
|
||||
+ "\">" + formatSize(uploaded) + "</td>\n\t");
|
||||
//out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentRate\">");
|
||||
//out.write("n/a"); //2KBps/12KBps/4KBps
|
||||
//out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentRate\">");
|
||||
if(isRunning && remaining > 0)
|
||||
out.write(formatSize(downBps) + "ps");
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentRate\">");
|
||||
if(isRunning)
|
||||
out.write(formatSize(upBps) + "ps");
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"left\" class=\"snarkTorrentAction " + rowClass + "\">");
|
||||
String parameters = "&nonce=" + _nonce + "&torrent=" + Base64.encode(snark.meta.getInfoHash());
|
||||
if (showPeers)
|
||||
parameters = parameters + "&p=1";
|
||||
if (isRunning) {
|
||||
out.write("<a href=\"" + uri + "?action=Stop&nonce=" + _nonce
|
||||
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
|
||||
out.write("<a href=\"" + uri + "?action=Stop" + parameters
|
||||
+ "\" title=\"Stop the torrent\">Stop</a>");
|
||||
} else {
|
||||
if (isValid)
|
||||
out.write("<a href=\"" + uri + "?action=Start&nonce=" + _nonce
|
||||
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
|
||||
out.write("<a href=\"" + uri + "?action=Start" + parameters
|
||||
+ "\" title=\"Start the torrent\">Start</a> ");
|
||||
out.write("<a href=\"" + uri + "?action=Remove&nonce=" + _nonce
|
||||
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
|
||||
out.write("<a href=\"" + uri + "?action=Remove" + parameters
|
||||
+ "\" title=\"Remove the torrent from the active list, deleting the .torrent file\">Remove</a><br />");
|
||||
out.write("<a href=\"" + uri + "?action=Delete&nonce=" + _nonce
|
||||
+ "&torrent=" + Base64.encode(snark.meta.getInfoHash())
|
||||
out.write("<a href=\"" + uri + "?action=Delete" + parameters
|
||||
+ "\" title=\"Delete the .torrent file and the associated data file(s)\">Delete</a> ");
|
||||
}
|
||||
out.write("</td>\n</tr>\n");
|
||||
if(showPeers && isRunning && curPeers > 0) {
|
||||
List peers = snark.coordinator.peerList();
|
||||
Iterator it = peers.iterator();
|
||||
while (it.hasNext()) {
|
||||
Peer peer = (Peer)it.next();
|
||||
if (!peer.isConnected())
|
||||
continue;
|
||||
out.write("<tr class=\"" + rowClass + "\">");
|
||||
out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">");
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">");
|
||||
String ch = peer.toString().substring(0, 4);
|
||||
String client;
|
||||
if ("AwMD".equals(ch))
|
||||
client = "I2PSnark";
|
||||
else if ("BFJT".equals(ch))
|
||||
client = "I2PRufus";
|
||||
else if ("TTMt".equals(ch))
|
||||
client = "I2P-BT";
|
||||
else if ("LUFa".equals(ch))
|
||||
client = "Azureus";
|
||||
else
|
||||
client = "Unknown";
|
||||
out.write("<font size=-1>" + client + "</font> <tt>" + peer.toString().substring(5, 9) + "</tt>");
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">");
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">");
|
||||
float pct = (float) (100.0 * (float) peer.completed() / snark.meta.getPieces());
|
||||
if (pct == 100.0)
|
||||
out.write("<font size=-1>Seed</font>");
|
||||
else {
|
||||
String ps = String.valueOf(pct);
|
||||
if (ps.length() > 5)
|
||||
ps = ps.substring(0, 5);
|
||||
out.write("<font size=-1>" + ps + "%</font>");
|
||||
}
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">");
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">");
|
||||
if (remaining > 0) {
|
||||
if (peer.isInteresting() && !peer.isChoked()) {
|
||||
out.write("<font color=#008000>");
|
||||
out.write("<font size=-1>" + formatSize(peer.getDownloadRate()) + "ps</font></font>");
|
||||
} else {
|
||||
out.write("<font color=#a00000><font size=-1><a title=\"");
|
||||
if (!peer.isInteresting())
|
||||
out.write("Uninteresting\">");
|
||||
else
|
||||
out.write("Choked\">");
|
||||
out.write(formatSize(peer.getDownloadRate()) + "ps</a></font></font>");
|
||||
}
|
||||
}
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td valign=\"top\" align=\"right\" class=\"snarkTorrentStatus " + rowClass + "\">");
|
||||
if (pct != 100.0) {
|
||||
if (peer.isInterested() && !peer.isChoking()) {
|
||||
out.write("<font color=#008000>");
|
||||
out.write("<font size=-1>" + formatSize(peer.getUploadRate()) + "ps</font></font>");
|
||||
} else {
|
||||
out.write("<font color=#a00000><font size=-1><a title=\"");
|
||||
if (!peer.isInterested())
|
||||
out.write("Uninterested\">");
|
||||
else
|
||||
out.write("Choking\">");
|
||||
out.write(formatSize(peer.getUploadRate()) + "ps</a></font></font>");
|
||||
}
|
||||
}
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td class=\"snarkTorrentStatus " + rowClass + "\">");
|
||||
out.write("</td></tr>\n\t");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeAddForm(PrintWriter out, HttpServletRequest req) throws IOException {
|
||||
@@ -425,7 +603,8 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
|
||||
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
|
||||
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
|
||||
out.write("From URL : <input type=\"text\" name=\"newURL\" size=\"50\" value=\"" + newURL + "\" /> \n");
|
||||
out.write("<span class=\"snarkConfigTitle\">Add Torrent:</span><br />\n");
|
||||
out.write("From URL : <input type=\"text\" name=\"newURL\" size=\"80\" value=\"" + newURL + "\" /> \n");
|
||||
// not supporting from file at the moment, since the file name passed isn't always absolute (so it may not resolve)
|
||||
//out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br />\n");
|
||||
out.write("<input type=\"submit\" value=\"Add torrent\" name=\"action\" /><br />\n");
|
||||
@@ -434,31 +613,34 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
out.write("</form>\n</span>\n");
|
||||
}
|
||||
|
||||
private static final String DEFAULT_TRACKERS[] = {
|
||||
"Postman's tracker", "http://YRgrgTLGnbTq2aZOZDJQ~o6Uk5k6TK-OZtx0St9pb0G-5EGYURZioxqYG8AQt~LgyyI~NCj6aYWpPO-150RcEvsfgXLR~CxkkZcVpgt6pns8SRc3Bi-QSAkXpJtloapRGcQfzTtwllokbdC-aMGpeDOjYLd8b5V9Im8wdCHYy7LRFxhEtGb~RL55DA8aYOgEXcTpr6RPPywbV~Qf3q5UK55el6Kex-6VCxreUnPEe4hmTAbqZNR7Fm0hpCiHKGoToRcygafpFqDw5frLXToYiqs9d4liyVB-BcOb0ihORbo0nS3CLmAwZGvdAP8BZ7cIYE3Z9IU9D1G8JCMxWarfKX1pix~6pIA-sp1gKlL1HhYhPMxwyxvuSqx34o3BqU7vdTYwWiLpGM~zU1~j9rHL7x60pVuYaXcFQDR4-QVy26b6Pt6BlAZoFmHhPcAuWfu-SFhjyZYsqzmEmHeYdAwa~HojSbofg0TMUgESRXMw6YThK1KXWeeJVeztGTz25sL8AAAA.i2p/announce.php",
|
||||
"Orion's tracker", "http://gKik1lMlRmuroXVGTZ~7v4Vez3L3ZSpddrGZBrxVriosCQf7iHu6CIk8t15BKsj~P0JJpxrofeuxtm7SCUAJEr0AIYSYw8XOmp35UfcRPQWyb1LsxUkMT4WqxAT3s1ClIICWlBu5An~q-Mm0VFlrYLIPBWlUFnfPR7jZ9uP5ZMSzTKSMYUWao3ejiykr~mtEmyls6g-ZbgKZawa9II4zjOy-hdxHgP-eXMDseFsrym4Gpxvy~3Fv9TuiSqhpgm~UeTo5YBfxn6~TahKtE~~sdCiSydqmKBhxAQ7uT9lda7xt96SS09OYMsIWxLeQUWhns-C~FjJPp1D~IuTrUpAFcVEGVL-BRMmdWbfOJEcWPZ~CBCQSO~VkuN1ebvIOr9JBerFMZSxZtFl8JwcrjCIBxeKPBmfh~xYh16BJm1BBBmN1fp2DKmZ2jBNkAmnUbjQOqWvUcehrykWk5lZbE7bjJMDFH48v3SXwRuDBiHZmSbsTY6zhGY~GkMQHNGxPMMSIAAAA.i2p/bt",
|
||||
"The freak's tracker", "http://mHKva9x24E5Ygfey2llR1KyQHv5f8hhMpDMwJDg1U-hABpJ2NrQJd6azirdfaR0OKt4jDlmP2o4Qx0H598~AteyD~RJU~xcWYdcOE0dmJ2e9Y8-HY51ie0B1yD9FtIV72ZI-V3TzFDcs6nkdX9b81DwrAwwFzx0EfNvK1GLVWl59Ow85muoRTBA1q8SsZImxdyZ-TApTVlMYIQbdI4iQRwU9OmmtefrCe~ZOf4UBS9-KvNIqUL0XeBSqm0OU1jq-D10Ykg6KfqvuPnBYT1BYHFDQJXW5DdPKwcaQE4MtAdSGmj1epDoaEBUa9btQlFsM2l9Cyn1hzxqNWXELmx8dRlomQLlV4b586dRzW~fLlOPIGC13ntPXogvYvHVyEyptXkv890jC7DZNHyxZd5cyrKC36r9huKvhQAmNABT2Y~pOGwVrb~RpPwT0tBuPZ3lHYhBFYmD8y~AOhhNHKMLzea1rfwTvovBMByDdFps54gMN1mX4MbCGT4w70vIopS9yAAAA.i2p/bytemonsoon/announce.php"
|
||||
};
|
||||
private void writeSeedForm(PrintWriter out, HttpServletRequest req) throws IOException {
|
||||
String uri = req.getRequestURI();
|
||||
String baseFile = req.getParameter("baseFile");
|
||||
if (baseFile == null)
|
||||
baseFile = "";
|
||||
|
||||
out.write("<span class=\"snarkNewTorrent\">\n");
|
||||
out.write("<span class=\"snarkNewTorrent\"><hr />\n");
|
||||
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
|
||||
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
|
||||
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" />\n");
|
||||
out.write("<span class=\"snarkConfigTitle\">Create Torrent:</span><br />\n");
|
||||
//out.write("From file: <input type=\"file\" name=\"newFile\" size=\"50\" value=\"" + newFile + "\" /><br />\n");
|
||||
out.write("Data to seed: <input type=\"text\" name=\"baseFile\" size=\"50\" value=\"" + baseFile
|
||||
+ "\" title=\"File within " + _manager.getDataDir().getAbsolutePath() + " to seed\" /><br />\n");
|
||||
out.write("Data to seed: " + _manager.getDataDir().getAbsolutePath() + File.separatorChar
|
||||
+ "<input type=\"text\" name=\"baseFile\" size=\"20\" value=\"" + baseFile
|
||||
+ "\" title=\"File to seed (must be within the specified path)\" /><br />\n");
|
||||
out.write("Tracker: <select name=\"announceURL\"><option value=\"\">Select a tracker</option>\n");
|
||||
for (int i = 0; i + 1 < DEFAULT_TRACKERS.length; i += 2)
|
||||
out.write("\t<option value=\"" + DEFAULT_TRACKERS[i+1] + "\">" + DEFAULT_TRACKERS[i] + "</option>\n");
|
||||
out.write("</select><br />\n");
|
||||
out.write(" or: ");
|
||||
out.write("<input type=\"text\" name=\"announceURLOther\" size=\"50\" value=\"http://\" " +
|
||||
"title=\"Custom tracker URL\" /><br />\n");
|
||||
Map trackers = _manager.getTrackers();
|
||||
for (Iterator iter = trackers.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
String announceURL = (String)trackers.get(name);
|
||||
int e = announceURL.indexOf('=');
|
||||
if (e > 0)
|
||||
announceURL = announceURL.substring(0, e);
|
||||
out.write("\t<option value=\"" + announceURL + "\">" + name + "</option>\n");
|
||||
}
|
||||
out.write("</select>\n");
|
||||
out.write("or <input type=\"text\" name=\"announceURLOther\" size=\"50\" value=\"http://\" " +
|
||||
"title=\"Custom tracker URL\" /> ");
|
||||
out.write("<input type=\"submit\" value=\"Create torrent\" name=\"action\" />\n");
|
||||
out.write("</form>\n</span>\n");
|
||||
}
|
||||
@@ -467,7 +649,7 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
String uri = req.getRequestURI();
|
||||
String dataDir = _manager.getDataDir().getAbsolutePath();
|
||||
boolean autoStart = _manager.shouldAutoStart();
|
||||
int seedPct = 0;
|
||||
//int seedPct = 0;
|
||||
|
||||
out.write("<form action=\"" + uri + "\" method=\"POST\">\n");
|
||||
out.write("<span class=\"snarkConfig\"><hr />\n");
|
||||
@@ -481,6 +663,7 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
//Auto add: <input type="checkbox" name="autoAdd" value="true" title="If true, automatically add torrents that are found in the data directory" />
|
||||
//Auto stop: <input type="checkbox" name="autoStop" value="true" title="If true, automatically stop torrents that are removed from the data directory" />
|
||||
//out.write("<br />\n");
|
||||
/*
|
||||
out.write("Seed percentage: <select name=\"seedPct\" disabled=\"true\" >\n\t");
|
||||
if (seedPct <= 0)
|
||||
out.write("<option value=\"0\" selected=\"true\">Unlimited</option>\n\t");
|
||||
@@ -495,6 +678,9 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
else
|
||||
out.write("<option value=\"150\">150%</option>\n\t");
|
||||
out.write("</select><br />\n");
|
||||
*/
|
||||
out.write("Total uploader limit: <input type=\"text\" name=\"upLimit\" value=\""
|
||||
+ I2PSnarkUtil.instance().getMaxUploaders() + "\" size=\"3\" /> peers<br />\n");
|
||||
|
||||
//out.write("<hr />\n");
|
||||
out.write("EepProxy host: <input type=\"text\" name=\"eepHost\" value=\""
|
||||
@@ -512,22 +698,23 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
String val = (String)options.get(key);
|
||||
opts.append(key).append('=').append(val).append(' ');
|
||||
}
|
||||
out.write("I2CP opts: <input type=\"text\" name=\"i2cpOpts\" size=\"40\" value=\""
|
||||
out.write("I2CP opts: <input type=\"text\" name=\"i2cpOpts\" size=\"80\" value=\""
|
||||
+ opts.toString() + "\" /><br />\n");
|
||||
out.write("<input type=\"submit\" value=\"Save configuration\" name=\"action\" />\n");
|
||||
out.write("</span>\n");
|
||||
out.write("</form>\n");
|
||||
}
|
||||
|
||||
// rounding makes us look faster :)
|
||||
private String formatSize(long bytes) {
|
||||
if (bytes < 5*1024)
|
||||
return bytes + "B";
|
||||
else if (bytes < 5*1024*1024)
|
||||
return (bytes/1024) + "KB";
|
||||
return ((bytes + 512)/1024) + "KB";
|
||||
else if (bytes < 5*1024*1024*1024l)
|
||||
return (bytes/(1024*1024)) + "MB";
|
||||
return ((bytes + 512*1024)/(1024*1024)) + "MB";
|
||||
else
|
||||
return (bytes/(1024*1024*1024)) + "GB";
|
||||
return ((bytes + 512*1024*1024)/(1024*1024*1024)) + "GB";
|
||||
}
|
||||
|
||||
private static final String HEADER_BEGIN = "<html>\n" +
|
||||
@@ -572,7 +759,10 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
"}\n" +
|
||||
"th {\n" +
|
||||
" background-color: #C7D5D5;\n" +
|
||||
" margin: 0px 0px 0px 0px;\n" +
|
||||
" padding: 0px 7px 0px 3px;\n" +
|
||||
"}\n" +
|
||||
"td {\n" +
|
||||
" padding: 0px 7px 0px 3px;\n" +
|
||||
"}\n" +
|
||||
".snarkTorrentEven {\n" +
|
||||
" background-color: #E7E7E7;\n" +
|
||||
@@ -581,9 +771,7 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
" background-color: #DDDDCC;\n" +
|
||||
"}\n" +
|
||||
".snarkNewTorrent {\n" +
|
||||
" font-size: 12pt;\n" +
|
||||
" font-family: monospace;\n" +
|
||||
" background-color: #ADAE9;\n" +
|
||||
" font-size: 10pt;\n" +
|
||||
"}\n" +
|
||||
".snarkAddInfo {\n" +
|
||||
" font-size: 10pt;\n" +
|
||||
@@ -600,21 +788,81 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
"<body>\n";
|
||||
|
||||
|
||||
private static final String TABLE_HEADER = "<table border=\"0\" class=\"snarkTorrents\" width=\"100%\">\n" +
|
||||
private static final String TABLE_HEADER = "<table border=\"0\" class=\"snarkTorrents\" width=\"100%\" cellpadding=\"0 10px\">\n" +
|
||||
"<thead>\n" +
|
||||
"<tr><th align=\"left\" valign=\"top\">Status</th>\n" +
|
||||
"<tr><th align=\"left\" valign=\"top\">Status \n";
|
||||
|
||||
private static final String TABLE_HEADER2 = "</th>\n" +
|
||||
" <th align=\"left\" valign=\"top\">Torrent</th>\n" +
|
||||
" <th align=\"left\" valign=\"top\">Downloaded</th>\n" +
|
||||
" <th align=\"left\" valign=\"top\">Uploaded</th>\n" +
|
||||
//" <th align=\"left\" valign=\"top\">Rate</th>\n" +
|
||||
" <th> </th></tr>\n" +
|
||||
"</thead>\n";
|
||||
" <th align=\"right\" valign=\"top\">ETA</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">Downloaded</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">Uploaded</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">Down Rate</th>\n" +
|
||||
" <th align=\"right\" valign=\"top\">Up Rate</th>\n";
|
||||
|
||||
private static final String TABLE_TOTAL = "<tfoot>\n" +
|
||||
"<tr><th align=\"left\" valign=\"top\">Totals</th>\n" +
|
||||
" <th> </th>\n" +
|
||||
" <th> </th>\n";
|
||||
|
||||
private static final String TABLE_EMPTY = "<tr class=\"snarkTorrentEven\">" +
|
||||
"<td class=\"snarkTorrentEven\" align=\"left\"" +
|
||||
" valign=\"top\" colspan=\"5\">No torrents</td></tr>\n";
|
||||
" valign=\"top\" colspan=\"8\">No torrents</td></tr>\n";
|
||||
|
||||
private static final String TABLE_FOOTER = "</table>\n";
|
||||
|
||||
private static final String FOOTER = "</body></html>";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FetchAndAdd implements Runnable {
|
||||
private SnarkManager _manager;
|
||||
private String _url;
|
||||
public FetchAndAdd(SnarkManager mgr, String url) {
|
||||
_manager = mgr;
|
||||
_url = url;
|
||||
}
|
||||
public void run() {
|
||||
_url = _url.trim();
|
||||
File file = I2PSnarkUtil.instance().get(_url, false);
|
||||
try {
|
||||
if ( (file != null) && (file.exists()) && (file.length() > 0) ) {
|
||||
_manager.addMessage("Torrent fetched from " + _url);
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(file);
|
||||
MetaInfo info = new MetaInfo(in);
|
||||
String name = info.getName();
|
||||
name = name.replace('/', '_');
|
||||
name = name.replace('\\', '_');
|
||||
name = name.replace('&', '+');
|
||||
name = name.replace('\'', '_');
|
||||
name = name.replace('"', '_');
|
||||
name = name.replace('`', '_');
|
||||
name = name + ".torrent";
|
||||
File torrentFile = new File(_manager.getDataDir(), name);
|
||||
|
||||
String canonical = torrentFile.getCanonicalPath();
|
||||
|
||||
if (torrentFile.exists()) {
|
||||
if (_manager.getTorrent(canonical) != null)
|
||||
_manager.addMessage("Torrent already running: " + name);
|
||||
else
|
||||
_manager.addMessage("Torrent already in the queue: " + name);
|
||||
} else {
|
||||
FileUtil.copy(file.getAbsolutePath(), canonical, true);
|
||||
_manager.addTorrent(canonical);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
_manager.addMessage("Torrent at " + _url + " was not valid: " + ioe.getMessage());
|
||||
} finally {
|
||||
try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
} else {
|
||||
_manager.addMessage("Torrent was not retrieved from " + _url);
|
||||
}
|
||||
} finally {
|
||||
if (file != null) file.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,31 +138,35 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
if (lastEnd == -1) {
|
||||
responseLine = new String(_headerBuffer.getData(), 0, i+1); // includes NL
|
||||
responseLine = filterResponseLine(responseLine);
|
||||
responseLine = (responseLine.trim() + "\n");
|
||||
responseLine = (responseLine.trim() + "\r\n");
|
||||
out.write(responseLine.getBytes());
|
||||
} else {
|
||||
for (int j = lastEnd+1; j < i; j++) {
|
||||
if (_headerBuffer.getData()[j] == ':') {
|
||||
int keyLen = j-(lastEnd+1);
|
||||
int valLen = i-(j+2);
|
||||
if ( (keyLen <= 0) || (valLen <= 0) )
|
||||
int valLen = i-(j+1);
|
||||
if ( (keyLen <= 0) || (valLen < 0) )
|
||||
throw new IOException("Invalid header @ " + j);
|
||||
String key = new String(_headerBuffer.getData(), lastEnd+1, keyLen);
|
||||
String val = new String(_headerBuffer.getData(), j+2, valLen).trim();
|
||||
String val = null;
|
||||
if (valLen == 0)
|
||||
val = "";
|
||||
else
|
||||
val = new String(_headerBuffer.getData(), j+2, valLen).trim();
|
||||
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Response header [" + key + "] = [" + val + "]");
|
||||
|
||||
if ("Connection".equalsIgnoreCase(key)) {
|
||||
out.write("Connection: close\n".getBytes());
|
||||
out.write("Connection: close\r\n".getBytes());
|
||||
connectionSent = true;
|
||||
} else if ("Proxy-Connection".equalsIgnoreCase(key)) {
|
||||
out.write("Proxy-Connection: close\n".getBytes());
|
||||
out.write("Proxy-Connection: close\r\n".getBytes());
|
||||
proxyConnectionSent = true;
|
||||
} else if ( ("Content-encoding".equalsIgnoreCase(key)) && ("x-i2p-gzip".equalsIgnoreCase(val)) ) {
|
||||
_gzip = true;
|
||||
} else {
|
||||
out.write((key.trim() + ": " + val.trim() + "\n").getBytes());
|
||||
out.write((key.trim() + ": " + val.trim() + "\r\n").getBytes());
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -173,9 +177,9 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
}
|
||||
|
||||
if (!connectionSent)
|
||||
out.write("Connection: close\n".getBytes());
|
||||
out.write("Connection: close\r\n".getBytes());
|
||||
if (!proxyConnectionSent)
|
||||
out.write("Proxy-Connection: close\n".getBytes());
|
||||
out.write("Proxy-Connection: close\r\n".getBytes());
|
||||
|
||||
finishHeaders();
|
||||
|
||||
@@ -196,7 +200,7 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
protected boolean shouldCompress() { return _gzip; }
|
||||
|
||||
protected void finishHeaders() throws IOException {
|
||||
out.write("\n".getBytes()); // end of the headers
|
||||
out.write("\r\n".getBytes()); // end of the headers
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
@@ -349,6 +353,10 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
"Content-length: 32\n" +
|
||||
"\n" +
|
||||
"hi ho, this is the body";
|
||||
String blankval = "HTTP/1.0 200 OK\n" +
|
||||
"A:\n" +
|
||||
"\n";
|
||||
|
||||
/* */
|
||||
test("Simple", simple, true);
|
||||
test("Filtered", filtered, true);
|
||||
@@ -356,6 +364,7 @@ class HTTPResponseOutputStream extends FilterOutputStream {
|
||||
test("Minimal", minimal, true);
|
||||
test("Windows", winmin, true);
|
||||
test("Large", large, true);
|
||||
test("Blank whitespace", blankval, true);
|
||||
test("Invalid (short headers)", invalid1, true);
|
||||
test("Invalid (no headers)", invalid2, true);
|
||||
test("Invalid (windows with short headers)", invalid3, true);
|
||||
|
||||
@@ -62,7 +62,7 @@ public abstract class I2PTunnelClientBase extends I2PTunnelTask implements Runna
|
||||
private Object conLock = new Object();
|
||||
|
||||
/** List of Socket for those accept()ed but not yet started up */
|
||||
private List _waitingSockets;
|
||||
private List _waitingSockets = new ArrayList();
|
||||
/** How many connections will we allow to be in the process of being built at once? */
|
||||
private int _numConnectionBuilders;
|
||||
/** How long will we allow sockets to sit in the _waitingSockets map before killing them? */
|
||||
|
||||
@@ -112,6 +112,35 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
"or naming one of them differently.<P/>")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_BAD_PROTOCOL =
|
||||
("HTTP/1.1 403 Bad Protocol\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: NON-HTTP PROTOCOL</H1>"+
|
||||
"The request uses a bad protocol. "+
|
||||
"The I2P HTTP Proxy supports http:// requests ONLY. Other protocols such as https:// and ftp:// are not allowed.<BR>")
|
||||
.getBytes();
|
||||
|
||||
private final static byte[] ERR_LOCALHOST =
|
||||
("HTTP/1.1 403 Access Denied\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: REQUEST DENIED</H1>"+
|
||||
"Your browser is misconfigured. Do not use the proxy to access the router console or other localhost destinations.<BR>")
|
||||
.getBytes();
|
||||
|
||||
private final static int MAX_POSTBYTES = 20*1024*1024; // arbitrary but huge - all in memory, no temp file
|
||||
private final static byte[] ERR_MAXPOST =
|
||||
("HTTP/1.1 503 Bad POST\r\n"+
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n"+
|
||||
"Cache-control: no-cache\r\n"+
|
||||
"\r\n"+
|
||||
"<html><body><H1>I2P ERROR: REQUEST DENIED</H1>"+
|
||||
"The maximum POST size is " + MAX_POSTBYTES + " bytes.<BR>")
|
||||
.getBytes();
|
||||
|
||||
/** used to assign unique IDs to the threads / clients. no logic or functionality */
|
||||
private static volatile long __clientId = 0;
|
||||
|
||||
@@ -224,6 +253,7 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix(requestId) + "Method is null for [" + line + "]");
|
||||
|
||||
line = line.trim();
|
||||
int pos = line.indexOf(" ");
|
||||
if (pos == -1) break;
|
||||
method = line.substring(0, pos);
|
||||
@@ -383,6 +413,16 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
usingWWWProxy = true;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix(requestId) + "Host doesnt end with .i2p and it contains a period [" + host + "]: wwwProxy!");
|
||||
} else if (host.toLowerCase().startsWith("localhost:")) {
|
||||
if (out != null) {
|
||||
out.write(ERR_LOCALHOST);
|
||||
out.write("<p /><i>Generated on: ".getBytes());
|
||||
out.write(new Date().toString().getBytes());
|
||||
out.write("</i></body></html>\n".getBytes());
|
||||
out.flush();
|
||||
}
|
||||
s.close();
|
||||
return;
|
||||
} else {
|
||||
request = request.substring(pos + 1);
|
||||
pos = request.indexOf("/");
|
||||
@@ -467,22 +507,38 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
newRequest.append("Connection: close\r\n\r\n");
|
||||
break;
|
||||
} else {
|
||||
newRequest.append(line).append("\r\n"); // HTTP spec
|
||||
newRequest.append(line.trim()).append("\r\n"); // HTTP spec
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getPrefix(requestId) + "NewRequest header: [" + newRequest.toString() + "]");
|
||||
|
||||
int postbytes = 0;
|
||||
while (br.ready()) { // empty the buffer (POST requests)
|
||||
int i = br.read();
|
||||
if (i != -1) {
|
||||
newRequest.append((char) i);
|
||||
if (++postbytes > MAX_POSTBYTES) {
|
||||
if (out != null) {
|
||||
out.write(ERR_MAXPOST);
|
||||
out.write("<p /><i>Generated on: ".getBytes());
|
||||
out.write(new Date().toString().getBytes());
|
||||
out.write("</i></body></html>\n".getBytes());
|
||||
out.flush();
|
||||
}
|
||||
s.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (method == null || destination == null) {
|
||||
l.log("No HTTP method found in the request.");
|
||||
if (out != null) {
|
||||
out.write(ERR_REQUEST_DENIED);
|
||||
if ("http://".equalsIgnoreCase(protocol))
|
||||
out.write(ERR_REQUEST_DENIED);
|
||||
else
|
||||
out.write(ERR_BAD_PROTOCOL);
|
||||
out.write("<p /><i>Generated on: ".getBytes());
|
||||
out.write(new Date().toString().getBytes());
|
||||
out.write("</i></body></html>\n".getBytes());
|
||||
@@ -545,6 +601,12 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
l.log(ex.getMessage());
|
||||
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
} catch (OutOfMemoryError oom) { // mainly for huge POSTs
|
||||
IOException ex = new IOException("OOM (in POST?)");
|
||||
_log.info("getPrefix(requestId) + Error trying to connect", ex);
|
||||
l.log(ex.getMessage());
|
||||
handleHTTPClientException(ex, out, targetRequest, usingWWWProxy, currentProxy, requestId);
|
||||
closeSocket(s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,13 +645,23 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
}
|
||||
|
||||
private static String jumpServers[] = {
|
||||
"http://i2host.i2p/cgi-bin/i2hostjump?",
|
||||
// "http://orion.i2p/jump/",
|
||||
"http://stats.i2p/cgi-bin/jump.cgi?a=",
|
||||
"http://trevorreznik.i2p/cgi-bin/jump.php?hostname="
|
||||
};
|
||||
private static void writeErrorMessage(byte[] errMessage, OutputStream out, String targetRequest,
|
||||
boolean usingWWWProxy, String wwwProxy, boolean showAddrHelper) throws IOException {
|
||||
if (out != null) {
|
||||
out.write(errMessage);
|
||||
if (targetRequest != null) {
|
||||
int protopos = targetRequest.indexOf(" ");
|
||||
String uri = targetRequest.substring(0, protopos);
|
||||
String uri = null;
|
||||
if (protopos >= 0)
|
||||
uri = targetRequest.substring(0, protopos);
|
||||
else
|
||||
uri = targetRequest;
|
||||
out.write("<a href=\"http://".getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("\">http://".getBytes());
|
||||
@@ -597,11 +669,16 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
out.write("</a>".getBytes());
|
||||
if (usingWWWProxy) out.write(("<br>WWW proxy: " + wwwProxy).getBytes());
|
||||
if (showAddrHelper) {
|
||||
out.write("<br><br>Click below to try an address helper link:<br><br><a href=\"http://orion.i2p/jump/".getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("\">http://orion.i2p/jump/".getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("</a>".getBytes());
|
||||
out.write("<br><br>Click a link below to look for an address helper by using a \"jump\" service:<br>".getBytes());
|
||||
for (int i = 0; i < jumpServers.length; i++) {
|
||||
out.write("<br><a href=\"".getBytes());
|
||||
out.write(jumpServers[i].getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("\">".getBytes());
|
||||
out.write(jumpServers[i].getBytes());
|
||||
out.write(uri.getBytes());
|
||||
out.write("</a>".getBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
out.write("</div><p><i>I2P HTTP Proxy Server<br>Generated on: ".getBytes());
|
||||
|
||||
@@ -38,18 +38,21 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
super(host, port, privData, l, notifyThis, tunnel);
|
||||
_spoofHost = spoofHost;
|
||||
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
|
||||
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpNullWorkaround", "How often an http server works around a streaming lib or i2ptunnel bug", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000 });
|
||||
}
|
||||
|
||||
public I2PTunnelHTTPServer(InetAddress host, int port, File privkey, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host, port, privkey, privkeyname, l, notifyThis, tunnel);
|
||||
_spoofHost = spoofHost;
|
||||
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
|
||||
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpNullWorkaround", "How often an http server works around a streaming lib or i2ptunnel bug", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000 });
|
||||
}
|
||||
|
||||
public I2PTunnelHTTPServer(InetAddress host, int port, InputStream privData, String privkeyname, String spoofHost, Logging l, EventDispatcher notifyThis, I2PTunnel tunnel) {
|
||||
super(host, port, privData, privkeyname, l, notifyThis, tunnel);
|
||||
_spoofHost = spoofHost;
|
||||
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpserver.blockingHandleTime", "how long the blocking handle takes to complete", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000, 3*60*60*1000 });
|
||||
getTunnel().getContext().statManager().createRateStat("i2ptunnel.httpNullWorkaround", "How often an http server works around a streaming lib or i2ptunnel bug", "I2PTunnel.HTTPServer", new long[] { 60*1000, 10*60*1000 });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,7 +110,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
useGZIP = true;
|
||||
|
||||
if (allowGZIP && useGZIP) {
|
||||
I2PThread req = new I2PThread(new CompressedRequestor(s, socket, modifiedHeader), "http compressor");
|
||||
I2PThread req = new I2PThread(new CompressedRequestor(s, socket, modifiedHeader), Thread.currentThread().getName()+".hc");
|
||||
req.start();
|
||||
} else {
|
||||
new I2PTunnelRunner(s, socket, slock, null, modifiedHeader.getBytes(), null);
|
||||
@@ -154,7 +157,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
_log.info("request headers: " + _headers);
|
||||
serverout.write(_headers.getBytes());
|
||||
browserin = _browser.getInputStream();
|
||||
I2PThread sender = new I2PThread(new Sender(serverout, browserin, "server: browser to server"), "http compressed sender");
|
||||
I2PThread sender = new I2PThread(new Sender(serverout, browserin, "server: browser to server"), Thread.currentThread().getName() + "hcs");
|
||||
sender.start();
|
||||
|
||||
browserout = _browser.getOutputStream();
|
||||
@@ -221,7 +224,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
protected void finishHeaders() throws IOException {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Including x-i2p-gzip as the content encoding in the response");
|
||||
out.write("Content-encoding: x-i2p-gzip\n".getBytes());
|
||||
out.write("Content-encoding: x-i2p-gzip\r\n".getBytes());
|
||||
super.finishHeaders();
|
||||
}
|
||||
|
||||
@@ -271,13 +274,13 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
|
||||
private String formatHeaders(Properties headers, StringBuffer command) {
|
||||
StringBuffer buf = new StringBuffer(command.length() + headers.size() * 64);
|
||||
buf.append(command.toString()).append('\n');
|
||||
buf.append(command.toString().trim()).append("\r\n");
|
||||
for (Iterator iter = headers.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
String val = headers.getProperty(name);
|
||||
buf.append(name).append(": ").append(val).append('\n');
|
||||
buf.append(name.trim()).append(": ").append(val.trim()).append("\r\n");
|
||||
}
|
||||
buf.append('\n');
|
||||
buf.append("\r\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@@ -291,18 +294,36 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Read the http command [" + command.toString() + "]");
|
||||
|
||||
int trimmed = 0;
|
||||
if (command.length() > 0) {
|
||||
for (int i = 0; i < command.length(); i++) {
|
||||
if (command.charAt(i) == 0) {
|
||||
command = command.deleteCharAt(i);
|
||||
i--;
|
||||
trimmed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (trimmed > 0)
|
||||
getTunnel().getContext().statManager().addRateData("i2ptunnel.httpNullWorkaround", trimmed, 0);
|
||||
|
||||
while (true) {
|
||||
buf.setLength(0);
|
||||
ok = DataHelper.readLine(in, buf);
|
||||
if (!ok) throw new IOException("EOF reached before the end of the headers [" + buf.toString() + "]");
|
||||
if ( (buf.length() <= 1) && ( (buf.charAt(0) == '\n') || (buf.charAt(0) == '\r') ) ) {
|
||||
if ( (buf.length() == 0) ||
|
||||
((buf.charAt(0) == '\n') || (buf.charAt(0) == '\r')) ) {
|
||||
// end of headers reached
|
||||
return headers;
|
||||
} else {
|
||||
int split = buf.indexOf(": ");
|
||||
int split = buf.indexOf(":");
|
||||
if (split <= 0) throw new IOException("Invalid HTTP header, missing colon [" + buf.toString() + "]");
|
||||
String name = buf.substring(0, split);
|
||||
String value = buf.substring(split+2); // ": "
|
||||
String name = buf.substring(0, split).trim();
|
||||
String value = null;
|
||||
if (buf.length() > split + 1)
|
||||
value = buf.substring(split+1).trim(); // ":"
|
||||
else
|
||||
value = "";
|
||||
if ("Accept-encoding".equalsIgnoreCase(name))
|
||||
name = "Accept-encoding";
|
||||
else if ("X-Accept-encoding".equalsIgnoreCase(name))
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
import java.lang.IndexOutOfBoundsException;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
@@ -43,7 +44,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
l,
|
||||
notifyThis,
|
||||
"IRCHandler " + (++__clientId), tunnel);
|
||||
|
||||
|
||||
StringTokenizer tok = new StringTokenizer(destinations, ",");
|
||||
dests = new ArrayList(1);
|
||||
while (tok.hasMoreTokens()) {
|
||||
@@ -80,9 +81,10 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
try {
|
||||
i2ps = createI2PSocket(dest);
|
||||
i2ps.setReadTimeout(readTimeout);
|
||||
Thread in = new I2PThread(new IrcInboundFilter(s,i2ps));
|
||||
StringBuffer expectedPong = new StringBuffer();
|
||||
Thread in = new I2PThread(new IrcInboundFilter(s,i2ps, expectedPong));
|
||||
in.start();
|
||||
Thread out = new I2PThread(new IrcOutboundFilter(s,i2ps));
|
||||
Thread out = new I2PThread(new IrcOutboundFilter(s,i2ps, expectedPong));
|
||||
out.start();
|
||||
} catch (Exception ex) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
@@ -118,17 +120,19 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
private Socket local;
|
||||
private I2PSocket remote;
|
||||
private StringBuffer expectedPong;
|
||||
|
||||
IrcInboundFilter(Socket _local, I2PSocket _remote) {
|
||||
IrcInboundFilter(Socket _local, I2PSocket _remote, StringBuffer pong) {
|
||||
local=_local;
|
||||
remote=_remote;
|
||||
expectedPong=pong;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
InputStream input;
|
||||
BufferedReader in;
|
||||
OutputStream output;
|
||||
try {
|
||||
input=remote.getInputStream();
|
||||
in = new BufferedReader(new InputStreamReader(remote.getInputStream(), "ISO-8859-1"));
|
||||
output=local.getOutputStream();
|
||||
} catch (IOException e) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
@@ -141,12 +145,14 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
while(true)
|
||||
{
|
||||
try {
|
||||
String inmsg = DataHelper.readLine(input);
|
||||
String inmsg = in.readLine();
|
||||
if(inmsg==null)
|
||||
break;
|
||||
if(inmsg.endsWith("\r"))
|
||||
inmsg=inmsg.substring(0,inmsg.length()-1);
|
||||
String outmsg = inboundFilter(inmsg);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("in: [" + inmsg + "]");
|
||||
String outmsg = inboundFilter(inmsg, expectedPong);
|
||||
if(outmsg!=null)
|
||||
{
|
||||
if(!inmsg.equals(outmsg)) {
|
||||
@@ -158,8 +164,8 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("inbound: "+outmsg);
|
||||
}
|
||||
outmsg=outmsg+"\n";
|
||||
output.write(outmsg.getBytes());
|
||||
outmsg=outmsg+"\r\n"; // rfc1459 sec. 2.3
|
||||
output.write(outmsg.getBytes("ISO-8859-1"));
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("inbound BLOCKED: "+inmsg);
|
||||
@@ -188,17 +194,19 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
private Socket local;
|
||||
private I2PSocket remote;
|
||||
private StringBuffer expectedPong;
|
||||
|
||||
IrcOutboundFilter(Socket _local, I2PSocket _remote) {
|
||||
IrcOutboundFilter(Socket _local, I2PSocket _remote, StringBuffer pong) {
|
||||
local=_local;
|
||||
remote=_remote;
|
||||
expectedPong=pong;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
InputStream input;
|
||||
BufferedReader in;
|
||||
OutputStream output;
|
||||
try {
|
||||
input=local.getInputStream();
|
||||
in = new BufferedReader(new InputStreamReader(local.getInputStream(), "ISO-8859-1"));
|
||||
output=remote.getOutputStream();
|
||||
} catch (IOException e) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
@@ -211,12 +219,14 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
while(true)
|
||||
{
|
||||
try {
|
||||
String inmsg = DataHelper.readLine(input);
|
||||
String inmsg = in.readLine();
|
||||
if(inmsg==null)
|
||||
break;
|
||||
if(inmsg.endsWith("\r"))
|
||||
inmsg=inmsg.substring(0,inmsg.length()-1);
|
||||
String outmsg = outboundFilter(inmsg);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("out: [" + inmsg + "]");
|
||||
String outmsg = outboundFilter(inmsg, expectedPong);
|
||||
if(outmsg!=null)
|
||||
{
|
||||
if(!inmsg.equals(outmsg)) {
|
||||
@@ -228,8 +238,8 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("outbound: "+outmsg);
|
||||
}
|
||||
outmsg=outmsg+"\n";
|
||||
output.write(outmsg.getBytes());
|
||||
outmsg=outmsg+"\r\n"; // rfc1459 sec. 2.3
|
||||
output.write(outmsg.getBytes("ISO-8859-1"));
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("outbound BLOCKED: "+"\""+inmsg+"\"");
|
||||
@@ -255,16 +265,16 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
*
|
||||
*/
|
||||
|
||||
public static String inboundFilter(String s) {
|
||||
public String inboundFilter(String s, StringBuffer expectedPong) {
|
||||
|
||||
String field[]=s.split(" ",4);
|
||||
String command;
|
||||
int idx=0;
|
||||
final String[] allowedCommands =
|
||||
{
|
||||
"NOTICE",
|
||||
"PING",
|
||||
"PONG",
|
||||
// "NOTICE", // can contain CTCP
|
||||
//"PING",
|
||||
//"PONG",
|
||||
"MODE",
|
||||
"JOIN",
|
||||
"NICK",
|
||||
@@ -272,14 +282,21 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
"PART",
|
||||
"WALLOPS",
|
||||
"ERROR",
|
||||
"KICK",
|
||||
"H", // "hide operator status" (after kicking an op)
|
||||
"TOPIC"
|
||||
};
|
||||
|
||||
if(field[0].charAt(0)==':')
|
||||
idx++;
|
||||
|
||||
command = field[idx++];
|
||||
|
||||
|
||||
try { command = field[idx++]; }
|
||||
catch (IndexOutOfBoundsException ioobe) // wtf, server sent borked command?
|
||||
{
|
||||
_log.warn("Dropping defective message: index out of bounds while extracting command.");
|
||||
return null;
|
||||
}
|
||||
|
||||
idx++; //skip victim
|
||||
|
||||
// Allow numerical responses
|
||||
@@ -287,21 +304,38 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
new Integer(command);
|
||||
return s;
|
||||
} catch(NumberFormatException nfe){}
|
||||
|
||||
|
||||
if ("PING".equalsIgnoreCase(command))
|
||||
return "PING 127.0.0.1"; // no way to know what the ircd to i2ptunnel server con is, so localhost works
|
||||
if ("PONG".equalsIgnoreCase(command)) {
|
||||
// Turn the received ":irc.freshcoffee.i2p PONG irc.freshcoffee.i2p :127.0.0.1"
|
||||
// into ":127.0.0.1 PONG 127.0.0.1 " so that the caller can append the client's extra parameter
|
||||
// though, does 127.0.0.1 work for irc clients connecting remotely? and for all of them? sure would
|
||||
// be great if irc clients actually followed the RFCs here, but i guess thats too much to ask.
|
||||
// If we haven't PINGed them, or the PING we sent isn't something we know how to filter, this
|
||||
// is blank.
|
||||
//
|
||||
// String pong = expectedPong.length() > 0 ? expectedPong.toString() : null;
|
||||
// If we aren't going to rewrite it, pass it through
|
||||
String pong = expectedPong.length() > 0 ? expectedPong.toString() : s;
|
||||
expectedPong.setLength(0);
|
||||
return pong;
|
||||
}
|
||||
|
||||
// Allow all allowedCommands
|
||||
for(int i=0;i<allowedCommands.length;i++) {
|
||||
if(allowedCommands[i].equals(command))
|
||||
if(allowedCommands[i].equalsIgnoreCase(command))
|
||||
return s;
|
||||
}
|
||||
|
||||
// Allow PRIVMSG, but block CTCP.
|
||||
if("PRIVMSG".equals(command))
|
||||
if("PRIVMSG".equalsIgnoreCase(command) || "NOTICE".equalsIgnoreCase(command))
|
||||
{
|
||||
String msg;
|
||||
msg = field[idx++];
|
||||
|
||||
byte[] bytes = msg.getBytes();
|
||||
if(bytes[1]==0x01)
|
||||
if(msg.indexOf(0x01) >= 0) // CTCP marker ^A can be anywhere, not just immediately after the ':'
|
||||
{
|
||||
// CTCP
|
||||
msg=msg.substring(2);
|
||||
@@ -318,14 +352,13 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String outboundFilter(String s) {
|
||||
public String outboundFilter(String s, StringBuffer expectedPong) {
|
||||
|
||||
String field[]=s.split(" ",3);
|
||||
String command;
|
||||
final String[] allowedCommands =
|
||||
{
|
||||
"NOTICE",
|
||||
"PONG",
|
||||
// "NOTICE", // can contain CTCP
|
||||
"MODE",
|
||||
"JOIN",
|
||||
"NICK",
|
||||
@@ -339,7 +372,8 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
"MAP", // seems safe enough, the ircd should protect themselves though
|
||||
"PART",
|
||||
"OPER",
|
||||
"PING",
|
||||
// "PONG", // replaced with a filtered PING/PONG since some clients send the server IP (thanks aardvax!)
|
||||
// "PING",
|
||||
"KICK",
|
||||
"HELPME",
|
||||
"RULES",
|
||||
@@ -355,21 +389,59 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
command = field[0].toUpperCase();
|
||||
|
||||
if ("PING".equalsIgnoreCase(command)) {
|
||||
// Most clients just send a PING and are happy with any old PONG. Others,
|
||||
// like BitchX, actually expect certain behavior. It sends two different pings:
|
||||
// "PING :irc.freshcoffee.i2p" and "PING 1234567890 127.0.0.1" (where the IP is the proxy)
|
||||
// the PONG to the former seems to be "PONG 127.0.0.1", while the PONG to the later is
|
||||
// ":irc.freshcoffee.i2p PONG irc.freshcoffe.i2p :1234567890".
|
||||
// We don't want to send them our proxy's IP address, so we need to rewrite the PING
|
||||
// sent to the server, but when we get a PONG back, use what we expected, rather than
|
||||
// what they sent.
|
||||
//
|
||||
// Yuck.
|
||||
|
||||
String rv = null;
|
||||
expectedPong.setLength(0);
|
||||
if (field.length == 1) { // PING
|
||||
rv = "PING";
|
||||
// If we aren't rewriting the PING don't rewrite the PONG
|
||||
// expectedPong.append("PONG 127.0.0.1");
|
||||
} else if (field.length == 2) { // PING nonce
|
||||
rv = "PING " + field[1];
|
||||
// If we aren't rewriting the PING don't rewrite the PONG
|
||||
// expectedPong.append("PONG ").append(field[1]);
|
||||
} else if (field.length == 3) { // PING nonce serverLocation
|
||||
rv = "PING " + field[1];
|
||||
expectedPong.append("PONG ").append(field[2]).append(" :").append(field[1]); // PONG serverLocation nonce
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("IRC client sent a PING we don't understand, filtering it (\"" + s + "\")");
|
||||
rv = null;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("sending ping [" + rv + "], waiting for [" + expectedPong + "] orig was [" + s + "]");
|
||||
|
||||
return rv;
|
||||
}
|
||||
if ("PONG".equalsIgnoreCase(command))
|
||||
return "PONG 127.0.0.1"; // no way to know what the ircd to i2ptunnel server con is, so localhost works
|
||||
|
||||
// Allow all allowedCommands
|
||||
for(int i=0;i<allowedCommands.length;i++)
|
||||
{
|
||||
if(allowedCommands[i].equals(command))
|
||||
if(allowedCommands[i].equalsIgnoreCase(command))
|
||||
return s;
|
||||
}
|
||||
|
||||
// Allow PRIVMSG, but block CTCP (except ACTION).
|
||||
if("PRIVMSG".equals(command))
|
||||
if("PRIVMSG".equalsIgnoreCase(command) || "NOTICE".equalsIgnoreCase(command))
|
||||
{
|
||||
String msg;
|
||||
msg = field[2];
|
||||
|
||||
byte[] bytes = msg.getBytes();
|
||||
if(bytes[1]==0x01)
|
||||
if(msg.indexOf(0x01) >= 0) // CTCP marker ^A can be anywhere, not just immediately after the ':'
|
||||
{
|
||||
// CTCP
|
||||
msg=msg.substring(2);
|
||||
@@ -382,14 +454,14 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
return s;
|
||||
}
|
||||
|
||||
if("USER".equals(command)) {
|
||||
if("USER".equalsIgnoreCase(command)) {
|
||||
int idx = field[2].lastIndexOf(":");
|
||||
if(idx<0)
|
||||
return "USER user hostname localhost :realname";
|
||||
String realname = field[2].substring(idx+1);
|
||||
String ret = "USER "+field[1]+" hostname localhost :"+realname;
|
||||
return ret;
|
||||
} else if ("QUIT".equals(command)) {
|
||||
} else if ("QUIT".equalsIgnoreCase(command)) {
|
||||
return "QUIT :leaving";
|
||||
}
|
||||
|
||||
|
||||
@@ -250,6 +250,10 @@ public class I2PTunnelRunner extends I2PThread implements I2PSocket.SocketErrorL
|
||||
+ from + " and " + to);
|
||||
}
|
||||
|
||||
// boo, hiss! shouldn't need this - the streaming lib should be configurable, but
|
||||
// somehow the inactivity timer is sometimes failing to get triggered properly
|
||||
//i2ps.setReadTimeout(2*60*1000);
|
||||
|
||||
ByteArray ba = _cache.acquire();
|
||||
byte[] buffer = ba.getData(); // new byte[NETWORK_BUFFER_SIZE];
|
||||
try {
|
||||
|
||||
@@ -190,6 +190,7 @@ public class IndexBean {
|
||||
}
|
||||
|
||||
private String saveChanges() {
|
||||
// Get current tunnel controller
|
||||
TunnelController cur = getController(_tunnel);
|
||||
|
||||
Properties config = getConfig();
|
||||
@@ -205,21 +206,28 @@ public class IndexBean {
|
||||
} else {
|
||||
cur.setConfig(config, "");
|
||||
}
|
||||
|
||||
if ("ircclient".equals(cur.getType()) ||
|
||||
"httpclient".equals(cur.getType()) ||
|
||||
"client".equals(cur.getType())) {
|
||||
// all clients use the same I2CP session, and as such, use the same
|
||||
// I2CP options
|
||||
// Only modify other shared tunnels
|
||||
// if the current tunnel is shared, and of supported type
|
||||
if ("true".equalsIgnoreCase(cur.getSharedClient()) &&
|
||||
("ircclient".equals(cur.getType()) ||
|
||||
"httpclient".equals(cur.getType()) ||
|
||||
"client".equals(cur.getType()))) {
|
||||
// all clients use the same I2CP session, and as such, use the same I2CP options
|
||||
List controllers = _group.getControllers();
|
||||
|
||||
for (int i = 0; i < controllers.size(); i++) {
|
||||
TunnelController c = (TunnelController)controllers.get(i);
|
||||
|
||||
// Current tunnel modified by user, skip
|
||||
if (c == cur) continue;
|
||||
//only change when they really are declared of beeing a sharedClient
|
||||
if (("httpclient".equals(c.getType()) ||
|
||||
"ircclient".equals(c.getType())||
|
||||
"client".equals(c.getType())
|
||||
) && "true".equalsIgnoreCase(c.getSharedClient())) {
|
||||
|
||||
// Only modify this non-current tunnel
|
||||
// if it belongs to a shared destination, and is of supported type
|
||||
if ("true".equalsIgnoreCase(c.getSharedClient()) &&
|
||||
("httpclient".equals(c.getType()) ||
|
||||
"ircclient".equals(c.getType()) ||
|
||||
"client".equals(c.getType()))) {
|
||||
|
||||
Properties cOpt = c.getConfig("");
|
||||
if (_tunnelQuantity != null) {
|
||||
cOpt.setProperty("option.inbound.quantity", _tunnelQuantity);
|
||||
@@ -450,7 +458,7 @@ public class IndexBean {
|
||||
_i2cpHost = (host != null ? host.trim() : null);
|
||||
}
|
||||
/** I2CP port the router is on */
|
||||
public void setClientPort(String port) {
|
||||
public void setClientport(String port) {
|
||||
_i2cpPort = (port != null ? port.trim() : null);
|
||||
}
|
||||
/** how many hops to use for inbound tunnels */
|
||||
@@ -636,10 +644,11 @@ public class IndexBean {
|
||||
config.setProperty("description", _description);
|
||||
if (_i2cpHost != null)
|
||||
config.setProperty("i2cpHost", _i2cpHost);
|
||||
if ( (_i2cpPort != null) && (_i2cpPort.trim().length() > 0) )
|
||||
if ( (_i2cpPort != null) && (_i2cpPort.trim().length() > 0) ) {
|
||||
config.setProperty("i2cpPort", _i2cpPort);
|
||||
else
|
||||
} else {
|
||||
config.setProperty("i2cpPort", "7654");
|
||||
}
|
||||
|
||||
if (_customOptions != null) {
|
||||
StringTokenizer tok = new StringTokenizer(_customOptions);
|
||||
|
||||
@@ -186,8 +186,9 @@
|
||||
%><option value="0"<%=(tunnelDepth == 0 ? " selected=\"selected\"" : "") %>>0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1"<%=(tunnelDepth == 1 ? " selected=\"selected\"" : "") %>>1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2"<%=(tunnelDepth == 2 ? " selected=\"selected\"" : "") %>>2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% if (tunnelDepth > 2) {
|
||||
%> <option value="<%=tunnelDepth%>" selected="selected"><%=tunnelDepth%> hop tunnel</option>
|
||||
<option value="3"<%=(tunnelDepth == 3 ? " selected=\"selected\"" : "") %>>3 hop tunnel (very high anonymity, poor performance)</option>
|
||||
<% if (tunnelDepth > 3) {
|
||||
%> <option value="<%=tunnelDepth%>" selected="selected"><%=tunnelDepth%> hop tunnel (very poor performance)</option>
|
||||
<% }
|
||||
%></select>
|
||||
</div>
|
||||
@@ -213,11 +214,11 @@
|
||||
</label>
|
||||
<select id="tunnelQuantity" name="tunnelQuantity" title="Number of Tunnels in Group" class="selectbox">
|
||||
<% int tunnelQuantity = editBean.getTunnelQuantity(curTunnel, 2);
|
||||
%><option value="1"<%=(tunnelQuantity == 1 ? " selected=\"selected\"" : "") %>>1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2"<%=(tunnelQuantity == 2 ? " selected=\"selected\"" : "") %>>2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3"<%=(tunnelQuantity == 3 ? " selected=\"selected\"" : "") %>>3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
%><option value="1"<%=(tunnelQuantity == 1 ? " selected=\"selected\"" : "") %>>1 inbound, 1 outbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2"<%=(tunnelQuantity == 2 ? " selected=\"selected\"" : "") %>>2 inbound, 2 outbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3"<%=(tunnelQuantity == 3 ? " selected=\"selected\"" : "") %>>3 inbound, 3 outbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% if (tunnelQuantity > 3) {
|
||||
%> <option value="<%=tunnelQuantity%>" selected="selected"><%=tunnelQuantity%> inbound tunnels</option>
|
||||
%> <option value="<%=tunnelQuantity%>" selected="selected"><%=tunnelQuantity%> tunnels</option>
|
||||
<% }
|
||||
%></select>
|
||||
</div>
|
||||
@@ -228,9 +229,9 @@
|
||||
<select id="tunnelBackupQuantity" name="tunnelBackupQuantity" title="Number of Reserve Tunnels" class="selectbox">
|
||||
<% int tunnelBackupQuantity = editBean.getTunnelBackupQuantity(curTunnel, 0);
|
||||
%><option value="0"<%=(tunnelBackupQuantity == 0 ? " selected=\"selected\"" : "") %>>0 backup tunnels (0 redundancy, no added resource usage)</option>
|
||||
<option value="1"<%=(tunnelBackupQuantity == 1 ? " selected=\"selected\"" : "") %>>1 backup tunnel (low redundancy, low resource usage)</option>
|
||||
<option value="2"<%=(tunnelBackupQuantity == 2 ? " selected=\"selected\"" : "") %>>2 backup tunnels (medium redundancy, medium resource usage)</option>
|
||||
<option value="3"<%=(tunnelBackupQuantity == 3 ? " selected=\"selected\"" : "") %>>3 backup tunnels (high redundancy, high resource usage)</option>
|
||||
<option value="1"<%=(tunnelBackupQuantity == 1 ? " selected=\"selected\"" : "") %>>1 backup tunnel each direction (low redundancy, low resource usage)</option>
|
||||
<option value="2"<%=(tunnelBackupQuantity == 2 ? " selected=\"selected\"" : "") %>>2 backup tunnels each direction (medium redundancy, medium resource usage)</option>
|
||||
<option value="3"<%=(tunnelBackupQuantity == 3 ? " selected=\"selected\"" : "") %>>3 backup tunnels each direction (high redundancy, high resource usage)</option>
|
||||
<% if (tunnelBackupQuantity > 3) {
|
||||
%> <option value="<%=tunnelBackupQuantity%>" selected="selected"><%=tunnelBackupQuantity%> backup tunnels</option>
|
||||
<% }
|
||||
@@ -254,7 +255,7 @@
|
||||
<label for="clientPort" accesskey="r">
|
||||
Po<span class="accessKey">r</span>t:
|
||||
</label>
|
||||
<input type="text" id="clientPort" name="clientPort" size="20" title="I2CP Port Number" value="<%=editBean.getI2CPPort(curTunnel)%>" class="freetext" />
|
||||
<input type="text" id="clientPort" name="clientport" size="20" title="I2CP Port Number" value="<%=editBean.getI2CPPort(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
|
||||
<div class="subdivider">
|
||||
@@ -284,4 +285,4 @@
|
||||
<div id="pageFooter">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -158,8 +158,9 @@
|
||||
%><option value="0"<%=(tunnelDepth == 0 ? " selected=\"selected\"" : "") %>>0 hop tunnel (low anonymity, low latency)</option>
|
||||
<option value="1"<%=(tunnelDepth == 1 ? " selected=\"selected\"" : "") %>>1 hop tunnel (medium anonymity, medium latency)</option>
|
||||
<option value="2"<%=(tunnelDepth == 2 ? " selected=\"selected\"" : "") %>>2 hop tunnel (high anonymity, high latency)</option>
|
||||
<% if (tunnelDepth > 2) {
|
||||
%> <option value="<%=tunnelDepth%>" selected="selected"><%=tunnelDepth%> hop tunnel</option>
|
||||
<option value="3"<%=(tunnelDepth == 3 ? " selected=\"selected\"" : "") %>>3 hop tunnel (very high anonymity, poor performance)</option>
|
||||
<% if (tunnelDepth > 3) {
|
||||
%> <option value="<%=tunnelDepth%>" selected="selected"><%=tunnelDepth%> hop tunnel (very poor performance)</option>
|
||||
<% }
|
||||
%></select>
|
||||
</div>
|
||||
@@ -185,11 +186,11 @@
|
||||
</label>
|
||||
<select id="tunnelQuantity" name="tunnelQuantity" title="Number of Tunnels in Group" class="selectbox">
|
||||
<% int tunnelQuantity = editBean.getTunnelQuantity(curTunnel, 2);
|
||||
%><option value="1"<%=(tunnelQuantity == 1 ? " selected=\"selected\"" : "") %>>1 inbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2"<%=(tunnelQuantity == 2 ? " selected=\"selected\"" : "") %>>2 inbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3"<%=(tunnelQuantity == 3 ? " selected=\"selected\"" : "") %>>3 inbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
%><option value="1"<%=(tunnelQuantity == 1 ? " selected=\"selected\"" : "") %>>1 inbound, 1 outbound tunnel (low bandwidth usage, less reliability)</option>
|
||||
<option value="2"<%=(tunnelQuantity == 2 ? " selected=\"selected\"" : "") %>>2 inbound, 2 outbound tunnels (standard bandwidth usage, standard reliability)</option>
|
||||
<option value="3"<%=(tunnelQuantity == 3 ? " selected=\"selected\"" : "") %>>3 inbound, 3 outbound tunnels (higher bandwidth usage, higher reliability)</option>
|
||||
<% if (tunnelQuantity > 3) {
|
||||
%> <option value="<%=tunnelQuantity%>" selected="selected"><%=tunnelQuantity%> inbound tunnels</option>
|
||||
%> <option value="<%=tunnelQuantity%>" selected="selected"><%=tunnelQuantity%> tunnels</option>
|
||||
<% }
|
||||
%></select>
|
||||
</div>
|
||||
@@ -200,9 +201,9 @@
|
||||
<select id="tunnelBackupQuantity" name="tunnelBackupQuantity" title="Number of Reserve Tunnels" class="selectbox">
|
||||
<% int tunnelBackupQuantity = editBean.getTunnelBackupQuantity(curTunnel, 0);
|
||||
%><option value="0"<%=(tunnelBackupQuantity == 0 ? " selected=\"selected\"" : "") %>>0 backup tunnels (0 redundancy, no added resource usage)</option>
|
||||
<option value="1"<%=(tunnelBackupQuantity == 1 ? " selected=\"selected\"" : "") %>>1 backup tunnel (low redundancy, low resource usage)</option>
|
||||
<option value="2"<%=(tunnelBackupQuantity == 2 ? " selected=\"selected\"" : "") %>>2 backup tunnels (medium redundancy, medium resource usage)</option>
|
||||
<option value="3"<%=(tunnelBackupQuantity == 3 ? " selected=\"selected\"" : "") %>>3 backup tunnels (high redundancy, high resource usage)</option>
|
||||
<option value="1"<%=(tunnelBackupQuantity == 1 ? " selected=\"selected\"" : "") %>>1 backup tunnel each direction (low redundancy, low resource usage)</option>
|
||||
<option value="2"<%=(tunnelBackupQuantity == 2 ? " selected=\"selected\"" : "") %>>2 backup tunnels each direction (medium redundancy, medium resource usage)</option>
|
||||
<option value="3"<%=(tunnelBackupQuantity == 3 ? " selected=\"selected\"" : "") %>>3 backup tunnels each direction (high redundancy, high resource usage)</option>
|
||||
<% if (tunnelBackupQuantity > 3) {
|
||||
%> <option value="<%=tunnelBackupQuantity%>" selected="selected"><%=tunnelBackupQuantity%> backup tunnels</option>
|
||||
<% }
|
||||
@@ -226,7 +227,7 @@
|
||||
<label for="clientPort" accesskey="r">
|
||||
Po<span class="accessKey">r</span>t:
|
||||
</label>
|
||||
<input type="text" id="clientPort" name="clientPort" size="20" title="I2CP Port Number" value="<%=editBean.getI2CPPort(curTunnel)%>" class="freetext" />
|
||||
<input type="text" id="clientPort" name="clientport" size="20" title="I2CP Port Number" value="<%=editBean.getI2CPPort(curTunnel)%>" class="freetext" />
|
||||
</div>
|
||||
|
||||
<div class="subdivider">
|
||||
@@ -256,4 +257,4 @@
|
||||
<div id="pageFooter">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -3,29 +3,34 @@
|
||||
|
||||
<target name="all" depends="build" />
|
||||
<target name="fetchJettylib" >
|
||||
<available property="jetty.available" file="jetty-5.1.6.zip" />
|
||||
<available property="jetty.zip.available" file="jetty-5.1.12.zip" type="file" />
|
||||
<available property="jetty.zip.extracted" file="jettylib" type="dir" />
|
||||
<ant target="doFetchJettylib" />
|
||||
<ant target="doExtractJettylib" />
|
||||
</target>
|
||||
<target name="doFetchJettylib" unless="jetty.available" >
|
||||
<echo message="The libraries contained within the fetched file are from Jetty's 5.1.6" />
|
||||
<target name="doFetchJettylib" unless="jetty.zip.available" >
|
||||
<echo message="The libraries contained within the fetched file are from Jetty's 5.1.12" />
|
||||
<echo message="distribution (http://jetty.mortbay.org/). These are not " />
|
||||
<echo message="necessary for using I2P, but are used by some applications on top of I2P," />
|
||||
<echo message="such as the routerconsole." />
|
||||
<get src="http://mesh.dl.sourceforge.net/sourceforge/jetty/jetty-5.1.6.zip" verbose="true" dest="jetty-5.1.6.zip" />
|
||||
<get src="http://mesh.dl.sourceforge.net/sourceforge/jetty/jetty-5.1.12.zip" verbose="true" dest="jetty-5.1.12.zip" />
|
||||
</target>
|
||||
<target name="doExtractJettylib" unless="jetty.zip.extracted" >
|
||||
<ant target="doExtract" />
|
||||
</target>
|
||||
<target name="doExtract">
|
||||
<unzip src="jetty-5.1.6.zip" dest="." />
|
||||
<unzip src="jetty-5.1.12.zip" dest="." />
|
||||
<mkdir dir="jettylib" />
|
||||
<copy todir="jettylib">
|
||||
<fileset dir="jetty-5.1.6/lib">
|
||||
<fileset dir="jetty-5.1.12/lib">
|
||||
<include name="*.jar" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<copy todir="jettylib">
|
||||
<fileset dir="jetty-5.1.6/ext">
|
||||
<fileset dir="jetty-5.1.12/ext">
|
||||
<include name="ant.jar" />
|
||||
<include name="commons-el.jar" />
|
||||
<include name="commons-logging.jar" />
|
||||
<include name="jasper-compiler.jar" />
|
||||
<include name="jasper-runtime.jar" />
|
||||
<include name="javax.servlet.jar" />
|
||||
@@ -33,9 +38,7 @@
|
||||
<include name="xercesImpl.jar" />
|
||||
</fileset>
|
||||
</copy>
|
||||
<!-- note the rename, to keep compat with old rev, since we only used the API anyway -->
|
||||
<copy file="jetty-5.1.6/ext/commons-logging-api.jar" tofile="jettylib/commons-logging.jar" />
|
||||
<delete dir="jetty-5.1.6" />
|
||||
<delete dir="jetty-5.1.12" />
|
||||
</target>
|
||||
<target name="build" depends="fetchJettylib" />
|
||||
<target name="builddep" />
|
||||
|
||||
@@ -1,590 +0,0 @@
|
||||
The code for the GUI applications netviewer and the
|
||||
heartbeat GUI have been released into the public domain,
|
||||
but they make use of the LGPL JFreeChart library (which
|
||||
in turn depends upon the APL log4j library). These
|
||||
external components, contained within the files:
|
||||
lib/jfreechart-0.9.17.jar
|
||||
lib/jcommon-0.9.2.jar
|
||||
lib/log4j-1.2.8.jar
|
||||
were retrieved and built from the source at
|
||||
http://www.jfree.org/jfreechart/jfreechart-0.9.17.zip
|
||||
|
||||
As a whole, the netviewer and heartbeat GUI applications
|
||||
therefore must state:
|
||||
This product includes software developed by the
|
||||
Apache Software Foundation (http://www.apache.org/).
|
||||
|
||||
The LGPL just makes us state prominently that we use LGPL'ed
|
||||
code (the JFreeChart code), and since we make no modifications
|
||||
to it, section 6.b of the LGPL seems to apply.
|
||||
|
||||
The relevent licenses are shown below.
|
||||
|
||||
*****************************************************************
|
||||
For the jfreechart-0.9.17.jar and jcommon-0.9.2.jar, the
|
||||
LGPL is relevent:
|
||||
*****************************************************************
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
|
||||
|
||||
*****************************************************************
|
||||
For the file log4j-1.2.8.jar, the APL is relevent:
|
||||
*****************************************************************
|
||||
/* ====================================================================
|
||||
*
|
||||
* The Apache Software License, Version 1.1
|
||||
*
|
||||
* Copyright (c) 2003 The Apache Software Foundation. All rights
|
||||
* reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. The end-user documentation included with the redistribution, if
|
||||
* any, must include the following acknowlegement:
|
||||
* "This product includes software developed by the
|
||||
* Apache Software Foundation (http://www.apache.org/)."
|
||||
* Alternately, this acknowlegement may appear in the software itself,
|
||||
* if and wherever such third-party acknowlegements normally appear.
|
||||
*
|
||||
* 4. The names "The Apache Logging Services Project", "log4j", and "Apache Software
|
||||
* Foundation" must not be used to endorse or promote products derived
|
||||
* from this software without prior written permission. For written
|
||||
* permission, please contact apache@apache.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "Apache"
|
||||
* nor may "Apache" appear in their names without prior written
|
||||
* permission of the Apache Group.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
|
||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
* [Additional notices, if required by prior licensing conditions]
|
||||
*
|
||||
*/
|
||||
@@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="jfreechart">
|
||||
<target name="all">
|
||||
<echo message="The code in the JFreeChart software contains LGPL and APL licensed software," />
|
||||
<echo message="and is not necessary for using I2P. However, there is a seperate GUI for the " />
|
||||
<echo message="heartbeat and netmonitor applications that uses this, so the retrieval of that " />
|
||||
<echo message="code from the JFreeChart distribution is being made available (though we make no" />
|
||||
<echo message="modifications to the code used here whatsoever - it is simply used by the public domain GUIs. " />
|
||||
<echo message="If you would like to fetch the code, run the ant task 'fetchJfreechart'" />
|
||||
<echo message="If you would like to build the code, run the ant task 'build'" />
|
||||
<echo message="If you would like to delete the code, run the ant task 'clean'" />
|
||||
</target>
|
||||
<target name="build">
|
||||
<ant dir="./jfreechart-0.9.17/" antfile="ant/build.xml" target="compile" />
|
||||
</target>
|
||||
<target name="fetchJfreechart">
|
||||
<mkdir dir="./lib" />
|
||||
<get src="http://www.jfree.org/jfreechart/jfreechart-0.9.17.zip" verbose="true" dest="jfreechart-0.9.17.zip" />
|
||||
<unzip src="jfreechart-0.9.17.zip" dest="." />
|
||||
</target>
|
||||
<target name="builddep" />
|
||||
<target name="compile" />
|
||||
<target name="jar" />
|
||||
<target name="clean">
|
||||
<delete dir="./jfreechart-0.9.17/" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean" />
|
||||
<target name="distclean" depends="clean" />
|
||||
</project>
|
||||
BIN
apps/jrobin/jrobin-1.4.0.jar
Normal file
BIN
apps/jrobin/jrobin-1.4.0.jar
Normal file
Binary file not shown.
@@ -138,8 +138,6 @@ public class I2PSocketManagerFactory {
|
||||
I2PSession session = client.createSession(myPrivateKeyStream, opts);
|
||||
session.connect();
|
||||
I2PSocketManager sockMgr = createManager(session, opts, "manager");
|
||||
if (sockMgr != null)
|
||||
sockMgr.setDefaultOptions(sockMgr.buildOptions(opts));
|
||||
return sockMgr;
|
||||
} catch (I2PSessionException ise) {
|
||||
_log.error("Error creating session for socket manager", ise);
|
||||
@@ -199,4 +197,4 @@ public class I2PSocketManagerFactory {
|
||||
}
|
||||
return i2cpPort;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ class I2PSocketOptionsImpl implements I2PSocketOptions {
|
||||
private int _maxBufferSize;
|
||||
|
||||
public static final int DEFAULT_BUFFER_SIZE = 1024*64;
|
||||
public static final int DEFAULT_WRITE_TIMEOUT = 60*1000;
|
||||
public static final int DEFAULT_WRITE_TIMEOUT = -1;
|
||||
public static final int DEFAULT_CONNECT_TIMEOUT = 60*1000;
|
||||
|
||||
public I2PSocketOptionsImpl() {
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="myi2p">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../../core/java/" target="build" />
|
||||
</target>
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac
|
||||
srcdir="./src"
|
||||
debug="true" deprecation="on" source="1.3" target="1.3"
|
||||
destdir="./build/obj"
|
||||
classpath="../../../core/java/build/i2p.jar" />
|
||||
</target>
|
||||
<target name="jar" depends="compile">
|
||||
<jar destfile="./build/myi2p.jar" basedir="./build/obj" includes="**/*.class" />
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="./src:../../../core/java/src" destdir="./build/javadoc"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
splitindex="true"
|
||||
windowtitle="MyI2P" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
</target>
|
||||
</project>
|
||||
@@ -1,116 +0,0 @@
|
||||
package net.i2p.myi2p;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
/**
|
||||
* Packages up a message for delivery. The raw format of the message within a
|
||||
* repliable datagram is
|
||||
* <code>MyI2P $maj.$min $service $type\n$payload</code>
|
||||
* where <code>$maj.$min</code> is currently 1.0, $service is the type of MyI2P
|
||||
* service, $type is the type of message within that service, and $payload is
|
||||
* the data specific to that type.
|
||||
*
|
||||
*/
|
||||
public class MyI2PMessage {
|
||||
private Destination _peer;
|
||||
private String _service;
|
||||
private String _type;
|
||||
private byte[] _payload;
|
||||
|
||||
private static final byte[] MESSAGE_PREFIX = "MyI2P 1.0 ".getBytes();
|
||||
|
||||
/**
|
||||
* Build a new MyI2P message to be sent.
|
||||
*
|
||||
* @param to address to send the message
|
||||
* @param service what MyI2P service is involved
|
||||
* @param type type of message within that service is involved
|
||||
* @param data payload of the message to deliver
|
||||
*/
|
||||
public MyI2PMessage(Destination to, String service, String type, byte data[]) {
|
||||
_peer = to;
|
||||
_service = service;
|
||||
_type = type;
|
||||
_payload = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read in the MyI2P data from the given datagram info.
|
||||
*
|
||||
* @param from authenticated from address
|
||||
* @param dgramPayload raw MyI2P formatted message
|
||||
* @throws IllegalArgumentException if the message is not a valid MyI2P message
|
||||
*/
|
||||
public MyI2PMessage(Destination from, byte dgramPayload[]) throws IllegalArgumentException {
|
||||
_peer = from;
|
||||
int index = 0;
|
||||
while (index < dgramPayload.length) {
|
||||
if (index >= MESSAGE_PREFIX.length) break;
|
||||
if (dgramPayload[index] != MESSAGE_PREFIX[index])
|
||||
throw new IllegalArgumentException("Invalid payload (not a MyI2P message)");
|
||||
index++;
|
||||
}
|
||||
|
||||
// $service $type\n$payload
|
||||
StringBuffer service = new StringBuffer(8);
|
||||
while (index < dgramPayload.length) {
|
||||
if (dgramPayload[index] == ' ') {
|
||||
_service = service.toString();
|
||||
index++;
|
||||
break;
|
||||
} else if (dgramPayload[index] == '\n') {
|
||||
throw new IllegalArgumentException("Ran into newline while reading the service");
|
||||
} else {
|
||||
service.append((char)dgramPayload[index]);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
StringBuffer type = new StringBuffer(8);
|
||||
while (index < dgramPayload.length) {
|
||||
if (dgramPayload[index] == '\n') {
|
||||
_type = type.toString();
|
||||
index++;
|
||||
break;
|
||||
} else {
|
||||
service.append((char)dgramPayload[index]);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
_payload = new byte[dgramPayload.length-index];
|
||||
System.arraycopy(dgramPayload, index, _payload, 0, _payload.length);
|
||||
}
|
||||
|
||||
/** who is this message from or who is it going to? */
|
||||
public Destination getPeer() { return _peer; }
|
||||
/** what MyI2P service is this bound for (addressBook, blog, etc)? */
|
||||
public String getServiceType() { return _service; }
|
||||
/** within that service, what type of message is this? */
|
||||
public String getMessageType() { return _type; }
|
||||
/** what is the raw data for the particular message? */
|
||||
public byte[] getPayload() { return _payload; }
|
||||
|
||||
/**
|
||||
* Retrieve the raw payload, suitable for wrapping in an I2PDatagramMaker
|
||||
* and sending to another MyI2P node.
|
||||
*
|
||||
* @throws IllegalStateException if some data is missing
|
||||
*/
|
||||
public byte[] toRawPayload() throws IllegalStateException {
|
||||
if (_service == null) throw new IllegalStateException("Service is null");
|
||||
if (_type == null) throw new IllegalStateException("Type is null");
|
||||
if (_payload == null) throw new IllegalStateException("Payload is null");
|
||||
|
||||
byte service[] = _service.getBytes();
|
||||
byte type[] = _type.getBytes();
|
||||
byte rv[] = new byte[MESSAGE_PREFIX.length + service.length + 1 + type.length + 1 + _payload.length];
|
||||
System.arraycopy(MESSAGE_PREFIX, 0, rv, 0, MESSAGE_PREFIX.length);
|
||||
System.arraycopy(service, 0, rv, MESSAGE_PREFIX.length, service.length);
|
||||
rv[MESSAGE_PREFIX.length + service.length] = ' ';
|
||||
System.arraycopy(type, 0, rv, MESSAGE_PREFIX.length + service.length + 1, type.length);
|
||||
rv[MESSAGE_PREFIX.length + service.length + 1 + type.length] = '\n';
|
||||
System.arraycopy(_payload, 0, rv, MESSAGE_PREFIX.length + service.length + 1 + type.length + 1, _payload.length);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
package net.i2p.myi2p;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Main controller for a MyI2P node, coordinating all network communication and
|
||||
* distributing messages to the appropriate services.
|
||||
*
|
||||
*/
|
||||
public class Node {
|
||||
private static List _nodes = new ArrayList(1);
|
||||
/**
|
||||
* Return a list of Node instances that are currently
|
||||
* operating in the JVM
|
||||
*/
|
||||
private static List nodes() {
|
||||
synchronized (_nodes) {
|
||||
return new ArrayList(_nodes);
|
||||
}
|
||||
}
|
||||
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private NodeAdapter _adapter;
|
||||
/**
|
||||
* contains configuration properties, such where our router is, what
|
||||
* services to run, etc
|
||||
*
|
||||
*/
|
||||
private Properties _config;
|
||||
/** filename where _config is stored */
|
||||
private String _configFile = DEFAULT_CONFIG_FILE;
|
||||
/** mapping of service name (String) to Service for all services loaded */
|
||||
private Map _services;
|
||||
|
||||
private static final String DEFAULT_CONFIG_FILE = "myi2p.config";
|
||||
private static final String DEFAULT_KEY_FILE = "myi2p.keys";
|
||||
private static final String PROP_KEY_FILE = "keyFile";
|
||||
|
||||
public Node(I2PAppContext context) {
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(Node.class);
|
||||
_config = new Properties();
|
||||
_services = new HashMap(1);
|
||||
if (_log.shouldLog(Log.CRIT))
|
||||
_log.log(Log.CRIT, "Node created");
|
||||
_adapter = new NodeAdapter(_context, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main driver for the node. Usage: <code>Node [configFile]</code>
|
||||
*
|
||||
*/
|
||||
public static void main(String args[]) {
|
||||
String filename = DEFAULT_CONFIG_FILE;
|
||||
if ( (args != null) && (args.length == 1) )
|
||||
filename = args[0];
|
||||
Node node = new Node(I2PAppContext.getGlobalContext());
|
||||
node.setConfigFile(filename);
|
||||
node.loadConfig();
|
||||
node.startup();
|
||||
while (true) {
|
||||
//try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
|
||||
//node.shutdown();
|
||||
//if (true) return;
|
||||
synchronized (node) {
|
||||
try { node.wait(); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Properties getConfig() {
|
||||
synchronized (_config) {
|
||||
return new Properties(_config);
|
||||
}
|
||||
}
|
||||
public void setConfig(Properties props) {
|
||||
synchronized (_config) {
|
||||
_config.clear();
|
||||
_config.putAll(props);
|
||||
}
|
||||
}
|
||||
|
||||
public String getConfigFile() { return _configFile; }
|
||||
public void setConfigFile(String filename) { _configFile = filename; }
|
||||
|
||||
/**
|
||||
* Load up the config and all of the services
|
||||
*
|
||||
*/
|
||||
public void loadConfig() {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
File cfgFile = new File(_configFile);
|
||||
if (cfgFile.exists()) {
|
||||
fis = new FileInputStream(cfgFile);
|
||||
Properties props = new Properties();
|
||||
props.load(fis);
|
||||
setConfig(props);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Config loaded from " + _configFile);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Config file " + _configFile + " does not exist, so we aren't going to do much");
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error reading config file " + _configFile, ioe);
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot up the node, connect to the router, and start all the services
|
||||
*
|
||||
*/
|
||||
public void startup() {
|
||||
boolean connected = connect();
|
||||
if (connected) {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
|
||||
public void run() { shutdown(); }
|
||||
}));
|
||||
loadServices();
|
||||
startServices();
|
||||
synchronized (_nodes) {
|
||||
_nodes.add(this);
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Node started");
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Unable to connect, startup didn't do much");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop any connections to the network and stop all services
|
||||
*
|
||||
*/
|
||||
public void shutdown() {
|
||||
disconnect();
|
||||
stopServices();
|
||||
synchronized (_nodes) {
|
||||
_nodes.remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message from the node to the peer as specified in the message
|
||||
*
|
||||
* @return true if it was sent
|
||||
*/
|
||||
public boolean sendMessage(MyI2PMessage msg) {
|
||||
return _adapter.sendMessage(msg);
|
||||
}
|
||||
|
||||
private void loadServices() {
|
||||
Properties config = getConfig();
|
||||
int i = 0;
|
||||
while (true) {
|
||||
String classname = config.getProperty("service."+i+".classname");
|
||||
if ( (classname == null) || (classname.trim().length() <= 0) )
|
||||
break;
|
||||
boolean ok = loadService("service." + i + ".", config);
|
||||
if (ok) i++;
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(i + " services loaded");
|
||||
}
|
||||
|
||||
private boolean loadService(String prefix, Properties config) {
|
||||
String classname = config.getProperty(prefix + "classname");
|
||||
String type = config.getProperty(prefix + "type");
|
||||
if (type == null) return false;
|
||||
|
||||
Properties opts = new Properties();
|
||||
int i = 0;
|
||||
while (true) {
|
||||
String name = config.getProperty(prefix + "option." + i + ".name");
|
||||
String value = config.getProperty(prefix + "option." + i + ".value");
|
||||
if ( (name == null) || (name.trim().length() <= 0) || (value == null) || (value.trim().length() <= 0) )
|
||||
break;
|
||||
opts.setProperty(name.trim(), value.trim());
|
||||
i++;
|
||||
}
|
||||
|
||||
try {
|
||||
Class cls = Class.forName(classname);
|
||||
Object obj = cls.newInstance();
|
||||
if (obj instanceof Service) {
|
||||
Service service = (Service)obj;
|
||||
service.setType(type);
|
||||
service.setOptions(opts);
|
||||
service.setNode(this);
|
||||
service.setContext(_context);
|
||||
synchronized (_services) {
|
||||
_services.put(type, service);
|
||||
}
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Service " + type + " loaded");
|
||||
return true;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error loading service " + type + ": not a service [" + classname + "]");
|
||||
}
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error loading service " + type + ": class " + classname + " is invalid", cnfe);
|
||||
} catch (InstantiationException ie) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error instantiating service " + type + ": class " + classname + " could not be created", ie);
|
||||
} catch (IllegalAccessException iae) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error creating service " + type + ": class " + classname + " could not be accessed", iae);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean connect() {
|
||||
Properties config = getConfig();
|
||||
File keyFile = new File(config.getProperty(PROP_KEY_FILE, DEFAULT_KEY_FILE));
|
||||
return _adapter.connect(config, keyFile);
|
||||
}
|
||||
|
||||
private void disconnect() {
|
||||
_adapter.disconnect();
|
||||
}
|
||||
|
||||
private void startServices() {
|
||||
for (Iterator iter = _services.values().iterator(); iter.hasNext(); ) {
|
||||
Service service = (Service)iter.next();
|
||||
service.startup();
|
||||
}
|
||||
}
|
||||
private void stopServices() {
|
||||
for (Iterator iter = _services.values().iterator(); iter.hasNext(); ) {
|
||||
Service service = (Service)iter.next();
|
||||
service.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void handleMessage(MyI2PMessage msg) {
|
||||
Service service = (Service)_services.get(msg.getServiceType());
|
||||
if (service == null) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Message received for an unknown service ["
|
||||
+ msg.getServiceType() + "] from "
|
||||
+ msg.getPeer().calculateHash().toBase64());
|
||||
} else {
|
||||
service.receiveMessage(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
package net.i2p.myi2p;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.I2PClientFactory;
|
||||
import net.i2p.client.I2PSession;
|
||||
import net.i2p.client.I2PSessionListener;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.client.datagram.I2PDatagramDissector;
|
||||
import net.i2p.client.datagram.I2PDatagramMaker;
|
||||
import net.i2p.client.datagram.I2PInvalidDatagramException;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Bind the MyI2P node to the I2P network, handling messages, sessions,
|
||||
* etc.
|
||||
*
|
||||
*/
|
||||
public class NodeAdapter implements I2PSessionListener {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private Node _node;
|
||||
private I2PSession _session;
|
||||
|
||||
public NodeAdapter(I2PAppContext context, Node node) {
|
||||
_node = node;
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(NodeAdapter.class);
|
||||
}
|
||||
|
||||
boolean sendMessage(MyI2PMessage msg) {
|
||||
if (_session == null) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Cannot send the message, as we are not connected");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
I2PDatagramMaker builder = new I2PDatagramMaker(_session);
|
||||
byte dgram[] = builder.makeI2PDatagram(msg.toRawPayload());
|
||||
return _session.sendMessage(msg.getPeer(), dgram);
|
||||
} catch (IllegalStateException ise) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("MyI2PMessage was not valid", ise);
|
||||
return false;
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error sending to the peer", ise);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the network using the current I2CP config and the private
|
||||
* key file specified in the node config. If the file does not exist, a new
|
||||
* destination will be created.
|
||||
*
|
||||
* @param config MyI2P node and I2CP configuration
|
||||
* @param keyFile file to load the private keystream from (if it doesn't
|
||||
* exist, a new one will be created and stored at that location)
|
||||
*
|
||||
* @return true if connection was successful, false otherwise
|
||||
*/
|
||||
boolean connect(Properties config, File keyFile) {
|
||||
I2PClient client = I2PClientFactory.createClient();
|
||||
if (!keyFile.exists()) {
|
||||
File parent = keyFile.getParentFile();
|
||||
if (parent != null) parent.mkdirs();
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(keyFile);
|
||||
Destination dest = client.createDestination(fos);
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("New destination created ["
|
||||
+ dest.calculateHash().toBase64()
|
||||
+ "] with keys at " + keyFile);
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error writing new keystream to " + keyFile, ioe);
|
||||
return false;
|
||||
} catch (I2PException ie) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Internal error creating new destination", ie);
|
||||
return false;
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(keyFile);
|
||||
_session = client.createSession(fis, config);
|
||||
if (_session == null) {
|
||||
_log.error("wtf, why did it create a null session?");
|
||||
return false;
|
||||
}
|
||||
_session.setSessionListener(this);
|
||||
_session.connect();
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("I2P session created");
|
||||
return true;
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Unable to read the keystream from " + keyFile, ioe);
|
||||
return false;
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Unable to connect to the router", ise);
|
||||
return false;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
void disconnect() {
|
||||
if (_session != null) {
|
||||
try {
|
||||
_session.destroySession();
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Error destroying the session in shutdown", ise);
|
||||
}
|
||||
_session = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void disconnected(I2PSession session) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Session disconnected");
|
||||
}
|
||||
|
||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Session error occurred - " + message, error);
|
||||
}
|
||||
|
||||
public void messageAvailable(I2PSession session, int msgId, long size) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("message available [" + msgId + "/"+ size + " bytes]");
|
||||
|
||||
try {
|
||||
byte data[] = session.receiveMessage(msgId);
|
||||
I2PDatagramDissector dissector = new I2PDatagramDissector();
|
||||
dissector.loadI2PDatagram(data);
|
||||
try {
|
||||
MyI2PMessage msg = new MyI2PMessage(dissector.getSender(), dissector.getPayload());
|
||||
_node.handleMessage(msg);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Message is a valid datagram but invalid MyI2P message", iae);
|
||||
}
|
||||
} catch (I2PSessionException ise) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Error retrieving message payload for message " + msgId, ise);
|
||||
} catch (I2PInvalidDatagramException iide) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Message received was not a valid repliable datagram", iide);
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Message received was a corrupt repliable datagram", dfe);
|
||||
}
|
||||
}
|
||||
|
||||
public void reportAbuse(I2PSession session, int severity) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("abuse occurred");
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package net.i2p.myi2p;
|
||||
|
||||
import java.util.Properties;
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
/**
|
||||
* Defines a service that can operate within a MyI2P node, responding to
|
||||
* messages and performing whatever tasks are necessary.
|
||||
*
|
||||
*/
|
||||
public interface Service {
|
||||
/** what type of message will this service respond to? */
|
||||
public String getType();
|
||||
public void setType(String type);
|
||||
|
||||
/** what node is this service hooked into */
|
||||
public Node getNode();
|
||||
public void setNode(Node node);
|
||||
|
||||
/** what options specific to this node does the service have? */
|
||||
public Properties getOptions();
|
||||
public void setOptions(Properties opts);
|
||||
|
||||
/** give the service a scope */
|
||||
public I2PAppContext getContext();
|
||||
public void setContext(I2PAppContext context);
|
||||
|
||||
/** called when a message is received for the service */
|
||||
public void receiveMessage(MyI2PMessage msg);
|
||||
|
||||
/** start the service up - the node is ready */
|
||||
public void startup();
|
||||
/** shut the service down - the node is going offline */
|
||||
public void shutdown();
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package net.i2p.myi2p;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.myi2p.Service;
|
||||
import net.i2p.myi2p.Node;
|
||||
import net.i2p.myi2p.MyI2PMessage;
|
||||
|
||||
/**
|
||||
* Base service implementation
|
||||
*
|
||||
*/
|
||||
public abstract class ServiceImpl implements Service {
|
||||
private I2PAppContext _context;
|
||||
private Node _node;
|
||||
private Properties _options;
|
||||
private String _serviceType;
|
||||
|
||||
public ServiceImpl() {
|
||||
_context = null;
|
||||
_node = null;
|
||||
_options = null;
|
||||
_serviceType = null;
|
||||
}
|
||||
|
||||
// base inspectors / mutators
|
||||
public Node getNode() { return _node; }
|
||||
public void setNode(Node node) { _node = node; }
|
||||
public I2PAppContext getContext() { return _context; }
|
||||
public void setContext(I2PAppContext context) { _context = context; }
|
||||
public Properties getOptions() { return _options; }
|
||||
public void setOptions(Properties opts) { _options = opts; }
|
||||
public String getType() { return _serviceType; }
|
||||
public void setType(String type) { _serviceType = type; }
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package net.i2p.myi2p.address;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataFormatException;
|
||||
|
||||
/**
|
||||
* Main lookup component for maintaining references to other I2P destinations.
|
||||
*
|
||||
*/
|
||||
public class AddressBook {
|
||||
private I2PAppContext _context;
|
||||
/** Name (String) to AddressBookEntry */
|
||||
private Map _entries;
|
||||
/**
|
||||
* List of NameReference that has been received but whose preferred
|
||||
* name conflicts with an existing entry.
|
||||
*/
|
||||
private List _conflictingReferences;
|
||||
|
||||
public AddressBook(I2PAppContext context) {
|
||||
_context = context;
|
||||
_entries = new HashMap(16);
|
||||
_conflictingReferences = new ArrayList(0);
|
||||
}
|
||||
|
||||
/** retrieve a list of entry names (strings) */
|
||||
public Set getEntryNames() {
|
||||
synchronized (_entries) {
|
||||
return new HashSet(_entries.keySet());
|
||||
}
|
||||
}
|
||||
public AddressBookEntry getEntry(String name) {
|
||||
synchronized (_entries) {
|
||||
return (AddressBookEntry)_entries.get(name);
|
||||
}
|
||||
}
|
||||
public AddressBookEntry addEntry(AddressBookEntry entry) {
|
||||
synchronized (_entries) {
|
||||
return (AddressBookEntry)_entries.put(entry.getLocalName(), entry);
|
||||
}
|
||||
}
|
||||
public void removeEntry(String name) {
|
||||
synchronized (_entries) {
|
||||
_entries.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
public int getConflictingReferenceCount() {
|
||||
synchronized (_conflictingReferences) {
|
||||
return _conflictingReferences.size();
|
||||
}
|
||||
}
|
||||
public NameReference getConflictingReference(int index) {
|
||||
synchronized (_conflictingReferences) {
|
||||
return (NameReference)_conflictingReferences.get(index);
|
||||
}
|
||||
}
|
||||
public void addConflictingReference(NameReference ref) {
|
||||
synchronized (_conflictingReferences) {
|
||||
_conflictingReferences.add(ref);
|
||||
}
|
||||
}
|
||||
public void removeConflictingReference(int index) {
|
||||
synchronized (_conflictingReferences) {
|
||||
_conflictingReferences.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
public void read(InputStream in) throws IOException {
|
||||
try {
|
||||
int numEntries = (int)DataHelper.readLong(in, 2);
|
||||
if (numEntries < 0) throw new IOException("Corrupt AddressBook - " + numEntries + " entries?");
|
||||
for (int i = 0; i < numEntries; i++) {
|
||||
AddressBookEntry entry = new AddressBookEntry(_context);
|
||||
entry.read(in);
|
||||
addEntry(entry);
|
||||
}
|
||||
int numConflicting = (int)DataHelper.readLong(in, 2);
|
||||
if (numConflicting < 0) throw new IOException("Corrupt AddressBook - " + numConflicting + " conflicting?");
|
||||
for (int i = 0; i < numConflicting; i++) {
|
||||
NameReference ref = new NameReference(_context);
|
||||
ref.read(in);
|
||||
addConflictingReference(ref);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new IOException("Corrupt address book - " + dfe.getMessage());
|
||||
}
|
||||
}
|
||||
public void write(OutputStream out) throws IOException {
|
||||
try {
|
||||
synchronized (_entries) {
|
||||
DataHelper.writeLong(out, 2, _entries.size());
|
||||
for (Iterator iter = _entries.values().iterator(); iter.hasNext(); ) {
|
||||
AddressBookEntry entry = (AddressBookEntry)iter.next();
|
||||
entry.write(out);
|
||||
}
|
||||
}
|
||||
synchronized (_conflictingReferences) {
|
||||
DataHelper.writeLong(out, 2, _conflictingReferences.size());
|
||||
for (int i = 0; i < _conflictingReferences.size(); i++) {
|
||||
NameReference ref = (NameReference)_conflictingReferences.get(i);
|
||||
ref.write(out);
|
||||
}
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new IOException("Corrupt address book - " + dfe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Entries: " + _entries.size() + " conflicting: " + _conflictingReferences.size();
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
package net.i2p.myi2p.address;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataFormatException;
|
||||
|
||||
/**
|
||||
* Implements a local address book entry, pointing at a known secure
|
||||
* NameReference as well as an optional Subscription.
|
||||
*
|
||||
*/
|
||||
public class AddressBookEntry {
|
||||
private I2PAppContext _context;
|
||||
private String _localName;
|
||||
private Properties _options;
|
||||
private NameReference _reference;
|
||||
private Subscription _subscription;
|
||||
private long _addedOn;
|
||||
|
||||
public AddressBookEntry(I2PAppContext context) {
|
||||
_context = context;
|
||||
_localName = null;
|
||||
_options = new Properties();
|
||||
_reference = null;
|
||||
_subscription = null;
|
||||
_addedOn = context.clock().now();
|
||||
}
|
||||
|
||||
/** Local (unique) name we use to reference the given destination */
|
||||
public String getLocalName() { return _localName; }
|
||||
public void setLocalName(String name) { _localName = name; }
|
||||
|
||||
public Properties getOptions() {
|
||||
synchronized (_options) {
|
||||
return new Properties(_options);
|
||||
}
|
||||
}
|
||||
public void setOptions(Properties props) {
|
||||
synchronized (_options) {
|
||||
_options.clear();
|
||||
if (props != null)
|
||||
_options.putAll(props);
|
||||
}
|
||||
}
|
||||
|
||||
/** Secure name reference, provided by the destination */
|
||||
public NameReference getNameReference() { return _reference; }
|
||||
public void setNameReference(NameReference ref) { _reference = ref; }
|
||||
|
||||
/**
|
||||
* If specified, the details of our subscription to the MyI2P address
|
||||
* book at the referenced destination.
|
||||
*
|
||||
*/
|
||||
public Subscription getSubscription() { return _subscription; }
|
||||
public void setSubscription(Subscription sub) { _subscription = sub; }
|
||||
|
||||
/** When this entry was added */
|
||||
public long getAddedOn() { return _addedOn; }
|
||||
public void setAddedOn(long when) { _addedOn = when; }
|
||||
|
||||
/** load the data from the stream */
|
||||
public void read(InputStream in) throws IOException {
|
||||
try {
|
||||
Boolean localNameDefined = DataHelper.readBoolean(in);
|
||||
if ( (localNameDefined != null) && (localNameDefined.booleanValue()) )
|
||||
_localName = DataHelper.readString(in);
|
||||
else
|
||||
_localName = null;
|
||||
|
||||
Date when = DataHelper.readDate(in);
|
||||
if (when == null)
|
||||
_addedOn = -1;
|
||||
else
|
||||
_addedOn = when.getTime();
|
||||
|
||||
Properties props = DataHelper.readProperties(in);
|
||||
setOptions(props);
|
||||
|
||||
Boolean refDefined = DataHelper.readBoolean(in);
|
||||
if ( (refDefined != null) && (refDefined.booleanValue()) ) {
|
||||
_reference = new NameReference(_context);
|
||||
_reference.read(in);
|
||||
} else {
|
||||
_reference = null;
|
||||
}
|
||||
|
||||
Boolean subDefined = DataHelper.readBoolean(in);
|
||||
if ( (subDefined != null) && (subDefined.booleanValue()) ) {
|
||||
Subscription sub = new Subscription(_context);
|
||||
sub.read(in);
|
||||
_subscription = sub;
|
||||
} else {
|
||||
_subscription = null;
|
||||
}
|
||||
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new IOException("Corrupt subscription: " + dfe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** persist the data to the stream */
|
||||
public void write(OutputStream out) throws IOException {
|
||||
try {
|
||||
if ( (_localName != null) && (_localName.trim().length() > 0) ) {
|
||||
DataHelper.writeBoolean(out, Boolean.TRUE);
|
||||
DataHelper.writeString(out, _localName);
|
||||
} else {
|
||||
DataHelper.writeBoolean(out, Boolean.FALSE);
|
||||
}
|
||||
|
||||
DataHelper.writeDate(out, new Date(_addedOn));
|
||||
|
||||
synchronized (_options) {
|
||||
DataHelper.writeProperties(out, _options);
|
||||
}
|
||||
|
||||
if (_reference != null) {
|
||||
DataHelper.writeBoolean(out, Boolean.TRUE);
|
||||
_reference.write(out);
|
||||
} else {
|
||||
DataHelper.writeBoolean(out, Boolean.FALSE);
|
||||
}
|
||||
|
||||
if (_subscription != null) {
|
||||
DataHelper.writeBoolean(out, Boolean.TRUE);
|
||||
_subscription.write(out);
|
||||
} else {
|
||||
DataHelper.writeBoolean(out, Boolean.FALSE);
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new IOException("Corrupt subscription: " + dfe.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package net.i2p.myi2p.address;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import net.i2p.myi2p.Service;
|
||||
import net.i2p.myi2p.ServiceImpl;
|
||||
import net.i2p.myi2p.Node;
|
||||
import net.i2p.myi2p.MyI2PMessage;
|
||||
|
||||
/**
|
||||
* Main service handler / coordinator for the MyI2P address book.
|
||||
*
|
||||
*/
|
||||
public class AddressBookService extends ServiceImpl {
|
||||
private Log _log;
|
||||
private AddressBook _addressBook;
|
||||
/** contains a mapping of event time (Long) to description (String) */
|
||||
private Map _activityLog;
|
||||
private String _addressBookFile;
|
||||
|
||||
private static String PROP_ADDRESSBOOK_FILE = "datafile";
|
||||
private static String DEFAULT_ADDRESSBOOK_FILE = "addressbook.dat";
|
||||
|
||||
public static final String SERVICE_TYPE = "AddressBook";
|
||||
public String getType() { return SERVICE_TYPE; }
|
||||
|
||||
public void startup() {
|
||||
_log = getContext().logManager().getLog(AddressBookService.class);
|
||||
|
||||
_addressBookFile = getOptions().getProperty(PROP_ADDRESSBOOK_FILE, DEFAULT_ADDRESSBOOK_FILE);
|
||||
File file = new File(_addressBookFile);
|
||||
|
||||
if (file.exists()) {
|
||||
loadData(file);
|
||||
} else {
|
||||
_addressBook = new AddressBook(getContext());
|
||||
_activityLog = new HashMap(16);
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
File file = new File(_addressBookFile);
|
||||
storeData(file);
|
||||
}
|
||||
|
||||
public void receiveMessage(MyI2PMessage msg) {
|
||||
_log.info("Received a " + msg.getMessageType() + " from "
|
||||
+ msg.getPeer().calculateHash().toBase64()
|
||||
+ new String(msg.getPayload()));
|
||||
}
|
||||
|
||||
/** load everything from disk */
|
||||
private void loadData(File dataFile) {
|
||||
AddressBookServiceData data = new AddressBookServiceData(getContext());
|
||||
data.load(dataFile);
|
||||
if (data.getErrorMessage() != null) {
|
||||
_log.warn(data.getErrorMessage(), data.getError());
|
||||
_addressBook = new AddressBook(getContext());
|
||||
_activityLog = new HashMap(16);
|
||||
} else {
|
||||
_addressBook = data.getAddressBook();
|
||||
_activityLog = data.getActivityLog();
|
||||
}
|
||||
}
|
||||
|
||||
/** persist everything to disk */
|
||||
private void storeData(File dataFile) {
|
||||
AddressBookServiceData data = new AddressBookServiceData(getContext());
|
||||
data.setActivityLog(_activityLog);
|
||||
data.setAddressBook(_addressBook);
|
||||
data.store(dataFile);
|
||||
if (data.getErrorMessage() != null) {
|
||||
_log.warn(data.getErrorMessage(), data.getError());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package net.i2p.myi2p.address;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Component for loading and storing the service data to disk
|
||||
*
|
||||
*/
|
||||
public class AddressBookServiceData {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private AddressBook _addressBook;
|
||||
private Map _activityLog;
|
||||
private Exception _error;
|
||||
private String _errorMessage;
|
||||
|
||||
public AddressBookServiceData(I2PAppContext context) {
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(AddressBookServiceData.class);
|
||||
_addressBook = null;
|
||||
_activityLog = null;
|
||||
_error = null;
|
||||
_errorMessage = null;
|
||||
}
|
||||
|
||||
public AddressBook getAddressBook() { return _addressBook; }
|
||||
public void setAddressBook(AddressBook book) { _addressBook = book; }
|
||||
public Map getActivityLog() { return _activityLog; }
|
||||
public void setActivityLog(Map log) { _activityLog = log; }
|
||||
|
||||
public Exception getError() { return _error; }
|
||||
public String getErrorMessage() { return _errorMessage; }
|
||||
|
||||
public void load(File from) {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(from);
|
||||
AddressBook addressBook = new AddressBook(_context);
|
||||
addressBook.read(fis);
|
||||
_addressBook = addressBook;
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Address book: " + addressBook);
|
||||
Properties props = DataHelper.readProperties(fis);
|
||||
Map log = new HashMap(props.size());
|
||||
for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
String event = props.getProperty(key);
|
||||
long when = 0;
|
||||
try {
|
||||
when = Long.parseLong(key);
|
||||
while (log.containsKey(new Long(when)))
|
||||
when++;
|
||||
log.put(new Long(when), event);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Activity log: on " + new Date(when) + ": " + event);
|
||||
} catch (NumberFormatException nfe) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Corrupt activity log entry: when=" + key, nfe);
|
||||
}
|
||||
}
|
||||
_activityLog = log;
|
||||
} catch (Exception e) {
|
||||
_error = e;
|
||||
_errorMessage = "Error reading the address book from " + from;
|
||||
}
|
||||
}
|
||||
|
||||
public void store(File to) {
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(to);
|
||||
_addressBook.write(fos);
|
||||
Properties props = new Properties();
|
||||
for (Iterator iter = _activityLog.keySet().iterator(); iter.hasNext(); ) {
|
||||
Long when = (Long)iter.next();
|
||||
String msg = (String)_activityLog.get(when);
|
||||
props.setProperty(when.toString(), msg);
|
||||
}
|
||||
DataHelper.writeProperties(fos, props);
|
||||
} catch (Exception e) {
|
||||
_error = e;
|
||||
_errorMessage = "Error writing the address book to " + to;
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
package net.i2p.myi2p.address;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* CreateEntryCLI addressBookFile referenceFile localName subscriptionFrequencyHours [ key=value]*
|
||||
*/
|
||||
public class CreateEntryCLI {
|
||||
private I2PAppContext _context;
|
||||
private String _args[];
|
||||
private String _addressBook;
|
||||
private String _referenceFile;
|
||||
private String _localName;
|
||||
private int _subscriptionFrequencyHours;
|
||||
private Properties _options;
|
||||
|
||||
public CreateEntryCLI(String args[]) {
|
||||
_context = new I2PAppContext();
|
||||
_args = args;
|
||||
_options = new Properties();
|
||||
}
|
||||
|
||||
public void execute() {
|
||||
if (parseArgs())
|
||||
doExecute();
|
||||
else
|
||||
System.err.println("Usage: CreateEntryCLI addressBookFile referenceFile localName subscriptionFrequencyHours[ key=value]*");
|
||||
}
|
||||
|
||||
private boolean parseArgs() {
|
||||
if ( (_args == null) || (_args.length < 3) )
|
||||
return false;
|
||||
_addressBook = _args[0];
|
||||
_referenceFile = _args[1];
|
||||
_localName = _args[2];
|
||||
try {
|
||||
_subscriptionFrequencyHours = Integer.parseInt(_args[3]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 4; i < _args.length; i++) {
|
||||
int eq = _args[i].indexOf('=');
|
||||
if ( (eq <= 0) || (eq >= _args[i].length() - 1) )
|
||||
continue;
|
||||
String key = _args[i].substring(0,eq);
|
||||
String val = _args[i].substring(eq+1);
|
||||
_options.setProperty(key, val);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void doExecute() {
|
||||
AddressBookServiceData data = new AddressBookServiceData(_context);
|
||||
File f = new File(_addressBook);
|
||||
if (f.exists()) {
|
||||
data.load(f);
|
||||
if (data.getError() != null) {
|
||||
if (data.getErrorMessage() != null)
|
||||
System.err.println(data.getErrorMessage());
|
||||
data.getError().printStackTrace();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
data.setAddressBook(new AddressBook(_context));
|
||||
data.setActivityLog(new HashMap());
|
||||
}
|
||||
NameReference ref = null;
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(_referenceFile);
|
||||
ref = new NameReference(_context);
|
||||
ref.read(fis);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Name reference under " + _referenceFile + " is corrupt");
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
AddressBook book = data.getAddressBook();
|
||||
Map activityLog = data.getActivityLog();
|
||||
|
||||
AddressBookEntry oldEntry = book.getEntry(_localName);
|
||||
|
||||
AddressBookEntry entry = new AddressBookEntry(_context);
|
||||
entry.setLocalName(_localName);
|
||||
entry.setNameReference(ref);
|
||||
|
||||
Subscription sub = new Subscription(_context);
|
||||
sub.setQueryFrequencyMinutes(60*_subscriptionFrequencyHours);
|
||||
|
||||
entry.setSubscription(sub);
|
||||
entry.setOptions(_options);
|
||||
|
||||
if (oldEntry == null) {
|
||||
book.addEntry(entry);
|
||||
System.out.println("New address book entry added for " + entry.getLocalName());
|
||||
activityLog.put(new Long(_context.clock().now()), "New address book entry added for " + entry.getLocalName());
|
||||
} else {
|
||||
Destination oldDest = oldEntry.getNameReference().getDestination();
|
||||
if (oldDest.equals(ref.getDestination())) {
|
||||
if (ref.getSequenceNum() < oldEntry.getNameReference().getSequenceNum()) {
|
||||
System.err.println("Not updating the address book - newer reference for " + entry.getLocalName() + " exists");
|
||||
return;
|
||||
} else {
|
||||
// same or newer rev
|
||||
if (null != entry.getSubscription()) {
|
||||
if (null != oldEntry.getSubscription()) {
|
||||
entry.getSubscription().setLastQueryAttempt(oldEntry.getSubscription().getLastQueryAttempt());
|
||||
entry.getSubscription().setLastQuerySuccess(oldEntry.getSubscription().getLastQuerySuccess());
|
||||
}
|
||||
}
|
||||
book.addEntry(entry);
|
||||
System.err.println("Updating the options and subscription for an existing reference to " + entry.getLocalName());
|
||||
activityLog.put(new Long(_context.clock().now()), "Updating options and subscription for " + entry.getLocalName());
|
||||
}
|
||||
} else {
|
||||
book.addConflictingReference(ref);
|
||||
System.out.println("Old entry exists for " + _localName + " - adding a conflicting reference");
|
||||
System.out.println("Existing entry points to " + oldEntry.getNameReference().getDestination().calculateHash().toBase64());
|
||||
System.out.println("New entry points to " + entry.getNameReference().getDestination().calculateHash().toBase64());
|
||||
|
||||
activityLog.put(new Long(_context.clock().now()), "Adding conflicting reference for " + entry.getLocalName());
|
||||
}
|
||||
}
|
||||
|
||||
data.setAddressBook(book);
|
||||
data.setActivityLog(activityLog);
|
||||
|
||||
data.store(f);
|
||||
if (data.getError() != null) {
|
||||
if (data.getErrorMessage() != null)
|
||||
System.err.println(data.getErrorMessage());
|
||||
data.getError().printStackTrace();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
CreateEntryCLI cli = new CreateEntryCLI(args);
|
||||
cli.execute();
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
package net.i2p.myi2p.address;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* CreateNameReferenceCLI outputFile privateDestFile preferredName sequenceNum serviceType[ key=value]*
|
||||
*/
|
||||
public class CreateNameReferenceCLI {
|
||||
private I2PAppContext _context;
|
||||
private String _args[];
|
||||
private String _outputFile;
|
||||
private String _destFile;
|
||||
private String _preferredName;
|
||||
private long _sequenceNum;
|
||||
private String _serviceType;
|
||||
private Properties _options;
|
||||
|
||||
public CreateNameReferenceCLI(String[] args) {
|
||||
_context = new I2PAppContext();
|
||||
_args = args;
|
||||
_options = new Properties();
|
||||
}
|
||||
|
||||
public void execute() {
|
||||
if (parseArgs())
|
||||
doExecute();
|
||||
else
|
||||
System.err.println("Usage: CreateNameReferenceCLI outputFile privateDestFile preferredName sequenceNum serviceType[ key=value]*");
|
||||
}
|
||||
|
||||
private boolean parseArgs() {
|
||||
if ( (_args == null) || (_args.length < 4) )
|
||||
return false;
|
||||
_outputFile = _args[0];
|
||||
_destFile = _args[1];
|
||||
_preferredName = _args[2];
|
||||
try {
|
||||
_sequenceNum = Long.parseLong(_args[3]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return false;
|
||||
}
|
||||
_serviceType = _args[4];
|
||||
|
||||
for (int i = 5; i < _args.length; i++) {
|
||||
int eq = _args[i].indexOf('=');
|
||||
if ( (eq <= 0) || (eq >= _args[i].length() - 1) )
|
||||
continue;
|
||||
String key = _args[i].substring(0,eq);
|
||||
String val = _args[i].substring(eq+1);
|
||||
_options.setProperty(key, val);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void doExecute() {
|
||||
Destination dest = null;
|
||||
SigningPrivateKey priv = null;
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(_destFile);
|
||||
dest = new Destination();
|
||||
dest.readBytes(fis);
|
||||
PrivateKey whocares = new PrivateKey();
|
||||
whocares.readBytes(fis);
|
||||
priv = new SigningPrivateKey();
|
||||
priv.readBytes(fis);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Destination private keys under " + _destFile + " are corrupt");
|
||||
e.printStackTrace();
|
||||
return;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
NameReference ref = new NameReference(_context);
|
||||
ref.setDestination(dest);
|
||||
ref.setPreferredName(_preferredName);
|
||||
ref.setSequenceNum(_sequenceNum);
|
||||
ref.setServiceType(_serviceType);
|
||||
if (_options != null) {
|
||||
for (Iterator iter = _options.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
String val = _options.getProperty(key);
|
||||
ref.setOption(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
ref.sign(priv);
|
||||
} catch (IllegalStateException ise) {
|
||||
System.err.println("Error signing the new reference");
|
||||
ise.printStackTrace();
|
||||
}
|
||||
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(_outputFile);
|
||||
ref.write(fos);
|
||||
} catch (IOException ioe) {
|
||||
System.err.println("Error writing out the new reference");
|
||||
ioe.printStackTrace();
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
|
||||
System.out.println("Reference created at " + _outputFile);
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
CreateNameReferenceCLI cli = new CreateNameReferenceCLI(args);
|
||||
cli.execute();
|
||||
}
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
package net.i2p.myi2p.address;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Define a verified and immutable reference to a particular I2P destination.
|
||||
*
|
||||
*/
|
||||
public class NameReference {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private Destination _destination;
|
||||
private String _preferredName;
|
||||
private String _serviceType;
|
||||
private long _sequenceNum;
|
||||
private Properties _options;
|
||||
private Signature _signature;
|
||||
|
||||
public static final byte[] VERSION_PREFIX = "MyI2P_NameReference_1.0".getBytes();
|
||||
|
||||
public NameReference(I2PAppContext context) {
|
||||
_context = context;
|
||||
_log = context.logManager().getLog(NameReference.class);
|
||||
_destination = null;
|
||||
_preferredName = null;
|
||||
_serviceType = null;
|
||||
_sequenceNum = -1;
|
||||
_options = new Properties();
|
||||
_signature = null;
|
||||
}
|
||||
|
||||
/** retrieve the destination this reference points at */
|
||||
public Destination getDestination() { return _destination; }
|
||||
public void setDestination(Destination dest) { _destination = dest; }
|
||||
|
||||
/** retrieve the name this destination would like to be called */
|
||||
public String getPreferredName() { return _preferredName; }
|
||||
public void setPreferredName(String name) { _preferredName = name; }
|
||||
|
||||
/** retrieve the type of service at this destination (eepsite, ircd, etc) */
|
||||
public String getServiceType() { return _serviceType; }
|
||||
public void setServiceType(String type) { _serviceType = type; }
|
||||
|
||||
/**
|
||||
* data point to allow the reference to be updated. The reference with the
|
||||
* larger sequence number should clobber an older reference.
|
||||
*
|
||||
*/
|
||||
public long getSequenceNum() { return _sequenceNum; }
|
||||
public void setSequenceNum(long num) { _sequenceNum = num; }
|
||||
|
||||
/** Get a list of option names (strings) */
|
||||
public Set getOptionNames() { return new HashSet(_options.keySet()); }
|
||||
|
||||
/** Access any options published in the reference */
|
||||
public String getOption(String name) { return (String)_options.getProperty(name); }
|
||||
|
||||
/**
|
||||
* Specify a particular value for an option. If the value is null, the
|
||||
* entry is removed. The name cannot have an '=' or newline, and the
|
||||
* value cannot have a newline.
|
||||
*
|
||||
* @throws IllegalArgumentException if the name or value is illegal
|
||||
*/
|
||||
public void setOption(String name, String value) {
|
||||
if (name == null) throw new IllegalArgumentException("Missing name");
|
||||
if (name.indexOf('=') != -1)
|
||||
throw new IllegalArgumentException("Name cannot have an = sign");
|
||||
if (name.indexOf('\n') != -1)
|
||||
throw new IllegalArgumentException("Name cannot have a newline");
|
||||
|
||||
if (value != null) {
|
||||
if (value.indexOf('\n') != -1)
|
||||
throw new IllegalArgumentException("Values do not allow newlines");
|
||||
_options.setProperty(name, value);
|
||||
} else {
|
||||
_options.remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
/** Access the DSA signature authenticating this reference */
|
||||
public Signature getSignature() { return _signature; }
|
||||
public void setSignature(Signature sig) { _signature = sig; }
|
||||
|
||||
/** Verify the DSA signature, returning true if it is valid, false otherwise */
|
||||
public boolean validate() {
|
||||
try {
|
||||
byte raw[] = toSignableByteArray();
|
||||
return _context.dsa().verifySignature(_signature, raw, _destination.getSigningPublicKey());
|
||||
} catch (IllegalStateException ise) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign the data
|
||||
*
|
||||
* @throws IllegalStateException if the data is invalid
|
||||
*/
|
||||
public void sign(SigningPrivateKey key) throws IllegalStateException {
|
||||
byte signable[] = toSignableByteArray();
|
||||
_signature = _context.dsa().sign(signable, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the full serialized and signed version of this name reference
|
||||
*
|
||||
* @throws IllegalStateException if the signature or other data is invalid
|
||||
*/
|
||||
public void write(OutputStream out) throws IllegalStateException, IOException {
|
||||
if (_signature == null) throw new IllegalStateException("No signature");
|
||||
byte signable[] = toSignableByteArray();
|
||||
out.write(signable);
|
||||
try {
|
||||
_signature.writeBytes(out);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new IllegalStateException("Signature was corrupt - " + dfe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the signable (but not including the signature) name reference
|
||||
*
|
||||
* @throws IllegalStateException if the data is invalid
|
||||
*/
|
||||
private byte[] toSignableByteArray() throws IllegalStateException {
|
||||
if (_sequenceNum < 0) throw new IllegalStateException("Sequence number is invalid");
|
||||
if (_preferredName == null) throw new IllegalStateException("Preferred name is invalid");
|
||||
if (_serviceType == null) throw new IllegalStateException("Service type is invalid");
|
||||
if (_options == null) throw new IllegalStateException("Options not constructed");
|
||||
if (_destination == null) throw new IllegalStateException("Destination not specified");
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
|
||||
try {
|
||||
baos.write(VERSION_PREFIX);
|
||||
_destination.writeBytes(baos);
|
||||
DataHelper.writeLong(baos, 4, _sequenceNum);
|
||||
DataHelper.writeString(baos, _preferredName);
|
||||
DataHelper.writeString(baos, _serviceType);
|
||||
DataHelper.writeProperties(baos, _options); // sorts alphabetically
|
||||
return baos.toByteArray();
|
||||
} catch (DataFormatException dfe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Corrupted trying to write entry", dfe);
|
||||
return null;
|
||||
} catch (IOException ioe) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("IOError writing to memory?", ioe);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a full signed reference from the given stream
|
||||
*
|
||||
* @throws DataFormatException if the reference is corrupt
|
||||
* @throws IOException if there is an error reading the stream
|
||||
*/
|
||||
public void read(InputStream in) throws DataFormatException, IOException {
|
||||
byte versionBuf[] = new byte[VERSION_PREFIX.length];
|
||||
int len = DataHelper.read(in, versionBuf);
|
||||
if (len != versionBuf.length)
|
||||
throw new IllegalArgumentException("Version length too short ("+ len + ")");
|
||||
if (!DataHelper.eq(versionBuf, VERSION_PREFIX))
|
||||
throw new IllegalArgumentException("Version mismatch (" + new String(versionBuf) + ")");
|
||||
Destination dest = new Destination();
|
||||
dest.readBytes(in);
|
||||
long seq = DataHelper.readLong(in, 4);
|
||||
String name = DataHelper.readString(in);
|
||||
String type = DataHelper.readString(in);
|
||||
Properties opts = DataHelper.readProperties(in);
|
||||
Signature sig = new Signature();
|
||||
sig.readBytes(in);
|
||||
|
||||
// ok, nothing b0rked
|
||||
_destination = dest;
|
||||
_sequenceNum = seq;
|
||||
_preferredName = name;
|
||||
_serviceType = type;
|
||||
_options = opts;
|
||||
_signature = sig;
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package net.i2p.myi2p.address;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataFormatException;
|
||||
|
||||
/**
|
||||
* Contains the preferences for subscribing to a particular peer's address
|
||||
* book.
|
||||
*/
|
||||
public class Subscription {
|
||||
private I2PAppContext _context;
|
||||
private int _queryFrequencyMinutes;
|
||||
private long _lastQueryAttempt;
|
||||
private long _lastQuerySuccess;
|
||||
|
||||
/** no subscription more often than 4 times a day */
|
||||
public static final int MIN_FREQUENCY = 6*60*60*1000;
|
||||
|
||||
public Subscription(I2PAppContext context) {
|
||||
_context = context;
|
||||
}
|
||||
|
||||
/** how often do we want to query the peer (in minutes) */
|
||||
public int getQueryFrequencyMinutes() { return _queryFrequencyMinutes; }
|
||||
public void setQueryFrequencyMinutes(int freq) { _queryFrequencyMinutes = freq; }
|
||||
|
||||
/** when did we last successfully query the peer */
|
||||
public long getLastQuerySuccess() { return _lastQuerySuccess; }
|
||||
public void setLastQuerySuccess(long when) { _lastQuerySuccess = when; }
|
||||
|
||||
/** when did we last attempt to query the peer */
|
||||
public long getLastQueryAttempt() { return _lastQueryAttempt; }
|
||||
public void setLastQueryAttempt(long when) { _lastQueryAttempt = when; }
|
||||
|
||||
/** load the data from the stream */
|
||||
public void read(InputStream in) throws IOException {
|
||||
try {
|
||||
int freq = (int)DataHelper.readLong(in, 2);
|
||||
Date attempt = DataHelper.readDate(in);
|
||||
Date success = DataHelper.readDate(in);
|
||||
_queryFrequencyMinutes = (freq < MIN_FREQUENCY ? MIN_FREQUENCY : freq);
|
||||
_lastQueryAttempt = (attempt != null ? attempt.getTime() : -1);
|
||||
_lastQuerySuccess = (success != null ? success.getTime() : -1);
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new IOException("Corrupt subscription: " + dfe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** persist the data to the stream */
|
||||
public void write(OutputStream out) throws IOException {
|
||||
try {
|
||||
DataHelper.writeLong(out, 2, _queryFrequencyMinutes);
|
||||
DataHelper.writeDate(out, new Date(_lastQueryAttempt));
|
||||
DataHelper.writeDate(out, new Date(_lastQuerySuccess));
|
||||
} catch (DataFormatException dfe) {
|
||||
throw new IOException("Corrupt subscription: " + dfe.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
keyFile=myi2p.keys
|
||||
service.0.classname=net.i2p.myi2p.address.AddressBookService
|
||||
service.0.type=AddressBook
|
||||
@@ -1,130 +0,0 @@
|
||||
# dropped jobs
|
||||
statGroup.0.name=droppedJobs
|
||||
statGroup.0.detail.0.name=num dropped jobs (minute)
|
||||
statGroup.0.detail.0.option=stat_jobQueue.droppedJobs.60m
|
||||
statGroup.0.detail.0.field=3
|
||||
statGroup.0.detail.1.name=num dropped jobs (hour)
|
||||
statGroup.0.detail.1.option=stat_jobQueue.droppedJobs.60h
|
||||
statGroup.0.detail.1.field=3
|
||||
#
|
||||
statGroup.1.name=encryptTime
|
||||
statGroup.1.detail.0.name=encryption time avg ms (minute)
|
||||
statGroup.1.detail.0.option=stat_crypto.elGamal.encrypt.60s
|
||||
statGroup.1.detail.0.field=0
|
||||
statGroup.1.detail.1.name=num encryptions (minute)
|
||||
statGroup.1.detail.1.option=stat_crypto.elGamal.encrypt.60s
|
||||
statGroup.1.detail.1.field=7
|
||||
statGroup.1.detail.2.name=encryption time avg ms (hour)
|
||||
statGroup.1.detail.2.option=stat_crypto.elGamal.encrypt.60s
|
||||
statGroup.1.detail.2.field=0
|
||||
statGroup.1.detail.3.name=num encryptions (hour)
|
||||
statGroup.1.detail.3.option=stat_crypto.elGamal.encrypt.60s
|
||||
statGroup.1.detail.3.field=7
|
||||
#
|
||||
statGroup.2.name=processingTime
|
||||
statGroup.2.detail.0.name=process time avg ms (minute)
|
||||
statGroup.2.detail.0.option=stat_transport.sendProcessingTime.60s
|
||||
statGroup.2.detail.0.field=0
|
||||
statGroup.2.detail.1.name=process events (minute)
|
||||
statGroup.2.detail.1.option=stat_transport.sendProcessingTime.60s
|
||||
statGroup.2.detail.1.field=7
|
||||
statGroup.2.detail.2.name=process time avg ms (hour)
|
||||
statGroup.2.detail.2.option=stat_transport.sendProcessingTime.60m
|
||||
statGroup.2.detail.2.field=0
|
||||
statGroup.2.detail.3.name=process events(hour)
|
||||
statGroup.2.detail.3.option=stat_transport.sendProcessingTime.60m
|
||||
statGroup.2.detail.3.field=7
|
||||
#
|
||||
statGroup.3.name=jobInfo
|
||||
statGroup.3.detail.0.name=job run avg ms (minute)
|
||||
statGroup.3.detail.0.option=stat_jobQueue.jobRun.60s
|
||||
statGroup.3.detail.0.field=0
|
||||
statGroup.3.detail.1.name=job lag avg ms (minute)
|
||||
statGroup.3.detail.1.option=stat_jobQueue.jobLag.60s
|
||||
statGroup.3.detail.1.field=0
|
||||
statGroup.3.detail.2.name=job count (minute)
|
||||
statGroup.3.detail.2.option=stat_jobQueue.jobRun.60s
|
||||
statGroup.3.detail.2.field=7
|
||||
statGroup.3.detail.3.name=job run avg ms (hour)
|
||||
statGroup.3.detail.3.option=stat_jobQueue.jobRun.60m
|
||||
statGroup.3.detail.3.field=0
|
||||
statGroup.3.detail.4.name=job lag avg ms (hour)
|
||||
statGroup.3.detail.4.option=stat_jobQueue.jobLag.60m
|
||||
statGroup.3.detail.4.field=0
|
||||
statGroup.3.detail.5.name=job count (hour)
|
||||
statGroup.3.detail.5.option=stat_jobQueue.jobRun.60m
|
||||
statGroup.3.detail.5.field=7
|
||||
#
|
||||
statGroup.4.name=tunnels
|
||||
statGroup.4.detail.0.name=participating tunnels count (5 minutes)
|
||||
statGroup.4.detail.0.option=stat_tunnel.participatingTunnels.5m
|
||||
statGroup.4.detail.0.field=0
|
||||
statGroup.4.detail.1.name=participating tunnels joined (5 minutes)
|
||||
statGroup.4.detail.1.option=stat_tunnel.participatingTunnels.5m
|
||||
statGroup.4.detail.1.field=3
|
||||
statGroup.4.detail.2.name=participating tunnels count (hour)
|
||||
statGroup.4.detail.2.option=stat_tunnel.participatingTunnels.60m
|
||||
statGroup.4.detail.2.field=0
|
||||
statGroup.4.detail.3.name=participating tunnels joined (hour)
|
||||
statGroup.4.detail.3.option=stat_tunnel.participatingTunnels.60m
|
||||
statGroup.4.detail.3.field=3
|
||||
#
|
||||
statGroup.5.name=transfer
|
||||
statGroup.5.detail.0.name=messages sent (5 minutes)
|
||||
statGroup.5.detail.0.option=stat_transport.sendMessageSize.5m
|
||||
statGroup.5.detail.0.field=7
|
||||
statGroup.5.detail.1.name=send message size avg (5 minutes)
|
||||
statGroup.5.detail.1.option=stat_transport.sendMessageSize.5m
|
||||
statGroup.5.detail.1.field=0
|
||||
statGroup.5.detail.2.name=messages sent (hour)
|
||||
statGroup.5.detail.2.option=stat_transport.sendMessageSize.60m
|
||||
statGroup.5.detail.2.field=7
|
||||
statGroup.5.detail.3.name=send message size avg (hour)
|
||||
statGroup.5.detail.3.option=stat_transport.sendMessageSize.60m
|
||||
statGroup.5.detail.3.field=0
|
||||
statGroup.5.detail.4.name=messages received (5 minutes)
|
||||
statGroup.5.detail.4.option=stat_transport.receiveMessageSize.5m
|
||||
statGroup.5.detail.4.field=7
|
||||
statGroup.5.detail.5.name=receive message size avg (5 minutes)
|
||||
statGroup.5.detail.5.option=stat_transport.receiveMessageSize.5m
|
||||
statGroup.5.detail.5.field=0
|
||||
statGroup.5.detail.6.name=messages received (hour)
|
||||
statGroup.5.detail.6.option=stat_transport.receiveMessageSize.60m
|
||||
statGroup.5.detail.6.field=7
|
||||
statGroup.5.detail.7.name=receive message size avg (hour)
|
||||
statGroup.5.detail.7.option=stat_transport.receiveMessageSize.60m
|
||||
statGroup.5.detail.7.field=0
|
||||
#
|
||||
statGroup.6.name=networkDbHandling
|
||||
statGroup.6.detail.0.name=lookups received (5 minutes)
|
||||
statGroup.6.detail.0.option=stat_netDb.lookupsReceived.5m
|
||||
statGroup.6.detail.0.field=3
|
||||
statGroup.6.detail.1.name=lookups handled (5 minutes)
|
||||
statGroup.6.detail.1.option=stat_netDb.lookupsHandled.5m
|
||||
statGroup.6.detail.1.field=3
|
||||
statGroup.6.detail.2.name=lookups matched (5 minutes)
|
||||
statGroup.6.detail.2.option=stat_netDb.lookupsReceived.5m
|
||||
statGroup.6.detail.2.field=3
|
||||
statGroup.6.detail.3.name=lookups received (hour)
|
||||
statGroup.6.detail.3.option=stat_netDb.lookupsReceived.60m
|
||||
statGroup.6.detail.3.field=3
|
||||
statGroup.6.detail.4.name=lookups handled (hour)
|
||||
statGroup.6.detail.4.option=stat_netDb.lookupsHandled.60m
|
||||
statGroup.6.detail.4.field=3
|
||||
statGroup.6.detail.5.name=lookups matched (hour)
|
||||
statGroup.6.detail.5.option=stat_netDb.lookupsReceived.60m
|
||||
statGroup.6.detail.5.field=3
|
||||
#
|
||||
statGroup.7.name=networkDbActivity
|
||||
statGroup.7.detail.0.name=lookups sent (hour)
|
||||
statGroup.7.detail.0.option=stat_netDb.successPeers.60m
|
||||
statGroup.7.detail.0.field=3
|
||||
statGroup.7.detail.1.name=lookup peers (hour)
|
||||
statGroup.7.detail.1.option=stat_netDb.successPeers.60m
|
||||
statGroup.7.detail.1.field=0
|
||||
statGroup.7.detail.2.name=db store sent (5 minutes)
|
||||
statGroup.7.detail.2.option=stat_netDb.storeSent.5m
|
||||
statGroup.7.detail.2.field=3
|
||||
statGroup.7.detail.3.name=db store sent (hour)
|
||||
statGroup.7.detail.3.option=stat_netDb.storeSent.60m
|
||||
statGroup.7.detail.3.field=3
|
||||
@@ -1,69 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="netmonitor">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../../core/java/" target="build" />
|
||||
</target>
|
||||
<target name="buildGUI" depends="build, jarGUI" />
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac srcdir="./src" debug="true" source="1.3" target="1.3" deprecation="on" destdir="./build/obj" includes="net/**/*.java" excludes="net/i2p/netmonitor/gui/**" classpath="../../../core/java/build/i2p.jar" />
|
||||
</target>
|
||||
|
||||
<target name="compileGUI" depends="builddep">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac debug="true" source="1.3" target="1.3" deprecation="on" destdir="./build/obj">
|
||||
<src path="src/" />
|
||||
<classpath path="../../../core/java/build/i2p.jar" />
|
||||
<classpath path="../../jfreechart/jfreechart-0.9.17/lib/jcommon-0.9.2.jar" />
|
||||
<classpath path="../../jfreechart/jfreechart-0.9.17/lib/log4j-1.2.8.jar" />
|
||||
<classpath path="../../jfreechart/jfreechart-0.9.17/jfreechart-0.9.17.jar" />
|
||||
</javac>
|
||||
</target>
|
||||
|
||||
<target name="jarGUI" depends="compileGUI">
|
||||
<copy file="../../jfreechart/jfreechart-0.9.17/jfreechart-0.9.17.jar" todir="build/" />
|
||||
<copy file="../../jfreechart/jfreechart-0.9.17/lib/log4j-1.2.8.jar" todir="build/" />
|
||||
<copy file="../../jfreechart/jfreechart-0.9.17/lib/jcommon-0.9.2.jar" todir="build/" />
|
||||
<jar destfile="./build/netviewer.jar" basedir="./build/obj" includes="**">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.netmonitor.gui.NetViewer" />
|
||||
<attribute name="Class-Path" value="log4j-1.2.8.jar jcommon-0.9.2.jar jfreechart-0.9.17.jar netviewer.jar i2p.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
<echo message="You will need to copy the log4j, jcommon, and jfreechart jar files into your lib dir" />
|
||||
</target>
|
||||
|
||||
<target name="jar" depends="compile">
|
||||
<jar destfile="./build/netmonitor.jar" basedir="./build/obj" includes="**/*.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.netmonitor.NetMonitor" />
|
||||
<attribute name="Class-Path" value="i2p.jar netmonitor.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="./src:../../../core/java/src:../../../core/java/test" destdir="./build/javadoc"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
access="package"
|
||||
splitindex="true"
|
||||
windowtitle="I2P netmonitor" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../../core/java/" target="cleandep" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
<delete dir="./lib/" />
|
||||
</target>
|
||||
</project>
|
||||
@@ -1,245 +0,0 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Pull out important data from the published routerInfo and stash it away
|
||||
* in the netMonitor
|
||||
*
|
||||
*/
|
||||
class DataHarvester {
|
||||
private static final Log _log = new Log(DataHarvester.class);
|
||||
private static final DataHarvester _instance = new DataHarvester();
|
||||
public static final DataHarvester getInstance() { return _instance; }
|
||||
/**
|
||||
* Contains the list of StatGroup objects loaded from the harvest.config file
|
||||
* {@see StatGroupLoader} where each statGroup defines a set of stats to pull
|
||||
* from each router's options.
|
||||
*
|
||||
*/
|
||||
private List _statGroups;
|
||||
|
||||
/**
|
||||
* Where are we reading the stat groups from? For now, "harvester.config".
|
||||
*/
|
||||
private static final String STAT_GROUP_CONFIG_FILENAME = "harvester.config";
|
||||
|
||||
protected DataHarvester() {
|
||||
_statGroups = StatGroupLoader.loadStatGroups(STAT_GROUP_CONFIG_FILENAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Harvest all of the data from the peers and store it in the monitor.
|
||||
*
|
||||
* @param peers list of RouterInfo structures to harvest from
|
||||
*/
|
||||
public void harvestData(NetMonitor monitor, List peers) {
|
||||
for (int i = 0; i < peers.size(); i++) {
|
||||
harvestData(monitor, (RouterInfo)peers.get(i), peers);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull out all the data we can for the specified peer
|
||||
*
|
||||
* @param peer who are we focusing on in this pass
|
||||
* @param peers everyone on the network, to co
|
||||
*/
|
||||
private void harvestData(NetMonitor monitor, RouterInfo peer, List peers) {
|
||||
_log.info("Harvest the data from " + peer.getIdentity().getHash().toBase64());
|
||||
harvestRank(monitor, peer, peers);
|
||||
harvestRankAs(monitor, peer);
|
||||
harvestGroups(monitor, peer);
|
||||
}
|
||||
|
||||
/**
|
||||
* How does the peer rank other routers? Stored in the peer summary as
|
||||
* "rankAs", containing 4 longs (numFast, numReliable, numNotFailing, numFailing)
|
||||
*
|
||||
* @param peer who is doing the ranking
|
||||
*/
|
||||
private void harvestRankAs(NetMonitor monitor, RouterInfo peer) {
|
||||
int numFast = 0;
|
||||
int numHighCapacity = 0;
|
||||
int numNotFailing = 0;
|
||||
int numFailing = 0;
|
||||
|
||||
Properties props = peer.getOptions();
|
||||
for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
if (key.startsWith("profile.")) {
|
||||
String val = (String)props.get(key);
|
||||
if (val.indexOf("fast") != -1)
|
||||
numFast++;
|
||||
else if (val.indexOf("highCapacity") != -1)
|
||||
numHighCapacity++;
|
||||
else if (val.indexOf("notFailing") != -1)
|
||||
numNotFailing++;
|
||||
else if (val.indexOf("failing") != -1)
|
||||
numFailing++;
|
||||
}
|
||||
}
|
||||
|
||||
long rankAs[] = new long[4];
|
||||
rankAs[0] = numFast;
|
||||
rankAs[1] = numHighCapacity;
|
||||
rankAs[2] = numNotFailing;
|
||||
rankAs[3] = numFailing;
|
||||
String description = "how we rank peers";
|
||||
String valDescr[] = new String[4];
|
||||
valDescr[0] = "# peers we rank as fast";
|
||||
valDescr[1] = "# peers we rank as high capacity";
|
||||
valDescr[2] = "# peers we rank as not failing";
|
||||
valDescr[3] = "# peers we rank as failing";
|
||||
monitor.addData(peer.getIdentity().getHash().toBase64(), "rankAs", description, valDescr, peer.getPublished(), rankAs);
|
||||
}
|
||||
|
||||
/**
|
||||
* How do other peers rank the current peer? Stored in the peer summary as
|
||||
* "rank", containing 4 longs (numFast, numReliable, numNotFailing, numFailing)
|
||||
*
|
||||
* @param peer who do we want to check the network's perception of
|
||||
* @param peers peers whose rankings we will use
|
||||
*/
|
||||
private void harvestRank(NetMonitor monitor, RouterInfo peer, List peers) {
|
||||
int numFast = 0;
|
||||
int numHighCapacity = 0;
|
||||
int numNotFailing = 0;
|
||||
int numFailing = 0;
|
||||
|
||||
// now count 'em
|
||||
for (int i = 0; i < peers.size(); i++) {
|
||||
RouterInfo cur = (RouterInfo)peers.get(i);
|
||||
if (peer == cur) continue;
|
||||
String prop = "profile." + peer.getIdentity().getHash().toBase64().replace('=', '_');
|
||||
String val = cur.getOptions().getProperty(prop);
|
||||
if ( (val == null) || (val.length() <= 0) ) continue;
|
||||
if (val.indexOf("fast") != -1)
|
||||
numFast++;
|
||||
else if (val.indexOf("highCapacity") != -1)
|
||||
numHighCapacity++;
|
||||
else if (val.indexOf("notFailing") != -1)
|
||||
numNotFailing++;
|
||||
else if (val.indexOf("failing") != -1)
|
||||
numFailing++;
|
||||
}
|
||||
|
||||
long rank[] = new long[4];
|
||||
rank[0] = numFast;
|
||||
rank[1] = numHighCapacity;
|
||||
rank[2] = numNotFailing;
|
||||
rank[3] = numFailing;
|
||||
String description = "how peers rank us";
|
||||
String valDescr[] = new String[4];
|
||||
valDescr[0] = "# peers ranking us as fast";
|
||||
valDescr[1] = "# peers ranking us as high capacity";
|
||||
valDescr[2] = "# peers ranking us as not failing";
|
||||
valDescr[3] = "# peers ranking us as failing";
|
||||
// we use the current date, not the published date, since this sample doesnt come from them
|
||||
monitor.addData(peer.getIdentity().getHash().toBase64(), "rank", description, valDescr, Clock.getInstance().now(), rank);
|
||||
}
|
||||
|
||||
/**
|
||||
* Harvest all data points from the peer
|
||||
*
|
||||
*/
|
||||
private void harvestGroups(NetMonitor monitor, RouterInfo peer) {
|
||||
_log.debug("Harvesting group data for " + peer.getIdentity().getHash().toBase64());
|
||||
for (int i = 0; i < _statGroups.size(); i++) {
|
||||
StatGroup group = (StatGroup)_statGroups.get(i);
|
||||
harvestGroup(monitor, peer, group);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Harvest the data points for the given group from the peer and toss them
|
||||
* into the monitor
|
||||
*
|
||||
*/
|
||||
private void harvestGroup(NetMonitor monitor, RouterInfo peer, StatGroup group) {
|
||||
_log.debug("Harvesting group data for " + peer.getIdentity().getHash().toBase64() + " / " + group.getDescription());
|
||||
double values[] = harvestGroupValues(peer, group);
|
||||
if (values == null) return;
|
||||
|
||||
String valDescr[] = new String[group.getStatCount()];
|
||||
for (int i = 0; i < group.getStatCount(); i++)
|
||||
valDescr[i] = group.getStat(i).getStatDescription();
|
||||
monitor.addData(peer.getIdentity().getHash().toBase64(), group.getDescription(), group.getDescription(), valDescr, peer.getPublished(), values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull up a list of all values associated with the group (in the order that the
|
||||
* group specifies).
|
||||
*
|
||||
* @return values or null on error
|
||||
*/
|
||||
private double[] harvestGroupValues(RouterInfo peer, StatGroup group) {
|
||||
List values = new ArrayList(8);
|
||||
for (int i = 0; i < group.getStatCount(); i++) {
|
||||
StatGroup.StatDescription stat = group.getStat(i);
|
||||
double val = getDouble(peer, stat.getOptionName(), stat.getOptionField());
|
||||
if (val == -1)
|
||||
return null;
|
||||
else
|
||||
values.add(new Double(val));
|
||||
}
|
||||
double rv[] = new double[values.size()];
|
||||
for (int i = 0; i < values.size(); i++)
|
||||
rv[i] = ((Double)values.get(i)).doubleValue();
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull a value from the peer's option as a double, assuming the standard semicolon
|
||||
* delimited formatting
|
||||
*
|
||||
* @param peer peer to query
|
||||
* @param key peer option to check
|
||||
* @param index 0-based index into the semicolon delimited values to pull out
|
||||
* @return value, or -1 if there was an error
|
||||
*/
|
||||
private static final double getDouble(RouterInfo peer, String key, int index) {
|
||||
String val = peer.getOptions().getProperty(key);
|
||||
if (val == null) return -1;
|
||||
StringTokenizer tok = new StringTokenizer(val, ";");
|
||||
for (int i = 0; i < index; i++) {
|
||||
if (!tok.hasMoreTokens()) return -1;
|
||||
tok.nextToken(); // ignore
|
||||
}
|
||||
if (!tok.hasMoreTokens()) return -1;
|
||||
String cur = tok.nextToken();
|
||||
try {
|
||||
return getDoubleValue(cur);
|
||||
} catch (ParseException pe) {
|
||||
_log.warn("Unable to parse out the double from field " + index + " out of " + val + " for " + key, pe);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/** this mimics the format used in the router's StatisticsManager */
|
||||
private static final DecimalFormat _numFmt = new DecimalFormat("###,###,###,###,##0.00", new DecimalFormatSymbols(Locale.UK));
|
||||
|
||||
/**
|
||||
* Converts a number (double) to text
|
||||
* @param val the number to convert
|
||||
* @return the textual representation
|
||||
*/
|
||||
private static final double getDoubleValue(String val) throws ParseException {
|
||||
synchronized (_numFmt) {
|
||||
Number n = _numFmt.parse(val);
|
||||
return n.doubleValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Main driver for the app that harvests data about the performance of the network,
|
||||
* building summaries for each peer that change over time. <p />
|
||||
*
|
||||
* Usage: <code>NetMonitor [configFilename] [--routers filename[,filename]*] [--netDbURL url] </code> <p />
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class NetMonitor {
|
||||
private static final Log _log = new Log(NetMonitor.class);
|
||||
public static final String CONFIG_LOCATION_DEFAULT = "netmonitor.config";
|
||||
public static final String HARVEST_DELAY_PROP = "harvestDelaySeconds";
|
||||
public static final int HARVEST_DELAY_DEFAULT = 5*60;
|
||||
public static final String EXPORT_DELAY_PROP = "exportDelaySeconds";
|
||||
public static final int EXPORT_DELAY_DEFAULT = 120;
|
||||
public static final String SUMMARY_DURATION_PROP = "summaryDurationHours";
|
||||
public static final int SUMMARY_DURATION_DEFAULT = 72;
|
||||
public static final String NETDB_DIR_PROP = "netDbDir";
|
||||
public static final String NETDB_DIR_DEFAULT = "netDb";
|
||||
public static final String EXPORT_DIR_PROP = "exportDir";
|
||||
public static final String EXPORT_DIR_DEFAULT = "monitorData";
|
||||
private String _configLocation;
|
||||
private int _harvestDelay;
|
||||
private int _exportDelay;
|
||||
private String _exportDir;
|
||||
private String _netDbDir;
|
||||
private String _netDbURL;
|
||||
private String _explicitRouters;
|
||||
private int _summaryDurationHours;
|
||||
private boolean _isRunning;
|
||||
private Map _peerSummaries;
|
||||
|
||||
public NetMonitor() {
|
||||
this(CONFIG_LOCATION_DEFAULT, null, null);
|
||||
}
|
||||
public NetMonitor(String configLocation) {
|
||||
this(configLocation, null, null);
|
||||
}
|
||||
public NetMonitor(String configLocation, String explicitFilenames, String url) {
|
||||
_configLocation = configLocation;
|
||||
_explicitRouters = explicitFilenames;
|
||||
_netDbURL = url;
|
||||
_peerSummaries = new HashMap(32);
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
/** read and call parse */
|
||||
private void loadConfig() {
|
||||
Properties props = new Properties();
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(_configLocation);
|
||||
props.load(fis);
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Error loading the net monitor config", ioe);
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
parseConfig(props);
|
||||
}
|
||||
|
||||
/** interpret the config elements and shove 'em in the vars */
|
||||
private void parseConfig(Properties props) {
|
||||
String val = props.getProperty(HARVEST_DELAY_PROP, ""+HARVEST_DELAY_DEFAULT);
|
||||
try {
|
||||
_harvestDelay = Integer.parseInt(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.warn("Error parsing the harvest delay [" + val + "]", nfe);
|
||||
_harvestDelay = HARVEST_DELAY_DEFAULT;
|
||||
}
|
||||
|
||||
val = props.getProperty(EXPORT_DELAY_PROP, ""+EXPORT_DELAY_DEFAULT);
|
||||
try {
|
||||
_exportDelay = Integer.parseInt(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.warn("Error parsing the export delay [" + val + "]", nfe);
|
||||
_exportDelay = EXPORT_DELAY_DEFAULT;
|
||||
}
|
||||
|
||||
val = props.getProperty(SUMMARY_DURATION_PROP, ""+SUMMARY_DURATION_DEFAULT);
|
||||
try {
|
||||
_summaryDurationHours = Integer.parseInt(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.warn("Error parsing the summary duration [" + val + "]", nfe);
|
||||
_summaryDurationHours = SUMMARY_DURATION_DEFAULT;
|
||||
}
|
||||
|
||||
_netDbDir = props.getProperty(NETDB_DIR_PROP, NETDB_DIR_DEFAULT);
|
||||
_exportDir = props.getProperty(EXPORT_DIR_PROP, EXPORT_DIR_DEFAULT);
|
||||
}
|
||||
|
||||
public void startMonitor() {
|
||||
_isRunning = true;
|
||||
I2PThread t = new I2PThread(new NetMonitorRunner(this));
|
||||
t.setName("DataHarvester");
|
||||
t.setPriority(I2PThread.MIN_PRIORITY);
|
||||
t.setDaemon(false);
|
||||
t.start();
|
||||
}
|
||||
|
||||
public void stopMonitor() { _isRunning = false; }
|
||||
public boolean isRunning() { return _isRunning; }
|
||||
/** how many seconds should we wait between harvestings? */
|
||||
public int getHarvestDelay() { return _harvestDelay; }
|
||||
/** how many seconds should we wait between exporting the data? */
|
||||
public int getExportDelay() { return _exportDelay; }
|
||||
/** where should we export the data? */
|
||||
public String getExportDir() { return _exportDir; }
|
||||
public void setExportDir(String dir) { _exportDir = dir; }
|
||||
public int getSummaryDurationHours() { return _summaryDurationHours; }
|
||||
/** where should we read the data from? */
|
||||
public String getNetDbDir() { return _netDbDir; }
|
||||
/** if specified, contains a set of filenames we want to harvest routerInfo data from */
|
||||
public String getExplicitRouters() { return _explicitRouters; }
|
||||
/** if specified, contains a URL to fetch references from */
|
||||
public String getNetDbURL() { return _netDbURL; }
|
||||
|
||||
/**
|
||||
* what peers are we keeping track of?
|
||||
*
|
||||
* @return list of peer names (H(routerIdentity).toBase64())
|
||||
*/
|
||||
public List getPeers() {
|
||||
synchronized (_peerSummaries) {
|
||||
return new ArrayList(_peerSummaries.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
/** what data do we have for the peer? */
|
||||
public PeerSummary getSummary(String peer) {
|
||||
synchronized (_peerSummaries) {
|
||||
return (PeerSummary)_peerSummaries.get(peer);
|
||||
}
|
||||
}
|
||||
|
||||
/** keep track of the given stat on the given peer */
|
||||
public void addData(String peer, String stat, String descr, String valDescr[], long sampleDate, double val[]) {
|
||||
synchronized (_peerSummaries) {
|
||||
if (!_peerSummaries.containsKey(peer))
|
||||
_peerSummaries.put(peer, new PeerSummary(peer));
|
||||
PeerSummary summary = (PeerSummary)_peerSummaries.get(peer);
|
||||
summary.addData(stat, descr, valDescr, sampleDate, val);
|
||||
}
|
||||
}
|
||||
|
||||
/** keep track of the given stat on the given peer */
|
||||
public void addData(String peer, String stat, String descr, String valDescr[], long sampleDate, long val[]) {
|
||||
synchronized (_peerSummaries) {
|
||||
if (!_peerSummaries.containsKey(peer))
|
||||
_peerSummaries.put(peer, new PeerSummary(peer));
|
||||
PeerSummary summary = (PeerSummary)_peerSummaries.get(peer);
|
||||
summary.addData(stat, descr, valDescr, sampleDate, val);
|
||||
}
|
||||
}
|
||||
|
||||
/** keep track of the loaded summary, overwriting any existing summary for the specified peer */
|
||||
public void addSummary(PeerSummary summary) {
|
||||
synchronized (_peerSummaries) {
|
||||
Object rv = _peerSummaries.put(summary.getPeer(), summary);
|
||||
if (rv != summary) _log.error("Updating the peer summary changed objects! old = " + rv + " new = " + summary);
|
||||
}
|
||||
}
|
||||
|
||||
public void importData() {
|
||||
_log.debug("Running import");
|
||||
File dataDir = new File(getExportDir());
|
||||
if (!dataDir.exists()) return;
|
||||
File dataFiles[] = dataDir.listFiles(new FilenameFilter() {
|
||||
public boolean accept(File f, String name) {
|
||||
return name.endsWith(".txt");
|
||||
}
|
||||
});
|
||||
if (dataFiles == null) return;
|
||||
for (int i = 0; i < dataFiles.length; i++) {
|
||||
FileInputStream fis = null;
|
||||
boolean delete = false;
|
||||
try {
|
||||
fis = new FileInputStream(dataFiles[i]);
|
||||
PeerSummaryReader.getInstance().read(this, fis);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error reading the data file " + dataFiles[i].getAbsolutePath(), ioe);
|
||||
delete = true;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
if (delete) dataFiles[i].delete();
|
||||
}
|
||||
}
|
||||
_log.debug(dataFiles.length + " summaries imported");
|
||||
}
|
||||
|
||||
/** drop all the old summary data */
|
||||
public void coalesceData() {
|
||||
synchronized (_peerSummaries) {
|
||||
for (Iterator iter = _peerSummaries.values().iterator(); iter.hasNext(); ) {
|
||||
PeerSummary summary = (PeerSummary)iter.next();
|
||||
summary.coalesceData(_summaryDurationHours * 60*60*1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* main driver for the netMonitor. the usage is:
|
||||
* <code>NetMonitor [configFilename] [--routers filename[,filename]*] [--netDbURL url]</code>
|
||||
*/
|
||||
public static final void main(String args[]) {
|
||||
String cfgLocation = CONFIG_LOCATION_DEFAULT;
|
||||
String explicitFilenames = null;
|
||||
String explicitURL = null;
|
||||
switch (args.length) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
cfgLocation = args[0];
|
||||
break;
|
||||
case 2:
|
||||
if ("--routers".equalsIgnoreCase(args[0]))
|
||||
explicitFilenames = args[1];
|
||||
else
|
||||
explicitURL = args[1];
|
||||
break;
|
||||
case 3:
|
||||
cfgLocation = args[0];
|
||||
if ("--routers".equalsIgnoreCase(args[1]))
|
||||
explicitFilenames = args[2];
|
||||
else
|
||||
explicitURL = args[2];
|
||||
break;
|
||||
default:
|
||||
System.err.println("Usage: NetMonitor [configFilename] [--routers filename[,filename]*] [--netDbURL url]");
|
||||
return;
|
||||
}
|
||||
new NetMonitor(cfgLocation, explicitFilenames, explicitURL).startMonitor();
|
||||
}
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Active process that drives the monitoring by periodically rading the
|
||||
* netDb dir, pumping the router data throug the data harvester, updating
|
||||
* the state, and coordinating the export.
|
||||
*
|
||||
*/
|
||||
class NetMonitorRunner implements Runnable {
|
||||
private static final Log _log = new Log(NetMonitorRunner.class);
|
||||
private NetMonitor _monitor;
|
||||
/**
|
||||
* @param monitor who do we give our data to?
|
||||
*/
|
||||
public NetMonitorRunner(NetMonitor monitor) {
|
||||
_monitor = monitor;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
runImport();
|
||||
long now = Clock.getInstance().now();
|
||||
long nextHarvest = now;
|
||||
long nextExport = now + _monitor.getExportDelay() * 1000;
|
||||
while (_monitor.isRunning()) {
|
||||
now = Clock.getInstance().now();
|
||||
_monitor.coalesceData();
|
||||
if (now >= nextHarvest) {
|
||||
runHarvest();
|
||||
nextHarvest = now + _monitor.getHarvestDelay() * 1000;
|
||||
}
|
||||
if (now >= nextExport) {
|
||||
runExport();
|
||||
nextExport = now + _monitor.getExportDelay() * 1000;
|
||||
}
|
||||
pauseHarvesting();
|
||||
}
|
||||
}
|
||||
|
||||
private void runHarvest() {
|
||||
try {
|
||||
List routers = getRouters();
|
||||
DataHarvester.getInstance().harvestData(_monitor, routers);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Unhandled exception harvesting the data", t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all of the available RouterInfo structures
|
||||
*
|
||||
*/
|
||||
private List getRouters() {
|
||||
if (_monitor.getNetDbURL() != null)
|
||||
return fetchRouters(_monitor.getNetDbURL());
|
||||
|
||||
File routers[] = listRouters();
|
||||
List rv = new ArrayList(64);
|
||||
if (routers != null) {
|
||||
for (int i = 0; i < routers.length; i++) {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(routers[i]);
|
||||
RouterInfo ri = new RouterInfo();
|
||||
ri.readBytes(fis);
|
||||
rv.add(ri);
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.warn("Unable to parse the routerInfo from " + routers[i].getAbsolutePath(), dfe);
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Unable to read the routerInfo from " + routers[i].getAbsolutePath(), ioe);
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
private List fetchRouters(String seedURL) {
|
||||
List rv = new ArrayList();
|
||||
try {
|
||||
URL dir = new URL(seedURL);
|
||||
String content = new String(readURL(dir));
|
||||
Set urls = new HashSet();
|
||||
int cur = 0;
|
||||
while (true) {
|
||||
int start = content.indexOf("href=\"routerInfo-", cur);
|
||||
if (start < 0)
|
||||
break;
|
||||
|
||||
int end = content.indexOf(".dat\">", start);
|
||||
String name = content.substring(start+"href=\"routerInfo-".length(), end);
|
||||
urls.add(name);
|
||||
cur = end + 1;
|
||||
}
|
||||
|
||||
for (Iterator iter = urls.iterator(); iter.hasNext(); ) {
|
||||
rv.add(fetchSeed((String)iter.next()));
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error fetching routers from " + seedURL, t);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private RouterInfo fetchSeed(String peer) throws Exception {
|
||||
URL url = new URL("http://i2p.net/i2pdb/routerInfo-" + peer + ".dat");
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Fetching seed from " + url.toExternalForm());
|
||||
|
||||
byte data[] = readURL(url);
|
||||
RouterInfo info = new RouterInfo();
|
||||
try {
|
||||
info.fromByteArray(data);
|
||||
return info;
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.error("Router data at " + url.toExternalForm() + " was corrupt", dfe);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] readURL(URL url) throws Exception {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
|
||||
URLConnection con = url.openConnection();
|
||||
InputStream in = con.getInputStream();
|
||||
byte buf[] = new byte[1024];
|
||||
while (true) {
|
||||
int read = in.read(buf);
|
||||
if (read < 0)
|
||||
break;
|
||||
baos.write(buf, 0, read);
|
||||
}
|
||||
in.close();
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* dump the data to the filesystem
|
||||
*/
|
||||
private void runExport() {
|
||||
_log.info("Export");
|
||||
List peers = _monitor.getPeers();
|
||||
File exportDir = new File(_monitor.getExportDir());
|
||||
if (!exportDir.exists())
|
||||
exportDir.mkdirs();
|
||||
for (int i = 0; i < peers.size(); i++) {
|
||||
String peerName = (String)peers.get(i);
|
||||
PeerSummary summary = (PeerSummary)_monitor.getSummary(peerName);
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
File summaryFile = new File(exportDir, peerName + ".txt");
|
||||
fos = new FileOutputStream(summaryFile);
|
||||
PeerSummaryWriter.getInstance().write(summary, fos);
|
||||
_log.debug("Peer summary written to " + summaryFile.getAbsolutePath());
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error exporting the peer summary for " + peerName, ioe);
|
||||
} catch (Throwable t) {
|
||||
_log.error("Unhandled exception exporting the data", t);
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read in all the peer summaries we had previously exported, overwriting any
|
||||
* existing ones in memory
|
||||
*
|
||||
*/
|
||||
private void runImport() {
|
||||
_monitor.importData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all of the routers to load
|
||||
*
|
||||
* @return list of File objects pointing at the routers around
|
||||
*/
|
||||
private File[] listRouters() {
|
||||
if (_monitor.getExplicitRouters() != null) {
|
||||
return listRoutersExplicit();
|
||||
} else {
|
||||
File dbDir = new File(_monitor.getNetDbDir());
|
||||
File files[] = dbDir.listFiles(new FilenameFilter() {
|
||||
public boolean accept(File f, String name) {
|
||||
return name.startsWith("routerInfo-");
|
||||
}
|
||||
});
|
||||
return files;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of router files that were explicitly specified by the netMonitor
|
||||
*
|
||||
*/
|
||||
private File[] listRoutersExplicit() {
|
||||
StringTokenizer tok = new StringTokenizer(_monitor.getExplicitRouters().trim(), ",");
|
||||
List rv = new ArrayList();
|
||||
while (tok.hasMoreTokens()) {
|
||||
String name = tok.nextToken();
|
||||
File cur = new File(name);
|
||||
if (cur.exists())
|
||||
rv.add(cur);
|
||||
}
|
||||
File files[] = new File[rv.size()];
|
||||
for (int i = 0; i < rv.size(); i++)
|
||||
files[i] = (File)rv.get(i);
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait the correct amount of time before harvesting again
|
||||
*
|
||||
*/
|
||||
private void pauseHarvesting() {
|
||||
try {
|
||||
Thread.sleep(_monitor.getHarvestDelay());
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
/**
|
||||
* Actual data point (though its not fully normalized, but KISS)
|
||||
*
|
||||
*/
|
||||
public class PeerStat {
|
||||
private String _statName;
|
||||
private String _description;
|
||||
private String _valueDescriptions[];
|
||||
private long _sampleDate;
|
||||
private long _lvalues[];
|
||||
private double _dvalues[];
|
||||
|
||||
public PeerStat(String name, String description, String valueDescriptions[], long sampleDate, double values[]) {
|
||||
this(name, description, valueDescriptions, sampleDate, null, values);
|
||||
}
|
||||
public PeerStat(String name, String description, String valueDescriptions[], long sampleDate, long values[]) {
|
||||
this(name, description, valueDescriptions, sampleDate, values, null);
|
||||
}
|
||||
private PeerStat(String name, String description, String valueDescriptions[], long sampleDate, long lvalues[], double dvalues[]) {
|
||||
_statName = name;
|
||||
_description = description;
|
||||
_valueDescriptions = valueDescriptions;
|
||||
_sampleDate = sampleDate;
|
||||
_lvalues = lvalues;
|
||||
_dvalues = dvalues;
|
||||
}
|
||||
|
||||
/** unique name of the stat */
|
||||
public String getStatName() { return _statName; }
|
||||
/** one line summary of the stat */
|
||||
public String getDescription() { return _description; }
|
||||
/** description of each value */
|
||||
public String getValueDescription(int index) { return _valueDescriptions[index]; }
|
||||
/** description of all values */
|
||||
public String[] getValueDescriptions() { return _valueDescriptions; }
|
||||
/** when did the router publish the info being sampled? */
|
||||
public long getSampleDate() { return _sampleDate; }
|
||||
/** if the values are integers, this contains their values */
|
||||
public long[] getLongValues() { return _lvalues; }
|
||||
/** if the values are floating point numbers, this contains their values */
|
||||
public double[] getDoubleValues() { return _dvalues; }
|
||||
/** are the values floating poing numbers? */
|
||||
public boolean getIsDouble() { return _dvalues != null; }
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* coordinate the data points summarizing the performance of a particular peer
|
||||
* within the network
|
||||
*/
|
||||
public class PeerSummary {
|
||||
private static final Log _log = new Log(PeerSummary.class);
|
||||
private String _peer;
|
||||
/** statName to a List of PeerStat elements (sorted by sample date, earliest first) */
|
||||
private Map _stats;
|
||||
/** lock on this when accessing stat data */
|
||||
private Object _coalesceLock = new Object();
|
||||
|
||||
public PeerSummary(String peer) {
|
||||
_peer = peer;
|
||||
_stats = new HashMap(16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Track a data point
|
||||
*
|
||||
* @param stat what data are we tracking?
|
||||
* @param description what does this data mean? (and what are the values?)
|
||||
* @param when what data set is this sample based off?
|
||||
* @param val actual data harvested
|
||||
*/
|
||||
public void addData(String stat, String description, String valueDescriptions[], long when, double val[]) {
|
||||
synchronized (_coalesceLock) {
|
||||
TreeMap stats = locked_getData(stat);
|
||||
stats.put(new Long(when), new PeerStat(stat, description, valueDescriptions, when, val));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Track a data point
|
||||
*
|
||||
* @param stat what data are we tracking?
|
||||
* @param description what does this data mean? (and what are the values?)
|
||||
* @param when what data set is this sample based off?
|
||||
* @param val actual data harvested
|
||||
*/
|
||||
public void addData(String stat, String description, String valueDescriptions[], long when, long val[]) {
|
||||
synchronized (_coalesceLock) {
|
||||
TreeMap stats = locked_getData(stat);
|
||||
stats.put(new Long(when), new PeerStat(stat, description, valueDescriptions, when, val));
|
||||
}
|
||||
}
|
||||
|
||||
/** get the peer's name (H(routerIdentity).toBase64()) */
|
||||
public String getPeer() { return _peer; }
|
||||
|
||||
/**
|
||||
* fetch the ordered list of PeerStat objects for the given stat (or null if
|
||||
* isn't being tracked or has no data)
|
||||
*
|
||||
*/
|
||||
public List getData(String statName) {
|
||||
synchronized (_coalesceLock) {
|
||||
return new ArrayList(((TreeMap)_stats.get(statName)).values());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the names of all of the stats that are being tracked
|
||||
*
|
||||
*/
|
||||
public Set getStatNames() {
|
||||
synchronized (_coalesceLock) {
|
||||
return new HashSet(_stats.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
/** drop old data points */
|
||||
public void coalesceData(long summaryDurationMs) {
|
||||
long earliest = Clock.getInstance().now() - summaryDurationMs;
|
||||
synchronized (_coalesceLock) {
|
||||
locked_coalesce(earliest);
|
||||
}
|
||||
}
|
||||
|
||||
/** go through all the stats and remove ones from before the given date */
|
||||
private void locked_coalesce(long earliestSampleDate) {
|
||||
if (true) return;
|
||||
for (Iterator iter = _stats.keySet().iterator(); iter.hasNext(); ) {
|
||||
String statName = (String)iter.next();
|
||||
TreeMap stats = (TreeMap)_stats.get(statName);
|
||||
while (stats.size() > 0) {
|
||||
Long when = (Long)stats.keySet().iterator().next();
|
||||
if (when.longValue() < earliestSampleDate) {
|
||||
stats.remove(when);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PeerStat elements, ordered by sample date (earliest first)
|
||||
*/
|
||||
private TreeMap locked_getData(String statName) {
|
||||
if (!_stats.containsKey(statName))
|
||||
_stats.put(statName, new TreeMap());
|
||||
return (TreeMap)_stats.get(statName);
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Load up the peer summary
|
||||
*
|
||||
*/
|
||||
class PeerSummaryReader {
|
||||
private static final Log _log = new Log(PeerSummaryReader.class);
|
||||
private static final PeerSummaryReader _instance = new PeerSummaryReader();
|
||||
public static final PeerSummaryReader getInstance() { return _instance; }
|
||||
private PeerSummaryReader() {}
|
||||
|
||||
/** */
|
||||
public void read(NetMonitor monitor, InputStream in) throws IOException {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
String line = null;
|
||||
PeerSummary summary = null;
|
||||
String curDescription = null;
|
||||
List curArgs = null;
|
||||
try {
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.startsWith("peer\t")) {
|
||||
String name = line.substring("peer\t".length()).trim();
|
||||
summary = monitor.getSummary(name);
|
||||
if (summary == null)
|
||||
summary = new PeerSummary(name);
|
||||
} else if (line.startsWith("## ")) {
|
||||
curDescription = line.substring("## ".length()).trim();
|
||||
curArgs = new ArrayList(4);
|
||||
} else if (line.startsWith("# param ")) {
|
||||
int start = line.indexOf(':');
|
||||
String arg = line.substring(start+1).trim();
|
||||
curArgs.add(arg);
|
||||
} else {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String name = tok.nextToken();
|
||||
try {
|
||||
long when = getTime(tok.nextToken());
|
||||
boolean isDouble = false;
|
||||
List argVals = new ArrayList(curArgs.size());
|
||||
while (tok.hasMoreTokens()) {
|
||||
String val = (String)tok.nextToken();
|
||||
if (val.indexOf('.') >= 0) {
|
||||
argVals.add(new Double(val));
|
||||
isDouble = true;
|
||||
} else {
|
||||
argVals.add(new Long(val));
|
||||
}
|
||||
}
|
||||
String valDescriptions[] = new String[curArgs.size()];
|
||||
for (int i = 0; i < curArgs.size(); i++)
|
||||
valDescriptions[i] = (String)curArgs.get(i);
|
||||
if (isDouble) {
|
||||
double values[] = new double[argVals.size()];
|
||||
for (int i = 0; i < argVals.size(); i++)
|
||||
values[i] = ((Double)argVals.get(i)).doubleValue();
|
||||
summary.addData(name, curDescription, valDescriptions, when, values);
|
||||
} else {
|
||||
long values[] = new long[argVals.size()];
|
||||
for (int i = 0; i < argVals.size(); i++)
|
||||
values[i] = ((Long)argVals.get(i)).longValue();
|
||||
summary.addData(name, curDescription, valDescriptions, when, values);
|
||||
}
|
||||
} catch (ParseException pe) {
|
||||
_log.error("Error parsing the data line [" + line + "]", pe);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Error parsing the data line [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
_log.error("Error handling the data", e);
|
||||
throw new IOException("Error parsing the data");
|
||||
}
|
||||
if (summary == null)
|
||||
return;
|
||||
summary.coalesceData(monitor.getSummaryDurationHours() * 60*60*1000);
|
||||
monitor.addSummary(summary);
|
||||
}
|
||||
|
||||
private static final SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd.HH:mm:ss.SSS", Locale.UK);
|
||||
|
||||
/**
|
||||
* Converts a time (long) to text
|
||||
* @param when the time to convert
|
||||
* @return the textual representation
|
||||
*/
|
||||
public long getTime(String when) throws ParseException {
|
||||
synchronized (_fmt) {
|
||||
return _fmt.parse(when).getTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user