forked from I2P_Developers/i2p.i2p
Compare commits
204 Commits
i2p_0_6_1_
...
i2p-0.6.1.
Author | SHA1 | Date | |
---|---|---|---|
ea0d4ffd7f | |||
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 |
@ -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,27 @@ 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 &&
|
||||
host.replaceAll("[a-z0-9.-]", "").length() == 0 &&
|
||||
|
||||
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 +195,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 +210,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
|
||||
|
@ -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,6 +124,22 @@ 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
|
||||
@ -129,4 +155,5 @@ public class BitField
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ public class I2PSnarkUtil {
|
||||
private I2PSocketManager _manager;
|
||||
private boolean _configured;
|
||||
private Set _shitlist;
|
||||
private int _maxUploaders;
|
||||
|
||||
private I2PSnarkUtil() {
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
@ -42,6 +43,7 @@ public class I2PSnarkUtil {
|
||||
setI2CPConfig("127.0.0.1", 7654, null);
|
||||
_shitlist = new HashSet(64);
|
||||
_configured = false;
|
||||
_maxUploaders = Snark.MAX_TOTAL_UPLOADERS;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,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
|
||||
@ -93,10 +101,16 @@ 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", "2"); // 1 == disconnect, 2 == ping
|
||||
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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
@ -392,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())
|
||||
{
|
||||
@ -403,9 +483,17 @@ public class PeerCoordinator implements PeerListener
|
||||
}
|
||||
if (piece == null) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("nothing to even rerequest from " + peer + ": requested = " + requested
|
||||
+ " wanted = " + wantedPieces + " peerHas = " + havePieces);
|
||||
_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);
|
||||
@ -417,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)
|
||||
{
|
||||
@ -504,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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -533,7 +634,7 @@ public class PeerCoordinator implements PeerListener
|
||||
{
|
||||
synchronized(peers)
|
||||
{
|
||||
if (uploaders < MAX_UPLOADERS)
|
||||
if (uploaders < allowedUploaders())
|
||||
{
|
||||
if(peer.isChoking())
|
||||
{
|
||||
@ -582,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))
|
||||
@ -414,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();
|
||||
@ -431,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;
|
||||
@ -456,7 +496,6 @@ class PeerState
|
||||
if (!choked)
|
||||
out.sendRequest(req);
|
||||
lastRequest = req;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -472,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);
|
||||
@ -491,7 +555,6 @@ class PeerState
|
||||
lastRequest = req;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -523,4 +586,15 @@ class PeerState
|
||||
out.sendChoke(choke);
|
||||
}
|
||||
}
|
||||
|
||||
void keepAlive()
|
||||
{
|
||||
out.sendAlive();
|
||||
}
|
||||
|
||||
synchronized void retransmitRequests()
|
||||
{
|
||||
if (interesting && !choked)
|
||||
out.retransmitRequests(outstandingRequests);
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ class Request
|
||||
final byte[] bs;
|
||||
final int off;
|
||||
final int len;
|
||||
long sendTime;
|
||||
|
||||
/**
|
||||
* Creates a new Request.
|
||||
|
@ -63,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;
|
||||
@ -234,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) {
|
||||
@ -268,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++)
|
||||
@ -283,6 +284,11 @@ 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();
|
||||
@ -292,6 +298,7 @@ public class Snark
|
||||
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;
|
||||
@ -361,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)
|
||||
{
|
||||
@ -371,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();
|
||||
}
|
||||
@ -386,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()) {
|
||||
@ -402,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();
|
||||
@ -422,6 +468,8 @@ public class Snark
|
||||
pc.halt();
|
||||
Storage st = storage;
|
||||
if (st != null) {
|
||||
if (storage.changed)
|
||||
SnarkManager.instance().saveTorrentStatus(storage.getMetaInfo(), storage.getBitField());
|
||||
try {
|
||||
storage.close();
|
||||
} catch (IOException ioe) {
|
||||
@ -457,6 +505,7 @@ public class Snark
|
||||
int i = 0;
|
||||
while (i < args.length)
|
||||
{
|
||||
/*
|
||||
if (args[i].equals("--debug"))
|
||||
{
|
||||
debug = INFO;
|
||||
@ -477,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");
|
||||
@ -693,6 +742,11 @@ public class Snark
|
||||
completeListener.torrentComplete(this);
|
||||
}
|
||||
|
||||
public void setWantedPieces(Storage storage)
|
||||
{
|
||||
coordinator.setWantedPieces();
|
||||
}
|
||||
|
||||
public void shutdown()
|
||||
{
|
||||
// Should not be necessary since all non-deamon threads should
|
||||
@ -703,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();
|
||||
@ -91,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))
|
||||
@ -125,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();
|
||||
}
|
||||
|
||||
@ -140,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();
|
||||
@ -155,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();
|
||||
@ -248,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 + "'");
|
||||
}
|
||||
@ -267,7 +295,7 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
public Snark getTorrent(String filename) { synchronized (_snarks) { return (Snark)_snarks.get(filename); } }
|
||||
public void addTorrent(String filename) { addTorrent(filename, false); }
|
||||
public void addTorrent(String filename, boolean dontAutoStart) {
|
||||
if (!I2PSnarkUtil.instance().connected()) {
|
||||
if ((!dontAutoStart) && !I2PSnarkUtil.instance().connected()) {
|
||||
addMessage("Connecting to I2P");
|
||||
boolean ok = I2PSnarkUtil.instance().connect();
|
||||
if (!ok) {
|
||||
@ -287,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);
|
||||
@ -303,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());
|
||||
@ -313,9 +352,9 @@ 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);
|
||||
@ -327,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());
|
||||
@ -392,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() + "'");
|
||||
}
|
||||
}
|
||||
@ -444,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...
|
||||
@ -463,22 +593,29 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
}
|
||||
|
||||
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/announce.php"
|
||||
"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 for the trackers to be displayed */
|
||||
/** comma delimited list of name=announceURL=baseURL for the trackers to be displayed */
|
||||
public static final String PROP_TRACKERS = "i2psnark.trackers";
|
||||
/** unordered map of announceURL to name */
|
||||
private static Map trackerMap = null;
|
||||
/** sorted map of name to announceURL=baseURL */
|
||||
public Map getTrackers() {
|
||||
HashMap rv = new HashMap();
|
||||
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+1], DEFAULT_TRACKERS[i]);
|
||||
rv.put(DEFAULT_TRACKERS[i], DEFAULT_TRACKERS[i+1]);
|
||||
} else {
|
||||
StringTokenizer tok = new StringTokenizer(trackers, ",");
|
||||
while (tok.hasMoreTokens()) {
|
||||
@ -489,11 +626,12 @@ public class SnarkManager implements Snark.CompleteListener {
|
||||
String name = pair.substring(0, split).trim();
|
||||
String url = pair.substring(split+1).trim();
|
||||
if ( (name.length() > 0) && (url.length() > 0) )
|
||||
rv.put(url, name);
|
||||
rv.put(name, url);
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
trackerMap = rv;
|
||||
return trackerMap;
|
||||
}
|
||||
|
||||
private static class TorrentFilenameFilter implements FilenameFilter {
|
||||
|
@ -39,15 +39,17 @@ public class Storage
|
||||
|
||||
private final StorageListener listener;
|
||||
|
||||
private final BitField bitfield; // BitField to represent the pieces
|
||||
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,6 +594,7 @@ public class Storage
|
||||
needed--;
|
||||
complete = needed == 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Early typecast, avoid possibly overflowing a temp integer
|
||||
@ -537,24 +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().
|
||||
|
||||
// Early typecast, avoid possibly overflowing a temp integer
|
||||
long start = (long) piece * (long) metainfo.getPieceLength(0);
|
||||
long start = ((long) piece * (long) metainfo.getPieceLength(0)) + off;
|
||||
|
||||
int length = metainfo.getPieceLength(piece);
|
||||
int i = 0;
|
||||
long raflen = lengths[i];
|
||||
while (start > raflen)
|
||||
@ -572,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,19 +93,42 @@ 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 (true) // seeding needs to register the torrent first, so we can't start it automatically (boo, hiss)
|
||||
writeSeedForm(out, req);
|
||||
@ -198,23 +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++) {
|
||||
// multifile torrents have the getFiles() return lists of lists of filenames, but
|
||||
// each of those lists just contain a single file afaict...
|
||||
File df = new File(_manager.getDataDir(), files.get(i).toString());
|
||||
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;
|
||||
}
|
||||
@ -230,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) {
|
||||
@ -240,30 +299,45 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
if ( (announceURLOther != null) && (announceURLOther.trim().length() > "http://.i2p/announce".length()) )
|
||||
announceURL = announceURLOther;
|
||||
|
||||
if (baseFile.exists() && baseFile.isFile()) {
|
||||
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, but don't automatically seed it
|
||||
_manager.addTorrent(torrentFile.getCanonicalPath(), false);
|
||||
_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 if (baseFile.exists()) {
|
||||
_manager.addMessage("I2PSnark doesn't yet support creating multifile torrents");
|
||||
} 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());
|
||||
}
|
||||
}
|
||||
} 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -279,12 +353,16 @@ 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();
|
||||
@ -292,32 +370,78 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
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";
|
||||
}
|
||||
@ -334,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 {
|
||||
@ -381,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");
|
||||
@ -396,21 +619,24 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
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: " + _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");
|
||||
Map trackers = sort(_manager.getTrackers());
|
||||
Map trackers = _manager.getTrackers();
|
||||
for (Iterator iter = trackers.keySet().iterator(); iter.hasNext(); ) {
|
||||
String name = (String)iter.next();
|
||||
String announceURL = (String)trackers.get(name);
|
||||
// we inject whitespace in sort(...) to guarantee uniqueness, but we can strip it off here
|
||||
out.write("\t<option value=\"" + announceURL + "\">" + name.trim() + "</option>\n");
|
||||
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://\" " +
|
||||
@ -419,23 +645,11 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
out.write("</form>\n</span>\n");
|
||||
}
|
||||
|
||||
private Map sort(Map trackers) {
|
||||
TreeMap rv = new TreeMap();
|
||||
for (Iterator iter = trackers.keySet().iterator(); iter.hasNext(); ) {
|
||||
String url = (String)iter.next();
|
||||
String name = (String)trackers.get(url);
|
||||
while (rv.containsKey(name))
|
||||
name = name + " ";
|
||||
rv.put(name, url);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
private void writeConfigForm(PrintWriter out, HttpServletRequest req) throws IOException {
|
||||
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");
|
||||
@ -449,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");
|
||||
@ -463,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=\""
|
||||
@ -480,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" +
|
||||
@ -540,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" +
|
||||
@ -550,8 +772,6 @@ public class I2PSnarkServlet extends HttpServlet {
|
||||
"}\n" +
|
||||
".snarkNewTorrent {\n" +
|
||||
" font-size: 10pt;\n" +
|
||||
" font-family: monospace;\n" +
|
||||
" background-color: #ADAE9;\n" +
|
||||
"}\n" +
|
||||
".snarkAddInfo {\n" +
|
||||
" font-size: 10pt;\n" +
|
||||
@ -568,19 +788,26 @@ 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";
|
||||
|
||||
|
@ -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;
|
||||
|
||||
@ -384,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("/");
|
||||
@ -474,16 +513,32 @@ public class I2PTunnelHTTPClient extends I2PTunnelClientBase implements Runnable
|
||||
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());
|
||||
@ -546,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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -584,6 +645,12 @@ 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) {
|
||||
@ -602,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());
|
||||
|
@ -272,7 +272,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
int idx=0;
|
||||
final String[] allowedCommands =
|
||||
{
|
||||
"NOTICE",
|
||||
// "NOTICE", // can contain CTCP
|
||||
//"PING",
|
||||
//"PONG",
|
||||
"MODE",
|
||||
@ -282,6 +282,8 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
"PART",
|
||||
"WALLOPS",
|
||||
"ERROR",
|
||||
"KICK",
|
||||
"H", // "hide operator status" (after kicking an op)
|
||||
"TOPIC"
|
||||
};
|
||||
|
||||
@ -304,34 +306,36 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
} catch(NumberFormatException nfe){}
|
||||
|
||||
|
||||
if ("PING".equals(command))
|
||||
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".equals(command)) {
|
||||
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;
|
||||
//
|
||||
// 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);
|
||||
@ -354,7 +358,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
String command;
|
||||
final String[] allowedCommands =
|
||||
{
|
||||
"NOTICE",
|
||||
// "NOTICE", // can contain CTCP
|
||||
"MODE",
|
||||
"JOIN",
|
||||
"NICK",
|
||||
@ -385,7 +389,7 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
|
||||
command = field[0].toUpperCase();
|
||||
|
||||
if ("PING".equals(command)) {
|
||||
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)
|
||||
@ -401,13 +405,15 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
expectedPong.setLength(0);
|
||||
if (field.length == 1) { // PING
|
||||
rv = "PING";
|
||||
expectedPong.append("PONG 127.0.0.1");
|
||||
// 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];
|
||||
expectedPong.append("PONG ").append(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[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 + "\")");
|
||||
@ -415,28 +421,27 @@ public class I2PTunnelIRCClient extends I2PTunnelClientBase implements Runnable
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("sending ping " + rv + ", waiting for " + expectedPong + " orig was [" + s + "]");
|
||||
_log.warn("sending ping [" + rv + "], waiting for [" + expectedPong + "] orig was [" + s + "]");
|
||||
|
||||
return rv;
|
||||
}
|
||||
if ("PONG".equals(command))
|
||||
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);
|
||||
@ -449,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";
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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" />
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,8 +37,12 @@ public class ConfigNetHandler extends FormHandler {
|
||||
private boolean _requireIntroductions;
|
||||
private boolean _hiddenMode;
|
||||
private boolean _dynamicKeys;
|
||||
private String _ntcpHostname;
|
||||
private String _ntcpPort;
|
||||
private String _tcpPort;
|
||||
private String _udpPort;
|
||||
private boolean _ntcpAutoIP;
|
||||
private boolean _ntcpAutoPort;
|
||||
private String _inboundRate;
|
||||
private String _inboundBurstRate;
|
||||
private String _inboundBurst;
|
||||
@ -51,9 +55,7 @@ public class ConfigNetHandler extends FormHandler {
|
||||
private boolean _ratesOnly;
|
||||
|
||||
protected void processForm() {
|
||||
if (_reseedRequested) {
|
||||
reseed();
|
||||
} else if (_saveRequested || ( (_action != null) && ("Save changes".equals(_action)) )) {
|
||||
if (_saveRequested || ( (_action != null) && ("Save changes".equals(_action)) )) {
|
||||
saveChanges();
|
||||
} else if (_recheckReachabilityRequested) {
|
||||
recheckReachability();
|
||||
@ -62,7 +64,6 @@ public class ConfigNetHandler extends FormHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public void setReseed(String moo) { _reseedRequested = true; }
|
||||
public void setSave(String moo) { _saveRequested = true; }
|
||||
public void setEnabletimesync(String moo) { _timeSyncEnabled = true; }
|
||||
public void setRecheckReachability(String moo) { _recheckReachabilityRequested = true; }
|
||||
@ -71,6 +72,8 @@ public class ConfigNetHandler extends FormHandler {
|
||||
public void setDynamicKeys(String moo) { _dynamicKeys = true; }
|
||||
public void setUpdateratesonly(String moo) { _ratesOnly = true; }
|
||||
public void setEnableloadtesting(String moo) { _enableLoadTesting = true; }
|
||||
public void setNtcpAutoIP(String moo) { _ntcpAutoIP = true; }
|
||||
public void setNtcpAutoPort(String moo) { _ntcpAutoPort = true; }
|
||||
|
||||
public void setHostname(String hostname) {
|
||||
_hostname = (hostname != null ? hostname.trim() : null);
|
||||
@ -78,6 +81,12 @@ public class ConfigNetHandler extends FormHandler {
|
||||
public void setTcpPort(String port) {
|
||||
_tcpPort = (port != null ? port.trim() : null);
|
||||
}
|
||||
public void setNtcphost(String host) {
|
||||
_ntcpHostname = (host != null ? host.trim() : null);
|
||||
}
|
||||
public void setNtcpport(String port) {
|
||||
_ntcpPort = (port != null ? port.trim() : null);
|
||||
}
|
||||
public void setUdpPort(String port) {
|
||||
_udpPort = (port != null ? port.trim() : null);
|
||||
}
|
||||
@ -99,95 +108,9 @@ public class ConfigNetHandler extends FormHandler {
|
||||
public void setOutboundburstfactor(String factor) {
|
||||
_outboundBurst = (factor != null ? factor.trim() : null);
|
||||
}
|
||||
public void setReseedfrom(String url) {
|
||||
_reseedFrom = (url != null ? url.trim() : null);
|
||||
}
|
||||
public void setSharePercentage(String pct) {
|
||||
_sharePct = (pct != null ? pct.trim() : null);
|
||||
}
|
||||
|
||||
|
||||
private static final String DEFAULT_SEED_URL = ReseedHandler.DEFAULT_SEED_URL;
|
||||
/**
|
||||
* Reseed has been requested, so lets go ahead and do it. Fetch all of
|
||||
* the routerInfo-*.dat files from the specified URL (or the default) and
|
||||
* save them into this router's netDb dir.
|
||||
*
|
||||
*/
|
||||
private void reseed() {
|
||||
String seedURL = DEFAULT_SEED_URL;
|
||||
if (_reseedFrom != null)
|
||||
seedURL = _reseedFrom;
|
||||
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;
|
||||
}
|
||||
|
||||
int fetched = 0;
|
||||
int errors = 0;
|
||||
for (Iterator iter = urls.iterator(); iter.hasNext(); ) {
|
||||
try {
|
||||
fetchSeed(seedURL, (String)iter.next());
|
||||
fetched++;
|
||||
} catch (Exception e) {
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
addFormNotice("Reseeded with " + fetched + " peers (and " + errors + " failures)");
|
||||
} catch (Throwable t) {
|
||||
_context.logManager().getLog(ConfigNetHandler.class).error("Error reseeding", t);
|
||||
addFormError("Error reseeding (RESEED_EXCEPTION)");
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchSeed(String seedURL, String peer) throws Exception {
|
||||
URL url = new URL(seedURL + (seedURL.endsWith("/") ? "" : "/") + "routerInfo-" + peer + ".dat");
|
||||
|
||||
byte data[] = readURL(url);
|
||||
writeSeed(peer, data);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
private void writeSeed(String name, byte data[]) throws Exception {
|
||||
// props taken from KademliaNetworkDatabaseFacade...
|
||||
String dirName = _context.getProperty("router.networkDatabase.dbDir", "netDb");
|
||||
File netDbDir = new File(dirName);
|
||||
if (!netDbDir.exists()) {
|
||||
boolean ok = netDbDir.mkdirs();
|
||||
if (ok)
|
||||
addFormNotice("Network database directory created: " + dirName);
|
||||
else
|
||||
addFormNotice("Error creating network database directory: " + dirName);
|
||||
}
|
||||
FileOutputStream fos = new FileOutputStream(new File(netDbDir, "routerInfo-" + name + ".dat"));
|
||||
fos.write(data);
|
||||
fos.close();
|
||||
}
|
||||
|
||||
private void recheckReachability() {
|
||||
_context.commSystem().recheckReachability();
|
||||
@ -222,6 +145,52 @@ public class ConfigNetHandler extends FormHandler {
|
||||
restartRequired = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize some things to make the following code a little easier...
|
||||
String oldNHost = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME);
|
||||
if (oldNHost == null) oldNHost = "";
|
||||
String oldNPort = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_PORT);
|
||||
if (oldNPort == null) oldNPort = "";
|
||||
String sAutoHost = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_AUTO_IP);
|
||||
String sAutoPort = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_AUTO_PORT);
|
||||
boolean oldAutoHost = "true".equalsIgnoreCase(sAutoHost);
|
||||
boolean oldAutoPort = "true".equalsIgnoreCase(sAutoPort);
|
||||
if (_ntcpHostname == null) _ntcpHostname = "";
|
||||
if (_ntcpPort == null) _ntcpPort = "";
|
||||
|
||||
if (oldAutoHost != _ntcpAutoIP || ! oldNHost.equalsIgnoreCase(_ntcpHostname)) {
|
||||
if (_ntcpAutoIP) {
|
||||
_context.router().setConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_AUTO_IP, "true");
|
||||
_context.router().removeConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME);
|
||||
addFormNotice("Updating inbound TCP address to auto");
|
||||
} else if (_ntcpHostname.length() > 0) {
|
||||
_context.router().setConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME, _ntcpHostname);
|
||||
_context.router().removeConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_AUTO_IP);
|
||||
addFormNotice("Updating inbound TCP address to " + _ntcpHostname);
|
||||
} else {
|
||||
_context.router().removeConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME);
|
||||
_context.router().removeConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_AUTO_IP);
|
||||
addFormNotice("Disabling inbound TCP");
|
||||
}
|
||||
restartRequired = true;
|
||||
}
|
||||
if (oldAutoPort != _ntcpAutoPort || ! oldNPort.equals(_ntcpPort)) {
|
||||
if ( _ntcpAutoPort ) {
|
||||
_context.router().setConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_AUTO_PORT, "true");
|
||||
_context.router().removeConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_PORT);
|
||||
addFormNotice("Updating inbound TCP port to auto");
|
||||
} else if (_ntcpPort.length() > 0) {
|
||||
_context.router().setConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_PORT, _ntcpPort);
|
||||
_context.router().removeConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_AUTO_PORT);
|
||||
addFormNotice("Updating inbound TCP port to " + _ntcpPort);
|
||||
} else {
|
||||
_context.router().removeConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_PORT);
|
||||
_context.router().removeConfigSetting(ConfigNetHelper.PROP_I2NP_NTCP_AUTO_PORT);
|
||||
addFormNotice("Disabling inbound TCP");
|
||||
}
|
||||
restartRequired = true;
|
||||
}
|
||||
|
||||
if ( (_udpPort != null) && (_udpPort.length() > 0) ) {
|
||||
String oldPort = _context.router().getConfigSetting(ConfigNetHelper.PROP_I2NP_UDP_PORT);
|
||||
if ( (oldPort == null) && (_udpPort.equals("8887")) ) {
|
||||
|
@ -48,6 +48,20 @@ public class ConfigNetHelper {
|
||||
}
|
||||
return "" + port;
|
||||
}
|
||||
public final static String PROP_I2NP_NTCP_HOSTNAME = "i2np.ntcp.hostname";
|
||||
public final static String PROP_I2NP_NTCP_PORT = "i2np.ntcp.port";
|
||||
public final static String PROP_I2NP_NTCP_AUTO_PORT = "i2np.ntcp.autoip";
|
||||
public final static String PROP_I2NP_NTCP_AUTO_IP = "i2np.ntcp.autoport";
|
||||
public String getNtcphostname() {
|
||||
String hostname = _context.getProperty(PROP_I2NP_NTCP_HOSTNAME);
|
||||
if (hostname == null) return "";
|
||||
return hostname;
|
||||
}
|
||||
public String getNtcpport() {
|
||||
String port = _context.getProperty(PROP_I2NP_NTCP_PORT);
|
||||
if (port == null) return "";
|
||||
return port;
|
||||
}
|
||||
|
||||
public String getUdpAddress() {
|
||||
RouterAddress addr = _context.router().getRouterInfo().getTargetAddress("SSU");
|
||||
@ -57,6 +71,26 @@ public class ConfigNetHelper {
|
||||
return ua.toString();
|
||||
}
|
||||
|
||||
public String getUdpIP() {
|
||||
RouterAddress addr = _context.router().getRouterInfo().getTargetAddress("SSU");
|
||||
if (addr == null)
|
||||
return "unknown";
|
||||
UDPAddress ua = new UDPAddress(addr);
|
||||
if (ua.getHost() == null)
|
||||
return "unknown";
|
||||
return ua.getHost();
|
||||
}
|
||||
|
||||
public String getUdpPort() {
|
||||
RouterAddress addr = _context.router().getRouterInfo().getTargetAddress("SSU");
|
||||
if (addr == null)
|
||||
return "unknown";
|
||||
UDPAddress ua = new UDPAddress(addr);
|
||||
if (ua.getPort() <= 0)
|
||||
return "unknown";
|
||||
return "" + ua.getPort();
|
||||
}
|
||||
|
||||
public String getEnableTimeSyncChecked() {
|
||||
String disabled = _context.getProperty(Timestamper.PROP_DISABLED, "false");
|
||||
if ( (disabled != null) && ("true".equalsIgnoreCase(disabled)) )
|
||||
@ -81,6 +115,22 @@ public class ConfigNetHelper {
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getTcpAutoPortChecked() {
|
||||
String enabled = _context.getProperty(PROP_I2NP_NTCP_AUTO_PORT, "false");
|
||||
if ( (enabled != null) && ("true".equalsIgnoreCase(enabled)) )
|
||||
return " checked ";
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getTcpAutoIPChecked() {
|
||||
String enabled = _context.getProperty(PROP_I2NP_NTCP_AUTO_IP, "false");
|
||||
if ( (enabled != null) && ("true".equalsIgnoreCase(enabled)) )
|
||||
return " checked ";
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getRequireIntroductionsChecked() {
|
||||
short status = _context.commSystem().getReachabilityStatus();
|
||||
switch (status) {
|
||||
@ -224,4 +274,23 @@ public class ConfigNetHelper {
|
||||
buf.append("</select>\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public int getShareBandwidth() {
|
||||
String irate = _context.getProperty(PROP_INBOUND_KBPS);
|
||||
String orate = _context.getProperty(PROP_OUTBOUND_KBPS);
|
||||
String pctStr = _context.getProperty(PROP_SHARE_PERCENTAGE);
|
||||
if ( (irate != null) && (orate != null) && (pctStr != null)) {
|
||||
try {
|
||||
int irateKBps = Integer.parseInt(irate);
|
||||
int orateKBps = Integer.parseInt(orate);
|
||||
if (irateKBps < 0 || orateKBps < 0)
|
||||
return 0;
|
||||
int pct = Integer.parseInt(pctStr);
|
||||
return (int) (((float) pct) * Math.min(irateKBps, orateKBps) / 100);
|
||||
} catch (NumberFormatException nfe) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import net.i2p.stat.StatManager;
|
||||
public class ConfigStatsHandler extends FormHandler {
|
||||
private String _filename;
|
||||
private List _stats;
|
||||
private String _graphs;
|
||||
private boolean _explicitFilter;
|
||||
private String _explicitFilterValue;
|
||||
|
||||
@ -48,6 +49,25 @@ public class ConfigStatsHandler extends FormHandler {
|
||||
_log.debug("Updated stats: " + _stats);
|
||||
}
|
||||
|
||||
public void setGraphList(String stats[]) {
|
||||
if (stats != null) {
|
||||
String s = "";
|
||||
for (int i = 0; i < stats.length; i++) {
|
||||
String cur = stats[i].trim();
|
||||
if (cur.length() > 0) {
|
||||
if (s.length() > 0)
|
||||
s = s + ",";
|
||||
s = s + cur;
|
||||
}
|
||||
}
|
||||
_graphs = s;
|
||||
} else {
|
||||
_graphs = "";
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Updated graphs: " + _graphs);
|
||||
}
|
||||
|
||||
public void setExplicitFilter(String foo) { _explicitFilter = true; }
|
||||
public void setExplicitFilterValue(String filter) { _explicitFilterValue = filter; }
|
||||
|
||||
@ -88,11 +108,13 @@ public class ConfigStatsHandler extends FormHandler {
|
||||
}
|
||||
|
||||
_context.router().setConfigSetting(StatManager.PROP_STAT_FILTER, stats.toString());
|
||||
_context.router().setConfigSetting("stat.summaries", _graphs);
|
||||
boolean ok = _context.router().saveConfig();
|
||||
if (ok)
|
||||
addFormNotice("Stat filter and location updated successfully to: " + stats.toString());
|
||||
else
|
||||
addFormError("Failed to update the stat filter and location");
|
||||
addFormNotice("Graph list updated, may take up to 60s to be reflected here and on the <a href=\"graphs.jsp\">Graphs Page</a>");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateStat;
|
||||
import net.i2p.stat.FrequencyStat;
|
||||
import net.i2p.router.RouterContext;
|
||||
@ -20,12 +21,15 @@ public class ConfigStatsHelper {
|
||||
/** list of names of stats which are remaining, ordered by nested groups */
|
||||
private List _stats;
|
||||
private String _currentStatName;
|
||||
private String _currentGraphName;
|
||||
private String _currentStatDescription;
|
||||
private String _currentGroup;
|
||||
/** true if the current stat is the first in the group */
|
||||
private boolean _currentIsFirstInGroup;
|
||||
/** true if the stat is being logged */
|
||||
private boolean _currentIsLogged;
|
||||
private boolean _currentIsGraphed;
|
||||
private boolean _currentCanBeGraphed;
|
||||
|
||||
/**
|
||||
* Configure this bean to query a particular router context
|
||||
@ -71,6 +75,7 @@ public class ConfigStatsHelper {
|
||||
public boolean hasMoreStats() {
|
||||
if (_stats.size() <= 0)
|
||||
return false;
|
||||
_currentIsGraphed = false;
|
||||
_currentStatName = (String)_stats.remove(0);
|
||||
RateStat rs = _context.statManager().getRate(_currentStatName);
|
||||
if (rs != null) {
|
||||
@ -82,6 +87,16 @@ public class ConfigStatsHelper {
|
||||
else
|
||||
_currentIsFirstInGroup = false;
|
||||
_currentGroup = rs.getGroupName();
|
||||
long period = rs.getPeriods()[0]; // should be the minimum
|
||||
if (period <= 10*60*1000) {
|
||||
Rate r = rs.getRate(period);
|
||||
_currentCanBeGraphed = r != null;
|
||||
if (_currentCanBeGraphed)
|
||||
_currentIsGraphed = r.getSummaryListener() != null;
|
||||
_currentGraphName = _currentStatName + "." + period;
|
||||
} else {
|
||||
_currentCanBeGraphed = false;
|
||||
}
|
||||
} else {
|
||||
FrequencyStat fs = _context.statManager().getFrequency(_currentStatName);
|
||||
if (fs != null) {
|
||||
@ -93,6 +108,7 @@ public class ConfigStatsHelper {
|
||||
else
|
||||
_currentIsFirstInGroup = false;
|
||||
_currentGroup = fs.getGroupName();
|
||||
_currentCanBeGraphed = false;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Stat does not exist?! [" + _currentStatName + "]");
|
||||
@ -119,7 +135,10 @@ public class ConfigStatsHelper {
|
||||
/** What group is the current stat in */
|
||||
public String getCurrentGroupName() { return _currentGroup; }
|
||||
public String getCurrentStatName() { return _currentStatName; }
|
||||
public String getCurrentGraphName() { return _currentGraphName; }
|
||||
public String getCurrentStatDescription() { return _currentStatDescription; }
|
||||
public boolean getCurrentIsLogged() { return _currentIsLogged; }
|
||||
public boolean getCurrentIsGraphed() { return _currentIsGraphed; }
|
||||
public boolean getCurrentCanBeGraphed() { return _currentCanBeGraphed; }
|
||||
public String getExplicitFilter() { return _filter; }
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package net.i2p.router.web;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.web.ConfigServiceHandler.UpdateWrapperManagerTask;
|
||||
import net.i2p.util.Log;
|
||||
@ -20,20 +21,29 @@ public class ConfigUpdateHandler extends FormHandler {
|
||||
private String _trustedKeys;
|
||||
|
||||
public static final String PROP_NEWS_URL = "router.newsURL";
|
||||
public static final String DEFAULT_NEWS_URL = "http://dev.i2p.net/cgi-bin/cvsweb.cgi/i2p/news.xml?rev=HEAD";
|
||||
// public static final String DEFAULT_NEWS_URL = "http://dev.i2p.net/cgi-bin/cvsweb.cgi/i2p/news.xml?rev=HEAD";
|
||||
public static final String DEFAULT_NEWS_URL = "http://complication.i2p/news.xml";
|
||||
public static final String PROP_REFRESH_FREQUENCY = "router.newsRefreshFrequency";
|
||||
public static final String DEFAULT_REFRESH_FREQUENCY = 12*60*60*1000 + "";
|
||||
public static final String PROP_UPDATE_URL = "router.updateURL";
|
||||
public static final String DEFAULT_UPDATE_URL = "http://dev.i2p.net/i2p/i2pupdate.sud";
|
||||
public static final String PROP_UPDATE_POLICY = "router.updatePolicy";
|
||||
public static final String DEFAULT_UPDATE_POLICY = "notify";
|
||||
public static final String PROP_SHOULD_PROXY = "router.updateThroughProxy";
|
||||
public static final String DEFAULT_SHOULD_PROXY = Boolean.FALSE.toString();
|
||||
public static final String DEFAULT_SHOULD_PROXY = Boolean.TRUE.toString();
|
||||
public static final String PROP_PROXY_HOST = "router.updateProxyHost";
|
||||
public static final String DEFAULT_PROXY_HOST = "localhost";
|
||||
public static final String PROP_PROXY_PORT = "router.updateProxyPort";
|
||||
public static final String DEFAULT_PROXY_PORT = "4444";
|
||||
|
||||
public static final String PROP_UPDATE_URL = "router.updateURL";
|
||||
// public static final String DEFAULT_UPDATE_URL = "http://dev.i2p.net/i2p/i2pupdate.sud";
|
||||
public static final String DEFAULT_UPDATE_URL =
|
||||
"http://amiga.i2p/i2p/i2pupdate.sud\r\n" +
|
||||
"http://stats.i2p/i2p/i2pupdate.sud\r\n" +
|
||||
"http://complication.i2p/i2p/i2pupdate.sud";
|
||||
|
||||
public static final String PROP_TRUSTED_KEYS = "router.trustedUpdateKeys";
|
||||
|
||||
|
||||
protected void processForm() {
|
||||
if ("Check for update now".equals(_action)) {
|
||||
NewsFetcher fetcher = NewsFetcher.getInstance(I2PAppContext.getGlobalContext());
|
||||
@ -52,14 +62,6 @@ public class ConfigUpdateHandler extends FormHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if ( (_updateURL != null) && (_updateURL.length() > 0) ) {
|
||||
String oldURL = _context.router().getConfigSetting(PROP_UPDATE_URL);
|
||||
if ( (oldURL == null) || (!_updateURL.equals(oldURL)) ) {
|
||||
_context.router().setConfigSetting(PROP_UPDATE_URL, _updateURL);
|
||||
addFormNotice("Updating update URL to " + _updateURL);
|
||||
}
|
||||
}
|
||||
|
||||
if ( (_proxyHost != null) && (_proxyHost.length() > 0) ) {
|
||||
String oldHost = _context.router().getConfigSetting(PROP_PROXY_HOST);
|
||||
if ( (oldHost == null) || (!_proxyHost.equals(oldHost)) ) {
|
||||
@ -98,8 +100,22 @@ public class ConfigUpdateHandler extends FormHandler {
|
||||
addFormNotice("Updating update policy to " + _updatePolicy);
|
||||
}
|
||||
}
|
||||
|
||||
// should save the keys...
|
||||
|
||||
if ( (_updateURL != null) && (_updateURL.length() > 0) ) {
|
||||
String oldURL = _context.router().getConfigSetting(PROP_UPDATE_URL);
|
||||
if ( (oldURL == null) || (!_updateURL.equals(oldURL)) ) {
|
||||
_context.router().setConfigSetting(PROP_UPDATE_URL, _updateURL);
|
||||
addFormNotice("Updating update URLs.");
|
||||
}
|
||||
}
|
||||
|
||||
if ( (_trustedKeys != null) && (_trustedKeys.length() > 0) ) {
|
||||
String oldKeys = new TrustedUpdate(_context).getTrustedKeysString();
|
||||
if ( (oldKeys == null) || (!_trustedKeys.equals(oldKeys)) ) {
|
||||
_context.router().setConfigSetting(PROP_TRUSTED_KEYS, _trustedKeys);
|
||||
addFormNotice("Updating trusted keys.");
|
||||
}
|
||||
}
|
||||
|
||||
_context.router().saveConfig();
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.i2p.router.web;
|
||||
|
||||
import java.util.List;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
import net.i2p.router.RouterContext;
|
||||
@ -113,11 +112,6 @@ public class ConfigUpdateHelper {
|
||||
}
|
||||
|
||||
public String getTrustedKeys() {
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
TrustedUpdate up = new TrustedUpdate(_context);
|
||||
List keys = up.getTrustedKeys();
|
||||
for (int i = 0; i < keys.size(); i++)
|
||||
buf.append((String)keys.get(i)).append('\n');
|
||||
return buf.toString();
|
||||
return new TrustedUpdate(_context).getTrustedKeysString();
|
||||
}
|
||||
}
|
||||
|
@ -55,20 +55,37 @@ public class GraphHelper {
|
||||
|
||||
public String getImages() {
|
||||
try {
|
||||
_out.write("<img src=\"viewstat.jsp?stat=bw.combined"
|
||||
+ "&periodCount=" + _periodCount
|
||||
+ "&width=" + _width
|
||||
+ "&height=" + _height
|
||||
+ "\" title=\"Combined bandwidth graph\" />\n");
|
||||
|
||||
List listeners = StatSummarizer.instance().getListeners();
|
||||
TreeSet ordered = new TreeSet(new AlphaComparator());
|
||||
ordered.addAll(listeners);
|
||||
|
||||
// go to some trouble to see if we have the data for the combined bw graph
|
||||
boolean hasTx = false;
|
||||
boolean hasRx = false;
|
||||
for (Iterator iter = ordered.iterator(); iter.hasNext(); ) {
|
||||
SummaryListener lsnr = (SummaryListener)iter.next();
|
||||
String title = lsnr.getRate().getRateStat().getName();
|
||||
if (title.equals("bw.sendRate")) hasTx = true;
|
||||
else if (title.equals("bw.recvRate")) hasRx = true;
|
||||
}
|
||||
|
||||
if (hasTx && hasRx && !_showEvents)
|
||||
_out.write("<img width=\""
|
||||
+ (_width + 83) + "\" height=\"" + (_height + 92)
|
||||
+ "\" src=\"viewstat.jsp?stat=bw.combined"
|
||||
+ "&periodCount=" + _periodCount
|
||||
+ "&width=" + _width
|
||||
+ "&height=" + (_height - 14)
|
||||
+ "\" title=\"Combined bandwidth graph\" />\n");
|
||||
|
||||
for (Iterator iter = ordered.iterator(); iter.hasNext(); ) {
|
||||
SummaryListener lsnr = (SummaryListener)iter.next();
|
||||
Rate r = lsnr.getRate();
|
||||
String title = r.getRateStat().getName() + " for " + DataHelper.formatDuration(_periodCount * r.getPeriod());
|
||||
_out.write("<img src=\"viewstat.jsp?stat=" + r.getRateStat().getName()
|
||||
_out.write("<img width=\""
|
||||
+ (_width + 83) + "\" height=\"" + (_height + 92)
|
||||
+ "\" src=\"viewstat.jsp?stat="
|
||||
+ r.getRateStat().getName()
|
||||
+ "&showEvents=" + _showEvents
|
||||
+ "&period=" + r.getPeriod()
|
||||
+ "&periodCount=" + _periodCount
|
||||
@ -86,6 +103,7 @@ public class GraphHelper {
|
||||
}
|
||||
public String getForm() {
|
||||
try {
|
||||
_out.write("<p /><a href=\"configstats.jsp\">Select Stats to Graph</a><p />");
|
||||
_out.write("<form action=\"graphs.jsp\" method=\"GET\">");
|
||||
_out.write("Periods: <input size=\"3\" type=\"text\" name=\"periodCount\" value=\"" + _periodCount + "\" /><br />\n");
|
||||
_out.write("Plot averages: <input type=\"radio\" name=\"showEvents\" value=\"false\" " + (_showEvents ? "" : "checked=\"true\" ") + " /> ");
|
||||
@ -119,4 +137,4 @@ class AlphaComparator implements Comparator {
|
||||
String rName = r.getRate().getRateStat().getName() + "." + r.getRate().getPeriod();
|
||||
return lName.compareTo(rName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
private Log _log;
|
||||
private boolean _updateAvailable;
|
||||
private long _lastFetch;
|
||||
private String _lastModified;
|
||||
private static NewsFetcher _instance;
|
||||
//public static final synchronized NewsFetcher getInstance() { return _instance; }
|
||||
public static final synchronized NewsFetcher getInstance(I2PAppContext ctx) {
|
||||
@ -105,11 +106,12 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
proxyPort = Integer.parseInt(port);
|
||||
EepGet get = null;
|
||||
if (shouldProxy)
|
||||
get = new EepGet(_context, proxyHost, proxyPort, 10, TEMP_NEWS_FILE, newsURL);
|
||||
get = new EepGet(_context, true, proxyHost, proxyPort, 2, TEMP_NEWS_FILE, newsURL, true, null, _lastModified);
|
||||
else
|
||||
get = new EepGet(_context, 10, TEMP_NEWS_FILE, newsURL);
|
||||
get = new EepGet(_context, false, null, 0, 0, TEMP_NEWS_FILE, newsURL, true, null, _lastModified);
|
||||
get.addStatusListener(this);
|
||||
get.fetch();
|
||||
if (get.fetch())
|
||||
_lastModified = get.getLastModified();
|
||||
} catch (Throwable t) {
|
||||
_log.error("Error fetching the news", t);
|
||||
}
|
||||
@ -212,17 +214,18 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
|
||||
_log.error("Failed to copy the news file!");
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Transfer complete, but no file?");
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Transfer complete, but no file? - probably 304 Not Modified");
|
||||
}
|
||||
checkForUpdates();
|
||||
}
|
||||
|
||||
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Failed to fetch the news from " + url);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Failed to fetch the news from " + url);
|
||||
File temp = new File(TEMP_NEWS_FILE);
|
||||
temp.delete();
|
||||
}
|
||||
public void headerReceived(String url, int attemptNum, String key, String val) {}
|
||||
public void attempting(String url) {}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package net.i2p.router.web;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.net.Socket;
|
||||
@ -13,17 +12,51 @@ import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.EepGet;
|
||||
|
||||
/**
|
||||
* Handler to deal with reseed requests. This reseed from the URL
|
||||
* http://dev.i2p.net/i2pdb2/ unless the java env property "i2p.reseedURL" is
|
||||
* Handler to deal with reseed requests. This will reseed from the URL
|
||||
* http://i2pdb.tin0.de/netDb/ unless the I2P configuration property "i2p.reseedURL" is
|
||||
* set. It always writes to ./netDb/, so don't mess with that.
|
||||
*
|
||||
*/
|
||||
public class ReseedHandler {
|
||||
private static ReseedRunner _reseedRunner = new ReseedRunner();
|
||||
|
||||
private static ReseedRunner _reseedRunner;
|
||||
private RouterContext _context;
|
||||
private Log _log;
|
||||
|
||||
// Reject unreasonably big files, because we download into a ByteArrayOutputStream.
|
||||
private static final long MAX_RESEED_RESPONSE_SIZE = 8 * 1024 * 1024;
|
||||
|
||||
private static final String DEFAULT_SEED_URL = "http://i2pdb.tin0.de/netDb/";
|
||||
|
||||
public ReseedHandler() {
|
||||
this(ContextHelper.getContext(null));
|
||||
}
|
||||
public ReseedHandler(RouterContext ctx) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(ReseedHandler.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure this bean to query a particular router context
|
||||
*
|
||||
* @param contextId begging few characters of the routerHash, or null to pick
|
||||
* the first one we come across.
|
||||
*/
|
||||
public void setContextId(String contextId) {
|
||||
try {
|
||||
_context = ContextHelper.getContext(contextId);
|
||||
_log = _context.logManager().getLog(ReseedHandler.class);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setReseedNonce(String nonce) {
|
||||
if (nonce == null) return;
|
||||
if (nonce.equals(System.getProperty("net.i2p.router.web.ReseedHandler.nonce")) ||
|
||||
@ -32,22 +65,28 @@ public class ReseedHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public static void requestReseed() {
|
||||
synchronized (_reseedRunner) {
|
||||
public void requestReseed() {
|
||||
synchronized (ReseedHandler.class) {
|
||||
if (_reseedRunner == null)
|
||||
_reseedRunner = new ReseedRunner();
|
||||
if (_reseedRunner.isRunning()) {
|
||||
return;
|
||||
} else {
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "true");
|
||||
System.out.println("Reseeding");
|
||||
I2PThread reseed = new I2PThread(_reseedRunner, "Reseed");
|
||||
reseed.start();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ReseedRunner implements Runnable {
|
||||
public class ReseedRunner implements Runnable, EepGet.StatusListener {
|
||||
private boolean _isRunning;
|
||||
public ReseedRunner() { _isRunning = false; }
|
||||
|
||||
public ReseedRunner() {
|
||||
_isRunning = false;
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.statusMessage","Reseeding.");
|
||||
}
|
||||
public boolean isRunning() { return _isRunning; }
|
||||
public void run() {
|
||||
_isRunning = true;
|
||||
@ -56,141 +95,159 @@ public class ReseedHandler {
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "false");
|
||||
_isRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
static final String DEFAULT_SEED_URL = "http://dev.i2p.net/i2pdb2/";
|
||||
/**
|
||||
* Reseed has been requested, so lets go ahead and do it. Fetch all of
|
||||
* the routerInfo-*.dat files from the specified URL (or the default) and
|
||||
* save them into this router's netDb dir.
|
||||
*
|
||||
*/
|
||||
private static void reseed(boolean echoStatus) {
|
||||
String seedURL = System.getProperty("i2p.reseedURL", DEFAULT_SEED_URL);
|
||||
if ( (seedURL == null) || (seedURL.trim().length() <= 0) )
|
||||
seedURL = DEFAULT_SEED_URL;
|
||||
try {
|
||||
URL dir = new URL(seedURL);
|
||||
byte contentRaw[] = readURL(dir);
|
||||
if (contentRaw == null) return;
|
||||
String content = new String(contentRaw);
|
||||
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;
|
||||
}
|
||||
// EepGet status listeners
|
||||
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) {
|
||||
// Since readURL() runs an EepGet with 0 retries,
|
||||
// we can report errors with attemptFailed() instead of transferFailed().
|
||||
// It has the benefit of providing cause of failure, which helps resolve issues.
|
||||
if (_log.shouldLog(Log.ERROR)) _log.error("EepGet failed on " + url, cause);
|
||||
}
|
||||
public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {}
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {}
|
||||
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {}
|
||||
public void headerReceived(String url, int attemptNum, String key, String val) {}
|
||||
public void attempting(String url) {}
|
||||
// End of EepGet status listeners
|
||||
|
||||
int fetched = 0;
|
||||
int errors = 0;
|
||||
for (Iterator iter = urls.iterator(); iter.hasNext(); ) {
|
||||
try {
|
||||
fetchSeed(seedURL, (String)iter.next());
|
||||
fetched++;
|
||||
if (echoStatus) {
|
||||
System.out.print(".");
|
||||
if (fetched % 60 == 0)
|
||||
System.out.println();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
errors++;
|
||||
/**
|
||||
* Reseed has been requested, so lets go ahead and do it. Fetch all of
|
||||
* the routerInfo-*.dat files from the specified URL (or the default) and
|
||||
* save them into this router's netDb dir.
|
||||
*
|
||||
*/
|
||||
private static final String RESEED_TIPS =
|
||||
"Ensure that nothing blocks outbound HTTP, check <a href=logs.jsp>logs</a> " +
|
||||
"and if nothing helps, read FAQ about reseeding manually.";
|
||||
|
||||
private void reseed(boolean echoStatus) {
|
||||
|
||||
String seedURL = _context.getProperty("i2p.reseedURL", DEFAULT_SEED_URL);
|
||||
if ( (seedURL == null) || (seedURL.trim().length() <= 0) )
|
||||
seedURL = DEFAULT_SEED_URL;
|
||||
try {
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.errorMessage","");
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.statusMessage","Reseeding: fetching seed URL.");
|
||||
URL dir = new URL(seedURL);
|
||||
byte contentRaw[] = readURL(dir);
|
||||
if (contentRaw == null) {
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.errorMessage",
|
||||
"Last reseed failed fully (failed reading seed URL). " +
|
||||
RESEED_TIPS);
|
||||
// Logging deprecated here since attemptFailed() provides better info
|
||||
_log.debug("Failed reading seed URL: " + seedURL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (echoStatus) System.out.println();
|
||||
} catch (Throwable t) {
|
||||
I2PAppContext.getGlobalContext().logManager().getLog(ReseedHandler.class).error("Error reseeding", t);
|
||||
}
|
||||
}
|
||||
|
||||
private static void fetchSeed(String seedURL, String peer) throws Exception {
|
||||
URL url = new URL(seedURL + (seedURL.endsWith("/") ? "" : "/") + "routerInfo-" + peer + ".dat");
|
||||
String content = new String(contentRaw);
|
||||
Set urls = new HashSet();
|
||||
int cur = 0;
|
||||
while (true) {
|
||||
int start = content.indexOf("href=\"routerInfo-", cur);
|
||||
if (start < 0)
|
||||
break;
|
||||
|
||||
byte data[] = readURL(url);
|
||||
//System.out.println("read: " + (data != null ? data.length : -1));
|
||||
writeSeed(peer, data);
|
||||
}
|
||||
|
||||
private static byte[] readURL(URL url) throws Exception {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(4*1024);
|
||||
String hostname = url.getHost();
|
||||
int port = url.getPort();
|
||||
if (port < 0)
|
||||
port = 80;
|
||||
Socket s = new Socket(hostname, port);
|
||||
OutputStream out = s.getOutputStream();
|
||||
InputStream in = s.getInputStream();
|
||||
String request = getRequest(url);
|
||||
//System.out.println("Sending to " + hostname +":"+ port + ": " + request);
|
||||
out.write(request.getBytes());
|
||||
out.flush();
|
||||
// skip the HTTP response headers
|
||||
// (if we were smart, we'd check for HTTP 200, content-length, etc)
|
||||
int consecutiveNL = 0;
|
||||
while (true) {
|
||||
int cur = in.read();
|
||||
switch (cur) {
|
||||
case -1:
|
||||
return null;
|
||||
case '\n':
|
||||
case '\r':
|
||||
consecutiveNL++;
|
||||
break;
|
||||
default:
|
||||
consecutiveNL = 0;
|
||||
int end = content.indexOf(".dat\">", start);
|
||||
String name = content.substring(start+"href=\"routerInfo-".length(), end);
|
||||
urls.add(name);
|
||||
cur = end + 1;
|
||||
}
|
||||
if (urls.size() <= 0) {
|
||||
_log.error("Read " + contentRaw.length + " bytes from seed " + seedURL + ", but found no routerInfo URLs.");
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.errorMessage",
|
||||
"Last reseed failed fully (no routerInfo URLs at seed URL). " +
|
||||
RESEED_TIPS);
|
||||
return;
|
||||
}
|
||||
|
||||
int fetched = 0;
|
||||
int errors = 0;
|
||||
for (Iterator iter = urls.iterator(); iter.hasNext(); ) {
|
||||
try {
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.statusMessage",
|
||||
"Reseeding: fetching router info from seed URL (" +
|
||||
fetched + " successful, " + errors + " errors, " + urls.size() + " total).");
|
||||
|
||||
fetchSeed(seedURL, (String)iter.next());
|
||||
fetched++;
|
||||
if (echoStatus) {
|
||||
System.out.print(".");
|
||||
if (fetched % 60 == 0)
|
||||
System.out.println();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
if (echoStatus) System.out.println();
|
||||
|
||||
int failPercent = 100 * errors / urls.size();
|
||||
|
||||
// Less than 10% of failures is considered success,
|
||||
// because some routerInfos will always fail.
|
||||
if ((failPercent >= 10) && (failPercent < 90)) {
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.errorMessage",
|
||||
"Last reseed failed partly (" + failPercent + "% of " + urls.size() + "). " +
|
||||
RESEED_TIPS);
|
||||
}
|
||||
if (failPercent >= 90) {
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.errorMessage",
|
||||
"Last reseed failed (" + failPercent + "% of " + urls.size() + "). " +
|
||||
RESEED_TIPS);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
System.setProperty("net.i2p.router.web.ReseedHandler.errorMessage",
|
||||
"Last reseed failed fully (exception caught). " +
|
||||
RESEED_TIPS);
|
||||
_log.error("Error reseeding", t);
|
||||
}
|
||||
if (consecutiveNL == 4)
|
||||
break;
|
||||
}
|
||||
// ok, past the headers, grab the goods
|
||||
byte buf[] = new byte[1024];
|
||||
while (true) {
|
||||
int read = in.read(buf);
|
||||
if (read < 0)
|
||||
break;
|
||||
baos.write(buf, 0, read);
|
||||
|
||||
/* Since we don't return a value, we should always throw an exception if something fails. */
|
||||
private void fetchSeed(String seedURL, String peer) throws Exception {
|
||||
Log log = I2PAppContext.getGlobalContext().logManager().getLog(ReseedHandler.class);
|
||||
URL url = new URL(seedURL + (seedURL.endsWith("/") ? "" : "/") + "routerInfo-" + peer + ".dat");
|
||||
|
||||
byte data[] = readURL(url);
|
||||
if (data == null) {
|
||||
// Logging deprecated here since attemptFailed() provides better info
|
||||
_log.debug("Failed fetching seed: " + url.toString());
|
||||
throw new Exception ("Failed fetching seed.");
|
||||
}
|
||||
//System.out.println("read: " + (data != null ? data.length : -1));
|
||||
writeSeed(peer, data);
|
||||
}
|
||||
in.close();
|
||||
s.close();
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
private static String getRequest(URL url) {
|
||||
StringBuffer buf = new StringBuffer(512);
|
||||
String path = url.getPath();
|
||||
if ("".equals(path))
|
||||
path = "/";
|
||||
buf.append("GET ").append(path).append(" HTTP/1.0\n");
|
||||
buf.append("Host: ").append(url.getHost());
|
||||
int port = url.getPort();
|
||||
if ( (port > 0) && (port != 80) )
|
||||
buf.append(":").append(port);
|
||||
buf.append("\nConnection: close\n\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static void writeSeed(String name, byte data[]) throws Exception {
|
||||
String dirName = "netDb"; // _context.getProperty("router.networkDatabase.dbDir", "netDb");
|
||||
File netDbDir = new File(dirName);
|
||||
if (!netDbDir.exists()) {
|
||||
boolean ok = netDbDir.mkdirs();
|
||||
|
||||
private byte[] readURL(URL url) throws Exception {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(4*1024);
|
||||
|
||||
// Do a non-proxied eepget into our ByteArrayOutputStream with 0 retries
|
||||
EepGet get = new EepGet( I2PAppContext.getGlobalContext(), false, null, -1, 0, 0, MAX_RESEED_RESPONSE_SIZE,
|
||||
null, baos, url.toString(), false, null, null);
|
||||
get.addStatusListener(ReseedRunner.this);
|
||||
if (get.fetch()) return baos.toByteArray(); else return null;
|
||||
}
|
||||
FileOutputStream fos = new FileOutputStream(new File(netDbDir, "routerInfo-" + name + ".dat"));
|
||||
fos.write(data);
|
||||
fos.close();
|
||||
}
|
||||
|
||||
private void writeSeed(String name, byte data[]) throws Exception {
|
||||
String dirName = "netDb"; // _context.getProperty("router.networkDatabase.dbDir", "netDb");
|
||||
File netDbDir = new File(dirName);
|
||||
if (!netDbDir.exists()) {
|
||||
boolean ok = netDbDir.mkdirs();
|
||||
}
|
||||
FileOutputStream fos = new FileOutputStream(new File(netDbDir, "routerInfo-" + name + ".dat"));
|
||||
fos.write(data);
|
||||
fos.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
if ( (args != null) && (args.length == 1) && (!Boolean.valueOf(args[0]).booleanValue()) ) {
|
||||
System.out.println("Not reseeding, as requested");
|
||||
return; // not reseeding on request
|
||||
}
|
||||
System.out.println("Reseeding");
|
||||
reseed(true);
|
||||
ReseedHandler reseedHandler = new ReseedHandler();
|
||||
reseedHandler.requestReseed();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -88,7 +88,8 @@ public class RouterConsoleRunner {
|
||||
// get i2p started - they can reseed later in the web console)
|
||||
String names[] = (netDb.exists() ? netDb.list() : null);
|
||||
if ( (names == null) || (names.length < 15) ) {
|
||||
ReseedHandler.requestReseed();
|
||||
ReseedHandler reseedHandler = new ReseedHandler();
|
||||
reseedHandler.requestReseed();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,23 +45,27 @@ public class StatSummarizer implements Runnable {
|
||||
|
||||
private static final String DEFAULT_DATABASES = "bw.sendRate.60000" +
|
||||
",bw.recvRate.60000" +
|
||||
",tunnel.testSuccessTime.60000" +
|
||||
",udp.outboundActiveCount.60000" +
|
||||
",udp.receivePacketSize.60000" +
|
||||
",udp.receivePacketSkew.60000" +
|
||||
",udp.sendConfirmTime.60000" +
|
||||
",udp.sendPacketSize.60000" +
|
||||
",router.activePeers.60000" +
|
||||
",router.activeSendPeers.60000" +
|
||||
",tunnel.acceptLoad.60000" +
|
||||
",tunnel.dropLoadProactive.60000" +
|
||||
",tunnel.buildExploratorySuccess.60000" +
|
||||
",tunnel.buildExploratoryReject.60000" +
|
||||
",tunnel.buildExploratoryExpire.60000" +
|
||||
",client.sendAckTime.60000" +
|
||||
",client.dispatchNoACK.60000" +
|
||||
",transport.sendMessageFailureLifetime.60000" +
|
||||
",transport.sendProcessingTime.60000";
|
||||
// ",tunnel.testSuccessTime.60000" +
|
||||
// ",udp.outboundActiveCount.60000" +
|
||||
// ",udp.receivePacketSize.60000" +
|
||||
// ",udp.receivePacketSkew.60000" +
|
||||
// ",udp.sendConfirmTime.60000" +
|
||||
// ",udp.sendPacketSize.60000" +
|
||||
",router.activePeers.60000";
|
||||
// ",router.activeSendPeers.60000" +
|
||||
// ",tunnel.acceptLoad.60000" +
|
||||
// ",tunnel.dropLoadProactive.60000" +
|
||||
// ",tunnel.buildExploratorySuccess.60000" +
|
||||
// ",tunnel.buildExploratoryReject.60000" +
|
||||
// ",tunnel.buildExploratoryExpire.60000" +
|
||||
// ",client.sendAckTime.60000" +
|
||||
// ",client.dispatchNoACK.60000" +
|
||||
// ",ntcp.sendTime.60000" +
|
||||
// ",ntcp.transmitTime.60000" +
|
||||
// ",ntcp.sendBacklogTime.60000" +
|
||||
// ",ntcp.receiveTime.60000" +
|
||||
// ",transport.sendMessageFailureLifetime.60000" +
|
||||
// ",transport.sendProcessingTime.60000";
|
||||
|
||||
private String adjustDatabases(String oldSpecs) {
|
||||
String spec = _context.getProperty("stat.summaries", DEFAULT_DATABASES);
|
||||
@ -138,16 +142,17 @@ public class StatSummarizer implements Runnable {
|
||||
}
|
||||
|
||||
public boolean renderRatePng(OutputStream out, int width, int height, boolean hideLegend, boolean hideGrid, boolean hideTitle, boolean showEvents, int periodCount, boolean showCredit) throws IOException {
|
||||
long end = _context.clock().now();
|
||||
long end = _context.clock().now() - 60*1000;
|
||||
if (periodCount <= 0) periodCount = SummaryListener.PERIODS;
|
||||
if (periodCount > SummaryListener.PERIODS)
|
||||
periodCount = SummaryListener.PERIODS;
|
||||
long period = 60*1000;
|
||||
long start = end - period*periodCount;
|
||||
long begin = System.currentTimeMillis();
|
||||
//long begin = System.currentTimeMillis();
|
||||
try {
|
||||
RrdGraphDef def = new RrdGraphDef();
|
||||
def.setTimePeriod(start/1000, end/1000);
|
||||
def.setTimePeriod(start/1000, 0);
|
||||
def.setBaseValue(1024);
|
||||
String title = "Bandwidth usage";
|
||||
if (!hideTitle)
|
||||
def.setTitle(title);
|
||||
@ -155,15 +160,15 @@ public class StatSummarizer implements Runnable {
|
||||
String recvName = SummaryListener.createName(_context, "bw.recvRate.60000");
|
||||
def.datasource(sendName, sendName, sendName, "AVERAGE", "MEMORY");
|
||||
def.datasource(recvName, recvName, recvName, "AVERAGE", "MEMORY");
|
||||
def.area(sendName, Color.BLUE, "Outbound bytes/second");
|
||||
//def.line(sendName, Color.BLUE, "Outbound bytes/second", 3);
|
||||
//def.line(recvName, Color.RED, "Inbound bytes/second@r", 3);
|
||||
def.area(recvName, Color.RED, "Inbound bytes/second@r");
|
||||
def.area(sendName, Color.BLUE, "Outbound bytes/sec");
|
||||
//def.line(sendName, Color.BLUE, "Outbound bytes/sec", 3);
|
||||
def.line(recvName, Color.RED, "Inbound bytes/sec@r", 3);
|
||||
//def.area(recvName, Color.RED, "Inbound bytes/sec@r");
|
||||
if (!hideLegend) {
|
||||
def.gprint(sendName, "AVERAGE", "outbound average: @2@sbytes/second");
|
||||
def.gprint(sendName, "MAX", " max: @2@sbytes/second@r");
|
||||
def.gprint(recvName, "AVERAGE", "inbound average: @2bytes/second@s");
|
||||
def.gprint(recvName, "MAX", " max: @2@sbytes/second@r");
|
||||
def.gprint(sendName, "AVERAGE", "out average: @2@sbytes/sec");
|
||||
def.gprint(sendName, "MAX", " max: @2@sbytes/sec@r");
|
||||
def.gprint(recvName, "AVERAGE", "in average: @2@sbytes/sec");
|
||||
def.gprint(recvName, "MAX", " max: @2@sbytes/sec@r");
|
||||
}
|
||||
if (!showCredit)
|
||||
def.setShowSignature(false);
|
||||
@ -184,7 +189,7 @@ public class StatSummarizer implements Runnable {
|
||||
data = graph.getPNGBytes();
|
||||
else
|
||||
data = graph.getPNGBytes(width, height);
|
||||
long timeToPlot = System.currentTimeMillis() - begin;
|
||||
//long timeToPlot = System.currentTimeMillis() - begin;
|
||||
out.write(data);
|
||||
//File t = File.createTempFile("jrobinData", ".xml");
|
||||
//_listener.getData().dumpXml(new FileOutputStream(t));
|
||||
@ -197,6 +202,9 @@ public class StatSummarizer implements Runnable {
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error rendering", ioe);
|
||||
throw ioe;
|
||||
} catch (OutOfMemoryError oom) {
|
||||
_log.error("Error rendering", oom);
|
||||
throw new IOException("Error plotting: " + oom.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,7 +107,8 @@ public class SummaryHelper {
|
||||
}
|
||||
|
||||
public boolean allowReseed() {
|
||||
return (_context.netDb().getKnownRouters() < 30);
|
||||
return (_context.netDb().getKnownRouters() < 30) ||
|
||||
Boolean.valueOf(_context.getProperty("i2p.alwaysAllowReseed", "false")).booleanValue();
|
||||
}
|
||||
|
||||
public int getAllPeers() { return _context.netDb().getKnownRouters(); }
|
||||
|
@ -154,7 +154,7 @@ class SummaryRenderer {
|
||||
*
|
||||
*/
|
||||
public static synchronized void render(I2PAppContext ctx, OutputStream out, String filename) throws IOException {
|
||||
long end = ctx.clock().now();
|
||||
long end = ctx.clock().now() - 60*1000;
|
||||
long start = end - 60*1000*SummaryListener.PERIODS;
|
||||
long begin = System.currentTimeMillis();
|
||||
try {
|
||||
@ -174,17 +174,26 @@ class SummaryRenderer {
|
||||
}
|
||||
public void render(OutputStream out) throws IOException { render(out, -1, -1, false, false, false, false, -1, true); }
|
||||
public void render(OutputStream out, int width, int height, boolean hideLegend, boolean hideGrid, boolean hideTitle, boolean showEvents, int periodCount, boolean showCredit) throws IOException {
|
||||
long end = _listener.now();
|
||||
long end = _listener.now() - 60*1000;
|
||||
if (periodCount <= 0) periodCount = SummaryListener.PERIODS;
|
||||
if (periodCount > SummaryListener.PERIODS)
|
||||
periodCount = SummaryListener.PERIODS;
|
||||
long start = end - _listener.getRate().getPeriod()*periodCount;
|
||||
long begin = System.currentTimeMillis();
|
||||
//long begin = System.currentTimeMillis();
|
||||
try {
|
||||
RrdGraphDef def = new RrdGraphDef();
|
||||
def.setTimePeriod(start/1000, end/1000);
|
||||
String title = _listener.getRate().getRateStat().getName() + " averaged for "
|
||||
+ DataHelper.formatDuration(_listener.getRate().getPeriod());
|
||||
def.setTimePeriod(start/1000, 0);
|
||||
String name = _listener.getRate().getRateStat().getName();
|
||||
// heuristic to set K=1024
|
||||
if ((name.startsWith("bw.") || name.indexOf("Size") >= 0 || name.indexOf("Bps") >= 0)
|
||||
&& !showEvents)
|
||||
def.setBaseValue(1024);
|
||||
String title = name;
|
||||
if (showEvents)
|
||||
title = title + " events in ";
|
||||
else
|
||||
title = title + " averaged for ";
|
||||
title = title + DataHelper.formatDuration(_listener.getRate().getPeriod());
|
||||
if (!hideTitle)
|
||||
def.setTitle(title);
|
||||
String path = _listener.getData().getPath();
|
||||
@ -203,8 +212,9 @@ class SummaryRenderer {
|
||||
def.datasource(plotName, path, plotName, "AVERAGE", "MEMORY");
|
||||
def.area(plotName, Color.BLUE, descr + "@r");
|
||||
if (!hideLegend) {
|
||||
def.gprint(plotName, "AVERAGE", "average: @2@s");
|
||||
def.gprint(plotName, "MAX", " max: @2@s@r");
|
||||
def.gprint(plotName, "AVERAGE", "avg: @2@s");
|
||||
def.gprint(plotName, "MAX", " max: @2@s");
|
||||
def.gprint(plotName, "LAST", " now: @2@s@r");
|
||||
}
|
||||
if (!showCredit)
|
||||
def.setShowSignature(false);
|
||||
@ -233,7 +243,7 @@ class SummaryRenderer {
|
||||
data = graph.getPNGBytes();
|
||||
else
|
||||
data = graph.getPNGBytes(width, height);
|
||||
long timeToPlot = System.currentTimeMillis() - begin;
|
||||
//long timeToPlot = System.currentTimeMillis() - begin;
|
||||
out.write(data);
|
||||
//File t = File.createTempFile("jrobinData", ".xml");
|
||||
//_listener.getData().dumpXml(new FileOutputStream(t));
|
||||
@ -245,6 +255,9 @@ class SummaryRenderer {
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error rendering", ioe);
|
||||
throw ioe;
|
||||
} catch (OutOfMemoryError oom) {
|
||||
_log.error("Error rendering", oom);
|
||||
throw new IOException("Error plotting: " + oom.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,9 @@ package net.i2p.router.web;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.TrustedUpdate;
|
||||
@ -102,7 +105,9 @@ public class UpdateHandler {
|
||||
private void update() {
|
||||
_startedOn = -1;
|
||||
_status = "<b>Updating</b><br />";
|
||||
String updateURL = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_URL, ConfigUpdateHandler.DEFAULT_UPDATE_URL);
|
||||
String updateURL = selectUpdateURL();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Selected update URL: " + updateURL);
|
||||
boolean shouldProxy = Boolean.valueOf(_context.getProperty(ConfigUpdateHandler.PROP_SHOULD_PROXY, ConfigUpdateHandler.DEFAULT_SHOULD_PROXY)).booleanValue();
|
||||
String proxyHost = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_HOST, ConfigUpdateHandler.DEFAULT_PROXY_HOST);
|
||||
String port = _context.getProperty(ConfigUpdateHandler.PROP_PROXY_PORT, ConfigUpdateHandler.DEFAULT_PROXY_PORT);
|
||||
@ -162,17 +167,35 @@ public class UpdateHandler {
|
||||
}
|
||||
}
|
||||
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {
|
||||
_log.log(Log.CRIT, "Update did not download completely (" + bytesTransferred + " with "
|
||||
_log.log(Log.CRIT, "Update from " + url + " did not download completely (" + bytesTransferred + " with "
|
||||
+ bytesRemaining + " after " + currentAttempt + " tries)");
|
||||
|
||||
_status = "<b>Transfer failed</b><br />";
|
||||
System.setProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false");
|
||||
}
|
||||
public void headerReceived(String url, int attemptNum, String key, String val) {}
|
||||
public void attempting(String url) {}
|
||||
}
|
||||
|
||||
private void restart() {
|
||||
_context.router().addShutdownTask(new ConfigServiceHandler.UpdateWrapperManagerTask(Router.EXIT_GRACEFUL_RESTART));
|
||||
_context.router().shutdownGracefully(Router.EXIT_GRACEFUL_RESTART);
|
||||
}
|
||||
|
||||
private String selectUpdateURL() {
|
||||
String URLs = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_URL, ConfigUpdateHandler.DEFAULT_UPDATE_URL);
|
||||
StringTokenizer tok = new StringTokenizer(URLs, " ,\r\n");
|
||||
List URLList = new ArrayList();
|
||||
while (tok.hasMoreTokens())
|
||||
URLList.add(tok.nextToken().trim());
|
||||
int size = URLList.size();
|
||||
_log.log(Log.DEBUG, "Picking update source from " + size + " candidates.");
|
||||
if (size <= 0) {
|
||||
_log.log(Log.WARN, "Update list is empty - no update available");
|
||||
return null;
|
||||
}
|
||||
int index = I2PAppContext.getGlobalContext().random().nextInt(size);
|
||||
_log.log(Log.DEBUG, "Picked update source " + index + ".");
|
||||
return (String) URLList.get(index);
|
||||
}
|
||||
}
|
||||
|
@ -43,9 +43,21 @@
|
||||
A negative rate means a default limit of 16KBytes per second.</i><br />
|
||||
Bandwidth share percentage:
|
||||
<jsp:getProperty name="nethelper" property="sharePercentageBox" /><br />
|
||||
Sharing a higher percentage will improve your anonymity and help the network<br />
|
||||
<% int share = nethelper.getShareBandwidth();
|
||||
if (share < 12) {
|
||||
out.print("<b>NOTE</b>: You have configured I2P to share only " + share + "KBps. ");
|
||||
out.print("I2P requires at least 12KBps to enable sharing. ");
|
||||
out.print("Please enable sharing (participating in tunnels) by configuring more bandwidth. ");
|
||||
out.print("It improves your anonymity by creating cover traffic, and helps the network.<br />");
|
||||
} else {
|
||||
out.print("You have configured I2P to share " + share + "KBps. ");
|
||||
out.print("The higher the share bandwidth the more you improve your anonymity and help the network.<br />");
|
||||
}
|
||||
%>
|
||||
<p>
|
||||
<input type="submit" name="save" value="Save changes" /> <input type="reset" value="Cancel" /><br />
|
||||
<hr />
|
||||
<!--
|
||||
<b>Enable load testing: </b>
|
||||
<input type="checkbox" name="enableloadtesting" value="true" <jsp:getProperty name="nethelper" property="enableLoadTesting" /> />
|
||||
<p>If enabled, your router will periodically anonymously probe some of your peers
|
||||
@ -54,6 +66,7 @@
|
||||
load testing is fed into the profiles as well as the
|
||||
<a href="oldstats.jsp#test.rtt">test.rtt</a> and related stats.</p>
|
||||
<hr />
|
||||
-->
|
||||
<b>External UDP address:</b> <i><jsp:getProperty name="nethelper" property="udpAddress" /></i><br />
|
||||
<b>Require SSU introductions? </b>
|
||||
<input type="checkbox" name="requireIntroductions" value="true" <jsp:getProperty name="nethelper" property="requireIntroductionsChecked" /> /><br />
|
||||
@ -66,6 +79,28 @@
|
||||
Users behind symmetric NATs, such as OpenBSD's pf, are not currently supported.</p>
|
||||
<input type="submit" name="recheckReachability" value="Check network reachability..." />
|
||||
<hr />
|
||||
<b>Inbound TCP connection configuration:</b><br />
|
||||
Externally reachable hostname or IP address:
|
||||
<input name ="ntcphost" type="text" size="16" value="<jsp:getProperty name="nethelper" property="ntcphostname" />" />
|
||||
(dyndns and the like are fine)<br />
|
||||
OR use IP address detected by SSU
|
||||
(currently <jsp:getProperty name="nethelper" property="udpIP" />)?
|
||||
<input type="checkbox" name="ntcpAutoIP" value="true" <jsp:getProperty name="nethelper" property="tcpAutoIPChecked" /> /><br />
|
||||
<p>
|
||||
Externally reachable TCP port:
|
||||
<input name ="ntcpport" type="text" size="6" value="<jsp:getProperty name="nethelper" property="ntcpport" />" /><br />
|
||||
OR use the same port configured for SSU
|
||||
(currently <jsp:getProperty name="nethelper" property="udpPort" />)?
|
||||
<input type="checkbox" name="ntcpAutoPort" value="true" <jsp:getProperty name="nethelper" property="tcpAutoPortChecked" /> /><br />
|
||||
<p>You do <i>not</i> need to allow inbound TCP connections - outbound connections work with no
|
||||
configuration. However, if you want to receive inbound TCP connections, you <b>must</b> poke a hole
|
||||
in your NAT or firewall for unsolicited TCP connections. If you specify the wrong IP address or
|
||||
hostname, or do not properly configure your NAT or firewall, your network performance will degrade
|
||||
substantially. When in doubt, leave the hostname and port number blank.</p>
|
||||
<p><b>Note: changing any of these settings will terminate all of your connections and effectively
|
||||
restart your router.</b>
|
||||
<hr />
|
||||
<!--
|
||||
<b>Dynamic Router Keys: </b>
|
||||
<input type="checkbox" name="dynamicKeys" value="true" <jsp:getProperty name="nethelper" property="dynamicKeysChecked" /> /><br />
|
||||
<p>
|
||||
@ -83,6 +118,7 @@
|
||||
though such would likely already be the case anyway, since the IP address changed.
|
||||
</p>
|
||||
<hr />
|
||||
-->
|
||||
<input type="submit" name="save" value="Save changes" /> <input type="reset" value="Cancel" /><br />
|
||||
</form>
|
||||
</div>
|
||||
|
@ -78,22 +78,26 @@ function toggleAll(category)
|
||||
<table>
|
||||
<% while (statshelper.hasMoreStats()) {
|
||||
while (statshelper.groupRequired()) { %>
|
||||
<tr><td valign="top" align="left" colspan="2">
|
||||
<tr><td valign="top" align="left" colspan="3">
|
||||
<b><%=statshelper.getCurrentGroupName()%></b>
|
||||
(<a href="javascript: void(null);" onclick="toggleAll('<%=statshelper.getCurrentGroupName()%>')">toggle all</a>)
|
||||
</td></tr><%
|
||||
</td></tr><tr><td>Log</td><td>Graph</td><td></td></tr><%
|
||||
} // end iterating over required groups for the current stat %>
|
||||
<tr><td valign="top" align="left">
|
||||
<input id="<%=statshelper.getCurrentGroupName()%>" type="checkbox" name="statList" value="<%=statshelper.getCurrentStatName()%>" <%
|
||||
if (statshelper.getCurrentIsLogged()) { %>checked="true" <% } %>/></td>
|
||||
<td valign="top" align="left">
|
||||
<% if (statshelper.getCurrentCanBeGraphed()) { %>
|
||||
<input id="<%=statshelper.getCurrentGroupName()%>" type="checkbox" name="graphList" value="<%=statshelper.getCurrentGraphName()%>" <%
|
||||
if (statshelper.getCurrentIsGraphed()) { %>checked="true" <% } %>/><% } %></td>
|
||||
<td valign="top" align="left"><b><%=statshelper.getCurrentStatName()%>:</b><br />
|
||||
<%=statshelper.getCurrentStatDescription()%></td></tr><%
|
||||
} // end iterating over all stats %>
|
||||
<tr><td colspan="2"><hr /></td></tr>
|
||||
<tr><td colspan="3"><hr /></td></tr>
|
||||
<tr><td><input type="checkbox" name="explicitFilter" /></td>
|
||||
<td>Advanced filter:
|
||||
<td colspan="2">Advanced filter:
|
||||
<input type="text" name="explicitFilterValue" value="<%=statshelper.getExplicitFilter()%>" size="40" /></td></tr>
|
||||
<tr><td colspan="2"><hr /></td></tr>
|
||||
<tr><td colspan="3"><hr /></td></tr>
|
||||
<tr><td><input type="submit" name="shouldsave" value="Save changes" /> </td>
|
||||
<td><input type="reset" value="Cancel" /></td></tr>
|
||||
</form>
|
||||
@ -101,4 +105,4 @@ function toggleAll(category)
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@ -32,17 +32,17 @@
|
||||
<input type="text" size="60" name="newsURL" value="<jsp:getProperty name="updatehelper" property="newsURL" />"><br />
|
||||
Refresh frequency:
|
||||
<jsp:getProperty name="updatehelper" property="refreshFrequencySelectBox" /><br />
|
||||
Update URL:
|
||||
<input type="text" size="60" name="updateURL" value="<jsp:getProperty name="updatehelper" property="updateURL" />"><br />
|
||||
Update policy:
|
||||
<jsp:getProperty name="updatehelper" property="updatePolicySelectBox" /><br />
|
||||
Update through the eepProxy?
|
||||
<p>Update through the eepProxy?
|
||||
<jsp:getProperty name="updatehelper" property="updateThroughProxy" /><br />
|
||||
eepProxy host: <input type="text" size="10" name="proxyHost" value="<jsp:getProperty name="updatehelper" property="proxyHost" />" /><br />
|
||||
eepProxy port: <input type="text" size="4" name="proxyPort" value="<jsp:getProperty name="updatehelper" property="proxyPort" />" /><br />
|
||||
<!-- prompt for the eepproxy -->
|
||||
Trusted keys:
|
||||
<textarea name="trustedKeys" disabled="true" cols="60" rows="2"><jsp:getProperty name="updatehelper" property="trustedKeys" /></textarea>
|
||||
eepProxy port: <input type="text" size="4" name="proxyPort" value="<jsp:getProperty name="updatehelper" property="proxyPort" />" /></p>
|
||||
<p>Update URLs:<br />
|
||||
<textarea name="updateURL" cols="90" rows="4"><jsp:getProperty name="updatehelper" property="updateURL" /></textarea></p>
|
||||
<p>Trusted keys:</br />
|
||||
<textarea name="trustedKeys" cols="90" rows="4"><jsp:getProperty name="updatehelper" property="trustedKeys" /></textarea></p>
|
||||
<br />
|
||||
<input type="submit" name="action" value="Save" />
|
||||
</form>
|
||||
</div>
|
||||
|
@ -34,7 +34,7 @@ Their XML parser requires the Sun XML APIs (JAXP) which is included in binary fo
|
||||
by their binary code license. This product includes software developed by the Apache Software Foundation
|
||||
(http://www.apache.org/). </p>
|
||||
|
||||
<p>Another application you can see on this webpage is <a href="http://www.i2p.net/i2ptunnel">I2PTunnel</a>
|
||||
<p>Another application you can see on this webpage is <a href="http://www.i2p2.i2p/i2ptunnel">I2PTunnel</a>
|
||||
(your <a href="i2ptunnel/" target="_blank">web interface</a>) - a GPL'ed application written by mihi that
|
||||
lets you tunnel normal TCP/IP traffic over I2P (such as the eepproxy and the irc proxy). There is also a
|
||||
<a href="http://susi.i2p/">susimail</a> web based mail client <a href="susimail/susimail">available</a> on
|
||||
@ -42,14 +42,14 @@ the console, which is a GPL'ed application written by susi23. The addressbook a
|
||||
<a href="http://ragnarok.i2p/">Ragnarok</a> helps maintain your hosts.txt files (see ./addressbook/ for
|
||||
more information).</p>
|
||||
|
||||
<p>The router by default also includes human's public domain <a href="http://www.i2p.net/sam">SAM</a> bridge,
|
||||
<p>The router by default also includes human's public domain <a href="http://www.i2p2.i2p/sam">SAM</a> bridge,
|
||||
which other client applications (such the <a href="http://duck.i2p/i2p-bt/">bittorrent port</a>) can use.
|
||||
There is also an optimized library for doing large number calculations - jbigi - which in turn uses the
|
||||
LGPL licensed <a href="http://swox.com/gmp/">GMP</a> library, tuned for various PC architectures. Launchers for windows users are built with <a href="http://launch4j.sourceforge.net/">Launch4J</a>, and the installer is built with <a href="http://www.izforge.com/izpack/">IzPack</a>. For
|
||||
details on other applications available, as well as their licenses, please see the
|
||||
<a href="http://www.i2p.net/licenses">license policy</a>. Source for the I2P code and most bundled
|
||||
client applications can be found on our <a href="http://www.i2p.net/download">download page</a>, and is
|
||||
in <a href="http://www.i2p.net/cvs">cvs</a>.</p>
|
||||
<a href="http://www.i2p2.i2p/licenses">license policy</a>. Source for the I2P code and most bundled
|
||||
client applications can be found on our <a href="http://www.i2p2.i2p/download">download page</a>.
|
||||
.</p>
|
||||
|
||||
<h2>Release history</h2>
|
||||
<jsp:useBean class="net.i2p.router.web.ContentHelper" id="contenthelper" scope="request" />
|
||||
@ -59,9 +59,8 @@ in <a href="http://www.i2p.net/cvs">cvs</a>.</p>
|
||||
<jsp:getProperty name="contenthelper" property="textContent" />
|
||||
|
||||
<p>
|
||||
A more complete list of updates can be found
|
||||
<a href="http://dev.i2p.net/cgi-bin/cvsweb.cgi/i2p/history.txt?rev=HEAD">online</a>
|
||||
(<a href="http://dev.i2p/cgi-bin/cvsweb.cgi/i2p/history.txt?rev=HEAD">anonymously</a>)
|
||||
A more complete list of changes can be found
|
||||
in the history.txt file in your i2p directory.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
<html><head>
|
||||
<title>I2P Router Console - home</title>
|
||||
<link rel="stylesheet" href="default.css" type="text/css" />
|
||||
<link rel="shortcut icon" href="favicon.ico" />
|
||||
</head><body>
|
||||
<%
|
||||
if (System.getProperty("router.consoleNonce") == null) {
|
||||
|
@ -24,7 +24,7 @@
|
||||
<!-- Could not find docs/toolbar.html! -->
|
||||
<a href="susimail/susimail">Susimail</a> |
|
||||
<a href="susidns/index.jsp">SusiDNS</a> |
|
||||
<a href="syndie/">Syndie</a> |
|
||||
<!-- <a href="syndie/">Syndie</a> | -->
|
||||
<a href="i2psnark/">I2PSnark</a> |
|
||||
<a href="http://localhost:7658/">My Eepsite</a> <br>
|
||||
<a href="i2ptunnel/index.jsp">I2PTunnel</a> |
|
||||
|
@ -13,8 +13,8 @@
|
||||
<b>Ident:</b> <jsp:getProperty name="helper" property="ident" /><br />
|
||||
<b>Version:</b> <jsp:getProperty name="helper" property="version" /><br />
|
||||
<b>Uptime:</b> <jsp:getProperty name="helper" property="uptime" /><br />
|
||||
<b>Now:</b> <jsp:getProperty name="helper" property="time" /><br />
|
||||
<b>Status:</b> <a href="config.jsp"><jsp:getProperty name="helper" property="reachability" /></a><%
|
||||
<b>Now:</b> <jsp:getProperty name="helper" property="time" /><!--<br />
|
||||
<b>Status:</b> <a href="config.jsp"><jsp:getProperty name="helper" property="reachability" /></a>--><%
|
||||
if (helper.updateAvailable()) {
|
||||
if ("true".equals(System.getProperty("net.i2p.router.web.UpdateHandler.updateInProgress", "false"))) {
|
||||
out.print("<br />" + update.getStatus());
|
||||
@ -43,13 +43,16 @@
|
||||
<b>Failing:</b> <jsp:getProperty name="helper" property="failingPeers" /><br />
|
||||
<!-- <b>Shitlisted:</b> <jsp:getProperty name="helper" property="shitlistedPeers" /><br /> -->
|
||||
<b>Known:</b> <jsp:getProperty name="helper" property="allPeers" /><br /><%
|
||||
if (helper.getActivePeers() <= 0) {
|
||||
if (helper.getActivePeers() <= 0) {
|
||||
%><b><a href="config.jsp">check your NAT/firewall</a></b><br /><%
|
||||
}
|
||||
}
|
||||
// If showing the reseed link is allowed
|
||||
if (helper.allowReseed()) {
|
||||
if ("true".equals(System.getProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "false"))) {
|
||||
out.print(" <i>reseeding</i>");
|
||||
// While reseed occurring, show status message instead
|
||||
out.print("<i>" + System.getProperty("net.i2p.router.web.ReseedHandler.statusMessage","") + "</i><br />");
|
||||
} else {
|
||||
// While no reseed occurring, show reseed link
|
||||
long nonce = new java.util.Random().nextLong();
|
||||
String prev = System.getProperty("net.i2p.router.web.ReseedHandler.nonce");
|
||||
if (prev != null) System.setProperty("net.i2p.router.web.ReseedHandler.noncePrev", prev);
|
||||
@ -59,7 +62,14 @@
|
||||
uri = uri + "&reseedNonce=" + nonce;
|
||||
else
|
||||
uri = uri + "?reseedNonce=" + nonce;
|
||||
out.print(" <a href=\"" + uri + "\">reseed</a>");
|
||||
out.print(" <a href=\"" + uri + "\">reseed</a><br />");
|
||||
}
|
||||
}
|
||||
// If a new reseed ain't running, and the last reseed had errors, show error message
|
||||
if ("false".equals(System.getProperty("net.i2p.router.web.ReseedHandler.reseedInProgress", "false"))) {
|
||||
String reseedErrorMessage = System.getProperty("net.i2p.router.web.ReseedHandler.errorMessage","");
|
||||
if (reseedErrorMessage.length() > 0) {
|
||||
out.print("<i>" + reseedErrorMessage + "</i><br />");
|
||||
}
|
||||
}
|
||||
%><hr />
|
||||
|
@ -112,12 +112,15 @@ public class SAMHandlerFactory {
|
||||
case 1:
|
||||
handler = new SAMv1Handler(s, verMajor, verMinor, i2cpProps);
|
||||
break;
|
||||
case 2:
|
||||
handler = new SAMv2Handler(s, verMajor, verMinor, i2cpProps);
|
||||
break;
|
||||
default:
|
||||
_log.error("BUG! Trying to initialize the wrong SAM version!");
|
||||
throw new SAMException("BUG! (in handler instantiation)");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
_log.error("Error creating the v1 handler", e);
|
||||
_log.error("Error creating the handler for version "+verMajor, e);
|
||||
throw new SAMException("IOException caught during SAM handler instantiation");
|
||||
}
|
||||
return handler;
|
||||
@ -133,15 +136,16 @@ public class SAMHandlerFactory {
|
||||
|| (maxMajor == -1) || (maxMinor == -1)) {
|
||||
return null;
|
||||
}
|
||||
if (minMajor > maxMajor) {
|
||||
return null;
|
||||
} else if ((minMajor == maxMajor) && (minMinor > maxMinor)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((minMajor >= 1) && (minMinor >= 0)) {
|
||||
return "1.0";
|
||||
}
|
||||
if ((minMinor >= 10) || (maxMinor >= 10)) return null ;
|
||||
|
||||
float fminVer = (float) minMajor + (float) minMinor / 10 ;
|
||||
float fmaxVer = (float) maxMajor + (float) maxMinor / 10 ;
|
||||
|
||||
|
||||
if ( ( fminVer <= 2.0 ) && ( fmaxVer >= 2.0 ) ) return "2.0" ;
|
||||
|
||||
if ( ( fminVer <= 1.0 ) && ( fmaxVer >= 1.0 ) ) return "1.0" ;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -15,14 +15,31 @@ import net.i2p.data.Destination;
|
||||
/**
|
||||
* Interface for sending streaming data to a SAM client
|
||||
*/
|
||||
|
||||
public interface SAMStreamReceiver {
|
||||
/**
|
||||
* Sends the result of a stream send operation
|
||||
*/
|
||||
public void streamSendAnswer( int id, String result, String bufferState ) throws IOException;
|
||||
|
||||
/**
|
||||
* Notifies that the outwards buffer is free for writing
|
||||
*/
|
||||
public void notifyStreamSendBufferFree( int id ) throws IOException;
|
||||
|
||||
/**
|
||||
* Notify about a new incoming connection
|
||||
*
|
||||
* @param id New connection id
|
||||
*/
|
||||
public void notifyStreamConnection(int id, Destination dest) throws IOException;
|
||||
public void notifyStreamIncomingConnection ( int id, Destination dest ) throws IOException;
|
||||
|
||||
/**
|
||||
* Notify about a new outgoing connection
|
||||
*
|
||||
* @param id New connection id
|
||||
*/
|
||||
public void notifyStreamOutgoingConnection(int id, String result, String msg) throws IOException;
|
||||
|
||||
/**
|
||||
* Send a byte array to a SAM client.
|
||||
|
@ -47,13 +47,13 @@ public class SAMStreamSession {
|
||||
|
||||
private final static Log _log = new Log(SAMStreamSession.class);
|
||||
|
||||
private final static int SOCKET_HANDLER_BUF_SIZE = 32768;
|
||||
protected final static int SOCKET_HANDLER_BUF_SIZE = 32768;
|
||||
|
||||
private SAMStreamReceiver recv = null;
|
||||
protected SAMStreamReceiver recv = null;
|
||||
|
||||
private SAMStreamSessionServer server = null;
|
||||
|
||||
private I2PSocketManager socketMgr = null;
|
||||
protected I2PSocketManager socketMgr = null;
|
||||
|
||||
private Object handlersMapLock = new Object();
|
||||
/** stream id (Long) to SAMStreamSessionSocketReader */
|
||||
@ -65,13 +65,14 @@ public class SAMStreamSession {
|
||||
private int lastNegativeId = 0;
|
||||
|
||||
// Can we create outgoing connections?
|
||||
private boolean canCreate = false;
|
||||
protected boolean canCreate = false;
|
||||
|
||||
/**
|
||||
* should we flush every time we get a STREAM SEND, or leave that up to
|
||||
* the streaming lib to decide?
|
||||
*/
|
||||
private boolean forceFlush = false;
|
||||
protected boolean forceFlush = false;
|
||||
|
||||
public static String PROP_FORCE_FLUSH = "sam.forceFlush";
|
||||
public static String DEFAULT_FORCE_FLUSH = "false";
|
||||
|
||||
@ -189,7 +190,7 @@ public class SAMStreamSession {
|
||||
* @throws InterruptedIOException if the connection timeouts
|
||||
* @throws I2PException if there's another I2P-related error
|
||||
*/
|
||||
public boolean connect(int id, String dest, Properties props) throws I2PException, ConnectException, NoRouteToHostException, DataFormatException, InterruptedIOException, SAMInvalidDirectionException {
|
||||
public boolean connect ( int id, String dest, Properties props ) throws I2PException, ConnectException, NoRouteToHostException, DataFormatException, InterruptedIOException, SAMInvalidDirectionException, IOException {
|
||||
if (!canCreate) {
|
||||
_log.debug("Trying to create an outgoing connection using a receive-only session");
|
||||
throw new SAMInvalidDirectionException("Trying to create connections through a receive-only session");
|
||||
@ -208,10 +209,15 @@ public class SAMStreamSession {
|
||||
opts.setConnectTimeout(60 * 1000);
|
||||
|
||||
_log.debug("Connecting new I2PSocket...");
|
||||
|
||||
// blocking connection (SAMv1)
|
||||
|
||||
I2PSocket i2ps = socketMgr.connect(d, opts);
|
||||
|
||||
createSocketHandler(i2ps, id);
|
||||
|
||||
recv.notifyStreamOutgoingConnection ( id, "OK", null );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -277,7 +283,7 @@ public class SAMStreamSession {
|
||||
*
|
||||
* @return An id associated to the socket handler
|
||||
*/
|
||||
private int createSocketHandler(I2PSocket s, int id) {
|
||||
protected int createSocketHandler ( I2PSocket s, int id ) {
|
||||
SAMStreamSessionSocketReader reader = null;
|
||||
StreamSender sender = null;
|
||||
if (id == 0) {
|
||||
@ -285,8 +291,8 @@ public class SAMStreamSession {
|
||||
}
|
||||
|
||||
try {
|
||||
reader = new SAMStreamSessionSocketReader(s, id);
|
||||
sender = new StreamSender(s, id);
|
||||
reader = newSAMStreamSessionSocketReader(s, id);
|
||||
sender = newStreamSender(s, id);
|
||||
} catch (IOException e) {
|
||||
_log.error("IOException when creating SAM STREAM session socket handler", e);
|
||||
recv.stopStreamReceiving();
|
||||
@ -318,7 +324,7 @@ public class SAMStreamSession {
|
||||
*
|
||||
* @param id Handler id
|
||||
*/
|
||||
private SAMStreamSessionSocketReader getSocketReader(int id) {
|
||||
protected SAMStreamSessionSocketReader getSocketReader ( int id ) {
|
||||
synchronized (handlersMapLock) {
|
||||
return (SAMStreamSessionSocketReader)handlersMap.get(new Integer(id));
|
||||
}
|
||||
@ -334,7 +340,7 @@ public class SAMStreamSession {
|
||||
*
|
||||
* @param id Handler id
|
||||
*/
|
||||
private boolean checkSocketHandlerId(int id) {
|
||||
protected boolean checkSocketHandlerId ( int id ) {
|
||||
synchronized (handlersMapLock) {
|
||||
return (!(handlersMap.get(new Integer(id)) == null));
|
||||
}
|
||||
@ -345,7 +351,7 @@ public class SAMStreamSession {
|
||||
*
|
||||
* @param id Handler id to be removed
|
||||
*/
|
||||
private void removeSocketHandler(int id) {
|
||||
protected void removeSocketHandler ( int id ) {
|
||||
SAMStreamSessionSocketReader reader = null;
|
||||
StreamSender sender = null;
|
||||
|
||||
@ -446,7 +452,8 @@ public class SAMStreamSession {
|
||||
}
|
||||
|
||||
_log.debug("New connection id: " + id);
|
||||
recv.notifyStreamConnection(id, i2ps.getPeerDestination());
|
||||
|
||||
recv.notifyStreamIncomingConnection ( id, i2ps.getPeerDestination() );
|
||||
} catch (I2PException e) {
|
||||
_log.debug("Caught I2PException", e);
|
||||
break;
|
||||
@ -469,29 +476,62 @@ public class SAMStreamSession {
|
||||
|
||||
}
|
||||
|
||||
|
||||
boolean setReceiveLimit ( int id, long limit, boolean nolimit )
|
||||
{
|
||||
_log.debug ( "Protocol v1 does not support a receive limit for streams" );
|
||||
return false ;
|
||||
}
|
||||
|
||||
/**
|
||||
* SAM STREAM socket handler, running in its own thread. It forwards
|
||||
* SAM STREAM socket reader, running in its own thread. It forwards
|
||||
* forward data to/from an I2P socket.
|
||||
*
|
||||
* @author human
|
||||
*/
|
||||
public class SAMStreamSessionSocketReader implements Runnable {
|
||||
|
||||
private I2PSocket i2pSocket = null;
|
||||
protected I2PSocket i2pSocket = null;
|
||||
|
||||
private Object runningLock = new Object();
|
||||
private boolean stillRunning = true;
|
||||
protected Object runningLock = new Object();
|
||||
|
||||
protected boolean stillRunning = true;
|
||||
|
||||
protected int id;
|
||||
|
||||
private int id;
|
||||
|
||||
/**
|
||||
* Create a new SAM STREAM session socket reader
|
||||
*
|
||||
* @param s Socket to be handled
|
||||
* @param id Unique id assigned to the handler
|
||||
*/
|
||||
public SAMStreamSessionSocketReader(I2PSocket s, int id) throws IOException {
|
||||
_log.debug("Instantiating new SAM STREAM session socket handler");
|
||||
public SAMStreamSessionSocketReader ( I2PSocket s, int id ) throws IOException {}
|
||||
|
||||
/**
|
||||
* Stop a SAM STREAM session socket reader thread immediately.
|
||||
*/
|
||||
public void stopRunning() {}
|
||||
|
||||
public void run() {}
|
||||
|
||||
}
|
||||
|
||||
protected SAMStreamSessionSocketReader
|
||||
newSAMStreamSessionSocketReader ( I2PSocket s, int id ) throws IOException {
|
||||
return new SAMv1StreamSessionSocketReader ( s, id );
|
||||
}
|
||||
|
||||
public class SAMv1StreamSessionSocketReader extends SAMStreamSessionSocketReader {
|
||||
/**
|
||||
* Create a new SAM STREAM session socket reader
|
||||
*
|
||||
* @param s Socket to be handled
|
||||
* @param id Unique id assigned to the handler
|
||||
*/
|
||||
|
||||
public SAMv1StreamSessionSocketReader ( I2PSocket s, int id ) throws IOException {
|
||||
super(s, id);
|
||||
_log.debug("Instantiating new SAM STREAM session socket reader");
|
||||
|
||||
i2pSocket = s;
|
||||
this.id = id;
|
||||
@ -507,6 +547,7 @@ public class SAMStreamSession {
|
||||
if (stillRunning) {
|
||||
stillRunning = false;
|
||||
}
|
||||
runningLock.notifyAll() ;
|
||||
}
|
||||
}
|
||||
|
||||
@ -558,7 +599,40 @@ public class SAMStreamSession {
|
||||
* Lets us push data through the stream without blocking, (even after exceeding
|
||||
* the I2PSocket's buffer)
|
||||
*/
|
||||
private class StreamSender implements Runnable {
|
||||
protected class StreamSender implements Runnable {
|
||||
public StreamSender ( I2PSocket s, int id ) throws IOException {}
|
||||
|
||||
/**
|
||||
* Send bytes through the SAM STREAM session socket sender
|
||||
*
|
||||
* @param data Data to be sent
|
||||
*
|
||||
* @throws IOException if the client didnt provide enough data
|
||||
*/
|
||||
public void sendBytes ( InputStream in, int size ) throws IOException {}
|
||||
|
||||
|
||||
/**
|
||||
* Stop a SAM STREAM session socket sender thread immediately
|
||||
*
|
||||
*/
|
||||
public void stopRunning() {}
|
||||
|
||||
/**
|
||||
* Stop a SAM STREAM session socket sender gracefully: stop the
|
||||
* sender thread once all pending data has been sent.
|
||||
*/
|
||||
public void shutDownGracefully() {}
|
||||
|
||||
public void run() {}
|
||||
}
|
||||
|
||||
protected StreamSender newStreamSender ( I2PSocket s, int id ) throws IOException {
|
||||
return new v1StreamSender ( s, id ) ;
|
||||
}
|
||||
|
||||
protected class v1StreamSender extends StreamSender
|
||||
{
|
||||
private List _data;
|
||||
private int _id;
|
||||
private ByteCache _cache;
|
||||
@ -567,7 +641,8 @@ public class SAMStreamSession {
|
||||
private Object runningLock = new Object();
|
||||
private I2PSocket i2pSocket = null;
|
||||
|
||||
public StreamSender(I2PSocket s, int id) throws IOException {
|
||||
public v1StreamSender ( I2PSocket s, int id ) throws IOException {
|
||||
super ( s, id );
|
||||
_data = new ArrayList(1);
|
||||
_id = id;
|
||||
_cache = ByteCache.getInstance(4, 32*1024);
|
||||
|
@ -35,6 +35,8 @@ import net.i2p.util.Log;
|
||||
* @author human
|
||||
*/
|
||||
public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramReceiver, SAMStreamReceiver {
|
||||
protected int verMajorId = 1;
|
||||
protected int verMinorId = 0;
|
||||
|
||||
private final static Log _log = new Log(SAMv1Handler.class);
|
||||
|
||||
@ -42,7 +44,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
|
||||
private SAMRawSession rawSession = null;
|
||||
private SAMDatagramSession datagramSession = null;
|
||||
private SAMStreamSession streamSession = null;
|
||||
protected SAMStreamSession streamSession = null;
|
||||
|
||||
private long _id;
|
||||
private static volatile long __id = 0;
|
||||
@ -74,11 +76,15 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
_id = ++__id;
|
||||
_log.debug("SAM version 1 handler instantiated");
|
||||
|
||||
if ((this.verMajor != 1) || (this.verMinor != 0)) {
|
||||
if ( ! verifVersion() ) {
|
||||
throw new SAMException("BUG! Wrong protocol version!");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean verifVersion() {
|
||||
return ( verMajor == 1 && verMinor == 0 ) ;
|
||||
}
|
||||
|
||||
public void handle() {
|
||||
String msg = null;
|
||||
String domain = null;
|
||||
@ -248,7 +254,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
}
|
||||
props.remove("DIRECTION");
|
||||
|
||||
streamSession = new SAMStreamSession(destKeystream, dir,props,this);
|
||||
streamSession = newSAMStreamSession(destKeystream, dir,props);
|
||||
} else {
|
||||
_log.debug("Unrecognized SESSION STYLE: \"" + style +"\"");
|
||||
return writeString("SESSION STATUS RESULT=I2P_ERROR MESSAGE=\"Unrecognized SESSION STYLE\"\n");
|
||||
@ -275,6 +281,13 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SAMStreamSession newSAMStreamSession(String destKeystream, String direction, Properties props )
|
||||
throws IOException, DataFormatException, SAMException
|
||||
{
|
||||
return new SAMStreamSession(destKeystream, direction, props, this) ;
|
||||
}
|
||||
|
||||
/* Parse and execute a DEST message*/
|
||||
private boolean execDestMessage(String opcode, Properties props) {
|
||||
|
||||
@ -489,7 +502,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
}
|
||||
|
||||
/* Parse and execute a STREAM message */
|
||||
private boolean execStreamMessage(String opcode, Properties props) {
|
||||
protected boolean execStreamMessage(String opcode, Properties props) {
|
||||
if (streamSession == null) {
|
||||
_log.error("STREAM message received, but no STREAM session exists");
|
||||
return false;
|
||||
@ -508,7 +521,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
}
|
||||
}
|
||||
|
||||
private boolean execStreamSend(Properties props) {
|
||||
protected boolean execStreamSend(Properties props) {
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in STREAM SEND message");
|
||||
return false;
|
||||
@ -570,7 +583,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
}
|
||||
}
|
||||
|
||||
private boolean execStreamConnect(Properties props) {
|
||||
protected boolean execStreamConnect(Properties props) {
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in STREAM CONNECT message");
|
||||
return false;
|
||||
@ -604,39 +617,38 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
props.remove("DESTINATION");
|
||||
|
||||
try {
|
||||
if (!streamSession.connect(id, dest, props)) {
|
||||
_log.debug("STREAM connection failed");
|
||||
return false;
|
||||
try {
|
||||
if (!streamSession.connect(id, dest, props)) {
|
||||
_log.debug("STREAM connection failed");
|
||||
return false;
|
||||
}
|
||||
} catch (DataFormatException e) {
|
||||
_log.debug("Invalid destination in STREAM CONNECT message");
|
||||
notifyStreamOutgoingConnection ( id, "INVALID_KEY", null );
|
||||
} catch (SAMInvalidDirectionException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
notifyStreamOutgoingConnection ( id, "INVALID_DIRECTION", null );
|
||||
} catch (ConnectException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
notifyStreamOutgoingConnection ( id, "CONNECTION_REFUSED", null );
|
||||
} catch (NoRouteToHostException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
notifyStreamOutgoingConnection ( id, "CANT_REACH_PEER", null );
|
||||
} catch (InterruptedIOException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
notifyStreamOutgoingConnection ( id, "TIMEOUT", null );
|
||||
} catch (I2PException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
notifyStreamOutgoingConnection ( id, "I2P_ERROR", null );
|
||||
}
|
||||
return writeString("STREAM STATUS RESULT=OK ID=" + id + "\n");
|
||||
} catch (DataFormatException e) {
|
||||
_log.debug("Invalid destination in STREAM CONNECT message");
|
||||
return writeString("STREAM STATUS RESULT=INVALID_KEY ID="
|
||||
+ id + "\n");
|
||||
} catch (SAMInvalidDirectionException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=INVALID_DIRECTION ID="
|
||||
+ id + "\n");
|
||||
} catch (ConnectException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=CONNECTION_REFUSED ID="
|
||||
+ id + "\n");
|
||||
} catch (NoRouteToHostException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=CANT_REACH_PEER ID="
|
||||
+ id + "\n");
|
||||
} catch (InterruptedIOException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=TIMEOUT ID="
|
||||
+ id + "\n");
|
||||
} catch (I2PException e) {
|
||||
_log.debug("STREAM CONNECT failed: " + e.getMessage());
|
||||
return writeString("STREAM STATUS RESULT=I2P_ERROR ID="
|
||||
+ id + "\n");
|
||||
} catch (IOException e) {
|
||||
return false ;
|
||||
}
|
||||
|
||||
return true ;
|
||||
}
|
||||
|
||||
private boolean execStreamClose(Properties props) {
|
||||
protected boolean execStreamClose(Properties props) {
|
||||
if (props == null) {
|
||||
_log.debug("No parameters specified in STREAM CLOSE message");
|
||||
return false;
|
||||
@ -745,7 +757,41 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
}
|
||||
|
||||
// SAMStreamReceiver implementation
|
||||
public void notifyStreamConnection(int id, Destination d) throws IOException {
|
||||
|
||||
public void streamSendAnswer( int id, String result, String bufferState ) throws IOException
|
||||
{
|
||||
if ( streamSession == null )
|
||||
{
|
||||
_log.error ( "BUG! Want to answer to stream SEND, but session is null!" );
|
||||
throw new NullPointerException ( "BUG! STREAM session is null!" );
|
||||
}
|
||||
|
||||
if ( !writeString ( "STREAM SEND ID=" + id
|
||||
+ " RESULT=" + result
|
||||
+ " STATE=" + bufferState
|
||||
+ "\n" ) )
|
||||
{
|
||||
throw new IOException ( "Error notifying connection to SAM client" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void notifyStreamSendBufferFree( int id ) throws IOException
|
||||
{
|
||||
if ( streamSession == null )
|
||||
{
|
||||
_log.error ( "BUG! Stream outgoing buffer is free, but session is null!" );
|
||||
throw new NullPointerException ( "BUG! STREAM session is null!" );
|
||||
}
|
||||
|
||||
if ( !writeString ( "STREAM READY_TO_SEND ID=" + id + "\n" ) )
|
||||
{
|
||||
throw new IOException ( "Error notifying connection to SAM client" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void notifyStreamIncomingConnection(int id, Destination d) throws IOException {
|
||||
if (streamSession == null) {
|
||||
_log.error("BUG! Received stream connection, but session is null!");
|
||||
throw new NullPointerException("BUG! STREAM session is null!");
|
||||
@ -758,6 +804,28 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatag
|
||||
}
|
||||
}
|
||||
|
||||
public void notifyStreamOutgoingConnection ( int id, String result, String msg ) throws IOException
|
||||
{
|
||||
if ( streamSession == null )
|
||||
{
|
||||
_log.error ( "BUG! Received stream connection, but session is null!" );
|
||||
throw new NullPointerException ( "BUG! STREAM session is null!" );
|
||||
}
|
||||
|
||||
String msgString = "" ;
|
||||
|
||||
if ( msg != null ) msgString = " MESSAGE=\"" + msg + "\"";
|
||||
|
||||
if ( !writeString ( "STREAM STATUS RESULT="
|
||||
+ result
|
||||
+ " ID=" + id
|
||||
+ msgString
|
||||
+ "\n" ) )
|
||||
{
|
||||
throw new IOException ( "Error notifying connection to SAM client" );
|
||||
}
|
||||
}
|
||||
|
||||
public void receiveStreamBytes(int id, byte data[], int len) throws IOException {
|
||||
if (streamSession == null) {
|
||||
_log.error("Received stream bytes, but session is null!");
|
||||
|
196
apps/sam/java/src/net/i2p/sam/SAMv2Handler.java
Normal file
196
apps/sam/java/src/net/i2p/sam/SAMv2Handler.java
Normal file
@ -0,0 +1,196 @@
|
||||
package net.i2p.sam;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by human in 2004 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.net.Socket;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Class able to handle a SAM version 2 client connection.
|
||||
*
|
||||
* @author mkvore
|
||||
*/
|
||||
|
||||
public class SAMv2Handler extends SAMv1Handler implements SAMRawReceiver, SAMDatagramReceiver, SAMStreamReceiver
|
||||
{
|
||||
|
||||
private final static Log _log = new Log ( SAMv2Handler.class );
|
||||
|
||||
|
||||
/**
|
||||
* Create a new SAM version 2 handler. This constructor expects
|
||||
* that the SAM HELLO message has been still answered (and
|
||||
* stripped) from the socket input stream.
|
||||
*
|
||||
* @param s Socket attached to a SAM client
|
||||
* @param verMajor SAM major version to manage (should be 2)
|
||||
* @param verMinor SAM minor version to manage
|
||||
*/
|
||||
public SAMv2Handler ( Socket s, int verMajor, int verMinor ) throws SAMException, IOException
|
||||
{
|
||||
this ( s, verMajor, verMinor, new Properties() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new SAM version 2 handler. This constructor expects
|
||||
* that the SAM HELLO message has been still answered (and
|
||||
* stripped) from the socket input stream.
|
||||
*
|
||||
* @param s Socket attached to a SAM client
|
||||
* @param verMajor SAM major version to manage (should be 2)
|
||||
* @param verMinor SAM minor version to manage
|
||||
* @param i2cpProps properties to configure the I2CP connection (host, port, etc)
|
||||
*/
|
||||
|
||||
public SAMv2Handler ( Socket s, int verMajor, int verMinor, Properties i2cpProps ) throws SAMException, IOException
|
||||
{
|
||||
super ( s, verMajor, verMinor, i2cpProps );
|
||||
}
|
||||
|
||||
public boolean verifVersion()
|
||||
{
|
||||
return (verMajor == 2 && verMinor == 0) ;
|
||||
}
|
||||
|
||||
SAMStreamSession newSAMStreamSession(String destKeystream, String direction, Properties props )
|
||||
throws IOException, DataFormatException, SAMException
|
||||
{
|
||||
return new SAMv2StreamSession(destKeystream, direction, props, this) ;
|
||||
}
|
||||
|
||||
|
||||
/* Parse and execute a STREAM message */
|
||||
protected boolean execStreamMessage ( String opcode, Properties props )
|
||||
{
|
||||
if ( streamSession == null )
|
||||
{
|
||||
_log.error ( "STREAM message received, but no STREAM session exists" );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( opcode.equals ( "SEND" ) )
|
||||
{
|
||||
return execStreamSend ( props );
|
||||
}
|
||||
else if ( opcode.equals ( "CONNECT" ) )
|
||||
{
|
||||
return execStreamConnect ( props );
|
||||
}
|
||||
else if ( opcode.equals ( "CLOSE" ) )
|
||||
{
|
||||
return execStreamClose ( props );
|
||||
}
|
||||
else if ( opcode.equals ( "RECEIVE") )
|
||||
{
|
||||
return execStreamReceive( props );
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.debug ( "Unrecognized RAW message opcode: \""
|
||||
+ opcode + "\"" );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private boolean execStreamReceive ( Properties props )
|
||||
{
|
||||
if ( props == null )
|
||||
{
|
||||
_log.debug ( "No parameters specified in STREAM RECEIVE message" );
|
||||
return false;
|
||||
}
|
||||
|
||||
int id;
|
||||
|
||||
{
|
||||
String strid = props.getProperty ( "ID" );
|
||||
|
||||
if ( strid == null )
|
||||
{
|
||||
_log.debug ( "ID not specified in STREAM RECEIVE message" );
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
id = Integer.parseInt ( strid );
|
||||
}
|
||||
catch ( NumberFormatException e )
|
||||
{
|
||||
_log.debug ( "Invalid STREAM RECEIVE ID specified: " + strid );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
boolean nolimit = false;
|
||||
|
||||
long limit = 0;
|
||||
{
|
||||
String strsize = props.getProperty ( "LIMIT" );
|
||||
|
||||
if ( strsize == null )
|
||||
{
|
||||
_log.debug ( "Limit not specified in STREAM RECEIVE message" );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( strsize.equals( "NONE" ) )
|
||||
{
|
||||
nolimit = true ;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
limit = Long.parseLong ( strsize );
|
||||
}
|
||||
catch ( NumberFormatException e )
|
||||
{
|
||||
_log.debug ( "Invalid STREAM RECEIVE size specified: " + strsize );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( limit < 0 )
|
||||
{
|
||||
_log.debug ( "Specified limit (" + limit
|
||||
+ ") is out of protocol limits" );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
streamSession.setReceiveLimit ( id, limit, nolimit ) ;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
574
apps/sam/java/src/net/i2p/sam/SAMv2StreamSession.java
Normal file
574
apps/sam/java/src/net/i2p/sam/SAMv2StreamSession.java
Normal file
@ -0,0 +1,574 @@
|
||||
package net.i2p.sam;
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by human in 2004 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.ConnectException;
|
||||
import java.net.NoRouteToHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.I2PException;
|
||||
import net.i2p.client.I2PClient;
|
||||
import net.i2p.client.streaming.I2PServerSocket;
|
||||
import net.i2p.client.streaming.I2PSocket;
|
||||
import net.i2p.client.streaming.I2PSocketManager;
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory;
|
||||
import net.i2p.client.streaming.I2PSocketOptions;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.ByteArray;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.ByteCache;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* SAMv2 STREAM session class.
|
||||
*
|
||||
* @author mkvore
|
||||
*/
|
||||
|
||||
public class SAMv2StreamSession extends SAMStreamSession
|
||||
{
|
||||
|
||||
private final static Log _log = new Log ( SAMv2StreamSession.class );
|
||||
|
||||
/**
|
||||
* Create a new SAM STREAM session.
|
||||
*
|
||||
* @param dest Base64-encoded destination (private key)
|
||||
* @param dir Session direction ("RECEIVE", "CREATE" or "BOTH")
|
||||
* @param props Properties to setup the I2P session
|
||||
* @param recv Object that will receive incoming data
|
||||
*/
|
||||
public SAMv2StreamSession ( String dest, String dir, Properties props,
|
||||
SAMStreamReceiver recv ) throws IOException, DataFormatException, SAMException
|
||||
{
|
||||
super ( dest, dir, props, recv );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new SAM STREAM session.
|
||||
*
|
||||
* @param destStream Input stream containing the destination keys
|
||||
* @param dir Session direction ("RECEIVE", "CREATE" or "BOTH")
|
||||
* @param props Properties to setup the I2P session
|
||||
* @param recv Object that will receive incoming data
|
||||
*/
|
||||
public SAMv2StreamSession ( InputStream destStream, String dir,
|
||||
Properties props, SAMStreamReceiver recv ) throws IOException, DataFormatException, SAMException
|
||||
{
|
||||
super ( destStream, dir, props, recv );
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect the SAM STREAM session to the specified Destination
|
||||
*
|
||||
* @param id Unique id for the connection
|
||||
* @param dest Base64-encoded Destination to connect to
|
||||
* @param props Options to be used for connection
|
||||
*
|
||||
* @throws DataFormatException if the destination is not valid
|
||||
* @throws SAMInvalidDirectionException if trying to connect through a
|
||||
* receive-only session
|
||||
* @return true if the communication with the SAM client is ok
|
||||
*/
|
||||
|
||||
public boolean connect ( int id, String dest, Properties props )
|
||||
throws DataFormatException, SAMInvalidDirectionException
|
||||
{
|
||||
if ( !canCreate )
|
||||
{
|
||||
_log.debug ( "Trying to create an outgoing connection using a receive-only session" );
|
||||
throw new SAMInvalidDirectionException ( "Trying to create connections through a receive-only session" );
|
||||
}
|
||||
|
||||
if ( checkSocketHandlerId ( id ) )
|
||||
{
|
||||
_log.debug ( "The specified id (" + id + ") is already in use" );
|
||||
return false ;
|
||||
}
|
||||
|
||||
Destination d = new Destination();
|
||||
|
||||
d.fromBase64 ( dest );
|
||||
|
||||
I2PSocketOptions opts = socketMgr.buildOptions ( props );
|
||||
|
||||
if ( props.getProperty ( I2PSocketOptions.PROP_CONNECT_TIMEOUT ) == null )
|
||||
opts.setConnectTimeout ( 60 * 1000 );
|
||||
|
||||
_log.debug ( "Connecting new I2PSocket..." );
|
||||
|
||||
|
||||
// non-blocking connection (SAMv2)
|
||||
|
||||
StreamConnector connector ;
|
||||
|
||||
connector = new StreamConnector ( id, d, opts );
|
||||
|
||||
I2PThread connectThread = new I2PThread ( connector, "StreamConnector" + id ) ;
|
||||
|
||||
connectThread.start() ;
|
||||
|
||||
return true ;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* SAM STREAM socket connecter, running in its own thread.
|
||||
*
|
||||
* @author mkvore
|
||||
*/
|
||||
|
||||
public class StreamConnector implements Runnable
|
||||
{
|
||||
|
||||
private Object runningLock = new Object();
|
||||
private boolean stillRunning = true;
|
||||
|
||||
private int id;
|
||||
private Destination dest ;
|
||||
private I2PSocketOptions opts ;
|
||||
|
||||
/**
|
||||
* Create a new SAM STREAM session socket reader
|
||||
*
|
||||
* @param id Unique id assigned to the handler
|
||||
* @param dest Destination to reach
|
||||
* @param opts Socket options (I2PSocketOptions)
|
||||
*/
|
||||
|
||||
|
||||
public StreamConnector ( int id, Destination dest, I2PSocketOptions opts )// throws IOException
|
||||
{
|
||||
_log.debug ( "Instantiating new SAM STREAM connector" );
|
||||
|
||||
this.id = id ;
|
||||
this.opts = opts ;
|
||||
this.dest = dest ;
|
||||
}
|
||||
|
||||
|
||||
public void run()
|
||||
{
|
||||
_log.debug ( "run() called for socket connector " + id );
|
||||
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
I2PSocket i2ps = socketMgr.connect ( dest, opts );
|
||||
|
||||
createSocketHandler ( i2ps, id );
|
||||
|
||||
recv.notifyStreamOutgoingConnection ( id, "OK", null );
|
||||
}
|
||||
|
||||
catch ( DataFormatException e )
|
||||
{
|
||||
_log.debug ( "Invalid destination in STREAM CONNECT message" );
|
||||
recv.notifyStreamOutgoingConnection ( id, "INVALID_KEY", e.getMessage() );
|
||||
}
|
||||
catch ( ConnectException e )
|
||||
{
|
||||
_log.debug ( "STREAM CONNECT failed: " + e.getMessage() );
|
||||
recv.notifyStreamOutgoingConnection ( id, "CONNECTION_REFUSED", e.getMessage() );
|
||||
}
|
||||
catch ( NoRouteToHostException e )
|
||||
{
|
||||
_log.debug ( "STREAM CONNECT failed: " + e.getMessage() );
|
||||
recv.notifyStreamOutgoingConnection ( id, "CANT_REACH_PEER", e.getMessage() );
|
||||
}
|
||||
catch ( InterruptedIOException e )
|
||||
{
|
||||
_log.debug ( "STREAM CONNECT failed: " + e.getMessage() );
|
||||
recv.notifyStreamOutgoingConnection ( id, "TIMEOUT", e.getMessage() );
|
||||
}
|
||||
catch ( I2PException e )
|
||||
{
|
||||
_log.debug ( "STREAM CONNECT failed: " + e.getMessage() );
|
||||
recv.notifyStreamOutgoingConnection ( id, "I2P_ERROR", e.getMessage() );
|
||||
}
|
||||
}
|
||||
catch ( IOException e )
|
||||
{
|
||||
_log.debug ( "Error sending disconnection notice for handler "
|
||||
+ id, e );
|
||||
}
|
||||
|
||||
_log.debug ( "Shutting down SAM STREAM session connector " + id );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Lets us push data through the stream without blocking, (even after exceeding
|
||||
* the I2PSocket's buffer)
|
||||
*/
|
||||
|
||||
protected StreamSender newStreamSender ( I2PSocket s, int id ) throws IOException
|
||||
{
|
||||
return new v2StreamSender ( s, id ) ;
|
||||
}
|
||||
|
||||
protected SAMStreamSessionSocketReader
|
||||
newSAMStreamSessionSocketReader(I2PSocket s, int id ) throws IOException
|
||||
{
|
||||
return new SAMv2StreamSessionSocketReader(s,id);
|
||||
}
|
||||
|
||||
protected class v2StreamSender extends StreamSender
|
||||
|
||||
{
|
||||
private List _data;
|
||||
private int _dataSize;
|
||||
private int _id;
|
||||
private ByteCache _cache;
|
||||
private OutputStream _out = null;
|
||||
private boolean _stillRunning, _shuttingDownGracefully;
|
||||
private Object runningLock = new Object();
|
||||
private I2PSocket i2pSocket = null;
|
||||
|
||||
public v2StreamSender ( I2PSocket s, int id ) throws IOException
|
||||
{
|
||||
super ( s, id );
|
||||
_data = new ArrayList ( 1 );
|
||||
_dataSize = 0;
|
||||
_id = id;
|
||||
_cache = ByteCache.getInstance ( 10, 32 * 1024 );
|
||||
_out = s.getOutputStream();
|
||||
_stillRunning = true;
|
||||
_shuttingDownGracefully = false;
|
||||
i2pSocket = s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send bytes through the SAM STREAM session socket sender
|
||||
*
|
||||
* @param data Data to be sent
|
||||
*
|
||||
* @throws IOException if the client didnt provide enough data
|
||||
*/
|
||||
public void sendBytes ( InputStream in, int size ) throws IOException
|
||||
{
|
||||
if ( _log.shouldLog ( Log.DEBUG ) )
|
||||
_log.debug ( "Handler " + _id + ": sending " + size + " bytes" );
|
||||
|
||||
ByteArray ba = _cache.acquire();
|
||||
|
||||
int read = DataHelper.read ( in, ba.getData(), 0, size );
|
||||
|
||||
if ( read != size )
|
||||
throw new IOException ( "Insufficient data from the SAM client (" + read + "/" + size + ")" );
|
||||
|
||||
ba.setValid ( read );
|
||||
|
||||
synchronized ( _data )
|
||||
{
|
||||
if ( _dataSize >= SOCKET_HANDLER_BUF_SIZE )
|
||||
{
|
||||
_cache.release ( ba, false );
|
||||
recv.streamSendAnswer ( _id, "FAILED", "BUFFER_FULL" ) ;
|
||||
}
|
||||
else
|
||||
{
|
||||
_dataSize += size ;
|
||||
_data.add ( ba );
|
||||
_data.notifyAll();
|
||||
|
||||
if ( _dataSize >= SOCKET_HANDLER_BUF_SIZE )
|
||||
{
|
||||
recv.streamSendAnswer ( _id, "OK", "BUFFER_FULL" ) ;
|
||||
}
|
||||
else
|
||||
{
|
||||
recv.streamSendAnswer ( _id, "OK", "READY" );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a SAM STREAM session socket sender thread immediately
|
||||
*
|
||||
*/
|
||||
public void stopRunning()
|
||||
{
|
||||
_log.debug ( "stopRunning() invoked on socket sender " + _id );
|
||||
|
||||
synchronized ( runningLock )
|
||||
{
|
||||
if ( _stillRunning )
|
||||
{
|
||||
_stillRunning = false;
|
||||
|
||||
try
|
||||
{
|
||||
i2pSocket.close();
|
||||
}
|
||||
catch ( IOException e )
|
||||
{
|
||||
_log.debug ( "Caught IOException", e );
|
||||
}
|
||||
|
||||
synchronized ( _data )
|
||||
{
|
||||
_data.clear();
|
||||
_data.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a SAM STREAM session socket sender gracefully: stop the
|
||||
* sender thread once all pending data has been sent.
|
||||
*/
|
||||
public void shutDownGracefully()
|
||||
{
|
||||
_log.debug ( "shutDownGracefully() invoked on socket sender " + _id );
|
||||
_shuttingDownGracefully = true;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
_log.debug ( "run() called for socket sender " + _id );
|
||||
ByteArray data = null;
|
||||
|
||||
while ( _stillRunning )
|
||||
{
|
||||
data = null;
|
||||
|
||||
try
|
||||
{
|
||||
synchronized ( _data )
|
||||
{
|
||||
if ( _data.size() > 0 )
|
||||
{
|
||||
int formerSize = _dataSize ;
|
||||
data = ( ByteArray ) _data.remove ( 0 );
|
||||
_dataSize -= data.getValid();
|
||||
|
||||
if ( ( formerSize >= SOCKET_HANDLER_BUF_SIZE ) && ( _dataSize < SOCKET_HANDLER_BUF_SIZE ) )
|
||||
recv.notifyStreamSendBufferFree ( _id );
|
||||
}
|
||||
else if ( _shuttingDownGracefully )
|
||||
{
|
||||
/* No data left and shutting down gracefully?
|
||||
If so, stop the sender. */
|
||||
stopRunning();
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Wait for data. */
|
||||
_data.wait ( 5000 );
|
||||
}
|
||||
}
|
||||
|
||||
if ( data != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
_out.write ( data.getData(), 0, data.getValid() );
|
||||
|
||||
if ( forceFlush )
|
||||
{
|
||||
// i dont like doing this, but it clears the buffer issues
|
||||
_out.flush();
|
||||
}
|
||||
}
|
||||
catch ( IOException ioe )
|
||||
{
|
||||
// ok, the stream failed, but the SAM client didn't
|
||||
|
||||
if ( _log.shouldLog ( Log.WARN ) )
|
||||
_log.warn ( "Stream failed", ioe );
|
||||
|
||||
removeSocketHandler ( _id );
|
||||
|
||||
stopRunning();
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
_cache.release ( data, false );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( InterruptedException ie ) {}
|
||||
catch ( IOException e ) {}}
|
||||
|
||||
synchronized ( _data )
|
||||
{
|
||||
_data.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Send bytes through a SAM STREAM session.
|
||||
*
|
||||
* @param data Bytes to be sent
|
||||
*
|
||||
* @return True if the data was queued for sending, false otherwise
|
||||
*/
|
||||
public boolean setReceiveLimit ( int id, long limit, boolean nolimit )
|
||||
{
|
||||
SAMStreamSessionSocketReader reader = getSocketReader ( id );
|
||||
|
||||
if ( reader == null )
|
||||
{
|
||||
if ( _log.shouldLog ( Log.WARN ) )
|
||||
_log.warn ( "Trying to set a limit to a nonexistent reader " + id );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
( (SAMv2StreamSessionSocketReader) reader).setLimit ( limit, nolimit );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* SAM STREAM socket reader, running in its own thread. It forwards
|
||||
* forward data to/from an I2P socket.
|
||||
*
|
||||
* @author human
|
||||
*/
|
||||
|
||||
|
||||
|
||||
public class SAMv2StreamSessionSocketReader extends SAMv1StreamSessionSocketReader
|
||||
{
|
||||
|
||||
protected boolean nolimit ;
|
||||
protected long limit ;
|
||||
protected long totalReceived ;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new SAM STREAM session socket reader
|
||||
*
|
||||
* @param s Socket to be handled
|
||||
* @param id Unique id assigned to the handler
|
||||
*/
|
||||
public SAMv2StreamSessionSocketReader ( I2PSocket s, int id ) throws IOException
|
||||
{
|
||||
super ( s, id );
|
||||
nolimit = false ;
|
||||
limit = 0 ;
|
||||
totalReceived = 0 ;
|
||||
}
|
||||
|
||||
public void setLimit ( long limit, boolean nolimit )
|
||||
{
|
||||
synchronized (runningLock)
|
||||
{
|
||||
this.limit = limit ;
|
||||
this.nolimit = nolimit ;
|
||||
runningLock.notify() ;
|
||||
}
|
||||
_log.debug ( "new limit set for socket reader " + id + " : " + (nolimit ? "NOLIMIT" : limit + " bytes" ) );
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
_log.debug ( "run() called for socket reader " + id );
|
||||
|
||||
int read = -1;
|
||||
byte[] data = new byte[SOCKET_HANDLER_BUF_SIZE];
|
||||
|
||||
try
|
||||
{
|
||||
InputStream in = i2pSocket.getInputStream();
|
||||
|
||||
while ( stillRunning )
|
||||
{
|
||||
synchronized (runningLock)
|
||||
{
|
||||
while ( stillRunning && ( !nolimit && totalReceived >= limit) )
|
||||
{
|
||||
try{
|
||||
runningLock.wait() ;
|
||||
}
|
||||
catch (InterruptedException ie)
|
||||
{}
|
||||
}
|
||||
if ( !stillRunning )
|
||||
break ;
|
||||
}
|
||||
|
||||
read = in.read ( data );
|
||||
|
||||
if ( read == -1 )
|
||||
{
|
||||
_log.debug ( "Handler " + id + ": connection closed" );
|
||||
break;
|
||||
}
|
||||
|
||||
totalReceived += read ;
|
||||
|
||||
recv.receiveStreamBytes ( id, data, read );
|
||||
}
|
||||
}
|
||||
catch ( IOException e )
|
||||
{
|
||||
_log.debug ( "Caught IOException", e );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
i2pSocket.close();
|
||||
}
|
||||
catch ( IOException e )
|
||||
{
|
||||
_log.debug ( "Caught IOException", e );
|
||||
}
|
||||
|
||||
if ( stillRunning )
|
||||
{
|
||||
removeSocketHandler ( id );
|
||||
// FIXME: we need error reporting here!
|
||||
|
||||
try
|
||||
{
|
||||
recv.notifyStreamDisconnection ( id, "OK", null );
|
||||
}
|
||||
catch ( IOException e )
|
||||
{
|
||||
_log.debug ( "Error sending disconnection notice for handler "
|
||||
+ id, e );
|
||||
}
|
||||
}
|
||||
|
||||
_log.debug ( "Shutting down SAM STREAM session socket handler " + id );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -72,7 +72,7 @@ public class Connection {
|
||||
private long _lifetimeDupMessageSent;
|
||||
private long _lifetimeDupMessageReceived;
|
||||
|
||||
public static final long MAX_RESEND_DELAY = 10*1000;
|
||||
public static final long MAX_RESEND_DELAY = 45*1000;
|
||||
public static final long MIN_RESEND_DELAY = 2*1000;
|
||||
|
||||
/** wait up to 5 minutes after disconnection so we can ack/close packets */
|
||||
@ -247,6 +247,7 @@ public class Connection {
|
||||
}
|
||||
long now = _context.clock().now();
|
||||
if (_resetSentOn + 10*1000 > now) return; // don't send resets too fast
|
||||
if (_resetReceived) return;
|
||||
_resetSent = true;
|
||||
if (_resetSentOn <= 0)
|
||||
_resetSentOn = now;
|
||||
|
@ -57,7 +57,7 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
static final int INITIAL_WINDOW_SIZE = 12;
|
||||
static final int DEFAULT_MAX_SENDS = 8;
|
||||
|
||||
static final int MIN_WINDOW_SIZE = INITIAL_WINDOW_SIZE;
|
||||
static final int MIN_WINDOW_SIZE = 1;
|
||||
|
||||
public ConnectionOptions() {
|
||||
super();
|
||||
@ -205,8 +205,8 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
public int getRTT() { return _rtt; }
|
||||
public void setRTT(int ms) {
|
||||
if (_rto == 0) {
|
||||
_rttDev = ms;
|
||||
_rto = (int)Connection.MAX_RESEND_DELAY;
|
||||
_rttDev = ms / 2;
|
||||
_rto = ms + ms / 2;
|
||||
}
|
||||
synchronized (_trend) {
|
||||
_trend[0] = _trend[1];
|
||||
|
@ -29,9 +29,15 @@ public class I2PSocketFull implements I2PSocket {
|
||||
if (c == null) return;
|
||||
if (c.getIsConnected()) {
|
||||
OutputStream out = c.getOutputStream();
|
||||
if (out != null)
|
||||
out.close();
|
||||
c.disconnect(true);
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException ioe) {
|
||||
// ignore any write error, as we want to keep on and kill the
|
||||
// con (thanks Complication!)
|
||||
}
|
||||
}
|
||||
c.disconnect(true);
|
||||
} else {
|
||||
//throw new IOException("Not connected");
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ public class I2PSocketManagerFull implements I2PSocketManager {
|
||||
public long getAcceptTimeout() { return _acceptTimeout; }
|
||||
|
||||
public void setDefaultOptions(I2PSocketOptions options) {
|
||||
_defaultOptions = new ConnectionOptions(options);
|
||||
_defaultOptions = new ConnectionOptions((ConnectionOptions) options);
|
||||
}
|
||||
|
||||
public I2PSocketOptions getDefaultOptions() {
|
||||
|
@ -19,7 +19,7 @@
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.7 $
|
||||
* $Revision: 1.2 $
|
||||
*/
|
||||
|
||||
package i2p.susi.dns;
|
||||
@ -76,6 +76,8 @@ public class AddressbookBean
|
||||
deletionMarks = new LinkedList();
|
||||
}
|
||||
private long configLastLoaded = 0;
|
||||
private static final String PRIVATE_BOOK = "private_addressbook";
|
||||
private static final String DEFAULT_PRIVATE_BOOK = "../privatehosts.txt";
|
||||
private void loadConfig()
|
||||
{
|
||||
long currentTime = System.currentTimeMillis();
|
||||
@ -86,6 +88,9 @@ public class AddressbookBean
|
||||
try {
|
||||
properties.clear();
|
||||
properties.load( new FileInputStream( ConfigBean.configFileName ) );
|
||||
// added in 0.5, for compatibility with 0.4 config.txt
|
||||
if( properties.getProperty(PRIVATE_BOOK) == null)
|
||||
properties.setProperty(PRIVATE_BOOK, DEFAULT_PRIVATE_BOOK);
|
||||
configLastLoaded = currentTime;
|
||||
}
|
||||
catch (Exception e) {
|
||||
@ -99,7 +104,6 @@ public class AddressbookBean
|
||||
return ConfigBean.addressbookPrefix + filename;
|
||||
}
|
||||
private Object[] entries;
|
||||
|
||||
public Object[] getEntries()
|
||||
{
|
||||
return entries;
|
||||
@ -113,8 +117,9 @@ public class AddressbookBean
|
||||
public String getBook()
|
||||
{
|
||||
if( book == null || ( book.compareToIgnoreCase( "master" ) != 0 &&
|
||||
book.compareToIgnoreCase( "router" ) != 0 ) &&
|
||||
book.compareToIgnoreCase( "published" ) != 0 )
|
||||
book.compareToIgnoreCase( "router" ) != 0 &&
|
||||
book.compareToIgnoreCase( "private" ) != 0 &&
|
||||
book.compareToIgnoreCase( "published" ) != 0 ))
|
||||
book = "master";
|
||||
|
||||
return book;
|
||||
@ -130,10 +135,63 @@ public class AddressbookBean
|
||||
public void setSerial(String serial) {
|
||||
this.serial = serial;
|
||||
}
|
||||
/** Load addressbook and apply filter, returning messages about this. */
|
||||
public String getLoadBookMessages()
|
||||
{
|
||||
// Config and addressbook now loaded here, hence not needed in getMessages()
|
||||
loadConfig();
|
||||
addressbook = new Properties();
|
||||
|
||||
String message = "";
|
||||
|
||||
try {
|
||||
addressbook.load( new FileInputStream( getFileName() ) );
|
||||
LinkedList list = new LinkedList();
|
||||
Enumeration e = addressbook.keys();
|
||||
while( e.hasMoreElements() ) {
|
||||
String name = (String)e.nextElement();
|
||||
String destination = addressbook.getProperty( name );
|
||||
if( filter != null && filter.length() > 0 ) {
|
||||
if( filter.compareTo( "0-9" ) == 0 ) {
|
||||
char first = name.charAt(0);
|
||||
if( first < '0' || first > '9' )
|
||||
continue;
|
||||
}
|
||||
else if( ! name.toLowerCase().startsWith( filter.toLowerCase() ) ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if( search != null && search.length() > 0 ) {
|
||||
if( name.indexOf( search ) == -1 ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
list.addLast( new AddressBean( name, destination ) );
|
||||
}
|
||||
// Format a message about filtered addressbook size, and the number of displayed entries
|
||||
if( filter != null && filter.length() > 0 )
|
||||
message = "Filtered l";
|
||||
else
|
||||
message = "L";
|
||||
message += "ist contains " + list.size() + " entries";
|
||||
if (list.size() > 300) message += ", displaying the first 300."; else message += ".";
|
||||
|
||||
Object array[] = list.toArray();
|
||||
Arrays.sort( array, sorter );
|
||||
entries = array;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Debug.debug( e.getClass().getName() + ": " + e.getMessage() );
|
||||
}
|
||||
|
||||
if( message.length() > 0 )
|
||||
message = "<p>" + message + "</p>";
|
||||
return message;
|
||||
}
|
||||
/** Perform actions, returning messages about this. */
|
||||
public String getMessages()
|
||||
{
|
||||
loadConfig();
|
||||
|
||||
// Loading config and addressbook moved into getLoadBookMessages()
|
||||
String message = "";
|
||||
|
||||
if( action != null ) {
|
||||
@ -175,42 +233,7 @@ public class AddressbookBean
|
||||
}
|
||||
|
||||
action = null;
|
||||
|
||||
addressbook = new Properties();
|
||||
|
||||
try {
|
||||
addressbook.load( new FileInputStream( getFileName() ) );
|
||||
LinkedList list = new LinkedList();
|
||||
Enumeration e = addressbook.keys();
|
||||
while( e.hasMoreElements() ) {
|
||||
String name = (String)e.nextElement();
|
||||
String destination = addressbook.getProperty( name );
|
||||
if( filter != null && filter.length() > 0 ) {
|
||||
if( filter.compareTo( "0-9" ) == 0 ) {
|
||||
char first = name.charAt(0);
|
||||
if( first < '0' || first > '9' )
|
||||
continue;
|
||||
}
|
||||
else if( ! name.toLowerCase().startsWith( filter.toLowerCase() ) ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if( search != null && search.length() > 0 ) {
|
||||
if( name.indexOf( search ) == -1 ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
list.addLast( new AddressBean( name, destination ) );
|
||||
}
|
||||
|
||||
Object array[] = list.toArray();
|
||||
Arrays.sort( array, sorter );
|
||||
entries = array;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Debug.debug( e.getClass().getName() + ": " + e.getMessage() );
|
||||
}
|
||||
|
||||
if( message.length() > 0 )
|
||||
message = "<p class=\"messages\">" + message + "</p>";
|
||||
return message;
|
||||
@ -234,6 +257,14 @@ public class AddressbookBean
|
||||
{
|
||||
return getBook().compareToIgnoreCase( "router" ) == 0;
|
||||
}
|
||||
public boolean isPublished()
|
||||
{
|
||||
return getBook().compareToIgnoreCase( "published" ) == 0;
|
||||
}
|
||||
public boolean isPrivate()
|
||||
{
|
||||
return getBook().compareToIgnoreCase( "private" ) == 0;
|
||||
}
|
||||
public void setFilter(String filter) {
|
||||
if( filter != null && ( filter.length() == 0 || filter.compareToIgnoreCase( "none" ) == 0 ) ) {
|
||||
filter = null;
|
||||
|
@ -19,14 +19,14 @@
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.4 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
|
||||
package i2p.susi.dns;
|
||||
|
||||
public class VersionBean {
|
||||
|
||||
private static String version = "0.4";
|
||||
private static String version = "0.5";
|
||||
private static String url = "http://susi.i2p/?i2paddresshelper=T2DU1KAz3meB0B53U8Y06-I7vHR7XmC0qXAJfLW6b-1L1FVKoySRZz4xazHAwyv2xtRpvKrv6ukLm1tThEW0zQWtZPtX8G6KkzMibD8t7IS~4yw-9VkBtUydyYfsX08AK3v~-egSW8HCXTdyIJVtrETJb337VDUHW-7D4L1JLbwSH4if2ooks6yFTrljK5aVMi-16dZOVvmoyJc3jBqSdK6kraO4gW5-vHTmbLwL498p9nug1KOg1DqgN2GeU5X1QlVrlpFb~IIfdP~O8NT7u-LAjW3jSJsMbLDHMSYTIhC7xmJIiBoi-qk8p6TLynAmvJ7HRvbx4N1EB-uJHyD16wsZkkHyEOfmXbj0ZqLyKEGb3thPwCz-M9v~c2Qt3WbwjXJAtHpjlHkdJ4Fg91cX2oak~JoapnPf6Syw8hko5syf6VVoCYLnrrYyM8oGl8mLclHkj~VCidQNqMSM74IhrHfK6HmRikqtZBexb5M6wfMTTqBvaHURdD21GOpFKYBUAAAA";
|
||||
|
||||
public String getVersion() {
|
||||
|
@ -20,7 +20,7 @@
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.1 $
|
||||
* $Revision: 1.3 $
|
||||
*/
|
||||
%>
|
||||
<%@ page contentType="text/html"%>
|
||||
@ -48,9 +48,11 @@
|
||||
<p>addressbooks
|
||||
<a href="addressbook.jsp?book=master">master</a> |
|
||||
<a href="addressbook.jsp?book=router">router</a> |
|
||||
<a href="addressbook.jsp?book=published">published</a> *
|
||||
<a href="addressbook.jsp?book=published">published</a> |
|
||||
<a href="addressbook.jsp?book=private">private</a> *
|
||||
<a href="subscriptions.jsp">subscriptions</a> *
|
||||
<a href="config.jsp">configuration</a>
|
||||
<a href="config.jsp">configuration</a> *
|
||||
<a href="index.jsp">overview</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -60,6 +62,8 @@
|
||||
|
||||
<div id="messages">${book.messages}</div>
|
||||
|
||||
<span>${book.loadBookMessages}</span>
|
||||
|
||||
<div id="filter">
|
||||
<p>Filter: <a href="addressbook.jsp?filter=a">a</a>
|
||||
<a href="addressbook.jsp?filter=b">b</a>
|
||||
@ -90,7 +94,8 @@
|
||||
<a href="addressbook.jsp?filter=0-9">0-9</a>
|
||||
<a href="addressbook.jsp?filter=none">all</a></p>
|
||||
<c:if test="${book.hasFilter}">
|
||||
<p>Current filter: ${book.filter}</p>
|
||||
<p>Current filter: ${book.filter}
|
||||
(<a href="addressbook.jsp?filter=none">clear filter</a>)</p>
|
||||
</c:if>
|
||||
</div>
|
||||
|
||||
@ -115,16 +120,17 @@
|
||||
<table class="book" cellspacing="0" cellpadding="5">
|
||||
<tr class="head">
|
||||
|
||||
<c:if test="${book.master || book.router}">
|
||||
<c:if test="${book.master || book.router || book.published || book.private}">
|
||||
<th> </th>
|
||||
</c:if>
|
||||
|
||||
<th>Name</th>
|
||||
<th>Destination</th>
|
||||
</tr>
|
||||
<c:forEach items="${book.entries}" var="addr">
|
||||
<!-- limit iterator to 300, or "Form too large" may result on submit -->
|
||||
<c:forEach items="${book.entries}" var="addr" begin="0" end="299">
|
||||
<tr class="list${book.trClass}">
|
||||
<c:if test="${book.master || book.router}">
|
||||
<c:if test="${book.master || book.router || book.published || book.private}">
|
||||
<td class="checkbox"><input type="checkbox" name="checked" value="${addr.name}" alt="Mark for deletion"></td>
|
||||
</c:if>
|
||||
<td class="names"><a href="http://${addr.name}/">${addr.name}</a> -
|
||||
@ -136,7 +142,7 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<c:if test="${book.master || book.router}">
|
||||
<c:if test="${book.master || book.router || book.published || book.private}">
|
||||
<div id="buttons">
|
||||
<p class="buttons"><input type="image" name="action" value="delete" src="images/delete.png" alt="Delete checked" />
|
||||
</p>
|
||||
|
@ -20,7 +20,7 @@
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.14 $
|
||||
* $Revision: 1.1 $
|
||||
*/
|
||||
%>
|
||||
<%@ page contentType="text/html" %>
|
||||
@ -43,9 +43,11 @@
|
||||
addressbooks
|
||||
<a href="addressbook.jsp?book=master">master</a> |
|
||||
<a href="addressbook.jsp?book=router">router</a> |
|
||||
<a href="addressbook.jsp?book=published">published</a> *
|
||||
<a href="addressbook.jsp?book=published">published</a> |
|
||||
<a href="addressbook.jsp?book=private">private</a> *
|
||||
<a href="subscriptions.jsp">subscriptions</a> *
|
||||
<a href="config.jsp">configuration</a>
|
||||
configuration *
|
||||
<a href="index.jsp">overview</a>
|
||||
</p>
|
||||
</div>
|
||||
<div id="headline">
|
||||
@ -65,22 +67,22 @@ addressbooks
|
||||
<div id="help">
|
||||
<h3>Hints</h3>
|
||||
<ol>
|
||||
<li>All file or directory paths here are relative to the addressbooks working directory, which normally
|
||||
<li>All file or directory paths here are relative to the addressbook's working directory, which normally
|
||||
is located at $I2P/addressbook/.</li>
|
||||
<li>If you want to manually add lines to an addressbook, add them to the master addressbook. The router
|
||||
<li>If you want to manually add lines to an addressbook, add them to the private or master addressbooks. The router
|
||||
addressbook and the published addressbook are overwritten by the addressbook application.</li>
|
||||
<li><b>Important:</b>When you publish your addressbook, <b>ALL</b> destinations appear there, even those
|
||||
from your master addressbook. Unfortunately the master addressbook points to your userhosts.txt, which was
|
||||
used for private destinations before. So if you want to keep the destinations in your userhosts.txt secret,
|
||||
please change the master addressbook to a different file before turning on addressbook publishing.</li>
|
||||
<li><b>Important:</b>When you publish your addressbook, <b>ALL</b> destinations from the master and router addressbooks appear there.
|
||||
Use the private addressbook for private destinations, these are not published.
|
||||
</li>
|
||||
</ol>
|
||||
<h3>Options</h3>
|
||||
<ul>
|
||||
<li><b>subscriptions</b> - file containing the list of subscriptions URLs (no need to change)</li>
|
||||
<li><b>update_delay</b> - update interval in hours (no need to change)</li>
|
||||
<li><b>published_addressbook</b> - your public hosts.txt file (choose a path within your webserver document root)</li>
|
||||
<li><b>router_addressbook</b> - your hosts.txt (no need to change)</li>
|
||||
<li><b>master_addressbook</b> - your personal addressbook, it gets never overwritten by the addressbook</li>
|
||||
<li><b>router_addressbook</b> - your hosts.txt (don't change)</li>
|
||||
<li><b>master_addressbook</b> - your personal addressbook, it never gets overwritten by the addressbook (don't change)</li>
|
||||
<li><b>private_addressbook</b> - your private addressbook, it is never published (defaults to ../privatehosts.txt, don't change)</li>
|
||||
<li><b>proxy_port</b> - http port for your eepProxy (no need to change)</li>
|
||||
<li><b>proxy_host</b> - hostname for your eepProxy (no need to change)</li>
|
||||
<li><b>should_publish</b> - true/false whether to write the published addressbook</li>
|
||||
@ -93,4 +95,4 @@ please change the master addressbook to a different file before turning on addre
|
||||
<p class="footer">susidns v${version.version} © <a href="${version.url}">susi</a> 2005 </p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@ -20,7 +20,7 @@
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.1 $
|
||||
* $Revision: 1.2 $
|
||||
*/
|
||||
%>
|
||||
<%@ page contentType="text/html"%>
|
||||
@ -42,9 +42,11 @@
|
||||
<p>addressbooks
|
||||
<a href="addressbook.jsp?book=master">master</a> |
|
||||
<a href="addressbook.jsp?book=router">router</a> |
|
||||
<a href="addressbook.jsp?book=published">published</a> *
|
||||
<a href="addressbook.jsp?book=published">published</a> |
|
||||
<a href="addressbook.jsp?book=private">private</a> *
|
||||
<a href="subscriptions.jsp">subscriptions</a> *
|
||||
<a href="config.jsp">configuration</a>
|
||||
<a href="config.jsp">configuration</a> *
|
||||
overview
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -52,23 +54,25 @@
|
||||
<h3>Huh? what addressbook?</h3>
|
||||
<p>
|
||||
The addressbook application is part of your i2p installation. It regularly updates your hosts.txt file
|
||||
from distributed sources. It keeps your hosts.txt up to date, so it automatically contains all new
|
||||
eepsites announced on <a href="http://orion.i2p">orion</a>
|
||||
or in the <a href="http://forum.i2p/viewforum.php?f=16">forum</a>.
|
||||
from distributed sources. It keeps your hosts.txt up to date, so it can automatically add
|
||||
eepsites announced on other sites if you subscribe to those sites' addressbooks.
|
||||
</p>
|
||||
<p>
|
||||
(To speak the truth: In its default configuration the addressbook does not poll
|
||||
orion, but dev.i2p only. Subscribing to <a href="http://orion.i2p">orion</a> is an easy task,
|
||||
just add <a href="http://orion.i2p/hosts.txt">http://orion.i2p/hosts.txt</a> to your <a href="subscriptions.jsp">subscriptions</a> file.)
|
||||
additional sites, but www.i2p2.i2p only. Subscribing to additional sites is an easy task,
|
||||
just add them to your <a href="subscriptions.jsp">subscriptions</a> file.)
|
||||
</p>
|
||||
<p>If you have questions about naming in i2p, there is an excellent <a href="http://forum.i2p.net/viewtopic.php?t=134">introduction</a>
|
||||
from duck in the forum.</p>
|
||||
<h3>How does the addressbook work?</h3>
|
||||
<p>The addressbook application regularly (normally once per hour) polls your subscriptions and merges their content
|
||||
into your so called router addressbook (normally your plain hosts.txt). Then it merges your so called master addressbook (normally
|
||||
your userhosts.txt) into the router addressbook as well. If configured the router addressbook is now written to the so published addressbook,
|
||||
which is a publicly available copy of your hosts.txt somewhere in your eepsites document root. (Yes, this means that, with activated publication,
|
||||
your once private keys from userhosts.txt now are publicly available for everybody.)
|
||||
into your so-called router addressbook (normally your plain hosts.txt). Then it merges your so-called master addressbook (normally
|
||||
your userhosts.txt) into the router addressbook as well. If configured, the router addressbook is now written to the published addressbook,
|
||||
which is a publicly available copy of your hosts.txt somewhere in your eepsite's document root.
|
||||
</p><p>
|
||||
The router also uses a private addressbook (privatehosts.txt, not shown in the picture), which is not merged or published.
|
||||
Hosts in the private addressbook can be accessed by you but their addresses are never distributed to others.
|
||||
The private addressbook can also be used for aliases of hosts in your other addressbooks.
|
||||
</p>
|
||||
<p><img src="images/how.png" border="0" alt="addressbook working scheme"/></p>
|
||||
</div>
|
||||
|
@ -20,7 +20,7 @@
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* $Revision: 1.7 $
|
||||
* $Revision: 1.2 $
|
||||
*/
|
||||
%>
|
||||
<%@ page contentType="text/html"%>
|
||||
@ -42,9 +42,11 @@
|
||||
<p>addressbooks
|
||||
<a href="addressbook.jsp?book=master">master</a> |
|
||||
<a href="addressbook.jsp?book=router">router</a> |
|
||||
<a href="addressbook.jsp?book=published">published</a> *
|
||||
<a href="subscriptions.jsp">subscriptions</a> *
|
||||
<a href="config.jsp">configuration</a>
|
||||
<a href="addressbook.jsp?book=published">published</a> |
|
||||
<a href="addressbook.jsp?book=private">private</a> *
|
||||
subscriptions *
|
||||
<a href="config.jsp">configuration</a> *
|
||||
<a href="index.jsp">overview</a>
|
||||
</p>
|
||||
</div>
|
||||
<div id="headline">
|
||||
@ -66,9 +68,8 @@
|
||||
<p class="help">
|
||||
The subscription file contains a list of (i2p) URLs. The addressbook application
|
||||
regularly (once per hour) checks this list for new eepsites. Those URLs simply contain the published hosts.txt
|
||||
file of other people. Default subscription is the hosts.txt from dev.i2p. The most
|
||||
popular collaboration site for eepsite is orion.i2p. So its a good idea to add http://orion.i2p/hosts.txt
|
||||
as a 2nd subscription.
|
||||
file of other people. The default subscription is the hosts.txt from www.i2p2.i2p, which is updated infrequently.
|
||||
So it is a good idea to add additional subscriptions to sites that have the latest addresses.
|
||||
</p>
|
||||
</div>
|
||||
<div id="footer">
|
||||
|
@ -28,10 +28,13 @@ public class UpdaterServlet extends GenericServlet {
|
||||
super.init(config);
|
||||
} catch (ServletException exp) {
|
||||
}
|
||||
/*
|
||||
UpdaterThread thread = new UpdaterThread();
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
System.out.println("INFO: Starting Syndie Updater " + Updater.VERSION);
|
||||
*/
|
||||
System.out.println("INFO: Syndie Updater DISABLED. Use the new Syndie from http://syndie.i2p.net/");
|
||||
}
|
||||
|
||||
}
|
@ -9,6 +9,11 @@
|
||||
<a href="blogs.jsp">blogs</a></p>
|
||||
<p><a href="post.jsp">Create</a> a new post of your own</p>
|
||||
<p><a href="about.html">Learn more</a> about Syndie</p>
|
||||
<p><b>NOTE:</b> This version of Syndie is being replaced by
|
||||
<a href="http://syndie.i2p.net">the new Syndie</a>!
|
||||
The new Syndie is a standalone application under active development.
|
||||
Please give the new Syndie a try, as it has lots more traffic
|
||||
than this version. Don't expect anybody to see your posts here.</p>
|
||||
</td></tr></table>
|
||||
</div>
|
||||
</body></html>
|
||||
|
31
build.xml
31
build.xml
@ -7,6 +7,7 @@
|
||||
<echo message=" installer: build the GUI installer" />
|
||||
<echo message=" tarball: tar the full install into i2p.tar.bz2 (extracts to build a new clean install)" />
|
||||
<echo message=" updater: tar the built i2p specific files into an i2pupdate.zip (extracts safely over existing installs)" />
|
||||
<echo message=" updaterWithJetty: tar the built i2p specific files and jetty into an i2pupdate.zip (extracts safely over existing installs)" />
|
||||
<echo message=" distclean: clean up all derived files" />
|
||||
<echo message=" syndie: generate a standalone syndie install" />
|
||||
<echo message=" i2psnark: generate a standalone i2psnark install" />
|
||||
@ -29,7 +30,7 @@
|
||||
<ant dir="apps/addressbook/" target="war" />
|
||||
<ant dir="apps/susimail/" target="war" />
|
||||
<ant dir="apps/susidns/src" target="all" />
|
||||
<ant dir="apps/syndie/java/" target="jar" />
|
||||
<!-- <ant dir="apps/syndie/java/" target="jar" /> -->
|
||||
<ant dir="apps/i2psnark/java/" target="standalone" />
|
||||
</target>
|
||||
<target name="buildrouter">
|
||||
@ -102,9 +103,11 @@
|
||||
<copy file="apps/addressbook/dist/addressbook.war" todir="build/" />
|
||||
<copy file="apps/susimail/susimail.war" todir="build/" />
|
||||
<copy file="apps/susidns/src/susidns.war" todir="build/" />
|
||||
<!--
|
||||
<copy file="apps/syndie/syndie.war" todir="build/" />
|
||||
<copy file="apps/syndie/java/build/syndie.jar" todir="build/" />
|
||||
<copy file="apps/syndie/java/build/sucker.jar" todir="build/" />
|
||||
-->
|
||||
<copy file="apps/i2psnark/i2psnark.war" todir="build/" />
|
||||
<copy file="apps/i2psnark/java/build/i2psnark.jar" todir="build/" />
|
||||
<copy file="apps/jdom/jdom.jar" todir="build/" />
|
||||
@ -135,6 +138,9 @@
|
||||
<pathelement location="apps/i2ptunnel/java/src" />
|
||||
<pathelement location="apps/systray/java/src" />
|
||||
<pathelement location="apps/routerconsole/java/src" />
|
||||
<pathelement location="apps/addressbook/java/src" />
|
||||
<pathelement location="apps/i2psnark/java/src" />
|
||||
<pathelement location="apps/sam/java/src" />
|
||||
</sourcepath>
|
||||
<classpath>
|
||||
<pathelement location="apps/jetty/jettylib/org.mortbay.jetty.jar" />
|
||||
@ -194,7 +200,7 @@
|
||||
<copy file="build/routerconsole.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/sam.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/systray.jar" todir="pkg-temp/lib" />
|
||||
<copy file="build/sucker.jar" todir="pkg-temp/lib" />
|
||||
<!-- <copy file="build/sucker.jar" todir="pkg-temp/lib" /> -->
|
||||
<copy file="build/i2psnark.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="i2p.exe" todir="pkg-temp/" failonerror="false" />
|
||||
<copy file="installer/resources/runplain.sh" todir="pkg-temp/" />
|
||||
@ -208,7 +214,7 @@
|
||||
<copy file="build/addressbook.war" todir="pkg-temp/webapps/" />
|
||||
<copy file="build/susimail.war" todir="pkg-temp/webapps/" />
|
||||
<copy file="build/susidns.war" todir="pkg-temp/webapps/" />
|
||||
<copy file="build/syndie.war" todir="pkg-temp/webapps/" />
|
||||
<!-- <copy file="build/syndie.war" todir="pkg-temp/webapps/" /> -->
|
||||
<copy file="build/i2psnark.war" todir="pkg-temp/webapps/" />
|
||||
<copy file="apps/i2psnark/java/build/launch-i2psnark.jar" todir="pkg-temp/" />
|
||||
<copy file="apps/i2psnark/jetty-i2psnark.xml" todir="pkg-temp/" />
|
||||
@ -278,11 +284,13 @@
|
||||
<copy file="installer/resources/eepsite_index.html" tofile="pkg-temp/eepsite/docroot/index.html" />
|
||||
<copy file="installer/resources/favicon.ico" tofile="pkg-temp/eepsite/docroot/favicon.ico" />
|
||||
<copy file="installer/resources/jetty.xml" tofile="pkg-temp/eepsite/jetty.xml" />
|
||||
<!--
|
||||
<mkdir dir="pkg-temp/syndie" />
|
||||
<mkdir dir="pkg-temp/syndie/archive" />
|
||||
<mkdir dir="pkg-temp/syndie/archive/ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=" />
|
||||
<copy file="installer/resources/blogMeta.snm" tofile="pkg-temp/syndie/archive/ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=/meta.snm" />
|
||||
<copy file="installer/resources/blogPost.snd" tofile="pkg-temp/syndie/archive/ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=/1132012800001.snd" />
|
||||
-->
|
||||
</target>
|
||||
<target name="tarball" depends="preppkg">
|
||||
<tar compression="bzip2" destfile="i2p.tar.bz2">
|
||||
@ -292,6 +300,9 @@
|
||||
<target name="updater" depends="prepupdate">
|
||||
<zip destfile="i2pupdate.zip" basedir="pkg-temp" />
|
||||
</target>
|
||||
<target name="updaterWithJetty" depends="prepjupdate">
|
||||
<zip destfile="i2pupdate.zip" basedir="pkg-temp" />
|
||||
</target>
|
||||
<target name="updateTest" depends="prepupdate">
|
||||
<ant dir="core/java/" target="jarTest" />
|
||||
<copy file="core/java/build/i2ptest.jar" todir="pkg-temp/lib" />
|
||||
@ -316,7 +327,7 @@
|
||||
<copy file="build/addressbook.war" todir="pkg-temp/webapps/" />
|
||||
<copy file="build/susimail.war" todir="pkg-temp/webapps/" />
|
||||
<copy file="build/susidns.war" todir="pkg-temp/webapps/" />
|
||||
<copy file="build/syndie.war" todir="pkg-temp/webapps/" />
|
||||
<!-- <copy file="build/syndie.war" todir="pkg-temp/webapps/" /> -->
|
||||
<copy file="build/i2psnark.war" todir="pkg-temp/webapps/" />
|
||||
<!-- <copy file="apps/i2psnark/java/build/launch-i2psnark.jar" todir="pkg-temp/" /> -->
|
||||
<copy file="apps/i2psnark/jetty-i2psnark.xml" todir="pkg-temp/" />
|
||||
@ -347,11 +358,23 @@
|
||||
<mkdir dir="pkg-temp/eepsite" />
|
||||
<mkdir dir="pkg-temp/eepsite/webapps" />
|
||||
<mkdir dir="pkg-temp/eepsite/cgi-bin" />
|
||||
<!--
|
||||
<mkdir dir="pkg-temp/syndie" />
|
||||
<mkdir dir="pkg-temp/syndie/archive" />
|
||||
<mkdir dir="pkg-temp/syndie/archive/ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=" />
|
||||
<copy file="installer/resources/blogMeta.snm" tofile="pkg-temp/syndie/archive/ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=/meta.snm" />
|
||||
<copy file="installer/resources/blogPost.snd" tofile="pkg-temp/syndie/archive/ovpBy2mpO1CQ7deYhQ1cDGAwI6pQzLbWOm1Sdd0W06c=/1132012800001.snd" />
|
||||
-->
|
||||
</target>
|
||||
<target name="prepjupdate" depends="prepupdate, buildWEB">
|
||||
<copy file="build/ant.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/jasper-compiler.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/jasper-runtime.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/commons-logging.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/commons-el.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/javax.servlet.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/org.mortbay.jetty.jar" todir="pkg-temp/lib/" />
|
||||
<copy file="build/xercesImpl.jar" todir="pkg-temp/lib/" />
|
||||
</target>
|
||||
<target name="installer" depends="preppkg">
|
||||
<taskdef name="izpack" classpath="${basedir}/installer/lib/izpack/standalone-compiler.jar" classname="com.izforge.izpack.ant.IzPackTask" />
|
||||
|
52
checklist.txt
Normal file
52
checklist.txt
Normal file
@ -0,0 +1,52 @@
|
||||
Release checklist
|
||||
-----------------
|
||||
|
||||
Sync with mtn.i2p2.i2p
|
||||
Start with a clean checkout mtn -d i2p.mtn co --branch=i2p.i2p
|
||||
|
||||
Change revision in:
|
||||
history.txt
|
||||
initialNews.xml
|
||||
installer/install.xml
|
||||
news.xml
|
||||
router/java/src/net/i2p/router/RouterVersion.java
|
||||
|
||||
Build and tag:
|
||||
ant dist
|
||||
mtn ci
|
||||
mtn tag i2p-0.6.1.xx
|
||||
Sync with mtn.i2p2.i2p
|
||||
|
||||
Create a signed update file with:
|
||||
export I2P=~/i2p
|
||||
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate sign i2pupdate.zip i2pupdate.sud /path/to/private.key 0.6.1.xx
|
||||
|
||||
Verify signed update file with:
|
||||
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate showversion i2pupdate.sud
|
||||
java -cp $I2P/lib/i2p.jar net.i2p.crypto.TrustedUpdate verifysig i2pupdate.sud
|
||||
|
||||
Make the source tarball:
|
||||
Start with a clean checkout mtn -d i2p.mtn co --branch=i2p.i2p i2p-0.6.1.xx
|
||||
tar tjf i2p-0.6.1.xx.tar.bz2 --exclude i2p-0.6.1.xx/_MTN i2p-0.6.1.xx
|
||||
mv i2p-0.6.1.xx.tar.bz2 i2p.i2p
|
||||
|
||||
More signatures:
|
||||
sha1sum i2pinstall.exe i2p.tar.bz2 i2p-0.6.1.xx.tar.bz2 i2pupdate.zip
|
||||
gpg -b i2pinstall.exe
|
||||
gpg -b i2p.tar.bz2
|
||||
gpg -b i2p-0.6.1.xx.tar.bz2
|
||||
gpg -b i2pupdate.zip
|
||||
|
||||
Distribute files to download locations and to www.i2p2.i2p
|
||||
|
||||
Website files to change:
|
||||
Sync with mtn.i2p2.i2p
|
||||
announcements.html
|
||||
download.html (change SHA1s)
|
||||
index.html
|
||||
hosts.txt (copy from mtn)
|
||||
Sync with mtn.i2p2.i2p
|
||||
|
||||
Copy news.xml to subscription location
|
||||
|
||||
Announce on #i2p, forum.i2p, Syndie
|
@ -32,7 +32,7 @@ public class CPUID {
|
||||
* initialization? this would otherwise use the Log component, but this makes
|
||||
* it easier for other systems to reuse this class
|
||||
*/
|
||||
private static final boolean _doLog = true;
|
||||
private static final boolean _doLog = System.getProperty("jcpuid.dontLog") == null;
|
||||
|
||||
//.matches() is a java 1.4+ addition, using a simplified version for 1.3+
|
||||
//private static final boolean isX86 = System.getProperty("os.arch").toLowerCase().matches("i?[x0-9]86(_64)?");
|
||||
|
@ -1,7 +1,7 @@
|
||||
package gnu.crypto.hash;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// $Id: BaseHash.java,v 1.10 2005/10/06 04:24:14 rsdio Exp $
|
||||
// $Id: BaseHashStandalone.java,v 1.1 2006/02/26 16:30:59 jrandom Exp $
|
||||
//
|
||||
// Copyright (C) 2001, 2002, Free Software Foundation, Inc.
|
||||
//
|
||||
@ -46,9 +46,9 @@ package gnu.crypto.hash;
|
||||
/**
|
||||
* <p>A base abstract class to facilitate hash implementations.</p>
|
||||
*
|
||||
* @version $Revision: 1.10 $
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public abstract class BaseHash implements IMessageDigest {
|
||||
public abstract class BaseHashStandalone implements IMessageDigestStandalone {
|
||||
|
||||
// Constants and variables
|
||||
// -------------------------------------------------------------------------
|
||||
@ -78,7 +78,7 @@ public abstract class BaseHash implements IMessageDigest {
|
||||
* @param hashSize the block size of the output in bytes.
|
||||
* @param blockSize the block size of the internal transform.
|
||||
*/
|
||||
protected BaseHash(String name, int hashSize, int blockSize) {
|
||||
protected BaseHashStandalone(String name, int hashSize, int blockSize) {
|
||||
super();
|
||||
|
||||
this.name = name;
|
||||
@ -95,7 +95,7 @@ public abstract class BaseHash implements IMessageDigest {
|
||||
// Instance methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// IMessageDigest interface implementation ---------------------------------
|
||||
// IMessageDigestStandalone interface implementation ---------------------------------
|
||||
|
||||
public String name() {
|
||||
return name;
|
@ -1,7 +1,7 @@
|
||||
package gnu.crypto.hash;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// $Id: IMessageDigest.java,v 1.11 2005/10/06 04:24:14 rsdio Exp $
|
||||
// $Id: IMessageDigestStandalone.java,v 1.1 2006/02/26 16:30:59 jrandom Exp $
|
||||
//
|
||||
// Copyright (C) 2001, 2002, Free Software Foundation, Inc.
|
||||
//
|
||||
@ -49,9 +49,9 @@ package gnu.crypto.hash;
|
||||
* <p>A hash (or message digest) algorithm produces its output by iterating a
|
||||
* basic compression function on blocks of data.</p>
|
||||
*
|
||||
* @version $Revision: 1.11 $
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public interface IMessageDigest extends Cloneable {
|
||||
public interface IMessageDigestStandalone extends Cloneable {
|
||||
|
||||
// Constants
|
||||
// -------------------------------------------------------------------------
|
@ -1,7 +1,7 @@
|
||||
package gnu.crypto.hash;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// $Id: Sha256Standalone.java,v 1.1 2006/02/26 16:30:59 jrandom Exp $
|
||||
// $Id: Sha256Standalone.java,v 1.2 2006/03/16 16:45:19 jrandom Exp $
|
||||
//
|
||||
// Copyright (C) 2003 Free Software Foundation, Inc.
|
||||
//
|
||||
@ -59,9 +59,9 @@ package gnu.crypto.hash;
|
||||
* renamed from Sha256 to avoid conflicts with JVMs using gnu-crypto as their JCE
|
||||
* provider.
|
||||
*
|
||||
* @version $Revision: 1.1 $
|
||||
* @version $Revision: 1.2 $
|
||||
*/
|
||||
public class Sha256Standalone extends BaseHash {
|
||||
public class Sha256Standalone extends BaseHashStandalone {
|
||||
// Constants and variables
|
||||
// -------------------------------------------------------------------------
|
||||
private static final int[] k = {
|
||||
@ -143,7 +143,7 @@ public class Sha256Standalone extends BaseHash {
|
||||
return new Sha256Standalone(this);
|
||||
}
|
||||
|
||||
// Implementation of concrete methods in BaseHash --------------------------
|
||||
// Implementation of concrete methods in BaseHashStandalone --------------------------
|
||||
|
||||
private int transformResult[] = new int[8];
|
||||
protected void transform(byte[] in, int offset) {
|
||||
|
175
core/java/src/gnu/crypto/prng/AsyncFortunaStandalone.java
Normal file
175
core/java/src/gnu/crypto/prng/AsyncFortunaStandalone.java
Normal file
@ -0,0 +1,175 @@
|
||||
package gnu.crypto.prng;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* fortuna instance that tries to avoid blocking if at all possible by using separate
|
||||
* filled buffer segments rather than one buffer (and blocking when that buffer's data
|
||||
* has been eaten)
|
||||
*/
|
||||
public class AsyncFortunaStandalone extends FortunaStandalone implements Runnable {
|
||||
private static final int BUFFERS = 16;
|
||||
private static final int BUFSIZE = 256*1024;
|
||||
private final byte asyncBuffers[][] = new byte[BUFFERS][BUFSIZE];
|
||||
private final int status[] = new int[BUFFERS];
|
||||
private int nextBuf = 0;
|
||||
|
||||
private static final int STATUS_NEED_FILL = 0;
|
||||
private static final int STATUS_FILLING = 1;
|
||||
private static final int STATUS_FILLED = 2;
|
||||
private static final int STATUS_LIVE = 3;
|
||||
|
||||
public AsyncFortunaStandalone() {
|
||||
super();
|
||||
for (int i = 0; i < BUFFERS; i++)
|
||||
status[i] = STATUS_NEED_FILL;
|
||||
}
|
||||
|
||||
public void startup() {
|
||||
Thread refillThread = new Thread(this, "PRNG");
|
||||
refillThread.setDaemon(true);
|
||||
refillThread.setPriority(Thread.MIN_PRIORITY+1);
|
||||
refillThread.start();
|
||||
}
|
||||
|
||||
/** the seed is only propogated once the prng is started with startup() */
|
||||
public void seed(byte val[]) {
|
||||
Map props = new HashMap(1);
|
||||
props.put(SEED, (Object)val);
|
||||
init(props);
|
||||
//fillBlock();
|
||||
}
|
||||
|
||||
protected void allocBuffer() {}
|
||||
|
||||
/**
|
||||
* make the next available filled buffer current, scheduling any unfilled
|
||||
* buffers for refill, and blocking until at least one buffer is ready
|
||||
*/
|
||||
protected void rotateBuffer() {
|
||||
synchronized (asyncBuffers) {
|
||||
// wait until we get some filled
|
||||
long before = System.currentTimeMillis();
|
||||
long waited = 0;
|
||||
while (status[nextBuf] != STATUS_FILLED) {
|
||||
//System.out.println(Thread.currentThread().getName() + ": Next PRNG buffer "
|
||||
// + nextBuf + " isn't ready (" + status[nextBuf] + ")");
|
||||
//new Exception("source").printStackTrace();
|
||||
asyncBuffers.notifyAll();
|
||||
try {
|
||||
asyncBuffers.wait();
|
||||
} catch (InterruptedException ie) {}
|
||||
waited = System.currentTimeMillis()-before;
|
||||
}
|
||||
if (waited > 10*1000)
|
||||
System.out.println(Thread.currentThread().getName() + ": Took " + waited
|
||||
+ "ms for a full PRNG buffer to be found");
|
||||
//System.out.println(Thread.currentThread().getName() + ": Switching to prng buffer " + nextBuf);
|
||||
buffer = asyncBuffers[nextBuf];
|
||||
status[nextBuf] = STATUS_LIVE;
|
||||
int prev=nextBuf-1;
|
||||
if (prev<0)
|
||||
prev = BUFFERS-1;
|
||||
if (status[prev] == STATUS_LIVE)
|
||||
status[prev] = STATUS_NEED_FILL;
|
||||
nextBuf++;
|
||||
if (nextBuf >= BUFFERS)
|
||||
nextBuf = 0;
|
||||
asyncBuffers.notify();
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
while (true) {
|
||||
int toFill = -1;
|
||||
try {
|
||||
synchronized (asyncBuffers) {
|
||||
for (int i = 0; i < BUFFERS; i++) {
|
||||
if (status[i] == STATUS_NEED_FILL) {
|
||||
status[i] = STATUS_FILLING;
|
||||
toFill = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (toFill == -1) {
|
||||
//System.out.println(Thread.currentThread().getName() + ": All pending buffers full");
|
||||
asyncBuffers.wait();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ie) {}
|
||||
|
||||
if (toFill != -1) {
|
||||
//System.out.println(Thread.currentThread().getName() + ": Filling prng buffer " + toFill);
|
||||
long before = System.currentTimeMillis();
|
||||
doFill(asyncBuffers[toFill]);
|
||||
long after = System.currentTimeMillis();
|
||||
synchronized (asyncBuffers) {
|
||||
status[toFill] = STATUS_FILLED;
|
||||
//System.out.println(Thread.currentThread().getName() + ": Prng buffer " + toFill + " filled after " + (after-before));
|
||||
asyncBuffers.notifyAll();
|
||||
}
|
||||
Thread.yield();
|
||||
long waitTime = (after-before)*5;
|
||||
if (waitTime <= 0) // somehow postman saw waitTime show up as negative
|
||||
waitTime = 50;
|
||||
try { Thread.sleep(waitTime); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void fillBlock()
|
||||
{
|
||||
rotateBuffer();
|
||||
}
|
||||
|
||||
private void doFill(byte buf[]) {
|
||||
long start = System.currentTimeMillis();
|
||||
if (pool0Count >= MIN_POOL_SIZE
|
||||
&& System.currentTimeMillis() - lastReseed > 100)
|
||||
{
|
||||
reseedCount++;
|
||||
//byte[] seed = new byte[0];
|
||||
for (int i = 0; i < NUM_POOLS; i++)
|
||||
{
|
||||
if (reseedCount % (1 << i) == 0) {
|
||||
generator.addRandomBytes(pools[i].digest());
|
||||
}
|
||||
}
|
||||
lastReseed = System.currentTimeMillis();
|
||||
}
|
||||
generator.nextBytes(buf);
|
||||
long now = System.currentTimeMillis();
|
||||
long diff = now-lastRefill;
|
||||
lastRefill = now;
|
||||
long refillTime = now-start;
|
||||
//System.out.println("Refilling " + (++refillCount) + " after " + diff + " for the PRNG took " + refillTime);
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
try {
|
||||
AsyncFortunaStandalone rand = new AsyncFortunaStandalone();
|
||||
|
||||
byte seed[] = new byte[1024];
|
||||
rand.seed(seed);
|
||||
System.out.println("Before starting prng");
|
||||
rand.startup();
|
||||
System.out.println("Starting prng, waiting 1 minute");
|
||||
try { Thread.sleep(60*1000); } catch (InterruptedException ie) {}
|
||||
System.out.println("PRNG started, beginning test");
|
||||
|
||||
long before = System.currentTimeMillis();
|
||||
byte buf[] = new byte[1024];
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
java.util.zip.GZIPOutputStream gos = new java.util.zip.GZIPOutputStream(baos);
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
rand.nextBytes(buf);
|
||||
gos.write(buf);
|
||||
}
|
||||
long after = System.currentTimeMillis();
|
||||
gos.finish();
|
||||
byte compressed[] = baos.toByteArray();
|
||||
System.out.println("Compressed size of 1MB: " + compressed.length + " took " + (after-before));
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
try { Thread.sleep(5*60*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
@ -49,7 +49,7 @@ import java.util.Map;
|
||||
* Modified slightly by jrandom for I2P (removing unneeded exceptions)
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public abstract class BasePRNGStandalone implements IRandom {
|
||||
public abstract class BasePRNGStandalone implements IRandomStandalone {
|
||||
|
||||
// Constants and variables
|
||||
// -------------------------------------------------------------------------
|
||||
@ -88,7 +88,7 @@ public abstract class BasePRNGStandalone implements IRandom {
|
||||
// Instance methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// IRandom interface implementation ----------------------------------------
|
||||
// IRandomStandalone interface implementation ----------------------------------------
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
|
@ -97,20 +97,22 @@ import net.i2p.crypto.CryptixAESKeyCache;
|
||||
* gnu-crypto implementation, which has been imported into GNU/classpath
|
||||
*
|
||||
*/
|
||||
public class FortunaStandalone extends BasePRNGStandalone implements Serializable, RandomEventListener
|
||||
public class FortunaStandalone extends BasePRNGStandalone implements Serializable, RandomEventListenerStandalone
|
||||
{
|
||||
|
||||
private static final long serialVersionUID = 0xFACADE;
|
||||
|
||||
private static final int SEED_FILE_SIZE = 64;
|
||||
private static final int NUM_POOLS = 32;
|
||||
private static final int MIN_POOL_SIZE = 64;
|
||||
private final Generator generator;
|
||||
private final Sha256Standalone[] pools;
|
||||
private long lastReseed;
|
||||
private int pool;
|
||||
private int pool0Count;
|
||||
private int reseedCount;
|
||||
static final int NUM_POOLS = 32;
|
||||
static final int MIN_POOL_SIZE = 64;
|
||||
final Generator generator;
|
||||
final Sha256Standalone[] pools;
|
||||
long lastReseed;
|
||||
int pool;
|
||||
int pool0Count;
|
||||
int reseedCount;
|
||||
static long refillCount = 0;
|
||||
static long lastRefill = System.currentTimeMillis();
|
||||
|
||||
public static final String SEED = "gnu.crypto.prng.fortuna.seed";
|
||||
|
||||
@ -124,6 +126,9 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl
|
||||
lastReseed = 0;
|
||||
pool = 0;
|
||||
pool0Count = 0;
|
||||
allocBuffer();
|
||||
}
|
||||
protected void allocBuffer() {
|
||||
buffer = new byte[4*1024*1024]; //256]; // larger buffer to reduce churn
|
||||
}
|
||||
|
||||
@ -145,6 +150,7 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl
|
||||
|
||||
public void fillBlock()
|
||||
{
|
||||
long start = System.currentTimeMillis();
|
||||
if (pool0Count >= MIN_POOL_SIZE
|
||||
&& System.currentTimeMillis() - lastReseed > 100)
|
||||
{
|
||||
@ -159,6 +165,11 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl
|
||||
lastReseed = System.currentTimeMillis();
|
||||
}
|
||||
generator.nextBytes(buffer);
|
||||
long now = System.currentTimeMillis();
|
||||
long diff = now-lastRefill;
|
||||
lastRefill = now;
|
||||
long refillTime = now-start;
|
||||
System.out.println("Refilling " + (++refillCount) + " after " + diff + " for the PRNG took " + refillTime);
|
||||
}
|
||||
|
||||
public void addRandomByte(byte b)
|
||||
@ -177,7 +188,7 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl
|
||||
pool = (pool + 1) % NUM_POOLS;
|
||||
}
|
||||
|
||||
public void addRandomEvent(RandomEvent event)
|
||||
public void addRandomEvent(RandomEventStandalone event)
|
||||
{
|
||||
if (event.getPoolNumber() < 0 || event.getPoolNumber() >= pools.length)
|
||||
throw new IllegalArgumentException("pool number out of range: "
|
||||
@ -338,6 +349,34 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
byte in[] = new byte[16];
|
||||
byte out[] = new byte[16];
|
||||
byte key[] = new byte[32];
|
||||
try {
|
||||
CryptixAESKeyCache.KeyCacheEntry buf = CryptixAESKeyCache.createNew();
|
||||
Object cryptixKey = CryptixRijndael_Algorithm.makeKey(key, 16, buf);
|
||||
long beforeAll = System.currentTimeMillis();
|
||||
for (int i = 0; i < 256; i++) {
|
||||
//long before =System.currentTimeMillis();
|
||||
for (int j = 0; j < 1024; j++)
|
||||
CryptixRijndael_Algorithm.blockEncrypt(in, out, 0, 0, cryptixKey);
|
||||
//long after = System.currentTimeMillis();
|
||||
//System.out.println("encrypting 16KB took " + (after-before));
|
||||
}
|
||||
long after = System.currentTimeMillis();
|
||||
System.out.println("encrypting 4MB took " + (after-beforeAll));
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
|
||||
try {
|
||||
CryptixAESKeyCache.KeyCacheEntry buf = CryptixAESKeyCache.createNew();
|
||||
Object cryptixKey = CryptixRijndael_Algorithm.makeKey(key, 16, buf);
|
||||
byte data[] = new byte[4*1024*1024];
|
||||
long beforeAll = System.currentTimeMillis();
|
||||
//CryptixRijndael_Algorithm.ecbBulkEncrypt(data, data, cryptixKey);
|
||||
long after = System.currentTimeMillis();
|
||||
System.out.println("encrypting 4MB took " + (after-beforeAll));
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
/*
|
||||
FortunaStandalone f = new FortunaStandalone();
|
||||
java.util.HashMap props = new java.util.HashMap();
|
||||
byte initSeed[] = new byte[1234];
|
||||
@ -351,5 +390,6 @@ public class FortunaStandalone extends BasePRNGStandalone implements Serializabl
|
||||
}
|
||||
long time = System.currentTimeMillis() - before;
|
||||
System.out.println("512MB took " + time + ", or " + (8*64d)/((double)time/1000d) +"MBps");
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package gnu.crypto.prng;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// $Id: IRandom.java,v 1.12 2005/10/06 04:24:17 rsdio Exp $
|
||||
// $Id: IRandomStandalone.java,v 1.1 2005/10/22 13:10:00 jrandom Exp $
|
||||
//
|
||||
// Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc.
|
||||
//
|
||||
@ -82,9 +82,9 @@ import java.util.Map;
|
||||
* Menezes, A., van Oorschot, P. and S. Vanstone.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @version $Revision: 1.12 $
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public interface IRandom extends Cloneable {
|
||||
public interface IRandomStandalone extends Cloneable {
|
||||
|
||||
// Constants
|
||||
// -------------------------------------------------------------------------
|
||||
@ -110,32 +110,32 @@ public interface IRandom extends Cloneable {
|
||||
void init(Map attributes);
|
||||
|
||||
/**
|
||||
* <p>Returns the next 8 bits of random data generated from this instance.</p>
|
||||
*
|
||||
* @return the next 8 bits of random data generated from this instance.
|
||||
* @exception IllegalStateException if the instance is not yet initialised.
|
||||
* @exception LimitReachedException if this instance has reached its
|
||||
* theoretical limit for generating non-repetitive pseudo-random data.
|
||||
*/
|
||||
byte nextByte() throws IllegalStateException, LimitReachedException;
|
||||
* <p>Returns the next 8 bits of random data generated from this instance.</p>
|
||||
*
|
||||
* @return the next 8 bits of random data generated from this instance.
|
||||
* @exception IllegalStateException if the instance is not yet initialised.
|
||||
* @exception LimLimitReachedExceptionStandalone this instance has reached its
|
||||
* theoretical limit for generating non-repetitive pseudo-random data.
|
||||
*/
|
||||
byte nextByte() throws IllegalStateException, LimitReachedExceptionStandalone;
|
||||
|
||||
/**
|
||||
* <p>Fills the designated byte array, starting from byte at index
|
||||
* <code>offset</code>, for a maximum of <code>length</code> bytes with the
|
||||
* output of this generator instance.
|
||||
*
|
||||
* @param out the placeholder to contain the generated random bytes.
|
||||
* @param offset the starting index in <i>out</i> to consider. This method
|
||||
* does nothing if this parameter is not within <code>0</code> and
|
||||
* <code>out.length</code>.
|
||||
* @param length the maximum number of required random bytes. This method
|
||||
* does nothing if this parameter is less than <code>1</code>.
|
||||
* @exception IllegalStateException if the instance is not yet initialised.
|
||||
* @exception LimitReachedException if this instance has reached its
|
||||
* theoretical limit for generating non-repetitive pseudo-random data.
|
||||
*/
|
||||
* <p>Fills the designated byte array, starting from byte at index
|
||||
* <code>offset</code>, for a maximum of <code>length</code> bytes with the
|
||||
* output of this generator instance.
|
||||
*
|
||||
* @param out the placeholder to contain the generated random bytes.
|
||||
* @param offset the starting index in <i>out</i> to consider. This method
|
||||
* does nothing if this parameter is not within <code>0</code> and
|
||||
* <code>out.length</code>.
|
||||
* @param length the maximum number of required random bytes. This method
|
||||
* does nothing if this parameter is less than <code>1</code>.
|
||||
* @exception IllegalStateException if the instance is not yet initialised.
|
||||
* @exception LimitLimitReachedExceptionStandalonehis instance has reached its
|
||||
* theoretical limit for generating non-repetitive pseudo-random data.
|
||||
*/
|
||||
void nextBytes(byte[] out, int offset, int length)
|
||||
throws IllegalStateException, LimitReachedException;
|
||||
throws IllegalStateException, LimitReachedExceptionStandalone;
|
||||
|
||||
/**
|
||||
* <p>Supplement, or possibly replace, the random state of this PRNG with
|
@ -1,7 +1,7 @@
|
||||
package gnu.crypto.prng;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// $Id: LimitReachedException.java,v 1.5 2005/10/06 04:24:17 rsdio Exp $
|
||||
// $Id: LimitReachedExceptionStandalone.java,v 1.1 2005/10/22 13:10:00 jrandom Exp $
|
||||
//
|
||||
// Copyright (C) 2001, 2002, Free Software Foundation, Inc.
|
||||
//
|
||||
@ -47,9 +47,9 @@ package gnu.crypto.prng;
|
||||
* A checked exception that indicates that a pseudo random number generated has
|
||||
* reached its theoretical limit in generating random bytes.
|
||||
*
|
||||
* @version $Revision: 1.5 $
|
||||
* @version $Revision: 1.1 $
|
||||
*/
|
||||
public class LimitReachedException extends Exception {
|
||||
public class LimitReachedExceptionStandalone extends Exception {
|
||||
|
||||
// Constants and variables
|
||||
// -------------------------------------------------------------------------
|
||||
@ -57,11 +57,11 @@ public class LimitReachedException extends Exception {
|
||||
// Constructor(s)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public LimitReachedException() {
|
||||
public LimitReachedExceptionStandalone() {
|
||||
super();
|
||||
}
|
||||
|
||||
public LimitReachedException(String msg) {
|
||||
public LimitReachedExceptionStandalone(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* RandomEventListener.java -- event listener
|
||||
/* RandomEventListenerStandalone.java -- event listener
|
||||
Copyright (C) 2004 Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU Crypto.
|
||||
@ -47,7 +47,7 @@ import java.util.EventListener;
|
||||
* An interface for entropy accumulators that will be notified of random
|
||||
* events.
|
||||
*/
|
||||
public interface RandomEventListener extends EventListener
|
||||
public interface RandomEventListenerStandalone extends EventListener
|
||||
{
|
||||
void addRandomEvent(RandomEvent event);
|
||||
void addRandomEvent(RandomEventStandalone event);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
/* RandomEvent.java -- a random event.
|
||||
/* RandomEventStandalone.java -- a random event.
|
||||
Copyright (C) 2004 Free Software Foundation, Inc.
|
||||
|
||||
This file is part of GNU Crypto.
|
||||
@ -47,14 +47,14 @@ import java.util.EventObject;
|
||||
* An interface for entropy accumulators that will be notified of random
|
||||
* events.
|
||||
*/
|
||||
public class RandomEvent extends EventObject
|
||||
public class RandomEventStandalone extends EventObject
|
||||
{
|
||||
|
||||
private final byte sourceNumber;
|
||||
private final byte poolNumber;
|
||||
private final byte[] data;
|
||||
|
||||
public RandomEvent(Object source, byte sourceNumber, byte poolNumber,
|
||||
public RandomEventStandalone(Object source, byte sourceNumber, byte poolNumber,
|
||||
byte[] data)
|
||||
{
|
||||
super(source);
|
@ -14,8 +14,8 @@ package net.i2p;
|
||||
*
|
||||
*/
|
||||
public class CoreVersion {
|
||||
public final static String ID = "$Revision: 1.63 $ $Date: 2006-06-04 17:25:14 $";
|
||||
public final static String VERSION = "0.6.1.21";
|
||||
public final static String ID = "$Revision: 1.72 $ $Date: 2007-08-23 19:33:31 $";
|
||||
public final static String VERSION = "0.6.1.30";
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println("I2P Core version: " + VERSION);
|
||||
|
@ -14,6 +14,7 @@ import net.i2p.crypto.DummyElGamalEngine;
|
||||
import net.i2p.crypto.DummyPooledRandomSource;
|
||||
import net.i2p.crypto.ElGamalAESEngine;
|
||||
import net.i2p.crypto.ElGamalEngine;
|
||||
import net.i2p.crypto.HMAC256Generator;
|
||||
import net.i2p.crypto.HMACGenerator;
|
||||
import net.i2p.crypto.KeyGenerator;
|
||||
import net.i2p.crypto.PersistentSessionKeyManager;
|
||||
@ -67,8 +68,9 @@ public class I2PAppContext {
|
||||
private AESEngine _AESEngine;
|
||||
private LogManager _logManager;
|
||||
private HMACGenerator _hmac;
|
||||
private HMAC256Generator _hmac256;
|
||||
private SHA256Generator _sha;
|
||||
private Clock _clock;
|
||||
protected Clock _clock; // overridden in RouterContext
|
||||
private DSAEngine _dsa;
|
||||
private RoutingKeyGenerator _routingKeyGenerator;
|
||||
private RandomSource _random;
|
||||
@ -82,8 +84,9 @@ public class I2PAppContext {
|
||||
private volatile boolean _AESEngineInitialized;
|
||||
private volatile boolean _logManagerInitialized;
|
||||
private volatile boolean _hmacInitialized;
|
||||
private volatile boolean _hmac256Initialized;
|
||||
private volatile boolean _shaInitialized;
|
||||
private volatile boolean _clockInitialized;
|
||||
protected volatile boolean _clockInitialized; // used in RouterContext
|
||||
private volatile boolean _dsaInitialized;
|
||||
private volatile boolean _routingKeyGeneratorInitialized;
|
||||
private volatile boolean _randomInitialized;
|
||||
@ -353,6 +356,19 @@ public class I2PAppContext {
|
||||
_hmacInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
public HMAC256Generator hmac256() {
|
||||
if (!_hmac256Initialized) initializeHMAC256();
|
||||
return _hmac256;
|
||||
}
|
||||
private void initializeHMAC256() {
|
||||
synchronized (this) {
|
||||
if (_hmac256 == null) {
|
||||
_hmac256 = new HMAC256Generator(this);
|
||||
}
|
||||
_hmac256Initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Our SHA256 instance (see the hmac discussion for why its context specific)
|
||||
@ -411,11 +427,11 @@ public class I2PAppContext {
|
||||
* enable simulators to play with clock skew among different instances.
|
||||
*
|
||||
*/
|
||||
public Clock clock() {
|
||||
public Clock clock() { // overridden in RouterContext
|
||||
if (!_clockInitialized) initializeClock();
|
||||
return _clock;
|
||||
}
|
||||
private void initializeClock() {
|
||||
protected void initializeClock() { // overridden in RouterContext
|
||||
synchronized (this) {
|
||||
if (_clock == null)
|
||||
_clock = new Clock(this);
|
||||
|
@ -320,9 +320,9 @@ public class DHSessionKeyBuilder {
|
||||
if (_myPrivateValue == null) generateMyValue();
|
||||
_sessionKey = calculateSessionKey(_myPrivateValue, _peerValue);
|
||||
} else {
|
||||
System.err.println("Not ready yet.. privateValue and peerValue must be set ("
|
||||
+ (_myPrivateValue != null ? "set" : "null") + ","
|
||||
+ (_peerValue != null ? "set" : "null") + ")");
|
||||
//System.err.println("Not ready yet.. privateValue and peerValue must be set ("
|
||||
// + (_myPrivateValue != null ? "set" : "null") + ","
|
||||
// + (_peerValue != null ? "set" : "null") + ")");
|
||||
}
|
||||
return _sessionKey;
|
||||
}
|
||||
|
51
core/java/src/net/i2p/crypto/HMAC256Generator.java
Normal file
51
core/java/src/net/i2p/crypto/HMAC256Generator.java
Normal file
@ -0,0 +1,51 @@
|
||||
package net.i2p.crypto;
|
||||
|
||||
import gnu.crypto.hash.Sha256Standalone;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.SessionKey;
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.macs.HMac;
|
||||
|
||||
/**
|
||||
* Calculate the HMAC-SHA256 of a key+message. All the good stuff occurs
|
||||
* in {@link org.bouncycastle.crypto.macs.HMac} and
|
||||
* {@link org.bouncycastle.crypto.digests.MD5Digest}.
|
||||
*
|
||||
*/
|
||||
public class HMAC256Generator extends HMACGenerator {
|
||||
public HMAC256Generator(I2PAppContext context) { super(context); }
|
||||
|
||||
protected HMac acquire() {
|
||||
synchronized (_available) {
|
||||
if (_available.size() > 0)
|
||||
return (HMac)_available.remove(0);
|
||||
}
|
||||
// the HMAC is hardcoded to use SHA256 digest size
|
||||
// for backwards compatability. next time we have a backwards
|
||||
// incompatible change, we should update this by removing ", 32"
|
||||
return new HMac(new Sha256ForMAC());
|
||||
}
|
||||
|
||||
private class Sha256ForMAC extends Sha256Standalone implements Digest {
|
||||
public String getAlgorithmName() { return "sha256 for hmac"; }
|
||||
public int getDigestSize() { return 32; }
|
||||
public int doFinal(byte[] out, int outOff) {
|
||||
byte rv[] = digest();
|
||||
System.arraycopy(rv, 0, out, outOff, rv.length);
|
||||
reset();
|
||||
return rv.length;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
byte data[] = new byte[64];
|
||||
ctx.random().nextBytes(data);
|
||||
SessionKey key = ctx.keyGenerator().generateSessionKey();
|
||||
Hash mac = ctx.hmac256().calculate(key, data);
|
||||
System.out.println(Base64.encode(mac.getData()));
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ import org.bouncycastle.crypto.macs.HMac;
|
||||
public class HMACGenerator {
|
||||
private I2PAppContext _context;
|
||||
/** set of available HMAC instances for calculate */
|
||||
private List _available;
|
||||
protected List _available;
|
||||
/** set of available byte[] buffers for verify */
|
||||
private List _availableTmp;
|
||||
|
||||
@ -85,7 +85,7 @@ public class HMACGenerator {
|
||||
return eq;
|
||||
}
|
||||
|
||||
private HMac acquire() {
|
||||
protected HMac acquire() {
|
||||
synchronized (_available) {
|
||||
if (_available.size() > 0)
|
||||
return (HMac)_available.remove(0);
|
||||
|
@ -9,10 +9,13 @@ package net.i2p.crypto;
|
||||
*
|
||||
*/
|
||||
|
||||
import gnu.crypto.hash.Sha256Standalone;
|
||||
import java.math.BigInteger;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SessionKey;
|
||||
@ -53,6 +56,18 @@ public class KeyGenerator {
|
||||
return key;
|
||||
}
|
||||
|
||||
private static final int PBE_ROUNDS = 1000;
|
||||
/** PBE the passphrase with the salt */
|
||||
public SessionKey generateSessionKey(byte salt[], byte passphrase[]) {
|
||||
byte salted[] = new byte[16+passphrase.length];
|
||||
System.arraycopy(salt, 0, salted, 0, Math.min(salt.length, 16));
|
||||
System.arraycopy(passphrase, 0, salted, 16, passphrase.length);
|
||||
byte h[] = _context.sha().calculateHash(salted).getData();
|
||||
for (int i = 1; i < PBE_ROUNDS; i++)
|
||||
_context.sha().calculateHash(h, 0, Hash.HASH_LENGTH, h, 0);
|
||||
return new SessionKey(h);
|
||||
}
|
||||
|
||||
/** standard exponent size */
|
||||
private static final int PUBKEY_EXPONENT_SIZE_FULL = 2048;
|
||||
/**
|
||||
@ -95,7 +110,7 @@ public class KeyGenerator {
|
||||
* @return the corresponding PublicKey object
|
||||
*/
|
||||
public static PublicKey getPublicKey(PrivateKey priv) {
|
||||
BigInteger a = new NativeBigInteger(priv.toByteArray());
|
||||
BigInteger a = new NativeBigInteger(1, priv.toByteArray());
|
||||
BigInteger aalpha = CryptoConstants.elgg.modPow(a, CryptoConstants.elgp);
|
||||
PublicKey pub = new PublicKey();
|
||||
byte [] pubBytes = aalpha.toByteArray();
|
||||
@ -132,7 +147,7 @@ public class KeyGenerator {
|
||||
* @return a SigningPublicKey object
|
||||
*/
|
||||
public static SigningPublicKey getSigningPublicKey(SigningPrivateKey priv) {
|
||||
BigInteger x = new NativeBigInteger(priv.toByteArray());
|
||||
BigInteger x = new NativeBigInteger(1, priv.toByteArray());
|
||||
BigInteger y = CryptoConstants.dsag.modPow(x, CryptoConstants.dsap);
|
||||
SigningPublicKey pub = new SigningPublicKey();
|
||||
byte [] pubBytes = padBuffer(y.toByteArray(), SigningPublicKey.KEYSIZE_BYTES);
|
||||
|
@ -67,7 +67,7 @@ class TransientSessionKeyManager extends SessionKeyManager {
|
||||
_log = context.logManager().getLog(TransientSessionKeyManager.class);
|
||||
_context = context;
|
||||
_outboundSessions = new HashMap(1024);
|
||||
_inboundTagSets = new HashMap(64*1024);
|
||||
_inboundTagSets = new HashMap(1024);
|
||||
context.statManager().createRateStat("crypto.sessionTagsExpired", "How many tags/sessions are expired?", "Encryption", new long[] { 10*60*1000, 60*60*1000, 3*60*60*1000 });
|
||||
context.statManager().createRateStat("crypto.sessionTagsRemaining", "How many tags/sessions are remaining after a cleanup?", "Encryption", new long[] { 10*60*1000, 60*60*1000, 3*60*60*1000 });
|
||||
SimpleTimer.getInstance().addEvent(new CleanupEvent(), 60*1000);
|
||||
|
@ -60,6 +60,45 @@ jP69nPbh4KLGhF+SD0+0bW4=
|
||||
=npPe
|
||||
-----END PGP SIGNATURE-----
|
||||
*/
|
||||
/*
|
||||
-----BEGIN PGP SIGNED MESSAGE-----
|
||||
Hash: SHA1
|
||||
|
||||
*/
|
||||
/* zzz's key */
|
||||
private static final String DEFAULT_TRUSTED_KEY2 =
|
||||
"lT54eq3SH0TWWwQ1wgH6XPelIno7wH7UfiZOpQg-ZuxdNhc4UjjrohKdK" +
|
||||
"Zqfswt1ANPnmOlMewLGBESl7kJB9c5sByz~IOlNyz5BMLRC~R~ZC9QI4W" +
|
||||
"XwUBYW8BhYO2mkvtdOrcy690lDkwzdf5xLxlCBpQlTaLYzQVjVWBcvbCA=";
|
||||
/*
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
Version: GnuPG v1.4.6 (GNU/Linux)
|
||||
|
||||
iD8DBQFHdupcQVV2uqduC+0RAocuAKCR4ILLuz3RB8QT7zkadmS2LmFuMwCgweqG
|
||||
lFm5Fqx/iW5+k0QaQZ3W9mY=
|
||||
=V3i7
|
||||
-----END PGP SIGNATURE-----
|
||||
*/
|
||||
/*
|
||||
-----BEGIN PGP SIGNED MESSAGE-----
|
||||
Hash: SHA1
|
||||
|
||||
*/
|
||||
/* Complication's key */
|
||||
private static final String DEFAULT_TRUSTED_KEY3 =
|
||||
"JHFA0yXUgKtmhajXFZH9Nk62OPRHbvvQHTi8EANV-D~3tjLjaz9p9cs6F" +
|
||||
"s8W3FSLfUwsQeFg7dfVSQQZga~1jMjboo94vIcm3j6XbW4mbcorVQ74uP" +
|
||||
"jd8EA1AQhJ6bBTxDAFk~6fVDOdhHT0Wo5CcUn7v8bAYY3x3UWiL8Remx0=";
|
||||
/*
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
Version: GnuPG v1.4.6 (GNU/Linux)
|
||||
|
||||
iD8DBQFHphOV+h38a3n8zjMRAll+AJ9KA6WiDJcTN4qfrslSemUMr+FBrwCeM8pF
|
||||
D8usM7Dxp5yrDrCYZ5AIijc=
|
||||
=SrXI
|
||||
-----END PGP SIGNATURE-----
|
||||
*/
|
||||
|
||||
private static final String VALID_VERSION_CHARS = "0123456789.";
|
||||
private static final int VERSION_BYTES = 16;
|
||||
private static final int HEADER_BYTES = Signature.SIGNATURE_BYTES + VERSION_BYTES;
|
||||
@ -92,14 +131,18 @@ jP69nPbh4KLGhF+SD0+0bW4=
|
||||
String propertyTrustedKeys = context.getProperty(PROP_TRUSTED_KEYS);
|
||||
|
||||
if ( (propertyTrustedKeys != null) && (propertyTrustedKeys.length() > 0) ) {
|
||||
StringTokenizer propertyTrustedKeysTokens = new StringTokenizer(propertyTrustedKeys, ",");
|
||||
StringTokenizer propertyTrustedKeysTokens = new StringTokenizer(propertyTrustedKeys, " ,\r\n");
|
||||
|
||||
while (propertyTrustedKeysTokens.hasMoreTokens())
|
||||
_trustedKeys.add(propertyTrustedKeysTokens.nextToken().trim());
|
||||
|
||||
} else {
|
||||
_trustedKeys.add(DEFAULT_TRUSTED_KEY);
|
||||
_trustedKeys.add(DEFAULT_TRUSTED_KEY2);
|
||||
_trustedKeys.add(DEFAULT_TRUSTED_KEY3);
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("TrustedUpdate created, trusting " + _trustedKeys.size() + " keys.");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -180,6 +223,7 @@ jP69nPbh4KLGhF+SD0+0bW4=
|
||||
private static final void genKeysCLI(String publicKeyFile, String privateKeyFile) {
|
||||
FileOutputStream fileOutputStream = null;
|
||||
|
||||
_context = I2PAppContext.getGlobalContext();
|
||||
try {
|
||||
Object signingKeypair[] = _context.keyGenerator().generateSigningKeypair();
|
||||
SigningPublicKey signingPublicKey = (SigningPublicKey) signingKeypair[0];
|
||||
@ -273,7 +317,26 @@ jP69nPbh4KLGhF+SD0+0bW4=
|
||||
public ArrayList getTrustedKeys() {
|
||||
return _trustedKeys;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetches the trusted keys for the current instance.
|
||||
*
|
||||
* @return A <code>String</code> containing the trusted keys,
|
||||
* delimited by CR LF line breaks.
|
||||
*/
|
||||
public String getTrustedKeysString() {
|
||||
StringBuffer buf = new StringBuffer(1024);
|
||||
for (int i = 0; i < _trustedKeys.size(); i++) {
|
||||
// If something already buffered, first add line break.
|
||||
if (buf.length() > 0) buf.append("\r\n");
|
||||
buf.append((String) _trustedKeys.get(i));
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads the version string from a signed update file.
|
||||
*
|
||||
|
@ -9,6 +9,7 @@ package net.i2p.data;
|
||||
*
|
||||
*/
|
||||
|
||||
import gnu.crypto.hash.Sha256Standalone;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayInputStream;
|
||||
@ -235,6 +236,10 @@ public class DataHelper {
|
||||
if (split <= 0) continue;
|
||||
String key = line.substring(0, split);
|
||||
String val = line.substring(split+1);
|
||||
// Unescape line breaks after loading.
|
||||
// Remember: "\" needs escaping both for regex and string.
|
||||
val = val.replaceAll("\\\\r","\r");
|
||||
val = val.replaceAll("\\\\n","\n");
|
||||
if ( (key.length() > 0) && (val.length() > 0) )
|
||||
if (forceLowerCase)
|
||||
props.setProperty(key.toLowerCase(), val);
|
||||
@ -770,9 +775,11 @@ public class DataHelper {
|
||||
* Read a newline delimited line from the stream, returning the line (without
|
||||
* the newline), or null if EOF reached before the newline was found
|
||||
*/
|
||||
public static String readLine(InputStream in) throws IOException {
|
||||
public static String readLine(InputStream in) throws IOException { return readLine(in, (Sha256Standalone)null); }
|
||||
/** update the hash along the way */
|
||||
public static String readLine(InputStream in, Sha256Standalone hash) throws IOException {
|
||||
StringBuffer buf = new StringBuffer(128);
|
||||
boolean ok = readLine(in, buf);
|
||||
boolean ok = readLine(in, buf, hash);
|
||||
if (ok)
|
||||
return buf.toString();
|
||||
else
|
||||
@ -785,8 +792,13 @@ public class DataHelper {
|
||||
* newline was found
|
||||
*/
|
||||
public static boolean readLine(InputStream in, StringBuffer buf) throws IOException {
|
||||
return readLine(in, buf, null);
|
||||
}
|
||||
/** update the hash along the way */
|
||||
public static boolean readLine(InputStream in, StringBuffer buf, Sha256Standalone hash) throws IOException {
|
||||
int c = -1;
|
||||
while ( (c = in.read()) != -1) {
|
||||
if (hash != null) hash.update((byte)c);
|
||||
if (c == '\n')
|
||||
break;
|
||||
buf.append((char)c);
|
||||
@ -797,6 +809,10 @@ public class DataHelper {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void write(OutputStream out, byte data[], Sha256Standalone hash) throws IOException {
|
||||
hash.update(data);
|
||||
out.write(data);
|
||||
}
|
||||
|
||||
public static List sortStructures(Collection dataStructures) {
|
||||
if (dataStructures == null) return new ArrayList();
|
||||
@ -822,6 +838,8 @@ public class DataHelper {
|
||||
return (ms / (60 * 1000)) + "m";
|
||||
} else if (ms < 3 * 24 * 60 * 60 * 1000) {
|
||||
return (ms / (60 * 60 * 1000)) + "h";
|
||||
} else if (ms > 365l * 24l * 60l * 60l * 1000l) {
|
||||
return "n/a";
|
||||
} else {
|
||||
return (ms / (24 * 60 * 60 * 1000)) + "d";
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ public class PrivateKey extends DataStructureImpl {
|
||||
public PrivateKey() {
|
||||
setData(null);
|
||||
}
|
||||
public PrivateKey(byte data[]) { setData(data); }
|
||||
|
||||
/** constructs from base64
|
||||
* @param base64Data a string of base64 data (the output of .toBase64() called
|
||||
|
@ -31,6 +31,11 @@ public class PublicKey extends DataStructureImpl {
|
||||
public PublicKey() {
|
||||
setData(null);
|
||||
}
|
||||
public PublicKey(byte data[]) {
|
||||
if ( (data == null) || (data.length != KEYSIZE_BYTES) )
|
||||
throw new IllegalArgumentException("Data must be specified, and the correct size");
|
||||
setData(data);
|
||||
}
|
||||
|
||||
/** constructs from base64
|
||||
* @param base64Data a string of base64 data (the output of .toBase64() called
|
||||
|
@ -86,7 +86,7 @@ public class RouterIdentity extends DataStructureImpl {
|
||||
|
||||
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
|
||||
if ((_certificate == null) || (_publicKey == null) || (_signingKey == null))
|
||||
throw new DataFormatException("Not enough data to format the destination");
|
||||
throw new DataFormatException("Not enough data to format the router identity");
|
||||
_publicKey.writeBytes(out);
|
||||
_signingKey.writeBytes(out);
|
||||
_certificate.writeBytes(out);
|
||||
|
@ -53,6 +53,9 @@ public class RouterInfo extends DataStructureImpl {
|
||||
public static final String PROP_CAPABILITIES = "caps";
|
||||
public static final char CAPABILITY_HIDDEN = 'H';
|
||||
|
||||
// Public string of chars which serve as bandwidth capacity markers
|
||||
// NOTE: individual chars defined in Router.java
|
||||
public static final String BW_CAPABILITY_CHARS = "KLMNO";
|
||||
|
||||
public RouterInfo() {
|
||||
setIdentity(null);
|
||||
@ -179,6 +182,12 @@ public class RouterInfo extends DataStructureImpl {
|
||||
return (Properties) _options.clone();
|
||||
}
|
||||
}
|
||||
public String getOption(String opt) {
|
||||
if (_options == null) return null;
|
||||
synchronized (_options) {
|
||||
return _options.getProperty(opt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a set of options or statistics that the router can expose
|
||||
@ -334,6 +343,24 @@ public class RouterInfo extends DataStructureImpl {
|
||||
return (getCapabilities().indexOf(CAPABILITY_HIDDEN) != -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation of this node's bandwidth tier,
|
||||
* or "Unknown"
|
||||
*/
|
||||
public String getBandwidthTier() {
|
||||
String bwTiers = BW_CAPABILITY_CHARS;
|
||||
String bwTier = "Unknown";
|
||||
String capabilities = getCapabilities();
|
||||
// Iterate through capabilities, searching for known bandwidth tier
|
||||
for (int i = 0; i < capabilities.length(); i++) {
|
||||
if (bwTiers.indexOf(String.valueOf(capabilities.charAt(i))) != -1) {
|
||||
bwTier = String.valueOf(capabilities.charAt(i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (bwTier);
|
||||
}
|
||||
|
||||
public void addCapability(char cap) {
|
||||
if (_options == null) _options = new OrderedProperties();
|
||||
synchronized (_options) {
|
||||
|
@ -184,6 +184,7 @@ public class Rate {
|
||||
private static final int SLACK = 2000;
|
||||
public void coalesce() {
|
||||
long now = now();
|
||||
double correctedTotalValue; // for summaryListener which divides by rounded EventCount
|
||||
synchronized (_lock) {
|
||||
long measuredPeriod = now - _lastCoalesceDate;
|
||||
if (measuredPeriod < _period - SLACK) {
|
||||
@ -198,9 +199,14 @@ public class Rate {
|
||||
// how much were we off by? (so that we can sample down the measured values)
|
||||
double periodFactor = measuredPeriod / (double)_period;
|
||||
_lastTotalValue = _currentTotalValue / periodFactor;
|
||||
_lastEventCount = (long) ( (_currentEventCount + periodFactor - 1) / periodFactor);
|
||||
_lastEventCount = (long) (0.499999 + (_currentEventCount / periodFactor));
|
||||
_lastTotalEventTime = (long) (_currentTotalEventTime / periodFactor);
|
||||
_lastCoalesceDate = now;
|
||||
if (_currentEventCount == 0)
|
||||
correctedTotalValue = 0;
|
||||
else
|
||||
correctedTotalValue = _currentTotalValue *
|
||||
(_lastEventCount / (double) _currentEventCount);
|
||||
|
||||
if (_lastTotalValue > _extremeTotalValue) {
|
||||
_extremeTotalValue = _lastTotalValue;
|
||||
@ -213,7 +219,7 @@ public class Rate {
|
||||
_currentTotalEventTime = 0;
|
||||
}
|
||||
if (_summaryListener != null)
|
||||
_summaryListener.add(_lastTotalValue, _lastEventCount, _lastTotalEventTime, _period);
|
||||
_summaryListener.add(correctedTotalValue, _lastEventCount, _lastTotalEventTime, _period);
|
||||
}
|
||||
|
||||
public void setSummaryListener(RateSummaryListener listener) { _summaryListener = listener; }
|
||||
|
@ -43,7 +43,7 @@ public class StatManager {
|
||||
_log = context.logManager().getLog(StatManager.class);
|
||||
_context = context;
|
||||
_frequencyStats = Collections.synchronizedMap(new HashMap(128));
|
||||
_rateStats = Collections.synchronizedMap(new HashMap(128));
|
||||
_rateStats = new HashMap(128); // synchronized only on add //Collections.synchronizedMap(new HashMap(128));
|
||||
_statLog = new BufferedStatLog(context);
|
||||
}
|
||||
|
||||
@ -80,10 +80,12 @@ public class StatManager {
|
||||
* @param periods array of period lengths (in milliseconds)
|
||||
*/
|
||||
public void createRateStat(String name, String description, String group, long periods[]) {
|
||||
if (_rateStats.containsKey(name)) return;
|
||||
RateStat rs = new RateStat(name, description, group, periods);
|
||||
if (_statLog != null) rs.setStatLog(_statLog);
|
||||
_rateStats.put(name, rs);
|
||||
synchronized (_rateStats) {
|
||||
if (_rateStats.containsKey(name)) return;
|
||||
RateStat rs = new RateStat(name, description, group, periods);
|
||||
if (_statLog != null) rs.setStatLog(_statLog);
|
||||
_rateStats.put(name, rs);
|
||||
}
|
||||
}
|
||||
|
||||
/** update the given frequency statistic, taking note that an event occurred (and recalculating all frequencies) */
|
||||
@ -94,7 +96,7 @@ public class StatManager {
|
||||
|
||||
/** update the given rate statistic, taking note that the given data point was received (and recalculating all rates) */
|
||||
public void addRateData(String name, long data, long eventDuration) {
|
||||
RateStat stat = (RateStat) _rateStats.get(name);
|
||||
RateStat stat = (RateStat) _rateStats.get(name); // unsynchronized
|
||||
if (stat != null) stat.addData(data, eventDuration);
|
||||
}
|
||||
|
||||
|
@ -112,11 +112,19 @@ public class NtpClient {
|
||||
|
||||
// Process response
|
||||
NtpMessage msg = new NtpMessage(packet.getData());
|
||||
|
||||
double roundTripDelay = (destinationTimestamp-msg.originateTimestamp) -
|
||||
(msg.receiveTimestamp-msg.transmitTimestamp);
|
||||
double localClockOffset = ((msg.receiveTimestamp - msg.originateTimestamp) +
|
||||
(msg.transmitTimestamp - destinationTimestamp)) / 2;
|
||||
socket.close();
|
||||
|
||||
// Stratum must be between 1 (atomic) and 15 (maximum defined value)
|
||||
// Anything else is right out, treat such responses like errors
|
||||
if ((msg.stratum < 1) || (msg.stratum > 15)) {
|
||||
//System.out.println("Response from NTP server of unacceptable stratum " + msg.stratum + ", failing.");
|
||||
return(-1);
|
||||
}
|
||||
|
||||
long rv = (long)(System.currentTimeMillis() + localClockOffset*1000);
|
||||
//System.out.println("host: " + address.getHostAddress() + " rtt: " + roundTripDelay + " offset: " + localClockOffset + " seconds");
|
||||
|
@ -26,7 +26,7 @@ public class Timestamper implements Runnable {
|
||||
private boolean _initialized;
|
||||
|
||||
private static final int DEFAULT_QUERY_FREQUENCY = 5*60*1000;
|
||||
private static final String DEFAULT_SERVER_LIST = "pool.ntp.org, pool.ntp.org, pool.ntp.org";
|
||||
private static final String DEFAULT_SERVER_LIST = "0.pool.ntp.org, 1.pool.ntp.org, 2.pool.ntp.org";
|
||||
private static final boolean DEFAULT_DISABLED = true;
|
||||
/** how many times do we have to query if we are changing the clock? */
|
||||
private static final int DEFAULT_CONCURRING_SERVERS = 3;
|
||||
|
@ -13,12 +13,16 @@ import net.i2p.time.Timestamper;
|
||||
* between the local computer's current time and the time as known by some reference
|
||||
* (such as an NTP synchronized clock).
|
||||
*
|
||||
* Protected members are used in the subclass RouterClock,
|
||||
* which has access to a router's transports (particularly peer clock skews)
|
||||
* to second-guess the sanity of clock adjustments.
|
||||
*
|
||||
*/
|
||||
public class Clock implements Timestamper.UpdateListener {
|
||||
private I2PAppContext _context;
|
||||
protected I2PAppContext _context;
|
||||
private Timestamper _timestamper;
|
||||
private long _startedOn;
|
||||
private boolean _statCreated;
|
||||
protected long _startedOn;
|
||||
protected boolean _statCreated;
|
||||
|
||||
public Clock(I2PAppContext context) {
|
||||
_context = context;
|
||||
@ -36,10 +40,10 @@ public class Clock implements Timestamper.UpdateListener {
|
||||
public Timestamper getTimestamper() { return _timestamper; }
|
||||
|
||||
/** we fetch it on demand to avoid circular dependencies (logging uses the clock) */
|
||||
private Log getLog() { return _context.logManager().getLog(Clock.class); }
|
||||
protected Log getLog() { return _context.logManager().getLog(Clock.class); }
|
||||
|
||||
private volatile long _offset;
|
||||
private boolean _alreadyChanged;
|
||||
protected volatile long _offset;
|
||||
protected boolean _alreadyChanged;
|
||||
private Set _listeners;
|
||||
|
||||
/** if the clock is skewed by 3+ days, fuck 'em */
|
||||
@ -132,7 +136,7 @@ public class Clock implements Timestamper.UpdateListener {
|
||||
}
|
||||
}
|
||||
|
||||
private void fireOffsetChanged(long delta) {
|
||||
protected void fireOffsetChanged(long delta) {
|
||||
synchronized (_listeners) {
|
||||
for (Iterator iter = _listeners.iterator(); iter.hasNext();) {
|
||||
ClockUpdateListener lsnr = (ClockUpdateListener) iter.next();
|
||||
|
@ -18,6 +18,7 @@ import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.util.SocketTimeout;
|
||||
|
||||
/**
|
||||
* EepGet [-p localhost:4444]
|
||||
@ -33,8 +34,14 @@ public class EepGet {
|
||||
private String _proxyHost;
|
||||
private int _proxyPort;
|
||||
private int _numRetries;
|
||||
private long _minSize; // minimum and maximum acceptable response size, -1 signifies unlimited,
|
||||
private long _maxSize; // applied both against whole responses and chunks
|
||||
private String _outputFile;
|
||||
private OutputStream _outputStream;
|
||||
/** url we were asked to fetch */
|
||||
private String _url;
|
||||
/** the URL we actually fetch from (may differ from the _url in case of redirect) */
|
||||
private String _actualURL;
|
||||
private String _postData;
|
||||
private boolean _allowCaching;
|
||||
private List _listeners;
|
||||
@ -49,9 +56,18 @@ public class EepGet {
|
||||
private long _bytesRemaining;
|
||||
private int _currentAttempt;
|
||||
private String _etag;
|
||||
private String _lastModified;
|
||||
private boolean _encodingChunked;
|
||||
private boolean _notModified;
|
||||
private String _contentType;
|
||||
private boolean _transferFailed;
|
||||
private boolean _headersRead;
|
||||
private boolean _aborted;
|
||||
private long _fetchHeaderTimeout;
|
||||
private long _fetchEndTime;
|
||||
private long _fetchInactivityTimeout;
|
||||
private int _redirects;
|
||||
private String _redirectLocation;
|
||||
|
||||
public EepGet(I2PAppContext ctx, String proxyHost, int proxyPort, int numRetries, String outputFile, String url) {
|
||||
this(ctx, true, proxyHost, proxyPort, numRetries, outputFile, url);
|
||||
@ -69,27 +85,44 @@ public class EepGet {
|
||||
this(ctx, shouldProxy, proxyHost, proxyPort, numRetries, outputFile, url, true, null);
|
||||
}
|
||||
public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, String outputFile, String url, String postData) {
|
||||
this(ctx, shouldProxy, proxyHost, proxyPort, numRetries, outputFile, url, true, null, postData);
|
||||
this(ctx, shouldProxy, proxyHost, proxyPort, numRetries, -1, -1, outputFile, null, url, true, null, postData);
|
||||
}
|
||||
public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, String outputFile, String url, boolean allowCaching, String etag) {
|
||||
this(ctx, shouldProxy, proxyHost, proxyPort, numRetries, outputFile, url, allowCaching, etag, null);
|
||||
this(ctx, shouldProxy, proxyHost, proxyPort, numRetries, -1, -1, outputFile, null, url, allowCaching, etag, null);
|
||||
}
|
||||
public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, String outputFile, String url, boolean allowCaching, String etag, String postData) {
|
||||
public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, String outputFile, String url, boolean allowCaching, String etag, String lastModified) {
|
||||
this(ctx, shouldProxy, proxyHost, proxyPort, numRetries, -1, -1, outputFile, null, url, allowCaching, etag, lastModified, null);
|
||||
}
|
||||
public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, long minSize, long maxSize, String outputFile, OutputStream outputStream, String url, boolean allowCaching, String etag, String postData) {
|
||||
this(ctx, shouldProxy, proxyHost, proxyPort, numRetries, minSize, maxSize, outputFile, outputStream, url, allowCaching, etag, null, postData);
|
||||
}
|
||||
public EepGet(I2PAppContext ctx, boolean shouldProxy, String proxyHost, int proxyPort, int numRetries, long minSize, long maxSize,
|
||||
String outputFile, OutputStream outputStream, String url, boolean allowCaching,
|
||||
String etag, String lastModified, String postData) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(EepGet.class);
|
||||
_shouldProxy = shouldProxy;
|
||||
_shouldProxy = (proxyHost != null) && (proxyHost.length() > 0) && (proxyPort > 0) && shouldProxy;
|
||||
_proxyHost = proxyHost;
|
||||
_proxyPort = proxyPort;
|
||||
_numRetries = numRetries;
|
||||
_outputFile = outputFile;
|
||||
_minSize = minSize;
|
||||
_maxSize = maxSize;
|
||||
_outputFile = outputFile; // if outputFile is set, outputStream must be null
|
||||
_outputStream = outputStream; // if both are set, outputStream overrides outputFile
|
||||
_url = url;
|
||||
_actualURL = url;
|
||||
_postData = postData;
|
||||
_alreadyTransferred = 0;
|
||||
_bytesTransferred = 0;
|
||||
_bytesRemaining = -1;
|
||||
_currentAttempt = 0;
|
||||
_transferFailed = false;
|
||||
_headersRead = false;
|
||||
_aborted = false;
|
||||
_fetchHeaderTimeout = 45*1000;
|
||||
_listeners = new ArrayList(1);
|
||||
_etag = etag;
|
||||
_lastModified = lastModified;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -187,6 +220,7 @@ public class EepGet {
|
||||
public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause);
|
||||
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt);
|
||||
public void headerReceived(String url, int currentAttempt, String key, String val);
|
||||
public void attempting(String url);
|
||||
}
|
||||
private class CLIStatusListener implements StatusListener {
|
||||
private int _markSize;
|
||||
@ -301,6 +335,7 @@ public class EepGet {
|
||||
buf.append("KBps");
|
||||
System.out.println(buf.toString());
|
||||
}
|
||||
public void attempting(String url) {}
|
||||
public void headerReceived(String url, int currentAttempt, String key, String val) {}
|
||||
}
|
||||
|
||||
@ -313,19 +348,52 @@ public class EepGet {
|
||||
* Blocking fetch, returning true if the URL was retrieved, false if all retries failed
|
||||
*
|
||||
*/
|
||||
public boolean fetch() {
|
||||
public boolean fetch() { return fetch(_fetchHeaderTimeout); }
|
||||
/**
|
||||
* Blocking fetch, timing out individual attempts if the HTTP response headers
|
||||
* don't come back in the time given. If the timeout is zero or less, this will
|
||||
* wait indefinitely.
|
||||
*/
|
||||
public boolean fetch(long fetchHeaderTimeout) {
|
||||
return fetch(fetchHeaderTimeout, -1, -1);
|
||||
}
|
||||
public boolean fetch(long fetchHeaderTimeout, long totalTimeout, long inactivityTimeout) {
|
||||
_fetchHeaderTimeout = fetchHeaderTimeout;
|
||||
_fetchEndTime = (totalTimeout > 0 ? System.currentTimeMillis() + totalTimeout : -1);
|
||||
_fetchInactivityTimeout = inactivityTimeout;
|
||||
_keepFetching = true;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Fetching (proxied? " + _shouldProxy + ") url=" + _url);
|
||||
_log.debug("Fetching (proxied? " + _shouldProxy + ") url=" + _actualURL);
|
||||
while (_keepFetching) {
|
||||
SocketTimeout timeout = null;
|
||||
if (_fetchHeaderTimeout > 0)
|
||||
timeout = new SocketTimeout(_fetchHeaderTimeout);
|
||||
final SocketTimeout stimeout = timeout; // ugly
|
||||
timeout.setTimeoutCommand(new Runnable() {
|
||||
public void run() {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("timeout reached on " + _url + ": " + stimeout);
|
||||
_aborted = true;
|
||||
}
|
||||
});
|
||||
timeout.setTotalTimeoutPeriod(_fetchEndTime);
|
||||
try {
|
||||
sendRequest();
|
||||
doFetch();
|
||||
return true;
|
||||
for (int i = 0; i < _listeners.size(); i++)
|
||||
((StatusListener)_listeners.get(i)).attempting(_url);
|
||||
sendRequest(timeout);
|
||||
timeout.resetTimer();
|
||||
doFetch(timeout);
|
||||
timeout.cancel();
|
||||
if (!_transferFailed)
|
||||
return true;
|
||||
break;
|
||||
} catch (IOException ioe) {
|
||||
timeout.cancel();
|
||||
for (int i = 0; i < _listeners.size(); i++)
|
||||
((StatusListener)_listeners.get(i)).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, ioe);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("ERR: doFetch failed " + ioe);
|
||||
} finally {
|
||||
if (_out != null) {
|
||||
try {
|
||||
@ -344,39 +412,122 @@ public class EepGet {
|
||||
_currentAttempt++;
|
||||
if (_currentAttempt > _numRetries)
|
||||
break;
|
||||
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
|
||||
try {
|
||||
long delay = _context.random().nextInt(60*1000);
|
||||
Thread.sleep(5*1000+delay);
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _listeners.size(); i++)
|
||||
((StatusListener)_listeners.get(i)).transferFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("All attempts failed for " + _url);
|
||||
return false;
|
||||
}
|
||||
|
||||
/** return true if the URL was completely retrieved */
|
||||
private void doFetch() throws IOException {
|
||||
readHeaders();
|
||||
private void doFetch(SocketTimeout timeout) throws IOException {
|
||||
_headersRead = false;
|
||||
_aborted = false;
|
||||
try {
|
||||
readHeaders();
|
||||
} finally {
|
||||
_headersRead = true;
|
||||
}
|
||||
if (_aborted)
|
||||
throw new IOException("Timed out reading the HTTP headers");
|
||||
|
||||
timeout.resetTimer();
|
||||
if (_fetchInactivityTimeout > 0)
|
||||
timeout.setInactivityTimeout(_fetchInactivityTimeout);
|
||||
else
|
||||
timeout.setInactivityTimeout(60*1000);
|
||||
|
||||
if (_redirectLocation != null) {
|
||||
try {
|
||||
URL oldURL = new URL(_actualURL);
|
||||
String query = oldURL.getQuery();
|
||||
if (query == null) query = "";
|
||||
if (_redirectLocation.startsWith("http://")) {
|
||||
if ( (_redirectLocation.indexOf('?') < 0) && (query.length() > 0) )
|
||||
_actualURL = _redirectLocation + "?" + query;
|
||||
else
|
||||
_actualURL = _redirectLocation;
|
||||
} else {
|
||||
URL url = new URL(_actualURL);
|
||||
if (_redirectLocation.startsWith("/"))
|
||||
_actualURL = "http://" + url.getHost() + ":" + url.getPort() + _redirectLocation;
|
||||
else
|
||||
_actualURL = "http://" + url.getHost() + ":" + url.getPort() + "/" + _redirectLocation;
|
||||
if ( (_actualURL.indexOf('?') < 0) && (query.length() > 0) )
|
||||
_actualURL = _actualURL + "?" + query;
|
||||
else
|
||||
_actualURL = _actualURL;
|
||||
}
|
||||
} catch (MalformedURLException mue) {
|
||||
throw new IOException("Redirected from an invalid URL");
|
||||
}
|
||||
_redirects++;
|
||||
if (_redirects > 5)
|
||||
throw new IOException("Too many redirects: to " + _redirectLocation);
|
||||
if (_log.shouldLog(Log.INFO)) _log.info("Redirecting to " + _redirectLocation);
|
||||
sendRequest(timeout);
|
||||
doFetch(timeout);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Headers read completely, reading " + _bytesRemaining);
|
||||
|
||||
boolean strictSize = (_bytesRemaining >= 0);
|
||||
|
||||
|
||||
// If minimum or maximum size defined, ensure they aren't exceeded
|
||||
if ((_minSize > 0) && (_bytesRemaining < _minSize))
|
||||
throw new IOException("HTTP response size " + _bytesRemaining + " violates minimum of " + _minSize + " bytes");
|
||||
if ((_maxSize > -1) && (_bytesRemaining > _maxSize))
|
||||
throw new IOException("HTTP response size " + _bytesRemaining + " violates maximum of " + _maxSize + " bytes");
|
||||
|
||||
int remaining = (int)_bytesRemaining;
|
||||
byte buf[] = new byte[1024];
|
||||
while (_keepFetching && ( (remaining > 0) || !strictSize )) {
|
||||
while (_keepFetching && ( (remaining > 0) || !strictSize ) && !_aborted) {
|
||||
int toRead = buf.length;
|
||||
if (strictSize && toRead > remaining)
|
||||
toRead = remaining;
|
||||
int read = _proxyIn.read(buf, 0, toRead);
|
||||
if (read == -1)
|
||||
break;
|
||||
timeout.resetTimer();
|
||||
_out.write(buf, 0, read);
|
||||
_bytesTransferred += read;
|
||||
// This seems necessary to properly resume a partial download into a stream,
|
||||
// as nothing else increments _alreadyTransferred, and there's no file length to check.
|
||||
// Hopefully this won't break compatibility with existing status listeners
|
||||
// (cause them to behave weird, or show weird numbers).
|
||||
_alreadyTransferred += read;
|
||||
if ((_maxSize > -1) && (_alreadyTransferred > _maxSize)) // could transfer a little over maxSize
|
||||
throw new IOException("Bytes transferred " + _alreadyTransferred + " violates maximum of " + _maxSize + " bytes");
|
||||
remaining -= read;
|
||||
if (remaining==0 && _encodingChunked) {
|
||||
if(_proxyIn.read()=='\r' && _proxyIn.read()=='\n') {
|
||||
remaining = (int) readChunkLength();
|
||||
int char1 = _proxyIn.read();
|
||||
if (char1 == '\r') {
|
||||
int char2 = _proxyIn.read();
|
||||
if (char2 == '\n') {
|
||||
remaining = (int) readChunkLength();
|
||||
} else {
|
||||
_out.write(char1);
|
||||
_out.write(char2);
|
||||
_bytesTransferred += 2;
|
||||
remaining -= 2;
|
||||
read += 2;
|
||||
}
|
||||
} else {
|
||||
_out.write(char1);
|
||||
_bytesTransferred++;
|
||||
remaining--;
|
||||
read++;
|
||||
}
|
||||
}
|
||||
timeout.resetTimer();
|
||||
if (read > 0)
|
||||
for (int i = 0; i < _listeners.size(); i++)
|
||||
((StatusListener)_listeners.get(i)).bytesTransferred(
|
||||
@ -386,15 +537,27 @@ public class EepGet {
|
||||
_encodingChunked?-1:_bytesRemaining,
|
||||
_url);
|
||||
}
|
||||
|
||||
|
||||
if (_out != null)
|
||||
_out.close();
|
||||
_out = null;
|
||||
|
||||
if (_aborted)
|
||||
throw new IOException("Timed out reading the HTTP data");
|
||||
|
||||
timeout.cancel();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Done transferring " + _bytesTransferred);
|
||||
_log.debug("Done transferring " + _bytesTransferred + " (ok? " + !_transferFailed + ")");
|
||||
|
||||
if ( (_bytesRemaining == -1) || (remaining == 0) ){
|
||||
|
||||
if (_transferFailed) {
|
||||
// 404, etc - transferFailed is called after all attempts fail, by fetch() above
|
||||
for (int i = 0; i < _listeners.size(); i++)
|
||||
((StatusListener)_listeners.get(i)).attemptFailed(_url, _bytesTransferred, _bytesRemaining, _currentAttempt, _numRetries, new Exception("Attempt failed"));
|
||||
} else if ((_minSize > 0) && (_alreadyTransferred < _minSize)) {
|
||||
throw new IOException("Bytes transferred " + _alreadyTransferred + " violates minimum of " + _minSize + " bytes");
|
||||
} else if ( (_bytesRemaining == -1) || (remaining == 0) ) {
|
||||
for (int i = 0; i < _listeners.size(); i++)
|
||||
((StatusListener)_listeners.get(i)).transferComplete(
|
||||
_alreadyTransferred,
|
||||
@ -415,30 +578,57 @@ public class EepGet {
|
||||
boolean read = DataHelper.readLine(_proxyIn, buf);
|
||||
if (!read) throw new IOException("Unable to read the first line");
|
||||
int responseCode = handleStatus(buf.toString());
|
||||
|
||||
boolean redirect = false;
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("rc: " + responseCode + " for " + _actualURL);
|
||||
boolean rcOk = false;
|
||||
switch (responseCode) {
|
||||
case 200: // full
|
||||
_out = new FileOutputStream(_outputFile, false);
|
||||
if (_outputStream != null)
|
||||
_out = _outputStream;
|
||||
else
|
||||
_out = new FileOutputStream(_outputFile, false);
|
||||
_alreadyTransferred = 0;
|
||||
rcOk = true;
|
||||
break;
|
||||
case 206: // partial
|
||||
_out = new FileOutputStream(_outputFile, true);
|
||||
if (_outputStream != null)
|
||||
_out = _outputStream;
|
||||
else
|
||||
_out = new FileOutputStream(_outputFile, true);
|
||||
rcOk = true;
|
||||
break;
|
||||
case 301: // various redirections
|
||||
case 302:
|
||||
case 303:
|
||||
case 307:
|
||||
_alreadyTransferred = 0;
|
||||
rcOk = true;
|
||||
redirect = true;
|
||||
break;
|
||||
case 304: // not modified
|
||||
_bytesRemaining = 0;
|
||||
_keepFetching = false;
|
||||
_notModified = true;
|
||||
return;
|
||||
return;
|
||||
case 404: // not found
|
||||
_keepFetching = false;
|
||||
_transferFailed = true;
|
||||
return;
|
||||
case 416: // completed (or range out of reach)
|
||||
_bytesRemaining = 0;
|
||||
_keepFetching = false;
|
||||
return;
|
||||
default:
|
||||
rcOk = false;
|
||||
_transferFailed = true;
|
||||
}
|
||||
|
||||
// clear out the arguments, as we use the same variables for return values
|
||||
_etag = null;
|
||||
_lastModified = null;
|
||||
|
||||
buf.setLength(0);
|
||||
byte lookahead[] = new byte[3];
|
||||
while (true) {
|
||||
@ -471,6 +661,7 @@ public class EepGet {
|
||||
if (_encodingChunked) {
|
||||
_bytesRemaining = readChunkLength();
|
||||
}
|
||||
if (!redirect) _redirectLocation = null;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@ -560,11 +751,15 @@ public class EepGet {
|
||||
}
|
||||
} else if (key.equalsIgnoreCase("ETag")) {
|
||||
_etag = val.trim();
|
||||
} else if (key.equalsIgnoreCase("Last-Modified")) {
|
||||
_lastModified = val.trim();
|
||||
} else if (key.equalsIgnoreCase("Transfer-encoding")) {
|
||||
if (val.indexOf("chunked") != -1)
|
||||
_encodingChunked = true;
|
||||
} else if (key.equalsIgnoreCase("Content-Type")) {
|
||||
_contentType=val;
|
||||
} else if (key.equalsIgnoreCase("Location")) {
|
||||
_redirectLocation=val.trim();
|
||||
} else {
|
||||
// ignore the rest
|
||||
}
|
||||
@ -587,31 +782,48 @@ public class EepGet {
|
||||
private static final byte NL = '\n';
|
||||
private boolean isNL(byte b) { return (b == NL); }
|
||||
|
||||
private void sendRequest() throws IOException {
|
||||
File outFile = new File(_outputFile);
|
||||
if (outFile.exists())
|
||||
_alreadyTransferred = outFile.length();
|
||||
private void sendRequest(SocketTimeout timeout) throws IOException {
|
||||
if (_outputStream != null) {
|
||||
// We are reading into a stream supplied by a caller,
|
||||
// for which we cannot easily determine how much we've written.
|
||||
// Assume that _alreadyTransferred holds the right value
|
||||
// (we should never be restarted to work on an old stream).
|
||||
} else {
|
||||
File outFile = new File(_outputFile);
|
||||
if (outFile.exists())
|
||||
_alreadyTransferred = outFile.length();
|
||||
}
|
||||
|
||||
String req = getRequest();
|
||||
|
||||
if (_proxyIn != null) try { _proxyIn.close(); } catch (IOException ioe) {}
|
||||
if (_proxyOut != null) try { _proxyOut.close(); } catch (IOException ioe) {}
|
||||
if (_proxy != null) try { _proxy.close(); } catch (IOException ioe) {}
|
||||
|
||||
if (_shouldProxy) {
|
||||
_proxy = new Socket(_proxyHost, _proxyPort);
|
||||
} else {
|
||||
try {
|
||||
URL url = new URL(_url);
|
||||
String host = url.getHost();
|
||||
int port = url.getPort();
|
||||
if (port == -1)
|
||||
port = 80;
|
||||
_proxy = new Socket(host, port);
|
||||
URL url = new URL(_actualURL);
|
||||
if ("http".equals(url.getProtocol())) {
|
||||
String host = url.getHost();
|
||||
int port = url.getPort();
|
||||
if (port == -1)
|
||||
port = 80;
|
||||
_proxy = new Socket(host, port);
|
||||
} else {
|
||||
throw new IOException("URL is not supported:" + _actualURL);
|
||||
}
|
||||
} catch (MalformedURLException mue) {
|
||||
throw new IOException("Request URL is invalid");
|
||||
}
|
||||
}
|
||||
_proxyIn = _proxy.getInputStream();
|
||||
_proxyOut = _proxy.getOutputStream();
|
||||
|
||||
_proxyOut.write(req.toString().getBytes());
|
||||
|
||||
timeout.setSocket(_proxy);
|
||||
|
||||
_proxyOut.write(DataHelper.getUTF8(req.toString()));
|
||||
_proxyOut.flush();
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@ -623,12 +835,24 @@ public class EepGet {
|
||||
boolean post = false;
|
||||
if ( (_postData != null) && (_postData.length() > 0) )
|
||||
post = true;
|
||||
URL url = new URL(_actualURL);
|
||||
String proto = url.getProtocol();
|
||||
String host = url.getHost();
|
||||
int port = url.getPort();
|
||||
String path = url.getPath();
|
||||
String query = url.getQuery();
|
||||
if (query != null)
|
||||
path = path + "?" + query;
|
||||
if (!path.startsWith("/"))
|
||||
path = "/" + path;
|
||||
if ( (port == 80) || (port == 443) || (port <= 0) ) path = proto + "://" + host + path;
|
||||
else path = proto + "://" + host + ":" + port + path;
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Requesting " + path);
|
||||
if (post) {
|
||||
buf.append("POST ").append(_url).append(" HTTP/1.1\r\n");
|
||||
buf.append("POST ").append(_actualURL).append(" HTTP/1.1\r\n");
|
||||
} else {
|
||||
buf.append("GET ").append(_url).append(" HTTP/1.1\r\n");
|
||||
buf.append("GET ").append(_actualURL).append(" HTTP/1.1\r\n");
|
||||
}
|
||||
URL url = new URL(_url);
|
||||
buf.append("Host: ").append(url.getHost()).append("\r\n");
|
||||
if (_alreadyTransferred > 0) {
|
||||
buf.append("Range: bytes=");
|
||||
@ -641,11 +865,16 @@ public class EepGet {
|
||||
buf.append("Cache-control: no-cache\r\n");
|
||||
buf.append("Pragma: no-cache\r\n");
|
||||
}
|
||||
if (_etag != null) {
|
||||
if ((_etag != null) && (_alreadyTransferred <= 0)) {
|
||||
buf.append("If-None-Match: ");
|
||||
buf.append(_etag);
|
||||
buf.append("\r\n");
|
||||
}
|
||||
if ((_lastModified != null) && (_alreadyTransferred <= 0)) {
|
||||
buf.append("If-Modified-Since: ");
|
||||
buf.append(_lastModified);
|
||||
buf.append("\r\n");
|
||||
}
|
||||
if (post)
|
||||
buf.append("Content-length: ").append(_postData.length()).append("\r\n");
|
||||
buf.append("Connection: close\r\n\r\n");
|
||||
@ -660,6 +889,10 @@ public class EepGet {
|
||||
return _etag;
|
||||
}
|
||||
|
||||
public String getLastModified() {
|
||||
return _lastModified;
|
||||
}
|
||||
|
||||
public boolean getNotModified() {
|
||||
return _notModified;
|
||||
}
|
||||
|
@ -36,7 +36,8 @@ public class EepGetScheduler implements EepGet.StatusListener {
|
||||
public void fetch(boolean shouldBlock) {
|
||||
//Checking for a valid index is done in fetchNext, so we don't have to worry about it.
|
||||
if (shouldBlock) {
|
||||
fetchNext();
|
||||
while (_curURL < _urls.size())
|
||||
fetchNext();
|
||||
} else {
|
||||
fetch();
|
||||
}
|
||||
@ -77,6 +78,7 @@ public class EepGetScheduler implements EepGet.StatusListener {
|
||||
_listener.transferFailed(url, bytesTransferred, bytesRemaining, currentAttempt);
|
||||
fetchNext();
|
||||
}
|
||||
public void attempting(String url) { _listener.attempting(url); }
|
||||
|
||||
public void headerReceived(String url, int attemptNum, String key, String val) {}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import net.i2p.util.Log;
|
||||
public class EepPost {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private static final String CRLF = "\r\n";
|
||||
|
||||
public EepPost() {
|
||||
this(I2PAppContext.getGlobalContext());
|
||||
@ -65,6 +66,7 @@ public class EepPost {
|
||||
_onCompletion = onCompletion;
|
||||
}
|
||||
public void run() {
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Running the post task");
|
||||
Socket s = null;
|
||||
try {
|
||||
URL u = new URL(_url);
|
||||
@ -81,17 +83,20 @@ public class EepPost {
|
||||
_proxyPort = p;
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Connecting to the server/proxy...");
|
||||
s = new Socket(_proxyHost, _proxyPort);
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Connected");
|
||||
OutputStream out = s.getOutputStream();
|
||||
String sep = getSeparator();
|
||||
long length = calcContentLength(sep, _fields);
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Separator: " + sep + " content length: " + length);
|
||||
String header = getHeader(isProxy, path, h, p, sep, length);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Header: \n" + header);
|
||||
out.write(header.getBytes());
|
||||
out.flush();
|
||||
if (false) {
|
||||
out.write(("--" + sep + "\ncontent-disposition: form-data; name=\"field1\"\n\nStuff goes here\n--" + sep + "--\n").getBytes());
|
||||
out.write(("--" + sep + CRLF + "content-disposition: form-data; name=\"field1\"" + CRLF + CRLF + "Stuff goes here" + CRLF + "--" + sep + "--" + CRLF).getBytes());
|
||||
} else {
|
||||
sendFields(out, sep, _fields);
|
||||
}
|
||||
@ -121,18 +126,18 @@ public class EepPost {
|
||||
Object val = fields.get(key);
|
||||
if (val instanceof File) {
|
||||
File f = (File)val;
|
||||
len += ("--" + sep + "\nContent-Disposition: form-data; name=\"" + key + "\"; filename=\"" + f.getName() + "\"\n").length();
|
||||
len += ("--" + sep + CRLF + "Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + f.getName() + "\"" + CRLF).length();
|
||||
//len += ("Content-length: " + f.length() + "\n").length();
|
||||
len += ("Content-Type: application/octet-stream\n\n").length();
|
||||
len += ("Content-Type: application/octet-stream" + CRLF + CRLF).length();
|
||||
len += f.length();
|
||||
len += 1; // nl
|
||||
len += CRLF.length(); // nl
|
||||
} else {
|
||||
len += ("--" + sep + "\nContent-Disposition: form-data; name=\"" + key + "\"\n\n").length();
|
||||
len += ("--" + sep + CRLF + "Content-Disposition: form-data; name=\"" + key + "\"" + CRLF + CRLF).length();
|
||||
len += val.toString().length();
|
||||
len += 1; // nl
|
||||
len += CRLF.length(); // nl
|
||||
}
|
||||
}
|
||||
len += 2 + sep.length() + 2;
|
||||
len += 2 + sep.length() + 2 + CRLF.length(); //2 + sep.length() + 2;
|
||||
//len += 2;
|
||||
return len;
|
||||
}
|
||||
@ -145,29 +150,29 @@ public class EepPost {
|
||||
else
|
||||
sendField(out, separator, field, val.toString());
|
||||
}
|
||||
out.write(("--" + separator + "--\n").getBytes());
|
||||
out.write(("--" + separator + "--" + CRLF).getBytes());
|
||||
}
|
||||
|
||||
private void sendFile(OutputStream out, String separator, String field, File file) throws IOException {
|
||||
long len = file.length();
|
||||
out.write(("--" + separator + "\n").getBytes());
|
||||
out.write(("Content-Disposition: form-data; name=\"" + field + "\"; filename=\"" + file.getName() + "\"\n").getBytes());
|
||||
out.write(("--" + separator + CRLF).getBytes());
|
||||
out.write(("Content-Disposition: form-data; name=\"" + field + "\"; filename=\"" + file.getName() + "\"" + CRLF).getBytes());
|
||||
//out.write(("Content-length: " + len + "\n").getBytes());
|
||||
out.write(("Content-Type: application/octet-stream\n\n").getBytes());
|
||||
out.write(("Content-Type: application/octet-stream" + CRLF + CRLF).getBytes());
|
||||
FileInputStream in = new FileInputStream(file);
|
||||
byte buf[] = new byte[1024];
|
||||
int read = -1;
|
||||
while ( (read = in.read(buf)) != -1)
|
||||
out.write(buf, 0, read);
|
||||
out.write("\n".getBytes());
|
||||
out.write(CRLF.getBytes());
|
||||
in.close();
|
||||
}
|
||||
|
||||
private void sendField(OutputStream out, String separator, String field, String val) throws IOException {
|
||||
out.write(("--" + separator + "\n").getBytes());
|
||||
out.write(("Content-Disposition: form-data; name=\"" + field + "\"\n\n").getBytes());
|
||||
out.write(("--" + separator + CRLF).getBytes());
|
||||
out.write(("Content-Disposition: form-data; name=\"" + field + "\"" + CRLF + CRLF).getBytes());
|
||||
out.write(val.getBytes());
|
||||
out.write("\n".getBytes());
|
||||
out.write(CRLF.getBytes());
|
||||
}
|
||||
|
||||
private String getHeader(boolean isProxy, String path, String host, int port, String separator, long length) {
|
||||
@ -179,16 +184,16 @@ public class EepPost {
|
||||
buf.append(":").append(port);
|
||||
}
|
||||
buf.append(path);
|
||||
buf.append(" HTTP/1.1\n");
|
||||
buf.append(" HTTP/1.1" + CRLF);
|
||||
buf.append("Host: ").append(host);
|
||||
if (port != 80)
|
||||
buf.append(":").append(port);
|
||||
buf.append("\n");
|
||||
buf.append("Connection: close\n");
|
||||
buf.append("Content-length: ").append(length).append("\n");
|
||||
buf.append(CRLF);
|
||||
buf.append("Connection: close" + CRLF);
|
||||
buf.append("Content-length: ").append(length).append(CRLF);
|
||||
buf.append("Content-type: multipart/form-data, boundary=").append(separator);
|
||||
buf.append("\n");
|
||||
buf.append("\n");
|
||||
buf.append(CRLF);
|
||||
buf.append(CRLF);
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
|
51
core/java/src/net/i2p/util/Executor.java
Normal file
51
core/java/src/net/i2p/util/Executor.java
Normal file
@ -0,0 +1,51 @@
|
||||
package net.i2p.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
|
||||
class Executor implements Runnable {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private List _readyEvents;
|
||||
public Executor(I2PAppContext ctx, Log log, List events) {
|
||||
_context = ctx;
|
||||
_readyEvents = events;
|
||||
}
|
||||
public void run() {
|
||||
while (true) {
|
||||
SimpleTimer.TimedEvent evt = null;
|
||||
synchronized (_readyEvents) {
|
||||
if (_readyEvents.size() <= 0)
|
||||
try { _readyEvents.wait(); } catch (InterruptedException ie) {}
|
||||
if (_readyEvents.size() > 0)
|
||||
evt = (SimpleTimer.TimedEvent)_readyEvents.remove(0);
|
||||
}
|
||||
|
||||
if (evt != null) {
|
||||
long before = _context.clock().now();
|
||||
try {
|
||||
evt.timeReached();
|
||||
} catch (Throwable t) {
|
||||
log("wtf, event borked: " + evt, t);
|
||||
}
|
||||
long time = _context.clock().now() - before;
|
||||
if ( (time > 1000) && (_log != null) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("wtf, event execution took " + time + ": " + evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void log(String msg, Throwable t) {
|
||||
synchronized (this) {
|
||||
if (_log == null)
|
||||
_log = I2PAppContext.getGlobalContext().logManager().getLog(SimpleTimer.class);
|
||||
}
|
||||
_log.log(Log.CRIT, msg, t);
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ import java.security.SecureRandom;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.EntropyHarvester;
|
||||
|
||||
import gnu.crypto.prng.FortunaStandalone;
|
||||
import gnu.crypto.prng.AsyncFortunaStandalone;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
@ -26,13 +26,13 @@ import java.io.IOException;
|
||||
*
|
||||
*/
|
||||
public class FortunaRandomSource extends RandomSource implements EntropyHarvester {
|
||||
private FortunaStandalone _fortuna;
|
||||
private AsyncFortunaStandalone _fortuna;
|
||||
private double _nextGaussian;
|
||||
private boolean _haveNextGaussian;
|
||||
|
||||
public FortunaRandomSource(I2PAppContext context) {
|
||||
super(context);
|
||||
_fortuna = new FortunaStandalone();
|
||||
_fortuna = new AsyncFortunaStandalone();
|
||||
byte seed[] = new byte[1024];
|
||||
if (initSeed(seed)) {
|
||||
_fortuna.seed(seed);
|
||||
@ -41,6 +41,7 @@ public class FortunaRandomSource extends RandomSource implements EntropyHarveste
|
||||
sr.nextBytes(seed);
|
||||
_fortuna.seed(seed);
|
||||
}
|
||||
_fortuna.startup();
|
||||
// kickstart it
|
||||
_fortuna.nextBytes(seed);
|
||||
_haveNextGaussian = false;
|
||||
@ -202,6 +203,13 @@ public class FortunaRandomSource extends RandomSource implements EntropyHarveste
|
||||
public static void main(String args[]) {
|
||||
try {
|
||||
RandomSource rand = I2PAppContext.getGlobalContext().random();
|
||||
if (true) {
|
||||
for (int i = 0; i < 1000; i++)
|
||||
if (rand.nextFloat() < 0)
|
||||
throw new RuntimeException("negative!");
|
||||
System.out.println("All positive");
|
||||
return;
|
||||
}
|
||||
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
||||
java.util.zip.GZIPOutputStream gos = new java.util.zip.GZIPOutputStream(baos);
|
||||
for (int i = 0; i < 1024*1024; i++) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user