propagate from branch 'i2p.i2p.zzz.gmp6-prop' (head 416ef26df4b91fb9de3e27623551c7f87ec2bfe0)

to branch 'i2p.i2p' (head 9466fdeae338d6b0bf049d86975db9b6ddbd3064)
This commit is contained in:
zzz
2016-04-28 01:34:48 +00:00
1529 changed files with 211145 additions and 119391 deletions

View File

@@ -28,9 +28,9 @@ web-fragment.xml
web-out.xml
# Temporary/build dirs
^build
^build$
^pkg-temp
/build
/build$
/classes
/dist
^installer/resources/locale/mo
@@ -45,7 +45,6 @@ core/c/jcpuid/msvc/*.user
# Debian-related
^debian/copyright
^debian/changelog
^.pc/
# Build property overrides

View File

@@ -23,6 +23,7 @@ trans.sv_SE = apps/i2ptunnel/locale/messages_sv.po
trans.uk_UA = apps/i2ptunnel/locale/messages_uk.po
trans.vi = apps/i2ptunnel/locale/messages_vi.po
trans.zh_CN = apps/i2ptunnel/locale/messages_zh.po
trans.zh_TW = apps/i2ptunnel/locale/messages_zh_TW.po
[I2P.proxy]
source_file = apps/i2ptunnel/locale-proxy/messages_en.po
@@ -33,6 +34,8 @@ trans.de = apps/i2ptunnel/locale-proxy/messages_de.po
trans.es = apps/i2ptunnel/locale-proxy/messages_es.po
trans.fr = apps/i2ptunnel/locale-proxy/messages_fr.po
trans.hu = apps/i2ptunnel/locale-proxy/messages_hu.po
;; Java converts id to in
trans.id = apps/i2ptunnel/locale-proxy/messages_in.po
trans.it = apps/i2ptunnel/locale-proxy/messages_it.po
trans.nb = apps/i2ptunnel/locale-proxy/messages_nb.po
trans.nl = apps/i2ptunnel/locale-proxy/messages_nl.po
@@ -74,6 +77,7 @@ trans.tr_TR = apps/routerconsole/locale/messages_tr.po
trans.uk_UA = apps/routerconsole/locale/messages_uk.po
trans.vi = apps/routerconsole/locale/messages_vi.po
trans.zh_CN = apps/routerconsole/locale/messages_zh.po
trans.zh_TW = apps/routerconsole/locale/messages_zh_TW.po
[I2P.welcome]
source_file = apps/routerconsole/locale-news/messages_en.po
@@ -81,11 +85,15 @@ source_lang = en
trans.ar = apps/routerconsole/locale-news/messages_ar.po
trans.de = apps/routerconsole/locale-news/messages_de.po
trans.es = apps/routerconsole/locale-news/messages_es.po
trans.fi = apps/routerconsole/locale-news/messages_fi.po
trans.fr = apps/routerconsole/locale-news/messages_fr.po
trans.he = apps/routerconsole/locale-news/messages_he.po
;; Java converts id to in
trans.id = apps/routerconsole/locale-news/messages_in.po
trans.it = apps/routerconsole/locale-news/messages_it.po
trans.ja = apps/routerconsole/locale-news/messages_ja.po
trans.ko = apps/routerconsole/locale-news/messages_ko.po
trans.mg = apps/routerconsole/locale-news/messages_mg.po
trans.nb = apps/routerconsole/locale-news/messages_nb.po
trans.nl = apps/routerconsole/locale-news/messages_nl.po
trans.pl = apps/routerconsole/locale-news/messages_pl.po
@@ -94,10 +102,12 @@ trans.pt_BR = apps/routerconsole/locale-news/messages_pt_BR.po
trans.ro = apps/routerconsole/locale-news/messages_ro.po
trans.ru_RU = apps/routerconsole/locale-news/messages_ru.po
trans.sk = apps/routerconsole/locale-news/messages_sk.po
trans.sq = apps/routerconsole/locale-news/messages_sq.po
trans.sv_SE = apps/routerconsole/locale-news/messages_sv.po
trans.tr_TR = apps/routerconsole/locale-news/messages_tr.po
trans.uk_UA = apps/routerconsole/locale-news/messages_uk.po
trans.zh_CN = apps/routerconsole/locale-news/messages_zh.po
trans.zh_TW = apps/routerconsole/locale-news/messages_zh_TW.po
[I2P.countries]
type = PO
@@ -114,6 +124,7 @@ trans.fr = apps/routerconsole/locale-countries/messages_fr.po
trans.hu = apps/routerconsole/locale-countries/messages_hu.po
trans.it = apps/routerconsole/locale-countries/messages_it.po
trans.ja = apps/routerconsole/locale-countries/messages_ja.po
trans.mg = apps/routerconsole/locale-countries/messages_mg.po
trans.nb = apps/routerconsole/locale-countries/messages_nb.po
trans.nl = apps/routerconsole/locale-countries/messages_nl.po
trans.pl = apps/routerconsole/locale-countries/messages_pl.po
@@ -128,6 +139,7 @@ trans.uk_UA = apps/routerconsole/locale-countries/messages_uk.po
trans.tr_TR = apps/routerconsole/locale-countries/messages_tr.po
trans.vi = apps/routerconsole/locale-countries/messages_vi.po
trans.zh_CN = apps/routerconsole/locale-countries/messages_zh.po
trans.zh_TW = apps/routerconsole/locale-countries/messages_zh_TW.po
[I2P.i2psnark]
source_file = apps/i2psnark/locale/messages_en.po
@@ -208,17 +220,22 @@ trans.cs = apps/susimail/locale/messages_cs.po
trans.da = apps/susimail/locale/messages_da.po
trans.de = apps/susimail/locale/messages_de.po
trans.es = apps/susimail/locale/messages_es.po
trans.fi = apps/susimail/locale/messages_fi.po
trans.fr = apps/susimail/locale/messages_fr.po
trans.hu = apps/susimail/locale/messages_hu.po
;; Java converts id to in
trans.id = apps/susimail/locale/messages_in.po
trans.it = apps/susimail/locale/messages_it.po
trans.ja = apps/susimail/locale/messages_ja.po
trans.mg = apps/susimail/locale/messages_mg.po
trans.nl = apps/susimail/locale/messages_nl.po
trans.ru_RU = apps/susimail/locale/messages_ru.po
trans.sv_SE = apps/susimail/locale/messages_sv.po
trans.pl = apps/susimail/locale/messages_pl.po
trans.pt = apps/susimail/locale/messages_pt.po
trans.pt_BR = apps/susimail/locale/messages_pt_BR.po
trans.ro = apps/susimail/locale/messages_ro.po
trans.ru_RU = apps/susimail/locale/messages_ru.po
trans.sq = apps/susimail/locale/messages_sq.po
trans.sv_SE = apps/susimail/locale/messages_sv.po
trans.uk_UA = apps/susimail/locale/messages_uk.po
trans.vi = apps/susimail/locale/messages_vi.po
trans.zh_CN = apps/susimail/locale/messages_zh.po
@@ -230,16 +247,21 @@ trans.cs = debian/po/cs.po
trans.de = debian/po/de.po
trans.el = debian/po/el.po
trans.es = debian/po/es.po
trans.fi = debian/po/fi.po
trans.fr = debian/po/fr.po
trans.id = debian/po/id.po
trans.it = debian/po/it.po
trans.hu = debian/po/hu.po
trans.ja = debian/po/ja.po
trans.ko = debian/po/ko.po
trans.nl = debian/po/nl.po
trans.pl = debian/po/pl.po
trans.pt = debian/po/pt.po
trans.pt_BR = debian/po/pt_BR.po
trans.ro = debian/po/ro.po
trans.ru_RU = debian/po/ru.po
trans.sk = debian/po/sk.po
trans.sq = debian/po/sq.po
trans.sv_SE = debian/po/sv.po
trans.uk_UA = debian/po/uk.po
trans.tr_TR = debian/po/tr.po
@@ -248,12 +270,20 @@ trans.zh_CN = debian/po/zh.po
[I2P.i2prouter-script]
source_file = installer/resources/locale/po/messages_en.po
source_lang = en
;; currently fails check
;;trans.ca = installer/resources/locale/po/messages_ca.po
trans.de = installer/resources/locale/po/messages_de.po
trans.es = installer/resources/locale/po/messages_es.po
;; currently fails check
;;trans.fi = installer/resources/locale/po/messages_fi.po
trans.fr = installer/resources/locale/po/messages_fr.po
trans.id = installer/resources/locale/po/messages_id.po
trans.it = installer/resources/locale/po/messages_it.po
trans.pl = installer/resources/locale/po/messages_pl.po
trans.ja = installer/resources/locale/po/messages_ja.po
;; currently fails check
;;trans.ko = installer/resources/locale/po/messages_ko.po
trans.nl = installer/resources/locale/po/messages_nl.po
trans.pl = installer/resources/locale/po/messages_pl.po
trans.pt = installer/resources/locale/po/messages_pt.po
trans.pt_BR = installer/resources/locale/po/messages_pt_BR.po
@@ -262,6 +292,8 @@ trans.ru_RU = installer/resources/locale/po/messages_ru.po
trans.sk = installer/resources/locale/po/messages_sk.po
trans.sv_SE = installer/resources/locale/po/messages_sv.po
trans.tr_TR = installer/resources/locale/po/messages_tr.po
;; currently fails check
;;trans.uk_UA = installer/resources/locale/po/messages_uk.po
trans.zh_CN = installer/resources/locale/po/messages_zh.po
[I2P.getopt]
@@ -271,28 +303,44 @@ type = PROPERTIES
trans.cs = core/java/src/gnu/getopt/MessagesBundle_cs.properties
trans.de = core/java/src/gnu/getopt/MessagesBundle_de.properties
trans.es = core/java/src/gnu/getopt/MessagesBundle_es.properties
trans.fi = core/java/src/gnu/getopt/MessagesBundle_fi.properties
trans.fr = core/java/src/gnu/getopt/MessagesBundle_fr.properties
trans.hu = core/java/src/gnu/getopt/MessagesBundle_hu.properties
;; Java converts id to in
trans.id = core/java/src/gnu/getopt/MessagesBundle_in.properties
trans.it = core/java/src/gnu/getopt/MessagesBundle_it.properties
trans.ja = core/java/src/gnu/getopt/MessagesBundle_ja.properties
trans.ko = core/java/src/gnu/getopt/MessagesBundle_ko.properties
trans.nl = core/java/src/gnu/getopt/MessagesBundle_nl.properties
trans.nb = core/java/src/gnu/getopt/MessagesBundle_nb.properties
trans.pl = core/java/src/gnu/getopt/MessagesBundle_pl.properties
trans.pt_BR = core/java/src/gnu/getopt/MessagesBundle_pt_BR.properties
;; currently corrupt, non-UTF-8
;;trans.pt = core/java/src/gnu/getopt/MessagesBundle_pt.properties
;; currently corrupt, non-UTF-8
;;trans.pt_BR = core/java/src/gnu/getopt/MessagesBundle_pt_BR.properties
trans.ro = core/java/src/gnu/getopt/MessagesBundle_ro.properties
trans.ru_RU = core/java/src/gnu/getopt/MessagesBundle_ru.properties
trans.sk = core/java/src/gnu/getopt/MessagesBundle_sk.properties
;; currently corrupt, non-UTF-8
;;trans.sq = core/java/src/gnu/getopt/MessagesBundle_sq.properties
trans.uk_UA = core/java/src/gnu/getopt/MessagesBundle_uk.properties
trans.zh_CN = core/java/src/gnu/getopt/MessagesBundle_zh.properties
[I2P.streaming]
source_file = apps/ministreaming/locale/messages_en.po
source_lang = en
trans.ca = apps/ministreaming/locale/messages_ca.po
trans.de = apps/ministreaming/locale/messages_de.po
trans.es = apps/ministreaming/locale/messages_es.po
trans.fr = apps/ministreaming/locale/messages_fr.po
;; Java converts id to in
trans.id = apps/ministreaming/locale/messages_in.po
trans.it = apps/ministreaming/locale/messages_it.po
trans.nb = apps/ministreaming/locale/messages_nb.po
trans.pl = apps/ministreaming/locale/messages_pl.po
trans.ro = apps/ministreaming/locale/messages_ro.po
trans.ru_RU = apps/ministreaming/locale/messages_ru.po
trans.sv_SE = apps/ministreaming/locale/messages_sv.po
trans.uk_UA = apps/ministreaming/locale/messages_uk.po
trans.zh_CN = apps/ministreaming/locale/messages_zh.po

View File

@@ -25,21 +25,22 @@ where there are comments labeled "PORTABLE". Do this before you
run I2P for the first time.
To start I2P:
(*nix): sh i2prouter start
(*nix, BSD, Mac): sh i2prouter start
(win*): I2P.exe
(non-x86 platforms PPC, ARM, etc): sh runplain.sh
(platforms without wrapper support): sh runplain.sh
To stop I2P (gracefully):
lynx http://localhost:7657/summaryframe (click "Shutdown")
or (*nix, BSD, Mac) sh i2prouter graceful
To stop I2P immediately:
sh i2prouter stop
(*nix, BSD, Mac) sh i2prouter stop
To uninstall I2P:
rm -rf $I2PInstallDir ~/.i2p
Supported JVMs:
All platforms: Java 1.6 or higher required; 1.7 or higher recommended
All platforms: Java 1.7 or higher required
Windows: OpenJDK or Oracle from http://java.com/download
Linux: OpenJDK or Oracle from http://java.com/download
FreeBSD: OpenJDK or Oracle from http://java.com/download

View File

@@ -1,11 +1,13 @@
I2P source installation instructions
Prerequisites to build from source:
Java SDK (preferably Oracle/Sun or OpenJDK) 1.6.0 or higher
Java SDK (preferably Oracle/Sun or OpenJDK) 1.7.0 or higher
Non-linux operating systems and JVMs: See https://trac.i2p2.de/wiki/java
Certain subsystems for embedded (core, router, mstreaming, streaming, i2ptunnel) require only Java 1.6
Apache Ant 1.7.0 or higher
The xgettext, msgfmt, and msgmerge tools installed
from the GNU gettext package http://www.gnu.org/software/gettext/
from the GNU gettext package http://www.gnu.org/software/gettext/
Build environment must use a UTF-8 locale.
To build and install I2P from source, you must first build
and package up the appropriate installer by running:
@@ -40,29 +42,30 @@ or on Windows, just double-click on i2pinstall.exe.
Or move the i2pupdate.zip file into an existing installation directory and restart.
To start I2P:
(*nix): sh i2prouter start
(*nix, BSD, Mac): sh i2prouter start
(win*): I2P.exe or i2prouter.bat
(non-x86 platforms PPC, ARM, etc): sh runplain.sh
(platforms without wrapper support): sh runplain.sh
To install I2P as a system service:
(*nix) sh i2prouter install
(*nix, BSD, Mac) sh i2prouter install
(win*) install_i2p_service_winnt.bat
To uninstall I2P as a system service:
(*nix) sh i2prouter remove
(*nix, BSD, Mac) sh i2prouter remove
(win*) uninstall_i2p-service_winnt.bat
To stop I2P (gracefully):
lynx http://localhost:7657/summaryframe (click "Shutdown")
or (*nix, BSD, Mac) sh i2prouter graceful
To stop I2P immediately:
sh i2prouter stop
(*nix, BSD, Mac) sh i2prouter stop
To uninstall I2P:
rm -rf $I2PInstallDir ~/.i2p
Supported JVMs:
Windows: Latest available from http://java.com/download (1.5+ supported)
Linux: Latest available from http://java.com/download (1.5+ supported)
FreeBSD: 1.5-compatible (NIO required)
Windows: Latest available from http://java.com/download (1.7+ supported)
Linux: Latest available from http://java.com/download (1.7+ supported)
FreeBSD: 1.7-compatible (NIO required)
Other operating systems and JVMs: See http://trac.i2p2.de/wiki/java

View File

@@ -40,6 +40,10 @@ Public domain except as listed below:
Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle
See licenses/LICENSE-SHA256.txt
ElGamal:
Copyright (c) 2000 - 2013 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org)
See licenses/LICENSE-SHA256.txt
AES code:
Copyright (c) 1995-2005 The Cryptix Foundation Limited.
See licenses/LICENSE-Cryptix.txt
@@ -80,6 +84,10 @@ Public domain except as listed below:
Copyright (c) 1998 by Aaron M. Renn (arenn@urbanophile.com)
See licenses/LICENSE-LGPLv2.1.txt
HostnameVerifier:
From Apache HttpClient 4.4.1 and HttpCore 4.4.1
See licenses/LICENSE-Apache2.0.txt
Router (router.jar):
Public domain except as listed below:
@@ -87,7 +95,7 @@ Public domain except as listed below:
From freenet
See licenses/LICENSE-GPLv2.txt
UPnP subsystem (CyberLink) 2.1:
UPnP subsystem (CyberLink) 3.0:
Copyright (C) 2003-2010 Satoshi Konno
See licenses/LICENSE-UPnP.txt
@@ -95,9 +103,14 @@ Public domain except as listed below:
http://creativecommons.org/licenses/by-sa/3.0/
This product includes GeoLite data created by MaxMind, available from http://www.maxmind.com/
GeoIP API 1.3.1:
See licenses/LICENSE-LGPLv2.1.txt
Installer:
Launch4j 3.0.1:
(Launch4j is only included in the upstream source package and Windows binaries.
Not applicable for non-Windows binaries or Debian/Launchpad packages.)
Copyright (c) 2004, 2008 Grzegorz Kowal
See licenses/LICENSE-Launch4j.txt (in binary packages)
See installer/lib/launch4j/LICENSE.txt (in source packages)
@@ -182,7 +195,18 @@ Applications:
By welterde.
See licenses/LICENSE-GPLv2.txt
Jetty 8.1.16.v20140903:
Imagegen:
Identicon:
Copyright (c) 2007-2014 Don Park <donpark@docuverse.com>
See licenses/LICENSE-Identicon.txt
RandomArt:
Copyright (c) 2000, 2001 Markus Friedl. All rights reserved.
Copyright (c) 2008 Alexander von Gernler. All rights reserved.
See licenses/LICENSE-BSD.txt
Zxing:
See licenses/LICENSE-Apache2.0.txt
Jetty 8.1.17.v20150415:
See licenses/ABOUT-Jetty.html
See licenses/NOTICE-Jetty.html
See licenses/LICENSE-Apache2.0.txt
@@ -248,8 +272,8 @@ Applications:
Bundles systray4j-2.4.1:
See licenses/LICENSE-LGPLv2.1.txt
Tomcat 6.0.41:
Copyright 1999-2014 The Apache Software Foundation
Tomcat 6.0.44:
Copyright 1999-2015 The Apache Software Foundation
See licenses/LICENSE-Apache2.0.txt
See licenses/NOTICE-Tomcat.txt

View File

@@ -1,9 +1,11 @@
Prerequisites to build from source:
Java SDK (preferably Oracle/Sun or OpenJDK) 1.6.0 or higher
Java SDK (preferably Oracle/Sun or OpenJDK) 1.7.0 or higher
Non-linux operating systems and JVMs: See https://trac.i2p2.de/wiki/java
Certain subsystems for embedded (core, router, mstreaming, streaming, i2ptunnel) require only Java 1.6
Apache Ant 1.7.0 or higher
The xgettext, msgfmt, and msgmerge tools installed
from the GNU gettext package http://www.gnu.org/software/gettext/
Build environment must use a UTF-8 locale.
To build:
On x86 systems do:
@@ -19,7 +21,8 @@ To build:
Documentation:
https://geti2p.net/how
API: run 'ant javadoc' then start at build/javadoc/index.html
API: http://docs.i2p-projekt.de/javadoc/
or run 'ant javadoc' then start at build/javadoc/index.html
Latest release:
https://geti2p.net/download
@@ -34,6 +37,15 @@ Need help?
IRC irc.freenode.net #i2p
http://forum.i2p/
Bug reports:
https://trac.i2p2.de/report/1
Contact information, security issues, press inquiries:
https://geti2p.net/en/contact
Twitter:
@i2p, @geti2p
Licenses:
See LICENSE.txt

View File

@@ -66,7 +66,7 @@ public class Main {
}
static void wrtxt(OutputStream CMDout, String s) throws IOException {
CMDout.write(s.getBytes());
CMDout.write(DataHelper.getUTF8(s));
CMDout.write('\n');
CMDout.flush();
}

View File

@@ -63,7 +63,7 @@ public class Main {
}
static void wrtxt(OutputStream CMDout, String s) throws IOException {
CMDout.write(s.getBytes());
CMDout.write(DataHelper.getUTF8(s));
CMDout.write('\n');
CMDout.flush();
}

View File

@@ -38,7 +38,6 @@ import net.i2p.I2PAppContext;
import net.i2p.app.*;
import net.i2p.client.I2PClient;
import net.i2p.util.I2PAppThread;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer2;
/**
@@ -144,6 +143,7 @@ public class BOB implements Runnable, ClientApp {
* Stop BOB gracefully
* @deprecated unused
*/
@Deprecated
public synchronized static void stop() {
if (_bob != null)
_bob.shutdown(null);
@@ -214,9 +214,7 @@ public class BOB implements Runnable, ClientApp {
// Re-reading the config file in each thread is pretty damn stupid.
String configLocation = System.getProperty(PROP_CONFIG_LOCATION, "bob.config");
// This is here just to ensure there is no interference with our threadgroups.
SimpleScheduler Y1 = SimpleScheduler.getInstance();
SimpleTimer2 Y2 = SimpleTimer2.getInstance();
i = Y1.hashCode();
i = Y2.hashCode();
{
File cfg = new File(configLocation);
@@ -250,11 +248,11 @@ public class BOB implements Runnable, ClientApp {
save = true;
}
if (!props.containsKey("inbound.length")) {
props.setProperty("inbound.length", "1");
props.setProperty("inbound.length", "3");
save = true;
}
if (!props.containsKey("outbound.length")) {
props.setProperty("outbound.length", "1");
props.setProperty("outbound.length", "3");
save = true;
}
if (!props.containsKey("inbound.lengthVariance")) {
@@ -341,7 +339,7 @@ public class BOB implements Runnable, ClientApp {
if (g) {
DoCMDS conn_c = new DoCMDS(spin, lock, server, props, database, _log);
Thread t = new Thread(conn_c);
Thread t = new I2PAppThread(conn_c);
t.setName("BOB.DoCMDS " + i);
t.start();
i++;

View File

@@ -25,12 +25,13 @@ import java.util.Locale;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicBoolean;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PClientFactory;
//import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
//import net.i2p.i2ptunnel.I2PTunnel;
import net.i2p.util.I2PAppThread;
// needed only for debugging.
// import java.util.logging.Level;
// import java.util.logging.Logger;
@@ -909,6 +910,8 @@ public class DoCMDS implements Runnable {
} else if (Command.equals(C_getnick)) {
// Get the NamedDB to work with...
boolean nsfail = false;
try {
database.getReadLock();
} catch (Exception ex) {
@@ -919,6 +922,7 @@ public class DoCMDS implements Runnable {
ns = true;
} catch (RuntimeException b) {
try {
nsfail = true;
nns(out);
} catch (Exception ex) {
try {
@@ -931,7 +935,7 @@ public class DoCMDS implements Runnable {
}
database.releaseReadLock();
if (ns) {
if (ns && !nsfail) {
try {
rlock();
} catch (Exception e) {
@@ -1307,7 +1311,7 @@ public class DoCMDS implements Runnable {
// wait
}
tunnel = new MUXlisten(lock, database, nickinfo, _log);
Thread t = new Thread(tunnel);
Thread t = new I2PAppThread(tunnel);
t.start();
// try {
// Thread.sleep(1000 * 10); // Slow down the startup.

View File

@@ -18,10 +18,12 @@ package net.i2p.BOB;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.util.I2PAppThread;
/**
* Listen on I2P and connect to TCP
@@ -30,16 +32,15 @@ import net.i2p.client.streaming.I2PSocketManager;
*/
public class I2Plistener implements Runnable {
private NamedDB info, database;
private Logger _log;
public I2PSocketManager socketManager;
public I2PServerSocket serverSocket;
private AtomicBoolean lives;
private final NamedDB info, database;
private final Logger _log;
private final I2PServerSocket serverSocket;
private final AtomicBoolean lives;
/**
* Constructor
* @param SS
* @param S
* @param S unused
* @param info
* @param database
* @param _log
@@ -48,7 +49,6 @@ public class I2Plistener implements Runnable {
this.database = database;
this.info = info;
this._log = _log;
this.socketManager = S;
this.serverSocket = SS;
this.lives = lives;
}
@@ -79,7 +79,7 @@ public class I2Plistener implements Runnable {
conn++;
// toss the connection to a new thread.
I2PtoTCP conn_c = new I2PtoTCP(sessSocket, info, database, lives);
Thread t = new Thread(conn_c, Thread.currentThread().getName() + " I2PtoTCP " + conn);
Thread t = new I2PAppThread(conn_c, Thread.currentThread().getName() + " I2PtoTCP " + conn);
t.start();
}

View File

@@ -19,7 +19,10 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.concurrent.atomic.AtomicBoolean;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.DataHelper;
import net.i2p.util.I2PAppThread;
/**
* Process I2P->TCP
@@ -104,15 +107,15 @@ public class I2PtoTCP implements Runnable {
if (tell) {
// tell who is connecting
out.write(I2P.getPeerDestination().toBase64().getBytes());
out.write(DataHelper.getASCII(I2P.getPeerDestination().toBase64()));
out.write(10); // nl
out.flush(); // not really needed, but...
}
// setup to cross the streams
TCPio conn_c = new TCPio(in, Iout, lives); // app -> I2P
TCPio conn_a = new TCPio(Iin, out, lives); // I2P -> app
t = new Thread(conn_c, Thread.currentThread().getName() + " TCPioA");
q = new Thread(conn_a, Thread.currentThread().getName() + " TCPioB");
t = new I2PAppThread(conn_c, Thread.currentThread().getName() + " TCPioA");
q = new I2PAppThread(conn_a, Thread.currentThread().getName() + " TCPioB");
// Fire!
t.start();
q.start();

View File

@@ -21,11 +21,13 @@ import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.client.streaming.I2PSocketManagerFactory;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
/**
@@ -201,14 +203,14 @@ public class MUXlisten implements Runnable {
// I2P -> TCP
SS = socketManager.getServerSocket();
I2Plistener conn = new I2Plistener(SS, socketManager, info, database, _log, lives);
t = new Thread(tg, conn, "BOBI2Plistener " + N);
t = new I2PAppThread(tg, conn, "BOBI2Plistener " + N);
t.start();
}
if (come_in) {
// TCP -> I2P
TCPlistener conn = new TCPlistener(listener, socketManager, info, database, _log, lives);
q = new Thread(tg, conn, "BOBTCPlistener " + N);
q = new I2PAppThread(tg, conn, "BOBTCPlistener " + N);
q.start();
}

View File

@@ -15,7 +15,6 @@
*/
package net.i2p.BOB;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer2;
/**
@@ -31,12 +30,10 @@ public class Main {
*/
public static void main(String[] args) {
// THINK THINK THINK THINK THINK THINK
SimpleScheduler Y1 = SimpleScheduler.getInstance();
SimpleTimer2 Y2 = SimpleTimer2.getInstance();
BOB.main(args);
Y2.stop();
Y1.stop();
}
}

View File

@@ -64,7 +64,7 @@ public class NamedDB {
}
/**
* Find objects in the array, returns it's index or throws exception
* Find objects in the array, returns its index or throws exception
* @param key
* @return an objects index
* @throws ArrayIndexOutOfBoundsException when key does not exist

View File

@@ -20,8 +20,10 @@ import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import net.i2p.client.streaming.I2PServerSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.util.I2PAppThread;
/**
* Listen on TCP port and connect to I2P
@@ -30,12 +32,11 @@ import net.i2p.client.streaming.I2PSocketManager;
*/
public class TCPlistener implements Runnable {
private NamedDB info, database;
private Logger _log;
public I2PSocketManager socketManager;
public I2PServerSocket serverSocket;
private ServerSocket listener;
private AtomicBoolean lives;
private final NamedDB info, database;
private final Logger _log;
private final I2PSocketManager socketManager;
private final ServerSocket listener;
private final AtomicBoolean lives;
/**
* Constructor
@@ -76,7 +77,7 @@ public class TCPlistener implements Runnable {
conn++;
// toss the connection to a new thread.
TCPtoI2P conn_c = new TCPtoI2P(socketManager, server, info, database, lives);
Thread t = new Thread(conn_c, Thread.currentThread().getName() + " TCPtoI2P " + conn);
Thread t = new I2PAppThread(conn_c, Thread.currentThread().getName() + " TCPtoI2P " + conn);
t.start();
g = false;
}

View File

@@ -24,13 +24,14 @@ import java.net.NoRouteToHostException;
import java.net.Socket;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.streaming.I2PSocket;
import net.i2p.client.streaming.I2PSocketManager;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
//import net.i2p.i2ptunnel.I2PTunnel;
import net.i2p.I2PAppContext;
import net.i2p.util.I2PAppThread;
/**
*
@@ -158,8 +159,8 @@ public class TCPtoI2P implements Runnable {
// setup to cross the streams
TCPio conn_c = new TCPio(in, Iout, lives); // app -> I2P
TCPio conn_a = new TCPio(Iin, out, lives); // I2P -> app
t = new Thread(conn_c, Thread.currentThread().getName() + " TCPioA");
q = new Thread(conn_a, Thread.currentThread().getName() + " TCPioB");
t = new I2PAppThread(conn_c, Thread.currentThread().getName() + " TCPioA");
q = new I2PAppThread(conn_a, Thread.currentThread().getName() + " TCPioB");
// Fire!
t.start();
q.start();

View File

@@ -34,15 +34,18 @@ import net.i2p.util.Log;
* The skeletal frame is here, just needs to be finished.
*
* @author sponge
* @deprecated incomplete, unused
*/
@Deprecated
public class UDPIOthread implements I2PSessionListener, Runnable {
private NamedDB info;
private Log _log;
private Socket socket;
private final NamedDB info;
private final Log _log;
private final Socket socket;
private DataInputStream in;
private DataOutputStream out;
private I2PSession _session;
private final I2PSession _session;
// FIXME never set
private Destination _peerDestination;
private boolean up;
@@ -58,7 +61,6 @@ public class UDPIOthread implements I2PSessionListener, Runnable {
this._log = _log;
this.socket = socket;
this._session = _session;
}
/**

View File

@@ -77,15 +77,15 @@
<!-- unused for now (except for Android), as we oddly ship addressbook as a .war -->
<target name="jar" depends="compile, changes">
<!-- set if unset -->
<property name="workspace.changes" value="" />
<property name="workspace.changes.tr" value="" />
<jar basedir="${build}" destfile="${dist}/${jar}">
<manifest>
<attribute name="Main-Class" value="addressbook.Daemon"/>
<attribute name="Main-Class" value="net.i2p.addressbook.Daemon"/>
<attribute name="Implementation-Version" value="${full.version}" />
<attribute name="Built-By" value="${build.built-by}" />
<attribute name="Build-Date" value="${build.timestamp}" />
<attribute name="Base-Revision" value="${workspace.version}" />
<attribute name="Workspace-Changes" value="${workspace.changes}" />
<attribute name="Workspace-Changes" value="${workspace.changes.tr}" />
</manifest>
</jar>
</target>

View File

@@ -27,8 +27,10 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Pattern;
import net.i2p.I2PAppContext;
import net.i2p.client.naming.HostTxtEntry;
import net.i2p.util.EepGet;
import net.i2p.util.SecureFile;
@@ -37,18 +39,40 @@ import net.i2p.util.SecureFile;
* destinations. AddressBooks can be created from local and remote files, merged
* together, and written out to local files.
*
* Methods are NOT thread-safe.
*
* @author Ragnarok
*
*/
class AddressBook {
class AddressBook implements Iterable<Map.Entry<String, HostTxtEntry>> {
private final String location;
/** either addresses or subFile will be non-null, but not both */
private final Map<String, String> addresses;
private final Map<String, HostTxtEntry> addresses;
private final File subFile;
private boolean modified;
private static final boolean DEBUG = false;
private static final int MIN_DEST_LENGTH = 516;
private static final int MAX_DEST_LENGTH = MIN_DEST_LENGTH + 100; // longer than any known cert type for now
/**
* 5-67 chars lower/upper case
*/
private static final Pattern HOST_PATTERN =
Pattern.compile("^[0-9a-zA-Z\\.-]{5,67}$");
/**
* 52 chars lower/upper case
* Always ends in 'a' or 'q'
*/
private static final Pattern B32_PATTERN =
Pattern.compile("^[2-7a-zA-Z]{51}[aAqQ]$");
/** not a complete qualification, just a quick check */
private static final Pattern B64_PATTERN =
Pattern.compile("^[0-9a-zA-Z~-]{" + MIN_DEST_LENGTH + ',' + MAX_DEST_LENGTH + "}={0,2}$");
/**
* Construct an AddressBook from the contents of the Map addresses.
*
@@ -56,7 +80,7 @@ class AddressBook {
* A Map containing human readable addresses as keys, mapped to
* base64 i2p destinations.
*/
public AddressBook(Map<String, String> addresses) {
public AddressBook(Map<String, HostTxtEntry> addresses) {
this.addresses = addresses;
this.subFile = null;
this.location = null;
@@ -86,7 +110,7 @@ class AddressBook {
new File("addressbook.tmp").delete();
}
*/
static final long MAX_SUB_SIZE = 3 * 1024 * 1024l; //about 5,000 hosts
static final long MAX_SUB_SIZE = 5 * 1024 * 1024l; //about 8,000 hosts
/**
* Construct an AddressBook from the Subscription subscription. If the
@@ -107,7 +131,7 @@ class AddressBook {
* @param proxyPort port number of proxy
*/
public AddressBook(Subscription subscription, String proxyHost, int proxyPort) {
Map<String, String> a = null;
Map<String, HostTxtEntry> a = null;
File subf = null;
try {
File tmp = SecureFile.createTempFile("addressbook", null, I2PAppContext.getGlobalContext().getTempDir());
@@ -144,23 +168,40 @@ class AddressBook {
*/
public AddressBook(File file) {
this.location = file.toString();
Map<String, String> a;
Map<String, HostTxtEntry> a;
try {
a = ConfigParser.parse(file);
a = HostTxtParser.parse(file);
} catch (IOException exp) {
a = new HashMap<String, String>();
a = new HashMap<String, HostTxtEntry>();
}
this.addresses = a;
this.subFile = null;
}
/**
* Test only.
*
* @param testsubfile path to a file containing the simulated fetch of a subscription
* @since 0.9.26
*/
public AddressBook(String testsubfile) {
this.location = testsubfile;
this.addresses = null;
this.subFile = new File(testsubfile);
}
/**
* Return an iterator over the addresses in the AddressBook.
* @since 0.8.7
*/
public Iterator<Map.Entry<String, String>> iterator() {
if (this.subFile != null)
return new ConfigIterator(this.subFile);
public Iterator<Map.Entry<String, HostTxtEntry>> iterator() {
if (this.subFile != null) {
try {
return new HostTxtIterator(this.subFile);
} catch (IOException ioe) {
return new HostTxtIterator();
}
}
return this.addresses.entrySet().iterator();
}
@@ -201,12 +242,9 @@ class AddressBook {
return "Map containing " + this.addresses.size() + " entries";
}
private static final int MIN_DEST_LENGTH = 516;
private static final int MAX_DEST_LENGTH = MIN_DEST_LENGTH + 100; // longer than any known cert type for now
/**
* Do basic validation of the hostname
* hostname was already converted to lower case by ConfigParser.parse()
* hostname was already converted to lower case by HostTxtParser.parse()
*/
public static boolean isValidKey(String host) {
return
@@ -220,9 +258,10 @@ class AddressBook {
host.indexOf("..") < 0 &&
// IDN - basic check, not complete validation
(host.indexOf("--") < 0 || host.startsWith("xn--") || host.indexOf(".xn--") > 0) &&
host.replaceAll("[a-z0-9.-]", "").length() == 0 &&
HOST_PATTERN.matcher(host).matches() &&
// Base32 spoofing (52chars.i2p)
(! (host.length() == 56 && host.substring(0,52).replaceAll("[a-z2-7]", "").length() == 0)) &&
// We didn't do it this way, we use a .b32.i2p suffix, but let's prohibit it anyway
(! (host.length() == 56 && B32_PATTERN.matcher(host.substring(0,52)).matches())) &&
// ... or maybe we do Base32 this way ...
(! host.equals("b32.i2p")) &&
(! host.endsWith(".b32.i2p")) &&
@@ -246,7 +285,7 @@ class AddressBook {
(dest.length() > MIN_DEST_LENGTH && dest.length() <= MAX_DEST_LENGTH)) &&
// B64 comes in groups of 2, 3, or 4 chars, but never 1
((dest.length() % 4) != 1) &&
dest.replaceAll("[a-zA-Z0-9~-]", "").length() == 0
B64_PATTERN.matcher(dest).matches()
;
}
@@ -267,15 +306,15 @@ class AddressBook {
public void merge(AddressBook other, boolean overwrite, Log log) {
if (this.addresses == null)
throw new IllegalStateException();
for (Iterator<Map.Entry<String, String>> iter = other.iterator(); iter.hasNext(); ) {
Map.Entry<String, String> entry = iter.next();
for (Iterator<Map.Entry<String, HostTxtEntry>> iter = other.iterator(); iter.hasNext(); ) {
Map.Entry<String, HostTxtEntry> entry = iter.next();
String otherKey = entry.getKey();
String otherValue = entry.getValue();
HostTxtEntry otherValue = entry.getValue();
if (isValidKey(otherKey) && isValidDest(otherValue)) {
if (isValidKey(otherKey) && isValidDest(otherValue.getDest())) {
if (this.addresses.containsKey(otherKey) && !overwrite) {
if (DEBUG && log != null &&
!this.addresses.get(otherKey).equals(otherValue)) {
!this.addresses.get(otherKey).equals(otherValue.getDest())) {
log.append("Conflict for " + otherKey + " from "
+ other.location
+ ". Destination in remote address book is "
@@ -308,7 +347,7 @@ class AddressBook {
throw new IllegalStateException();
if (this.modified) {
try {
ConfigParser.write(this.addresses, file);
HostTxtParser.write(this.addresses, file);
} catch (IOException exp) {
System.err.println("Error writing addressbook " + file.getAbsolutePath() + " : " + exp.toString());
}
@@ -332,4 +371,27 @@ class AddressBook {
protected void finalize() {
delete();
}
/****
public static void main(String[] args) {
String[] tests = { "foo.i2p",
"3bnipzzu67cdq2rcygyxz52xhvy6ylokn4zfrk36ywn6pixmaoza.b32.i2p",
"9rhEy4dT9fMlcSOhDzfWRxCV2aen4Zp4eSthOf5f9gVKMa4PtQJ-wEzm2KEYeDXkbM6wEDvMQ6ou4LIniSE6bSAwy7fokiXk5oabels-sJmftnQWRbZyyXEAsLc3gpJJvp9km7kDyZ0z0YGL5tf3S~OaWdptB5tSBOAOjm6ramcYZMWhyUqm~xSL1JyXUqWEHRYwhoDJNL6-L516VpDYVigMBpIwskjeFGcqK8BqWAe0bRwxIiFTPN6Ck8SDzQvS1l1Yj-zfzg3X3gOknzwR8nrHUkjsWtEB6nhbOr8AR21C9Hs0a7MUJvSe2NOuBoNTrtxT76jDruI78JcG5r~WKl6M12yM-SqeBNE9hQn2QCHeHAKju7FdRCbqaZ99IwyjfwvZbkiYYQVN1xlUuGaXrj98XDzK7GORYdH-PrVGfEbMXQ40KLHUWHz8w4tQXAOQrCHEichod0RIzuuxo3XltCWKrf1xGZhkAo9bk2qXi6digCijvYNaKmQdXZYWW~RtAAAA",
"6IZTYacjlXjSAxu-uXEO5oGsj-f4tfePHEvGjs5pu-AMXMwD7-xFdi8kdobDMJp9yRAl96U7yLl~0t9zHeqqYmNeZnDSkTmAcSC2PT45ZJDXBobKi1~a77zuqfPwnzEatYfW3GL1JQAEkAmiwNJoG7ThTZ3zT7W9ekVJpHi9mivpTbaI~rALLfuAg~Mvr60nntZHjqhEZuiU4dTXrmc5nykl~UaMnBdwHL4jKmoN5CotqHyLYZfp74fdD-Oq4SkhuBhU8wkBIM3lz3Ul1o6-s0lNUMdYJq1CyxnyP7jeekdfAlSx4P4sU4M0dPaYvPdOFWPWwBuEh0pCs5Mj01B2xeEBhpV~xSLn6ru5Vq98TrmaR33KHxd76OYYFsWwzVbBuMVSd800XpBghGFucGw01YHYsPh3Afb01sXbf8Nb1bkxCy~DsrmoH4Ww3bpx66JhRTWvg5al3oWlCX51CnJUqaaK~dPL-pBvAyLKIA5aYvl8ca66jtA7AFDxsOb2texBBQAEAAcAAA==",
"te9Ky7XvVcLLr5vQqvfmOasg915P3-ddP3iDqpMMk7v5ufFKobLAX~1k-E4WVsJVlkYvkHVOjxix-uT1IdewKmLd81s5wZtz0GQ3ZC6p0C3S2cOxz7kQqf7QYSR0BrhZC~2du3-GdQO9TqNmsnHrah5lOZf0LN2JFEFPqg8ZB5JNm3JjJeSqePBRk3zAUogNaNK3voB1MVI0ZROKopXAJM4XMERNqI8tIH4ngGtV41SEJJ5pUFrrTx~EiUPqmSEaEA6UDYZiqd23ZlewZ31ExXQj97zvkuhKCoS9A9MNkzZejJhP-TEXWF8~KHur9f51H--EhwZ42Aj69-3GuNjsMdTwglG5zyIfhd2OspxJrXzCPqIV2sXn80IbPgwxHu0CKIJ6X43B5vTyVu87QDI13MIRNGWNZY5KmM5pilGP7jPkOs4xQDo4NHzpuJR5igjWgJIBPU6fI9Pzq~BMzjLiZOMp8xNWey1zKC96L0eX4of1MG~oUvq0qmIHGNa1TlUwBQAEAAEAAA==",
"(*&(*&(*&(*",
"9rhEy4dT9fMlcSOhDzfWRxCV2aen4Zp4eSthOf5f9gVKMa4PtQJ-wEzm2KEYeDXkbM6wEDvMQ6ou4LIniSE6bSAwy7fokiXk5oabels-sJmftnQWRbZyyXEAsLc3gpJJvp9km7kDyZ0z0YGL5tf3S~OaWdptB5tSBOAOjm6ramcYZMWhyUqm~xSL1JyXUqWEHRYwhoDJNL6-L516VpDYVigMBpIwskjeFGcqK8BqWAe0bRwxIiFTPN6Ck8SDzQvS1l1Yj-zfzg3X3gOknzwR8nrHUkjsWtEB6nhbOr8AR21C9Hs0a7MUJvSe2NOuBoNTrtxT76jDruI78JcG5r~WKl6M12yM-SqeBNE9hQn2QCHeHAKju7FdRCbqaZ99IwyjfwvZbkiYYQVN1xlUuGaXrj98XDzK7GORYdH-PrVGfEbMXQ40KLHUWHz8w4tQXAOQrCHEichod0RIzuuxo3XltCWKrf1xGZhkAo9bk2qXi6digCijvYNaKmQdXZYWW~RtAAA",
"6IZTYacjlXjSAxu-uXEO5oGsj-f4tfePHEvGjs5pu-AMXMwD7-xFdi8kdobDMJp9yRAl96U7yLl~0t9zHeqqYmNeZnDSkTmAcSC2PT45ZJDXBobKi1~a77zuqfPwnzEatYfW3GL1JQAEkAmiwNJoG7ThTZ3zT7W9ekVJpHi9mivpTbaI~rALLfuAg~Mvr60nntZHjqhEZuiU4dTXrmc5nykl~UaMnBdwHL4jKmoN5CotqHyLYZfp74fdD-Oq4SkhuBhU8wkBIM3lz3Ul1o6-s0lNUMdYJq1CyxnyP7jeekdfAlSx4P4sU4M0dPaYvPdOFWPWwBuEh0pCs5Mj01B2xeEBhpV~xSLn6ru5Vq98TrmaR33KHxd76OYYFsWwzVbBuMVSd800XpBghGFucGw01YHYsPh3Afb01sXbf8Nb1bkxCy~DsrmoH4Ww3bpx66JhRTWvg5al3oWlCX51CnJUqaaK~dPL-pBvAyLKIA5aYvl8ca66jtA7AFDxsOb2texBBQAEAAcAAA===",
"!e9Ky7XvVcLLr5vQqvfmOasg915P3-ddP3iDqpMMk7v5ufFKobLAX~1k-E4WVsJVlkYvkHVOjxix-uT1IdewKmLd81s5wZtz0GQ3ZC6p0C3S2cOxz7kQqf7QYSR0BrhZC~2du3-GdQO9TqNmsnHrah5lOZf0LN2JFEFPqg8ZB5JNm3JjJeSqePBRk3zAUogNaNK3voB1MVI0ZROKopXAJM4XMERNqI8tIH4ngGtV41SEJJ5pUFrrTx~EiUPqmSEaEA6UDYZiqd23ZlewZ31ExXQj97zvkuhKCoS9A9MNkzZejJhP-TEXWF8~KHur9f51H--EhwZ42Aj69-3GuNjsMdTwglG5zyIfhd2OspxJrXzCPqIV2sXn80IbPgwxHu0CKIJ6X43B5vTyVu87QDI13MIRNGWNZY5KmM5pilGP7jPkOs4xQDo4NHzpuJR5igjWgJIBPU6fI9Pzq~BMzjLiZOMp8xNWey1zKC96L0eX4of1MG~oUvq0qmIHGNa1TlUwBQAEAAEAAA==",
"x"
};
for (String s : tests) {
test(s);
}
}
public static void test(String s) {
System.out.println(s + " valid host? " + isValidKey(s) + " valid dest? " + isValidDest(s));
}
****/
}

View File

@@ -29,12 +29,13 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import net.i2p.data.DataHelper;
import net.i2p.util.SecureFile;
import net.i2p.util.SecureFileOutputStream;
import net.i2p.util.SystemVersion;
@@ -43,8 +44,7 @@ import net.i2p.util.SystemVersion;
* Utility class providing methods to parse and write files in config file
* format, and subscription file format.
*
* TODO: Change file encoding from default to UTF-8?
* Or switch to the DataHelper loadProps/storeProps methods?
* TODO: switch to the DataHelper loadProps/storeProps methods?
*
* @author Ragnarok
*/
@@ -87,20 +87,23 @@ class ConfigParser {
* if the BufferedReader cannot be read.
*
*/
public static Map<String, String> parse(BufferedReader input) throws IOException {
Map<String, String> result = new HashMap<String, String>();
String inputLine;
inputLine = input.readLine();
while (inputLine != null) {
inputLine = stripComments(inputLine);
String[] splitLine = inputLine.split("=");
if (splitLine.length == 2) {
result.put(splitLine[0].trim().toLowerCase(Locale.US), splitLine[1].trim());
private static Map<String, String> parse(BufferedReader input) throws IOException {
try {
Map<String, String> result = new HashMap<String, String>();
String inputLine;
while ((inputLine = input.readLine()) != null) {
inputLine = stripComments(inputLine);
if (inputLine.length() == 0)
continue;
String[] splitLine = DataHelper.split(inputLine, "=", 2);
if (splitLine.length == 2) {
result.put(splitLine[0].trim().toLowerCase(Locale.US), splitLine[1].trim());
}
}
inputLine = input.readLine();
return result;
} finally {
try { input.close(); } catch (IOException ioe) {}
}
input.close();
return result;
}
/**
@@ -113,15 +116,21 @@ class ConfigParser {
* @throws IOException
* if file cannot be read.
*/
public static Map<String, String> parse(File file) throws IOException {
FileInputStream fileStream = new FileInputStream(file);
BufferedReader input = new BufferedReader(new InputStreamReader(
fileStream));
Map<String, String> rv = parse(input);
public static Map<String, String> parse(File file) throws IOException {
FileInputStream fileStream = null;
try {
fileStream.close();
} catch (IOException ioe) {}
return rv;
fileStream = new FileInputStream(file);
BufferedReader input = new BufferedReader(new InputStreamReader(
fileStream, "UTF-8"));
Map<String, String> rv = parse(input);
return rv;
} finally {
if (fileStream != null) {
try {
fileStream.close();
} catch (IOException ioe) {}
}
}
}
/**
@@ -134,11 +143,13 @@ class ConfigParser {
* @throws IOException
* if file cannot be read.
*/
public static Map<String, String> parse(String string) throws IOException {
/****
public static Map<String, String> parse(String string) throws IOException {
StringReader stringReader = new StringReader(string);
BufferedReader input = new BufferedReader(stringReader);
return parse(input);
}
****/
/**
* Return a Map using the contents of the File file. If file cannot be read,
@@ -151,8 +162,8 @@ class ConfigParser {
* @return A Map containing the key, value pairs from file, or if file
* cannot be read, map.
*/
public static Map<String, String> parse(File file, Map<String, String> map) {
Map<String, String> result;
public static Map<String, String> parse(File file, Map<String, String> map) {
Map<String, String> result;
try {
result = parse(file);
for (Map.Entry<String, String> entry : map.entrySet()) {
@@ -178,19 +189,21 @@ class ConfigParser {
* @throws IOException
* if input cannot be read.
*/
public static List<String> parseSubscriptions(BufferedReader input)
private static List<String> parseSubscriptions(BufferedReader input)
throws IOException {
List<String> result = new LinkedList<String>();
String inputLine = input.readLine();
while (inputLine != null) {
inputLine = stripComments(inputLine).trim();
if (inputLine.length() > 0) {
result.add(inputLine);
try {
List<String> result = new ArrayList<String>(4);
String inputLine;
while ((inputLine = input.readLine()) != null) {
inputLine = stripComments(inputLine).trim();
if (inputLine.length() > 0) {
result.add(inputLine);
}
}
inputLine = input.readLine();
return result;
} finally {
try { input.close(); } catch (IOException ioe) {}
}
input.close();
return result;
}
/**
@@ -202,15 +215,21 @@ class ConfigParser {
* @throws IOException
* if file cannot be read.
*/
public static List<String> parseSubscriptions(File file) throws IOException {
FileInputStream fileStream = new FileInputStream(file);
BufferedReader input = new BufferedReader(new InputStreamReader(
fileStream));
List<String> rv = parseSubscriptions(input);
private static List<String> parseSubscriptions(File file) throws IOException {
FileInputStream fileStream = null;
try {
fileStream.close();
} catch (IOException ioe) {}
return rv;
fileStream = new FileInputStream(file);
BufferedReader input = new BufferedReader(new InputStreamReader(
fileStream, "UTF-8"));
List<String> rv = parseSubscriptions(input);
return rv;
} finally {
if (fileStream != null) {
try {
fileStream.close();
} catch (IOException ioe) {}
}
}
}
/**
@@ -222,11 +241,13 @@ class ConfigParser {
* @throws IOException
* if string cannot be read.
*/
/****
public static List<String> parseSubscriptions(String string) throws IOException {
StringReader stringReader = new StringReader(string);
BufferedReader input = new BufferedReader(stringReader);
return parseSubscriptions(input);
}
****/
/**
* Return a List using the contents of the File file. If file cannot be
@@ -276,12 +297,15 @@ class ConfigParser {
* @throws IOException
* if the BufferedWriter cannot be written to.
*/
public static void write(Map<String, String> map, BufferedWriter output) throws IOException {
for (Map.Entry<String, String> entry : map.entrySet()) {
output.write(entry.getKey() + '=' + entry.getValue());
output.newLine();
private static void write(Map<String, String> map, BufferedWriter output) throws IOException {
try {
for (Map.Entry<String, String> entry : map.entrySet()) {
output.write(entry.getKey() + '=' + entry.getValue());
output.newLine();
}
} finally {
try { output.close(); } catch (IOException ioe) {}
}
output.close();
}
/**
@@ -298,7 +322,7 @@ class ConfigParser {
* @throws IOException
* if file cannot be written to.
*/
public static void write(Map<String, String> map, File file) throws IOException {
public static void write(Map<String, String> map, File file) throws IOException {
boolean success = false;
if (!isWindows) {
File tmp = SecureFile.createTempFile("temp-", ".tmp", file.getAbsoluteFile().getParentFile());
@@ -326,13 +350,16 @@ class ConfigParser {
* @throws IOException
* if output cannot be written to.
*/
public static void writeSubscriptions(List<String> list, BufferedWriter output)
private static void writeSubscriptions(List<String> list, BufferedWriter output)
throws IOException {
for (String s : list) {
output.write(s);
output.newLine();
try {
for (String s : list) {
output.write(s);
output.newLine();
}
} finally {
try { output.close(); } catch (IOException ioe) {}
}
output.close();
}
/**
@@ -346,7 +373,7 @@ class ConfigParser {
* @throws IOException
* if output cannot be written to.
*/
public static void writeSubscriptions(List<String> list, File file)
private static void writeSubscriptions(List<String> list, File file)
throws IOException {
writeSubscriptions(list, new BufferedWriter(
new OutputStreamWriter(new SecureFileOutputStream(file), "UTF-8")));

View File

@@ -23,19 +23,21 @@ package net.i2p.addressbook;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.client.naming.HostTxtEntry;
import net.i2p.client.naming.NamingService;
import net.i2p.client.naming.SingleFileNamingService;
import net.i2p.data.DataFormatException;
import net.i2p.data.Destination;
import net.i2p.util.OrderedProperties;
import net.i2p.util.SecureDirectory;
import net.i2p.util.SystemVersion;
@@ -53,6 +55,12 @@ public class Daemon {
private static final String DEFAULT_SUB = "http://i2p-projekt.i2p/hosts.txt";
/** @since 0.9.12 */
static final String OLD_DEFAULT_SUB = "http://www.i2p2.i2p/hosts.txt";
/** Any properties we receive from the subscription, we store to the
* addressbook with this prefix, so it knows it's part of the signature.
* This is also chosen so that it can't be spoofed.
*/
private static final String RCVD_PROP_PREFIX = "=";
private static final boolean MUST_VALIDATE = false;
/**
* Update the router and published address books using remote data from the
@@ -80,10 +88,9 @@ public class Daemon {
*/
public static void update(AddressBook master, AddressBook router,
File published, SubscriptionList subscriptions, Log log) {
Iterator<AddressBook> iter = subscriptions.iterator();
while (iter.hasNext()) {
for (AddressBook book : subscriptions) {
// yes, the EepGet fetch() is done in next()
router.merge(iter.next(), false, log);
router.merge(book, false, log);
}
router.write();
if (published != null) {
@@ -129,18 +136,22 @@ public class Daemon {
while (iter.hasNext()) {
// yes, the EepGet fetch() is done in next()
long start = System.currentTimeMillis();
AddressBook sub = iter.next();
long end = System.currentTimeMillis();
AddressBook addressbook = iter.next();
// SubscriptionIterator puts in a dummy AddressBook with no location if no fetch is done
if (DEBUG && log != null && sub.getLocation() != null)
log.append("Fetch of " + sub.getLocation() + " took " + (end - start));
start = end;
if (DEBUG && log != null && addressbook.getLocation() != null) {
long end = System.currentTimeMillis();
log.append("Fetch of " + addressbook.getLocation() + " took " + (end - start));
start = end;
}
int old = 0, nnew = 0, invalid = 0, conflict = 0, total = 0;
for (Iterator<Map.Entry<String, String>> eIter = sub.iterator(); eIter.hasNext(); ) {
Map.Entry<String, String> entry = eIter.next();
int deleted = 0;
for (Map.Entry<String, HostTxtEntry> entry : addressbook) {
total++;
// may be null for 'remove' entries
String key = entry.getKey();
boolean isKnown;
Destination oldDest = null;
// NOT set for text file NamingService
Destination oldDest;
if (isTextFile) {
if (knownNames == null) {
// load the hostname set
@@ -148,22 +159,305 @@ public class Daemon {
opts.setProperty("file", "hosts.txt");
knownNames = router.getNames(opts);
}
isKnown = knownNames.contains(key);
oldDest = null;
isKnown = key != null ? knownNames.contains(key) : null;
} else {
oldDest = router.lookup(key);
oldDest = key != null ? router.lookup(key) : null;
isKnown = oldDest != null;
}
try {
if (!isKnown) {
if (AddressBook.isValidKey(key)) {
Destination dest = new Destination(entry.getValue());
Properties props = new Properties();
props.setProperty("s", sub.getLocation());
HostTxtEntry he = entry.getValue();
Properties hprops = he.getProps();
boolean mustValidate = MUST_VALIDATE || hprops != null;
String action = hprops != null ? hprops.getProperty(HostTxtEntry.PROP_ACTION) : null;
if (key == null && !he.hasValidRemoveSig()) {
if (log != null) {
log.append("Bad signature of action " + action + " for key " +
hprops.getProperty(HostTxtEntry.PROP_NAME) +
". From: " + addressbook.getLocation());
}
invalid++;
} else if (key != null && mustValidate && !he.hasValidSig()) {
if (log != null) {
log.append("Bad signature of action " + action + " for key " + key +
". From: " + addressbook.getLocation());
}
invalid++;
} else if (action != null || !isKnown) {
if (key != null && AddressBook.isValidKey(key)) {
Destination dest = new Destination(he.getDest());
Properties props = new OrderedProperties();
props.setProperty("s", addressbook.getLocation());
boolean allowExistingKeyInPublished = false;
if (mustValidate) {
// sig checked above
props.setProperty("v", "true");
}
if (hprops != null) {
// merge in all the received properties
for (Map.Entry<Object, Object> e : hprops.entrySet()) {
// Add prefix to indicate received property
props.setProperty(RCVD_PROP_PREFIX + e.getKey(), (String) e.getValue());
}
}
if (action != null) {
// Process commands. hprops is non-null.
// Must handle isKnown in each case.
if (action.equals(HostTxtEntry.ACTION_ADDDEST)) {
// Add an alternate destination (new crypto) for existing hostname
// Requires new NamingService support if the key exists
String polddest = hprops.getProperty(HostTxtEntry.PROP_OLDDEST);
if (polddest != null) {
Destination pod = new Destination(polddest);
List<Destination> pod2 = router.lookupAll(key);
if (pod2 == null) {
// we didn't know it before, so we'll add it
// check inner sig anyway
if (!he.hasValidInnerSig()) {
logInner(log, action, key, addressbook);
invalid++;
continue;
}
} else if (pod2.contains(dest)) {
// we knew it before, with the same dest
old++;
continue;
} else if (pod2.contains(pod)) {
// checks out, so verify the inner sig
if (!he.hasValidInnerSig()) {
logInner(log, action, key, addressbook);
invalid++;
continue;
}
// TODO Requires NamingService support
// if (isTextFile), do we replace or not? check sigType.isAvailable()
boolean success = router.addDestination(key, dest, props);
if (log != null) {
if (success)
log.append("Additional address for " + key +
" added to address book. From: " + addressbook.getLocation());
else
log.append("Failed to add additional address for " + key +
" From: " + addressbook.getLocation());
}
// now update the published addressbook
// ditto
if (published != null) {
if (publishedNS == null)
publishedNS = new SingleFileNamingService(I2PAppContext.getGlobalContext(), published.getAbsolutePath());
// FIXME this fails, no support in SFNS
success = publishedNS.addDestination(key, dest, props);
if (log != null && !success)
log.append("Add to published address book " + published.getAbsolutePath() + " failed for " + key);
}
nnew++;
continue;
} else {
// mismatch, disallow
logMismatch(log, action, key, pod2, he.getDest(), addressbook);
invalid++;
continue;
}
} else {
logMissing(log, action, key, addressbook);
invalid++;
continue;
}
} else if (action.equals(HostTxtEntry.ACTION_ADDNAME)) {
// Add an alias for an existing hostname, same dest
if (isKnown) {
// could be same or different dest
old++;
continue;
}
String poldname = hprops.getProperty(HostTxtEntry.PROP_OLDNAME);
if (poldname != null) {
List<Destination> pod = router.lookupAll(poldname);
if (pod == null) {
// we didn't have the old one, so we'll add the new one
} else if (pod.contains(dest)) {
// checks out, so we'll add the new one
} else {
// mismatch, disallow
logMismatch(log, action, key, pod, he.getDest(), addressbook);
invalid++;
continue;
}
} else {
logMissing(log, action, key, addressbook);
invalid++;
continue;
}
} else if (action.equals(HostTxtEntry.ACTION_ADDSUBDOMAIN)) {
// add a subdomain with verification
if (isKnown) {
old++;
continue;
}
String polddest = hprops.getProperty(HostTxtEntry.PROP_OLDDEST);
String poldname = hprops.getProperty(HostTxtEntry.PROP_OLDNAME);
if (polddest != null && poldname != null) {
// check for valid subdomain
if (!AddressBook.isValidKey(poldname) ||
key.indexOf('.' + poldname) <= 0) {
if (log != null)
log.append("Action: " + action + " failed because" +
" old name " + poldname +
" is invalid" +
". From: " + addressbook.getLocation());
invalid++;
continue;
}
Destination pod = new Destination(polddest);
List<Destination> pod2 = router.lookupAll(poldname);
if (pod2 == null) {
// we didn't have the old name
// check inner sig anyway
if (!he.hasValidInnerSig()) {
logInner(log, action, key, addressbook);
invalid++;
continue;
}
} else if (pod2.contains(pod)) {
// checks out, so verify the inner sig
if (!he.hasValidInnerSig()) {
logInner(log, action, key, addressbook);
invalid++;
continue;
}
} else {
// mismatch, disallow
logMismatch(log, action, key, pod2, polddest, addressbook);
invalid++;
continue;
}
} else {
logMissing(log, action, key, addressbook);
invalid++;
continue;
}
} else if (action.equals(HostTxtEntry.ACTION_CHANGEDEST)) {
// change destination on an existing entry
// This removes all previous destinations under that hostname,
// is this what we want?
String polddest = hprops.getProperty(HostTxtEntry.PROP_OLDDEST);
if (polddest != null) {
Destination pod = new Destination(polddest);
List<Destination> pod2 = router.lookupAll(key);
if (pod2 == null) {
// we didn't have the old name
// check inner sig anyway
if (!he.hasValidInnerSig()) {
logInner(log, action, key, addressbook);
invalid++;
continue;
}
} else if (pod2.contains(dest)) {
// we already have the new dest
old++;
continue;
} else if (pod2.contains(pod)) {
// checks out, so verify the inner sig
if (!he.hasValidInnerSig()) {
logInner(log, action, key, addressbook);
invalid++;
continue;
}
if (log != null) {
if (pod2.size() == 1)
log.append("Changing destination for " + key +
". From: " + addressbook.getLocation());
else
log.append("Replacing " + pod2.size() + " destinations for " + key +
". From: " + addressbook.getLocation());
}
allowExistingKeyInPublished = true;
props.setProperty("m", Long.toString(I2PAppContext.getGlobalContext().clock().now()));
} else {
// mismatch, disallow
logMismatch(log, action, key, pod2, polddest, addressbook);
invalid++;
continue;
}
} else {
logMissing(log, action, key, addressbook);
invalid++;
continue;
}
} else if (action.equals(HostTxtEntry.ACTION_CHANGENAME)) {
// Delete old name, replace with new
// This removes all previous destinations under that hostname,
// is this what we want?
if (isKnown) {
old++;
continue;
}
String poldname = hprops.getProperty(HostTxtEntry.PROP_OLDNAME);
if (poldname != null) {
List<Destination> pod = router.lookupAll(poldname);
if (pod == null) {
// we didn't have the old name
} else if (pod.contains(dest)) {
// checks out, so we'll delete it
if (knownNames != null)
knownNames.remove(poldname);
boolean success = router.remove(poldname, dest);
if (success)
deleted++;
if (log != null) {
if (success)
log.append("Removed: " + poldname +
" to be replaced with " + key +
". From: " + addressbook.getLocation());
else
log.append("Remove failed for: " + poldname +
" to be replaced with " + key +
". From: " + addressbook.getLocation());
}
// now update the published addressbook
if (published != null) {
if (publishedNS == null)
publishedNS = new SingleFileNamingService(I2PAppContext.getGlobalContext(), published.getAbsolutePath());
success = publishedNS.remove(poldname, dest);
if (log != null && !success)
log.append("Remove from published address book " + published.getAbsolutePath() + " failed for " + poldname);
}
} else {
// mismatch, disallow
logMismatch(log, action, key, pod, he.getDest(), addressbook);
continue;
}
} else {
logMissing(log, action, key, addressbook);
invalid++;
continue;
}
} else if (action.equals(HostTxtEntry.ACTION_REMOVE) ||
action.equals(HostTxtEntry.ACTION_REMOVEALL)) {
// w/o name=dest handled below
if (log != null)
log.append("Action: " + action + " with name=dest invalid" +
". From: " + addressbook.getLocation());
invalid++;
continue;
} else if (action.equals(HostTxtEntry.ACTION_UPDATE)) {
if (isKnown) {
allowExistingKeyInPublished = true;
props.setProperty("m", Long.toString(I2PAppContext.getGlobalContext().clock().now()));
}
} else {
if (log != null)
log.append("Action: " + action + " unrecognized" +
". From: " + addressbook.getLocation());
invalid++;
continue;
}
} // action != null
boolean success = router.put(key, dest, props);
if (log != null) {
if (success)
log.append("New address " + key +
" added to address book. From: " + sub.getLocation());
" added to address book. From: " + addressbook.getLocation());
else
log.append("Save to naming service " + router + " failed for new key " + key);
}
@@ -171,59 +465,236 @@ public class Daemon {
if (published != null) {
if (publishedNS == null)
publishedNS = new SingleFileNamingService(I2PAppContext.getGlobalContext(), published.getAbsolutePath());
success = publishedNS.putIfAbsent(key, dest);
if (allowExistingKeyInPublished)
success = publishedNS.put(key, dest, props);
else
success = publishedNS.putIfAbsent(key, dest, props);
if (log != null && !success) {
try {
log.append("Save to published address book " + published.getCanonicalPath() + " failed for new key " + key);
} catch (IOException ioe) {}
log.append("Save to published address book " + published.getAbsolutePath() + " failed for new key " + key);
}
}
if (isTextFile)
// keep track for later dup check
knownNames.add(key);
nnew++;
} else if (key == null) {
// 'remove' actions
// isKnown is false
if (action != null) {
// Process commands. hprops is non-null.
if (action.equals(HostTxtEntry.ACTION_REMOVE)) {
// delete this entry
String polddest = hprops.getProperty(HostTxtEntry.PROP_DEST);
String poldname = hprops.getProperty(HostTxtEntry.PROP_NAME);
if (polddest != null && poldname != null) {
Destination pod = new Destination(polddest);
List<Destination> pod2 = router.lookupAll(poldname);
if (pod2 != null && pod2.contains(pod)) {
if (knownNames != null && pod2.size() == 1)
knownNames.remove(poldname);
boolean success = router.remove(poldname, pod);
if (success)
deleted++;
if (log != null) {
if (success)
log.append("Removed: " + poldname +
" as requested" +
". From: " + addressbook.getLocation());
else
log.append("Remove failed for: " + poldname +
" as requested" +
". From: " + addressbook.getLocation());
}
// now update the published addressbook
if (published != null) {
if (publishedNS == null)
publishedNS = new SingleFileNamingService(I2PAppContext.getGlobalContext(), published.getAbsolutePath());
success = publishedNS.remove(poldname, pod);
if (log != null && !success)
log.append("Remove from published address book " + published.getAbsolutePath() + " failed for " + poldname);
}
} else if (pod2 != null) {
// mismatch, disallow
logMismatch(log, action, key, pod2, polddest, addressbook);
invalid++;
} else {
old++;
}
} else {
logMissing(log, action, "delete", addressbook);
invalid++;
}
} else if (action.equals(HostTxtEntry.ACTION_REMOVEALL)) {
// delete all entries with this destination
String polddest = hprops.getProperty(HostTxtEntry.PROP_DEST);
// oldname is optional, but nice because not all books support reverse lookup
if (polddest != null) {
Destination pod = new Destination(polddest);
String poldname = hprops.getProperty(HostTxtEntry.PROP_NAME);
if (poldname != null) {
List<Destination> pod2 = router.lookupAll(poldname);
if (pod2 != null && pod2.contains(pod)) {
if (knownNames != null)
knownNames.remove(poldname);
boolean success = router.remove(poldname, pod);
if (success)
deleted++;
if (log != null) {
if (success)
log.append("Removed: " + poldname +
" as requested" +
". From: " + addressbook.getLocation());
else
log.append("Remove failed for: " + poldname +
" as requested" +
". From: " + addressbook.getLocation());
}
// now update the published addressbook
if (published != null) {
if (publishedNS == null)
publishedNS = new SingleFileNamingService(I2PAppContext.getGlobalContext(), published.getAbsolutePath());
success = publishedNS.remove(poldname, pod);
if (log != null && !success)
log.append("Remove from published address book " + published.getAbsolutePath() + " failed for " + poldname);
}
} else if (pod2 != null) {
// mismatch, disallow
logMismatch(log, action, key, pod2, polddest, addressbook);
invalid++;
} else {
old++;
}
}
// reverse lookup, delete all
List<String> revs = router.reverseLookupAll(pod);
if (revs != null) {
for (String rev : revs) {
if (knownNames != null)
knownNames.remove(rev);
boolean success = router.remove(rev, pod);
if (success)
deleted++;
if (log != null) {
if (success)
log.append("Removed: " + rev +
" as requested" +
". From: " + addressbook.getLocation());
else
log.append("Remove failed for: " + rev +
" as requested" +
". From: " + addressbook.getLocation());
}
// now update the published addressbook
if (published != null) {
if (publishedNS == null)
publishedNS = new SingleFileNamingService(I2PAppContext.getGlobalContext(), published.getAbsolutePath());
success = publishedNS.remove(rev, pod);
if (log != null && !success)
log.append("Remove from published address book " + published.getAbsolutePath() + " failed for " + rev);
}
}
}
} else {
logMissing(log, action, "delete", addressbook);
invalid++;
}
} else {
if (log != null)
log.append("Action: " + action + " w/o name=dest unrecognized" +
". From: " + addressbook.getLocation());
invalid++;
}
continue;
} else {
if (log != null)
log.append("No action in command line" +
". From: " + addressbook.getLocation());
invalid++;
continue;
}
} else if (log != null) {
log.append("Bad hostname " + key + " from "
+ sub.getLocation());
log.append("Bad hostname " + key + ". From: "
+ addressbook.getLocation());
invalid++;
}
/****
} else if (false && DEBUG && log != null) {
// lookup the conflict if we haven't yet (O(n**2) for text file)
if (isTextFile)
oldDest = router.lookup(key);
if (oldDest != null && !oldDest.toBase64().equals(entry.getValue())) {
log.append("Conflict for " + key + " from "
+ sub.getLocation()
log.append("Conflict for " + key + ". From: "
+ addressbook.getLocation()
+ ". Destination in remote address book is "
+ entry.getValue());
conflict++;
} else {
old++;
}
****/
} else {
old++;
}
} catch (DataFormatException dfe) {
if (log != null)
log.append("Invalid b64 for " + key + " From: " + sub.getLocation());
log.append("Invalid b64 for " + key + " From: " + addressbook.getLocation());
invalid++;
}
total++;
}
if (DEBUG && log != null && total > 0) {
log.append("Merge of " + sub.getLocation() + " into " + router +
log.append("Merge of " + addressbook.getLocation() + " into " + router +
" took " + (System.currentTimeMillis() - start) + " ms with " +
total + " total, " +
nnew + " new, " +
old + " old, " +
deleted + " deleted, " +
invalid + " invalid, " +
conflict + " conflicts");
}
sub.delete();
}
} // entries
addressbook.delete();
} // subscriptions
subscriptions.write();
}
/** @since 0.9.26 */
private static void logInner(Log log, String action, String name, AddressBook addressbook) {
if (log != null) {
log.append("Action: " + action + " failed because" +
" inner signature for key " + name +
" failed" +
". From: " + addressbook.getLocation());
}
}
/** @since 0.9.26 */
private static void logMissing(Log log, String action, String name, AddressBook addressbook) {
if (log != null) {
log.append("Action: " + action + " for " + name +
" failed, missing required parameters" +
". From: " + addressbook.getLocation());
}
}
/** @since 0.9.26 */
private static void logMismatch(Log log, String action, String name, List<Destination> dests,
String olddest, AddressBook addressbook) {
if (log != null) {
StringBuilder buf = new StringBuilder(16);
final int sz = dests.size();
for (int i = 0; i < sz; i++) {
buf.append(dests.get(i).toBase64().substring(0, 6));
if (i != sz - 1)
buf.append(", ");
}
log.append("Action: " + action + " failed because" +
" destinations for " + name +
" (" + buf + ')' +
" do not include" +
" (" + olddest.substring(0, 6) + ')' +
". From: " + addressbook.getLocation());
}
}
/**
* Run an update, using the Map settings to provide the parameters.
*
@@ -236,16 +707,12 @@ public class Daemon {
File published = null;
boolean should_publish = Boolean.parseBoolean(settings.get("should_publish"));
if (should_publish)
published = new File(home, settings
.get("published_addressbook"));
File subscriptionFile = new File(home, settings
.get("subscriptions"));
published = new File(home, settings.get("published_addressbook"));
File subscriptionFile = new File(home, settings.get("subscriptions"));
File logFile = new File(home, settings.get("log"));
File etagsFile = new File(home, settings.get("etags"));
File lastModifiedFile = new File(home, settings
.get("last_modified"));
File lastFetchedFile = new File(home, settings
.get("last_fetched"));
File lastModifiedFile = new File(home, settings.get("last_modified"));
File lastFetchedFile = new File(home, settings.get("last_fetched"));
long delay;
try {
delay = Long.parseLong(settings.get("update_delay"));
@@ -254,13 +721,14 @@ public class Daemon {
}
delay *= 60 * 60 * 1000;
List<String> defaultSubs = new LinkedList<String>();
List<String> defaultSubs = new ArrayList<String>(4);
// defaultSubs.add("http://i2p/NF2RLVUxVulR3IqK0sGJR0dHQcGXAzwa6rEO4WAWYXOHw-DoZhKnlbf1nzHXwMEJoex5nFTyiNMqxJMWlY54cvU~UenZdkyQQeUSBZXyuSweflUXFqKN-y8xIoK2w9Ylq1k8IcrAFDsITyOzjUKoOPfVq34rKNDo7fYyis4kT5bAHy~2N1EVMs34pi2RFabATIOBk38Qhab57Umpa6yEoE~rbyR~suDRvD7gjBvBiIKFqhFueXsR2uSrPB-yzwAGofTXuklofK3DdKspciclTVzqbDjsk5UXfu2nTrC1agkhLyqlOfjhyqC~t1IXm-Vs2o7911k7KKLGjB4lmH508YJ7G9fLAUyjuB-wwwhejoWqvg7oWvqo4oIok8LG6ECR71C3dzCvIjY2QcrhoaazA9G4zcGMm6NKND-H4XY6tUWhpB~5GefB3YczOqMbHq4wi0O9MzBFrOJEOs3X4hwboKWANf7DT5PZKJZ5KorQPsYRSq0E3wSOsFCSsdVCKUGsAAAA/i2p/hosts.txt");
defaultSubs.add(DEFAULT_SUB);
SubscriptionList subscriptions = new SubscriptionList(subscriptionFile,
etagsFile, lastModifiedFile, lastFetchedFile, delay, defaultSubs, settings
.get("proxy_host"), Integer.parseInt(settings.get("proxy_port")));
etagsFile, lastModifiedFile, lastFetchedFile,
delay, defaultSubs, settings.get("proxy_host"),
Integer.parseInt(settings.get("proxy_port")));
Log log = SystemVersion.isAndroid() ? null : new Log(logFile);
// If false, add hosts via naming service; if true, write hosts.txt file directly
@@ -319,7 +787,24 @@ public class Daemon {
* others are ignored.
*/
public static void main(String[] args) {
_instance.run(args);
if (args != null && args.length > 0 && args[0].equals("test"))
_instance.test(args);
else
_instance.run(args);
}
/** @since 0.9.26 */
private static void test(String[] args) {
Properties ctxProps = new Properties();
String PROP_FORCE = "i2p.naming.blockfile.writeInAppContext";
ctxProps.setProperty(PROP_FORCE, "true");
I2PAppContext ctx = new I2PAppContext(ctxProps);
NamingService ns = getNamingService("hosts.txt");
File published = new File("test-published.txt");
Log log = new Log(new File("test-log.txt"));
SubscriptionList subscriptions = new SubscriptionList("test-sub.txt");
update(ns, published, subscriptions, log);
ctx.logManager().flush();
}
public void run(String[] args) {

View File

@@ -25,6 +25,7 @@ import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.client.naming.NamingServiceUpdater;
import net.i2p.util.I2PAppThread;
/**
* A thread that waits five minutes, then runs the addressbook daemon.
@@ -32,9 +33,9 @@ import net.i2p.client.naming.NamingServiceUpdater;
* @author Ragnarok
*
*/
public class DaemonThread extends Thread implements NamingServiceUpdater {
public class DaemonThread extends I2PAppThread implements NamingServiceUpdater {
private String[] args;
private final String[] args;
/**
* Construct a DaemonThread with the command line arguments args.

View File

@@ -22,6 +22,7 @@
package net.i2p.addressbook;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -31,6 +32,9 @@ import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import net.i2p.client.naming.HostTxtEntry;
import net.i2p.data.DataHelper;
/**
* A class to iterate through a hosts.txt or config file without
* reading the whole thing into memory.
@@ -39,26 +43,27 @@ import java.util.NoSuchElementException;
* Callers should iterate all the way through or call close()
* to ensure the underlying stream is closed.
*
* @since 0.8.7
* This is not used for config files.
* It is only used for subscriptions.
*
* @since 0.8.7, renamed from ConfigIterator in 0.9.26
*/
class ConfigIterator implements Iterator<Map.Entry<String, String>> {
class HostTxtIterator implements Iterator<Map.Entry<String, HostTxtEntry>>, Closeable {
private BufferedReader input;
private ConfigEntry next;
private MapEntry next;
/**
* A dummy iterator in which hasNext() is always false.
*/
public ConfigIterator() {}
public HostTxtIterator() {}
/**
* An iterator over the key/value pairs in the file.
*/
public ConfigIterator(File file) {
try {
public HostTxtIterator(File file) throws IOException {
FileInputStream fileStream = new FileInputStream(file);
input = new BufferedReader(new InputStreamReader(fileStream));
} catch (IOException ioe) {}
input = new BufferedReader(new InputStreamReader(fileStream, "UTF-8"));
}
public boolean hasNext() {
@@ -67,15 +72,13 @@ class ConfigIterator implements Iterator<Map.Entry<String, String>> {
if (next != null)
return true;
try {
String inputLine = input.readLine();
while (inputLine != null) {
inputLine = ConfigParser.stripComments(inputLine);
String[] splitLine = inputLine.split("=");
if (splitLine.length == 2) {
next = new ConfigEntry(splitLine[0].trim().toLowerCase(Locale.US), splitLine[1].trim());
return true;
}
inputLine = input.readLine();
String inputLine;
while ((inputLine = input.readLine()) != null) {
HostTxtEntry he = HostTxtParser.parse(inputLine, true);
if (he == null)
continue;
next = new MapEntry(he.getName(), he);
return true;
}
} catch (IOException ioe) {}
try { input.close(); } catch (IOException ioe) {}
@@ -84,10 +87,15 @@ class ConfigIterator implements Iterator<Map.Entry<String, String>> {
return false;
}
public Map.Entry<String, String> next() {
/**
* 'remove' entries will be returned with a null key,
* and the value will contain a null name, null dest,
* and non-null props.
*/
public Map.Entry<String, HostTxtEntry> next() {
if (!hasNext())
throw new NoSuchElementException();
Map.Entry<String, String> rv = next;
Map.Entry<String, HostTxtEntry> rv = next;
next = null;
return rv;
}
@@ -110,11 +118,11 @@ class ConfigIterator implements Iterator<Map.Entry<String, String>> {
/**
* The object returned by the iterator.
*/
private static class ConfigEntry implements Map.Entry<String, String> {
private static class MapEntry implements Map.Entry<String, HostTxtEntry> {
private final String key;
private final String value;
private final HostTxtEntry value;
public ConfigEntry(String k, String v) {
public MapEntry(String k, HostTxtEntry v) {
key = k;
value = v;
}
@@ -123,11 +131,11 @@ class ConfigIterator implements Iterator<Map.Entry<String, String>> {
return key;
}
public String getValue() {
public HostTxtEntry getValue() {
return value;
}
public String setValue(String v) {
public HostTxtEntry setValue(HostTxtEntry v) {
throw new UnsupportedOperationException();
}

View File

@@ -0,0 +1,250 @@
package net.i2p.addressbook;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import net.i2p.client.naming.HostTxtEntry;
import net.i2p.data.DataHelper;
import net.i2p.util.SecureFile;
import net.i2p.util.SecureFileOutputStream;
import net.i2p.util.SystemVersion;
/**
* Utility class providing methods to parse and write files in a hosts.txt file
* format, and subscription file format.
*
* @since 0.9.26 modified from ConfigParser
*/
class HostTxtParser {
private static final boolean isWindows = SystemVersion.isWindows();
/**
* Return a Map using the contents of BufferedReader input. input must have
* 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.
*
* Returned map will not contain null ("remove") entries.
*
* @param input
* A BufferedReader with lines in key=value format to parse into
* a Map.
* @return A Map containing the key, value pairs from input.
* @throws IOException
* if the BufferedReader cannot be read.
*
*/
private static Map<String, HostTxtEntry> parse(BufferedReader input) throws IOException {
try {
Map<String, HostTxtEntry> result = new HashMap<String, HostTxtEntry>();
String inputLine;
while ((inputLine = input.readLine()) != null) {
HostTxtEntry he = parse(inputLine, false);
if (he == null)
continue;
result.put(he.getName(), he);
}
return result;
} finally {
try { input.close(); } catch (IOException ioe) {}
}
}
/**
* Return a HostTxtEntry from the contents of the inputLine.
*
* @param inputLine key=value[#!k1=v1#k2=v2...]
* @param allowCommandOnly if true, a line starting with #! will return
* a HostTxtEntry with a null name and dest and non-null props.
* If false, these lines will return null.
* @return null if no entry found or on error
*/
public static HostTxtEntry parse(String inputLine, boolean allowCommandOnly) {
if (inputLine.startsWith(";"))
return null;
int comment = inputLine.indexOf("#");
String kv;
String sprops;
if (comment >= 0) {
int shebang = inputLine.indexOf(HostTxtEntry.PROPS_SEPARATOR);
if (shebang == comment && shebang + 2 < inputLine.length()) {
if (comment == 0 && !allowCommandOnly)
return null;
sprops = inputLine.substring(shebang + 2);
} else {
if (comment == 0)
return null;
sprops = null;
}
kv = inputLine.substring(0, comment);
} else {
sprops = null;
kv = inputLine;
}
String name, dest;
if (comment != 0) {
// we have a name=dest
String[] splitLine = DataHelper.split(kv, "=", 2);
if (splitLine.length < 2)
return null;
name = splitLine[0].trim().toLowerCase(Locale.US);
dest = splitLine[1].trim();
if (name.length() == 0 || dest.length() == 0)
return null;
} else {
// line starts with #!, rv will contain props only
name = null;
dest = null;
}
HostTxtEntry he;
if (sprops != null) {
try {
he = new HostTxtEntry(name, dest, sprops);
} catch (IllegalArgumentException iae) {
return null;
}
} else {
he = new HostTxtEntry(name, dest);
}
return he;
}
/**
* Return a Map using the contents of the File file. See parse(BufferedReader)
* for details of the input format.
*
* Returned map will not contain null ("remove") entries.
*
* @param file
* A File to parse.
* @return A Map containing the key, value pairs from file.
* @throws IOException
* if file cannot be read.
*/
public static Map<String, HostTxtEntry> parse(File file) throws IOException {
FileInputStream fileStream = null;
try {
fileStream = new FileInputStream(file);
BufferedReader input = new BufferedReader(new InputStreamReader(
fileStream, "UTF-8"));
Map<String, HostTxtEntry> rv = parse(input);
return rv;
} finally {
if (fileStream != null) {
try {
fileStream.close();
} catch (IOException ioe) {}
}
}
}
/**
* Return a Map using the contents of the File file. If file cannot be read,
* use map instead, and write the result to where file should have been.
*
* Returned map will not contain null ("remove") entries.
*
* @param file
* A File to attempt to parse.
* @param map
* A Map containing values to use as defaults.
* @return A Map containing the key, value pairs from file, or if file
* cannot be read, map.
*/
public static Map<String, HostTxtEntry> parse(File file, Map<String, HostTxtEntry> map) {
Map<String, HostTxtEntry> result;
try {
result = parse(file);
for (Map.Entry<String, HostTxtEntry> entry : map.entrySet()) {
if (!result.containsKey(entry.getKey()))
result.put(entry.getKey(), entry.getValue());
}
} catch (IOException exp) {
result = map;
try {
write(result, file);
} catch (IOException exp2) {
}
}
return result;
}
/**
* Write contents of Map map to BufferedWriter output. Output is written
* with one key, value pair on each line, in the format: key=value.
*
* @param map
* A Map to write to output.
* @param output
* A BufferedWriter to write the Map to.
* @throws IOException
* if the BufferedWriter cannot be written to.
*/
private static void write(Map<String, HostTxtEntry> map, BufferedWriter output) throws IOException {
try {
for (Map.Entry<String, HostTxtEntry> entry : map.entrySet()) {
entry.getValue().write(output);
}
} finally {
try { output.close(); } catch (IOException ioe) {}
}
}
/**
* Write contents of Map map to the File file. Output is written
* with one key, value pair on each line, in the format: key=value.
* Write to a temp file in the same directory and then rename, to not corrupt
* simultaneous accesses by the router. Except on Windows where renameTo()
* will fail if the target exists.
*
* @param map
* A Map to write to file.
* @param file
* A File to write the Map to.
* @throws IOException
* if file cannot be written to.
*/
public static void write(Map<String, HostTxtEntry> map, File file) throws IOException {
boolean success = false;
if (!isWindows) {
File tmp = SecureFile.createTempFile("temp-", ".tmp", file.getAbsoluteFile().getParentFile());
write(map, new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(tmp), "UTF-8")));
success = tmp.renameTo(file);
if (!success) {
tmp.delete();
//System.out.println("Warning: addressbook rename fail from " + tmp + " to " + file);
}
}
if (!success) {
// hmm, that didn't work, try it the old way
write(map, new BufferedWriter(new OutputStreamWriter(new SecureFileOutputStream(file), "UTF-8")));
}
}
public static void main(String[] args) throws Exception {
File f = new File("tmp-hosts.txt");
Map<String, HostTxtEntry> map = parse(f);
for (HostTxtEntry e : map.values()) {
System.out.println("Host: " + e.getName() +
"\nDest: " + e.getDest() +
"\nAction: " + (e.getProps() != null ? e.getProps().getProperty("action") : "(none)") +
"\nValid Inner? " + e.hasValidInnerSig() +
"\nValid? " + e.hasValidSig() +
'\n');
}
}
}

View File

@@ -23,8 +23,9 @@ package net.i2p.addressbook;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Date;
/**
@@ -35,7 +36,7 @@ import java.util.Date;
*/
class Log {
private File file;
private final File file;
/**
* Construct a Log instance that writes to the File file.
@@ -56,8 +57,8 @@ class Log {
public void append(String entry) {
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new FileWriter(this.file,
true));
bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(this.file,
true), "UTF-8"));
String timestamp = new Date().toString();
bw.write(timestamp + " -- " + entry);
bw.newLine();
@@ -73,7 +74,9 @@ class Log {
*
* @return The File that the log is writing to.
*/
/****
public File getFile() {
return this.file;
}
****/
}

View File

@@ -29,10 +29,8 @@ package net.i2p.addressbook;
*/
class Subscription {
private String location;
private final String location;
private String etag;
private String lastModified;
private long lastFetched;
@@ -41,14 +39,15 @@ class Subscription {
* was last read at the time represented by etag and lastModified.
*
* @param location
* A String representing a url to a remote address book.
* A String representing a url to a remote address book. Non-null.
* @param etag
* The etag header that we recieved the last time we read this
* subscription.
* The etag header that we received the last time we read this
* subscription. May be null.
* @param lastModified
* the last-modified header we recieved the last time we read
* this subscription.
* @param lastFetched when the subscription was last fetched (Java time, as a String)
* the last-modified header we received the last time we read
* this subscription. May be null.
* @param lastFetched when the subscription was last fetched (Java time, as a String).
* May be null.
*/
public Subscription(String location, String etag, String lastModified, String lastFetched) {
this.location = location;
@@ -71,7 +70,7 @@ class Subscription {
}
/**
* Return the etag header that we recieved the last time we read this
* Return the etag header that we received the last time we read this
* subscription.
*
* @return A String containing the etag header.
@@ -91,7 +90,7 @@ class Subscription {
}
/**
* Return the last-modified header that we recieved the last time we read
* Return the last-modified header that we received the last time we read
* this subscription.
*
* @return A String containing the last-modified header.

View File

@@ -26,6 +26,8 @@ import java.util.Iterator;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.client.naming.HostTxtEntry;
import net.i2p.util.PortMapper;
/**
* An iterator over the subscriptions in a SubscriptionList. Note that this iterator
@@ -36,9 +38,9 @@ import net.i2p.I2PAppContext;
*/
class SubscriptionIterator implements Iterator<AddressBook> {
private Iterator<Subscription> subIterator;
private String proxyHost;
private int proxyPort;
private final Iterator<Subscription> subIterator;
private final String proxyHost;
private final int proxyPort;
private final long delay;
/**
@@ -69,11 +71,17 @@ class SubscriptionIterator implements Iterator<AddressBook> {
* Yes, the EepGet fetch() is done in here in next().
*
* see java.util.Iterator#next()
* @return an AddressBook (empty if the minimum delay has not been met)
* @return non-null AddressBook (empty if the minimum delay has not been met,
* or there is no proxy tunnel, or the fetch otherwise fails)
*/
public AddressBook next() {
Subscription sub = this.subIterator.next();
if (sub.getLastFetched() + this.delay < I2PAppContext.getGlobalContext().clock().now()) {
if (sub.getLocation().startsWith("file:")) {
// test only
return new AddressBook(sub.getLocation().substring(5));
} else if (sub.getLastFetched() + this.delay < I2PAppContext.getGlobalContext().clock().now() &&
I2PAppContext.getGlobalContext().portMapper().getPort(PortMapper.SVC_HTTP_PROXY) >= 0 &&
!I2PAppContext.getGlobalContext().getBooleanProperty("i2p.vmCommSystem")) {
//System.err.println("Fetching addressbook from " + sub.getLocation());
return new AddressBook(sub, this.proxyHost, this.proxyPort);
} else {
@@ -81,7 +89,7 @@ class SubscriptionIterator implements Iterator<AddressBook> {
// DataHelper.formatDuration(I2PAppContext.getGlobalContext().clock().now() - sub.getLastFetched()) +
// " ago but the minimum delay is " +
// DataHelper.formatDuration(this.delay));
return new AddressBook(Collections.<String, String> emptyMap());
return new AddressBook(Collections.<String, HostTxtEntry>emptyMap());
}
}

View File

@@ -23,8 +23,9 @@ package net.i2p.addressbook;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -34,19 +35,15 @@ import java.util.Map;
* @author Ragnarok
*
*/
class SubscriptionList {
class SubscriptionList implements Iterable<AddressBook> {
private List<Subscription> subscriptions;
private File etagsFile;
private File lastModifiedFile;
private File lastFetchedFile;
private final List<Subscription> subscriptions;
private final File etagsFile;
private final File lastModifiedFile;
private final File lastFetchedFile;
private final long delay;
private String proxyHost;
private int proxyPort;
private final String proxyHost;
private final int proxyPort;
/**
* Construct a SubscriptionList using the urls from locationsFile and, if
@@ -69,7 +66,7 @@ class SubscriptionList {
public SubscriptionList(File locationsFile, File etagsFile,
File lastModifiedFile, File lastFetchedFile, long delay, List<String> defaultSubs, String proxyHost,
int proxyPort) {
this.subscriptions = new LinkedList<Subscription>();
this.subscriptions = new ArrayList<Subscription>(4);
this.etagsFile = etagsFile;
this.lastModifiedFile = lastModifiedFile;
this.lastFetchedFile = lastFetchedFile;
@@ -84,17 +81,17 @@ class SubscriptionList {
try {
etags = ConfigParser.parse(etagsFile);
} catch (IOException exp) {
etags = new HashMap<String, String>();
etags = Collections.<String, String>emptyMap();
}
try {
lastModified = ConfigParser.parse(lastModifiedFile);
} catch (IOException exp) {
lastModified = new HashMap<String, String>();
lastModified = Collections.<String, String>emptyMap();
}
try {
lastFetched = ConfigParser.parse(lastFetchedFile);
} catch (IOException exp) {
lastFetched = new HashMap<String, String>();
lastFetched = Collections.<String, String>emptyMap();
}
for (String location : locations) {
this.subscriptions.add(new Subscription(location, etags.get(location),
@@ -103,6 +100,24 @@ class SubscriptionList {
}
}
/**
* Testing only.
*
* @param hoststxt path to a local file used as the test 'subscription' input
* @since 0.9.26
*/
public SubscriptionList(String hoststxt) {
File dummy = new File("/dev/null");
this.etagsFile = dummy;
this.lastModifiedFile = dummy;
this.lastFetchedFile = dummy;
this.delay = 0;
this.proxyHost = "127.0.0.1";
this.proxyPort = 4444;
Subscription sub = new Subscription("file:" + hoststxt, null, null, null);
this.subscriptions = Collections.singletonList(sub);
}
/**
* Return an iterator over the AddressBooks represented by the Subscriptions
* in this SubscriptionList.
@@ -121,9 +136,10 @@ class SubscriptionList {
* won't be read back correctly; the '=' should be escaped.
*/
public void write() {
Map<String, String> etags = new HashMap<String, String>();
Map<String, String> lastModified = new HashMap<String, String>();
Map<String, String> lastFetched = new HashMap<String, String>();
int sz = subscriptions.size();
Map<String, String> etags = new HashMap<String, String>(sz);
Map<String, String> lastModified = new HashMap<String, String>(sz);
Map<String, String> lastFetched = new HashMap<String, String>(sz);
for (Subscription sub : this.subscriptions) {
if (sub.getEtag() != null) {
etags.put(sub.getLocation(), sub.getEtag());
@@ -131,13 +147,16 @@ class SubscriptionList {
if (sub.getLastModified() != null) {
lastModified.put(sub.getLocation(), sub.getLastModified());
}
lastFetched.put(sub.getLocation(), "" + sub.getLastFetched());
lastFetched.put(sub.getLocation(), Long.toString(sub.getLastFetched()));
}
try {
ConfigParser.write(etags, this.etagsFile);
} catch (IOException exp) {}
try {
ConfigParser.write(lastModified, this.lastModifiedFile);
} catch (IOException exp) {}
try {
ConfigParser.write(lastFetched, this.lastFetchedFile);
} catch (IOException exp) {
}
} catch (IOException exp) {}
}
}

View File

@@ -0,0 +1,11 @@
<html>
<body>
<p>
The addressbook application, which fetches hosts.txt files from subscription URLs via
HTTP and adds new hosts to the local database.
While implemented as a webapp, this application contains no user interface.
May also be packaged as a jar, as is done for Android.
The webapp named 'addressbook' in the console is actually SusiDNS.
</p>
</body>
</html>

View File

@@ -28,4 +28,11 @@
<url-pattern>/*</url-pattern>
</servlet-mapping>
<!-- this webapp doesn't actually use sessions or cookies -->
<session-config>
<session-timeout>30</session-timeout>
<cookie-config>
<http-only>true</http-only>
</cookie-config>
</session-config>
</web-app>

View File

@@ -11,6 +11,7 @@ import java.util.Iterator;
import java.util.Set;
import net.i2p.data.Hash;
import net.i2p.data.DataHelper
import net.i2p.router.Router;
import net.i2p.router.RouterContext;
import net.i2p.util.I2PThread;
@@ -47,7 +48,7 @@ class AdminRunner implements Runnable {
reply(out, "this is not a website");
} else if ( (command.indexOf("routerStats.html") >= 0) || (command.indexOf("oldstats.jsp") >= 0) ) {
try {
out.write("HTTP/1.1 200 OK\nConnection: close\nCache-control: no-cache\nContent-type: text/html\n\n".getBytes());
out.write(DataHelper.getASCII("HTTP/1.1 200 OK\nConnection: close\nCache-control: no-cache\nContent-type: text/html\n\n"));
_generator.generateStatsPage(new OutputStreamWriter(out));
out.close();
} catch (IOException ioe) {
@@ -61,7 +62,7 @@ class AdminRunner implements Runnable {
reply(out, shutdown(command));
} else if (true || command.indexOf("routerConsole.html") > 0) {
try {
out.write("HTTP/1.1 200 OK\nConnection: close\nCache-control: no-cache\nContent-type: text/html\n\n".getBytes());
out.write(DataHelper.getASCII("HTTP/1.1 200 OK\nConnection: close\nCache-control: no-cache\nContent-type: text/html\n\n"));
_context.router().renderStatusHTML(new OutputStreamWriter(out));
out.close();
} catch (IOException ioe) {
@@ -80,7 +81,7 @@ class AdminRunner implements Runnable {
reply.append("Content-type: text/html\n\n");
reply.append(content);
try {
out.write(reply.toString().getBytes());
out.write(DataHelper.getASCII(reply.toString()));
out.close();
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))
@@ -97,7 +98,7 @@ class AdminRunner implements Runnable {
reply.append("Content-type: text/plain\n\n");
reply.append(content);
try {
out.write(reply.toString().getBytes());
out.write(DataHelper.getASCII(reply.toString()));
out.close();
} catch (IOException ioe) {
if (_log.shouldLog(Log.WARN))

View File

@@ -0,0 +1,99 @@
#Last Modified: Sun Dec 06 12:30:32 2015
# vim:syntax=apparmor et ts=8 sw=4
#include <tunables/global>
$INSTALL_PATH/{i2prouter,runplain.sh} flags=(complain) {
#include <abstractions/base>
#include <abstractions/fonts>
#include <abstractions/nameservice>
#include <abstractions/ssl_certs>
capability sys_ptrace,
network inet stream,
network inet6 stream,
$INSTALL_PATH/ r,
$INSTALL_PATH/{i2psvc,wrapper} rmix,
owner $INSTALL_PATH/** rwkm,
# Needed for Java
owner @{PROC} r,
owner @{PROC}/[0-9]*/ r,
owner @{PROC}/[0-9]*/status r,
owner @{PROC}/[0-9]*/stat r,
owner @{PROC}/[0-9]*/cmdline r,
@{PROC}/uptime r,
@{PROC}/sys/kernel/pid_max r,
/sys/devices/system/cpu/ r,
/sys/devices/system/cpu/** r,
/dev/random r,
/dev/urandom r,
@{PROC}/1/comm r,
/etc/ssl/certs/java/** r,
/etc/timezone r,
/usr/share/javazi/** r,
# Debian
/etc/java-{6,7,8}-openjdk/** r,
/usr/lib/jvm/default-java/jre/bin/java rix,
# Debian, Ubuntu, openSUSE
/usr/lib{,32,64}/jvm/java-*-openjdk-*/jre/bin/java rix,
/usr/lib{,32,64}/jvm/java-*-openjdk-*/jre/bin/keytool rix,
# Raspbian
/usr/lib/jvm/jdk-*-oracle-*/jre/bin/java rix,
/usr/lib/jvm/jdk-*-oracle-*/jre/bin/keytool rix,
# Fonts are needed for I2P's graphs
/usr/share/java/java-atk-wrapper.jar r,
# Used by some plugins
/usr/share/java/eclipse-ecj-*.jar r,
/{,var/}tmp/ rwm,
owner /{,var/}tmp/** rwkm,
/{,usr/}bin/{,b,d}ash rix,
/{,usr/}bin/cat rix,
/{,usr/}bin/cut rix,
/{,usr/}bin/dirname rix,
/{,usr/}bin/expr rix,
/{,usr/}bin/{,g,m}awk rix,
/{,usr/}bin/grep rix,
/{,usr/}bin/id rix,
/{,usr/}bin/ldd rix,
/{,usr/}bin/ls rix,
/{,usr/}bin/mkdir rix,
/{,usr/}bin/nohup rix,
/{,usr/}bin/ps rix,
/{,usr/}bin/rm rix,
/{,usr/}bin/sed rix,
/{,usr/}bin/sleep rix,
/{,usr/}bin/tail rix,
/{,usr/}bin/tr rix,
/{,usr/}bin/uname rix,
/{,usr/}bin/which rix,
@{HOME}/.java/fonts/** r,
owner @{HOME}/.i2p/ rw,
owner @{HOME}/.i2p/** rwk,
# Prevent spamming the logs
deny owner @{HOME}/.java/ wk,
deny @{HOME}/.fontconfig/ wk,
deny @{HOME}/.java/fonts/** w,
deny /dev/tty rw,
deny /dev/pts/[0-9]* rw,
deny @{PROC}/[0-9]*/fd/ r,
deny /usr/local/share/fonts/ r,
deny /var/cache/fontconfig/ wk,
# Used by some versions of the Tanuki wrapper but never used by I2P
deny /usr/share/java/hamcrest*.jar r,
deny /usr/share/java/junit*.jar r,
}

View File

@@ -26,12 +26,12 @@ then
fi
# on windows, one must specify the path of commnad find
# since windows has its own retarded version of find.
# since windows has its own version of find.
if which find|grep -q -i windows ; then
export PATH=.:/bin:/usr/local/bin:$PATH
fi
# Fast mode - update ondemond
# set LG2 to the language you need in envrionment varibales to enable this
# set LG2 to the language you need in environment variables to enable this
# add ../java/ so the refs will work in the po file
JPATHS="src"
@@ -64,19 +64,19 @@ do
echo "Updating the $i file from the tags..."
# extract strings from java and jsp files, and update messages.po files
# translate calls must be one of the forms:
# _("foo")
# _t("foo")
# _x("foo")
# intl._("foo")
# intl._t("foo")
# intl.title("foo")
# handler._("foo")
# formhandler._("foo")
# handler._t("foo")
# formhandler._t("foo")
# net.i2p.router.web.Messages.getString("foo")
# In a jsp, you must use a helper or handler that has the context set.
# To start a new translation, copy the header from an old translation to the new .po file,
# then ant distclean updater.
find $JPATHS -name *.java > $TMPFILE
xgettext -f $TMPFILE -F -L java --from-code=UTF-8 --add-comments\
--keyword=_ --keyword=_x --keyword=intl._ --keyword=intl.title \
--keyword=_t --keyword=_x --keyword=intl._ --keyword=intl.title \
--keyword=handler._ --keyword=formhandler._ \
--keyword=net.i2p.router.web.Messages.getString \
-o ${i}t

View File

@@ -3,20 +3,22 @@
# This file is distributed under the same license as the desktopgui package.
# To contribute translations, see http://www.i2p2.de/newdevelopers
#
# <kia___@hushmail.com>, 2011.
# Translators:
# Aesthese, 2016
# KIA <kia___@hushmail.com>, 2011
msgid ""
msgstr ""
"Project-Id-Version: I2P\n"
"Report-Msgid-Bugs-To: https://trac.i2p2.de/\n"
"POT-Creation-Date: 2011-03-03 18:29+0000\n"
"PO-Revision-Date: 2011-07-12 19:41+0000\n"
"Last-Translator: KIA <kia___@hushmail.com>\n"
"Language-Team: Danish (http://www.transifex.net/projects/p/I2P/team/da/)\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-01-09 19:27+0000\n"
"PO-Revision-Date: 2016-01-08 07:54+0000\n"
"Last-Translator: Aesthese\n"
"Language-Team: Danish (http://www.transifex.com/otf/I2P/language/da/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: da\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: src/net/i2p/desktopgui/ExternalTrayManager.java:23
msgid "Start I2P"
@@ -24,7 +26,7 @@ msgstr "Start I2P"
#: src/net/i2p/desktopgui/ExternalTrayManager.java:38
msgid "I2P is starting!"
msgstr "I2P starter nu!"
msgstr "I2P starter!"
#: src/net/i2p/desktopgui/ExternalTrayManager.java:38
msgid "Starting"
@@ -46,12 +48,10 @@ msgstr "Genstart I2P"
msgid "Stop I2P"
msgstr "Stop I2P"
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:44
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:43
msgid "Tray icon configuration"
msgstr "Konfiguration af processbar ikonet"
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:47
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:46
msgid "Should tray icon be enabled?"
msgstr "Skal processbar ikonet være aktivt?"

View File

@@ -3,20 +3,22 @@
# This file is distributed under the same license as the desktopgui package.
# To contribute translations, see http://www.i2p2.de/newdevelopers
#
# <b790979@klzlk.com>, 2011.
# Translators:
# PolishAnon <b790979@klzlk.com>, 2011
# polacco <polacco@i2pmail.org>, 2015
msgid ""
msgstr ""
"Project-Id-Version: I2P\n"
"Report-Msgid-Bugs-To: https://trac.i2p2.de/\n"
"POT-Creation-Date: 2011-03-03 18:29+0000\n"
"PO-Revision-Date: 2011-05-25 18:36+0000\n"
"Last-Translator: PolishAnon <b790979@klzlk.com>\n"
"Language-Team: Polish (http://www.transifex.net/projects/p/I2P/team/pl/)\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-01-09 19:27+0000\n"
"PO-Revision-Date: 2015-02-17 20:54+0000\n"
"Last-Translator: polacco <polacco@i2pmail.org>\n"
"Language-Team: Polish (http://www.transifex.com/projects/p/I2P/language/pl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: pl\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
#: src/net/i2p/desktopgui/ExternalTrayManager.java:23
msgid "Start I2P"
@@ -32,7 +34,7 @@ msgstr "Uruchamianie"
#: src/net/i2p/desktopgui/InternalTrayManager.java:26
msgid "Launch I2P Browser"
msgstr "Uruchom Przeglądarke I2P"
msgstr "Uruchom przeglądarkę I2P"
#: src/net/i2p/desktopgui/InternalTrayManager.java:50
msgid "Configure desktopgui"
@@ -46,12 +48,10 @@ msgstr "Zrestartuj I2P"
msgid "Stop I2P"
msgstr "Zatrzymaj I2P"
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:44
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:43
msgid "Tray icon configuration"
msgstr "Konfiguracja ikony zasobnika"
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:47
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:46
msgid "Should tray icon be enabled?"
msgstr "Czy ikona zasobnika powinna być aktywna?"

View File

@@ -6,14 +6,15 @@
# Translators:
# testsubject67 <deborinha97@hotmail.com>, 2014
# blueboy, 2013
# blueboy, 2015
msgid ""
msgstr ""
"Project-Id-Version: I2P\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-01-09 19:27+0000\n"
"PO-Revision-Date: 2014-07-05 17:40+0000\n"
"Last-Translator: testsubject67 <deborinha97@hotmail.com>\n"
"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/I2P/language/pt_BR/)\n"
"PO-Revision-Date: 2015-12-23 00:12+0000\n"
"Last-Translator: blueboy\n"
"Language-Team: Portuguese (Brazil) (http://www.transifex.com/otf/I2P/language/pt_BR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -50,7 +51,7 @@ msgstr "Interromper o roteador I2P"
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:43
msgid "Tray icon configuration"
msgstr "Configuração de ícone de bandeja"
msgstr "Configuração do ícone de bandeja"
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:46
msgid "Should tray icon be enabled?"

View File

@@ -6,20 +6,21 @@
# Translators:
# ducki2p <ducki2p@gmail.com>, 2011
# foo <foo@bar>, 2009
# Роман Азаренко <transifex@basicxp.ru>, 2013
# Foster Snowhill, 2013
# brianhopes <voganc-12@live.ru>, 2015
msgid ""
msgstr ""
"Project-Id-Version: I2P\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-03-03 18:29+0000\n"
"PO-Revision-Date: 2013-12-04 11:46+0000\n"
"Last-Translator: Bergitte <alvina_alexandrova@mail.ru>\n"
"Language-Team: Russian (Russia) (http://www.transifex.com/projects/p/I2P/language/ru_RU/)\n"
"POT-Creation-Date: 2014-01-09 19:27+0000\n"
"PO-Revision-Date: 2015-11-01 08:19+0000\n"
"Last-Translator: brianhopes <voganc-12@live.ru>\n"
"Language-Team: Russian (Russia) (http://www.transifex.com/otf/I2P/language/ru_RU/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ru_RU\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
#: src/net/i2p/desktopgui/ExternalTrayManager.java:23
msgid "Start I2P"
@@ -39,7 +40,7 @@ msgstr "Запустить браузер I2P"
#: src/net/i2p/desktopgui/InternalTrayManager.java:50
msgid "Configure desktopgui"
msgstr "Настроить desktopgui"
msgstr "Настроить рабочий стол"
#: src/net/i2p/desktopgui/InternalTrayManager.java:67
msgid "Restart I2P"
@@ -49,10 +50,10 @@ msgstr "Перезапустить I2P"
msgid "Stop I2P"
msgstr "Остановить I2P"
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:44
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:43
msgid "Tray icon configuration"
msgstr "Конфигурация значка в области уведомлений"
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:47
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:46
msgid "Should tray icon be enabled?"
msgstr "Отображать ли значок в области уведомлений?"

View File

@@ -4,16 +4,17 @@
# To contribute translations, see http://www.i2p2.de/newdevelopers
#
# Translators:
# Denis Blank <gribua@gmail.com>, 2011
# Denis Lysenko <gribua@gmail.com>, 2011
# LinuxChata, 2014
# madjong <madjong@i2pmail.org>, 2014
msgid ""
msgstr ""
"Project-Id-Version: I2P\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-01-09 19:27+0000\n"
"PO-Revision-Date: 2014-06-22 10:20+0000\n"
"Last-Translator: LinuxChata\n"
"Language-Team: Ukrainian (Ukraine) (http://www.transifex.com/projects/p/I2P/language/uk_UA/)\n"
"PO-Revision-Date: 2015-08-07 16:31+0000\n"
"Last-Translator: Denis Lysenko <gribua@gmail.com>\n"
"Language-Team: Ukrainian (Ukraine) (http://www.transifex.com/otf/I2P/language/uk_UA/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -50,7 +51,7 @@ msgstr "Зупинити I2P"
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:43
msgid "Tray icon configuration"
msgstr "Настройка трей-іконки"
msgstr "Налаштування трей-іконки"
#: src/net/i2p/desktopgui/gui/DesktopguiConfigurationFrame.java:46
msgid "Should tray icon be enabled?"

View File

@@ -20,7 +20,7 @@ public class ExternalTrayManager extends TrayManager {
@Override
public PopupMenu getMainMenu() {
PopupMenu popup = new PopupMenu();
MenuItem startItem = new MenuItem(_("Start I2P"));
MenuItem startItem = new MenuItem(_t("Start I2P"));
startItem.addActionListener(new ActionListener() {
@Override
@@ -35,7 +35,7 @@ public class ExternalTrayManager extends TrayManager {
@Override
protected void done() {
trayIcon.displayMessage(_("Starting"), _("I2P is starting!"), TrayIcon.MessageType.INFO);
trayIcon.displayMessage(_t("Starting"), _t("I2P is starting!"), TrayIcon.MessageType.INFO);
//Hide the tray icon.
//We cannot stop the desktopgui program entirely,
//since that risks killing the I2P process as well.

View File

@@ -23,7 +23,7 @@ public class InternalTrayManager extends TrayManager {
public PopupMenu getMainMenu() {
PopupMenu popup = new PopupMenu();
MenuItem browserLauncher = new MenuItem(_("Launch I2P Browser"));
MenuItem browserLauncher = new MenuItem(_t("Launch I2P Browser"));
browserLauncher.addActionListener(new ActionListener() {
@Override
@@ -47,7 +47,7 @@ public class InternalTrayManager extends TrayManager {
}.execute();
}
});
MenuItem desktopguiConfigurationLauncher = new MenuItem(_("Configure desktopgui"));
MenuItem desktopguiConfigurationLauncher = new MenuItem(_t("Configure desktopgui"));
desktopguiConfigurationLauncher.addActionListener(new ActionListener() {
@Override
@@ -64,7 +64,7 @@ public class InternalTrayManager extends TrayManager {
}
});
MenuItem restartItem = new MenuItem(_("Restart I2P"));
MenuItem restartItem = new MenuItem(_t("Restart I2P"));
restartItem.addActionListener(new ActionListener() {
@Override
@@ -82,7 +82,7 @@ public class InternalTrayManager extends TrayManager {
}
});
MenuItem stopItem = new MenuItem(_("Stop I2P"));
MenuItem stopItem = new MenuItem(_t("Stop I2P"));
stopItem.addActionListener(new ActionListener() {
@Override

View File

@@ -78,7 +78,7 @@ public abstract class TrayManager {
return image;
}
protected static String _(String s) {
return DesktopguiTranslator._(s);
protected static String _t(String s) {
return DesktopguiTranslator._t(s);
}
}

View File

@@ -40,10 +40,10 @@ public class DesktopguiConfigurationFrame extends javax.swing.JFrame {
cancelButton = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
setTitle(_("Tray icon configuration"));
setTitle(_t("Tray icon configuration"));
desktopguiEnabled.setSelected(true);
desktopguiEnabled.setText(_("Should tray icon be enabled?"));
desktopguiEnabled.setText(_t("Should tray icon be enabled?"));
desktopguiEnabled.setActionCommand("shouldDesktopguiBeEnabled");
okButton.setText("OK");
@@ -98,8 +98,8 @@ public class DesktopguiConfigurationFrame extends javax.swing.JFrame {
configureDesktopgui();
}//GEN-LAST:event_okButtonMouseReleased
protected static String _(String s) {
return DesktopguiTranslator._(s);
protected static String _t(String s) {
return DesktopguiTranslator._t(s);
}
private void configureDesktopgui() {

View File

@@ -16,11 +16,11 @@ public class DesktopguiTranslator {
return ctx;
}
public static String _(String s) {
public static String _t(String s) {
return Translate.getString(s, getRouterContext(), BUNDLE_NAME);
}
public static String _(String s, Object o) {
public static String _t(String s, Object o) {
return Translate.getString(s, o, getRouterContext(), BUNDLE_NAME);
}
}

View File

@@ -1,340 +1 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.
See ../../licenses/LICENSE-GPLv2.txt

View File

@@ -1,24 +0,0 @@
- I2PSnark:
- add multitorrent support by checking the metainfo hash in the
PeerAcceptor and feeding it off to the appropriate coordinator
- add a web interface
- BEncode
- Byte array length indicator can overflow.
- Support really big BigNums (only 256 chars allowed now)
- Better BEValue toString(). Uses stupid heuristic now for debugging.
- Implemented bencoding.
- Remove application level hack to calculate sha1 hash for metainfo
(But can it be done as efficiently?)
- Storage
- Check file name filter.
- TrackerClient
- Support undocumented &numwant= request.
- PeerCoordinator
- Disconnect from other seeds as soon as you are a seed yourself.
- Text UI
- Make it completely silent.

View File

@@ -18,6 +18,8 @@
<pathelement location="../../../core/java/build/obj" />
<pathelement location="../../ministreaming/java/build/obj" />
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
<!-- jsp-api.jar only present for debian builds -->
<pathelement location="../../jetty/jettylib/jsp-api.jar" />
</classpath>
</depend>
</target>
@@ -37,9 +39,15 @@
srcdir="./src"
debug="true" deprecation="on" source="${javac.version}" target="${javac.version}"
destdir="./build/obj"
includeAntRuntime="false"
classpath="../../../core/java/build/i2p.jar:../../jetty/jettylib/javax.servlet.jar:../../ministreaming/java/build/mstreaming.jar" >
includeAntRuntime="false" >
<compilerarg line="${javac.compilerargs}" />
<classpath>
<pathelement location="../../../core/java/build/i2p.jar" />
<pathelement location="../../ministreaming/java/build/mstreaming.jar" />
<pathelement location="../../jetty/jettylib/javax.servlet.jar" />
<!-- jsp-api.jar only present for debian builds -->
<pathelement location="../../jetty/jettylib/jsp-api.jar" />
</classpath>
</javac>
</target>
@@ -62,7 +70,7 @@
<property name="workspace.changes.tr" value="" />
<jar destfile="./build/i2psnark.jar" basedir="./build/obj" includes="**/*.class" excludes="**/web/* **/messages_*.class">
<manifest>
<attribute name="Main-Class" value="org.klomp.snark.Snark" />
<attribute name="Main-Class" value="org.klomp.snark.CommandLine" />
<attribute name="Class-Path" value="i2p.jar mstreaming.jar streaming.jar" />
<attribute name="Implementation-Version" value="${full.version}" />
<attribute name="Built-By" value="${build.built-by}" />

View File

@@ -25,12 +25,12 @@ then
fi
# on windows, one must specify the path of commnad find
# since windows has its own retarded version of find.
# since windows has its own version of find.
if which find|grep -q -i windows ; then
export PATH=.:/bin:/usr/local/bin:$PATH
fi
# Fast mode - update ondemond
# set LG2 to the language you need in envrionment varibales to enable this
# set LG2 to the language you need in environment variables to enable this
# add ../java/ so the refs will work in the po file
JPATHS="../java/src"
@@ -63,13 +63,13 @@ do
echo "Updating the $i file from the tags..."
# extract strings from java and jsp files, and update messages.po files
# translate calls must be one of the forms:
# _("foo")
# _t("foo")
# _x("foo")
# To start a new translation, copy the header from an old translation to the new .po file,
# then ant distclean poupdate.
find $JPATHS -name *.java > $TMPFILE
xgettext -f $TMPFILE -F -L java --from-code=UTF-8 --add-comments\
--keyword=_ --keyword=_x \
--keyword=_t --keyword=_x \
-o ${i}t
if [ $? -ne 0 ]
then

View File

@@ -20,6 +20,8 @@
package org.klomp.snark;
import java.util.Arrays;
/**
* Container of a byte array representing set and unset bits.
@@ -66,7 +68,7 @@ public class BitField
/**
* This returns the actual byte array used. Changes to this array
* effect this BitField. Note that some bits at the end of the byte
* affect this BitField. Note that some bits at the end of the byte
* array are supposed to be always unset if they represent bits
* bigger then the size of the bitfield.
*/
@@ -105,6 +107,37 @@ public class BitField
}
}
/**
* Sets the given bit to false.
*
* @exception IndexOutOfBoundsException if bit is smaller then zero
* bigger then size (inclusive).
* @since 0.9.22
*/
public void clear(int bit)
{
if (bit < 0 || bit >= size)
throw new IndexOutOfBoundsException(Integer.toString(bit));
int index = bit/8;
int mask = 128 >> (bit % 8);
synchronized(this) {
if ((bitfield[index] & mask) != 0) {
count--;
bitfield[index] &= ~mask;
}
}
}
/**
* Sets all bits to true.
*
* @since 0.9.21
*/
public void setAll() {
Arrays.fill(bitfield, (byte) 0xff);
count = size;
}
/**
* Return true if the bit is set or false if it is not.
*

View File

@@ -0,0 +1,45 @@
package org.klomp.snark;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.i2p.CoreVersion;
/**
* Simple command line access to various utilities.
* Not a public API. Subject to change.
* Apps and plugins should use specific classes.
*
* @since 0.9.26
*/
public class CommandLine extends net.i2p.util.CommandLine {
protected static final List<String> SCLASSES = Arrays.asList(new String[] {
"org.klomp.snark.MetaInfo",
//"org.klomp.snark.Snark",
//"org.klomp.snark.StaticSnark",
"org.klomp.snark.Storage",
"org.klomp.snark.bencode.BDecoder",
//"org.klomp.snark.web.RunStandalone",
});
protected CommandLine() {}
public static void main(String args[]) {
List<String> classes = new ArrayList<String>(SCLASSES.size() + CLASSES.size());
classes.addAll(SCLASSES);
classes.addAll(CLASSES);
if (args.length > 0) {
exec(args, classes);
}
usage(classes);
System.exit(1);
}
private static void usage(List<String> classes) {
System.err.println("I2PSnark version " + CoreVersion.VERSION + '\n' +
"USAGE: java -jar /path/to/i2psnark.jar command [args]");
printCommands(classes);
}
}

View File

@@ -24,6 +24,7 @@ import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
@@ -213,6 +214,20 @@ class ConnectionAcceptor implements Runnable
}
}
}
catch (ConnectException ioe)
{
// This is presumed to be due to socket closing by I2PSnarkUtil.disconnect(),
// which does not currently call our halt(), although it should
if (_log.shouldWarn())
_log.warn("Error while accepting", ioe);
synchronized(this) {
if (!stop) {
locked_halt();
thread = null;
stop = true;
}
}
}
catch (IOException ioe)
{
int level = stop ? Log.WARN : Log.ERROR;

View File

@@ -42,7 +42,7 @@ abstract class ExtensionHandler {
* @param dht advertise DHT capability
* @return bencoded outgoing handshake message
*/
public static byte[] getHandshake(int metasize, boolean pexAndMetadata, boolean dht) {
public static byte[] getHandshake(int metasize, boolean pexAndMetadata, boolean dht, boolean uploadOnly) {
Map<String, Object> handshake = new HashMap<String, Object>();
Map<String, Integer> m = new HashMap<String, Integer>();
if (pexAndMetadata) {
@@ -59,6 +59,9 @@ abstract class ExtensionHandler {
handshake.put("p", Integer.valueOf(TrackerClient.PORT));
handshake.put("v", "I2PSnark");
handshake.put("reqq", Integer.valueOf(5));
// BEP 21
if (uploadOnly)
handshake.put("upload_only", Integer.valueOf(1));
return BEncoder.bencode(handshake);
}
@@ -90,17 +93,20 @@ abstract class ExtensionHandler {
peer.setHandshakeMap(map);
Map<String, BEValue> msgmap = map.get("m").getMap();
if (msgmap.get(TYPE_PEX) != null) {
if (log.shouldLog(Log.DEBUG))
log.debug("Peer supports PEX extension: " + peer);
// peer state calls peer listener calls sendPEX()
}
if (log.shouldLog(Log.DEBUG))
log.debug("Peer " + peer + " supports extensions: " + msgmap.keySet());
if (msgmap.get(TYPE_DHT) != null) {
if (log.shouldLog(Log.DEBUG))
log.debug("Peer supports DHT extension: " + peer);
// peer state calls peer listener calls sendDHT()
}
//if (msgmap.get(TYPE_PEX) != null) {
// if (log.shouldLog(Log.DEBUG))
// log.debug("Peer supports PEX extension: " + peer);
// // peer state calls peer listener calls sendPEX()
//}
//if (msgmap.get(TYPE_DHT) != null) {
// if (log.shouldLog(Log.DEBUG))
// log.debug("Peer supports DHT extension: " + peer);
// // peer state calls peer listener calls sendDHT()
//}
MagnetState state = peer.getMagnetState();
@@ -204,30 +210,31 @@ abstract class ExtensionHandler {
if (log.shouldLog(Log.DEBUG))
log.debug("Got request for " + piece + " from: " + peer);
byte[] pc;
int totalSize;
synchronized(state) {
pc = state.getChunk(piece);
totalSize = state.getSize();
}
sendPiece(peer, piece, pc);
sendPiece(peer, piece, pc, totalSize);
// Do this here because PeerConnectionOut only reports for PIECE messages
peer.uploaded(pc.length);
listener.uploaded(peer, pc.length);
} else if (type == TYPE_DATA) {
int size = map.get("total_size").getInt();
if (log.shouldLog(Log.DEBUG))
log.debug("Got data for " + piece + " length " + size + " from: " + peer);
// On close reading of BEP 9, this is the total metadata size.
// Prior to 0.9.21, we sent the piece size, so we can't count on it.
// just ignore it. The actual length will be verified in saveChunk()
//int size = map.get("total_size").getInt();
//if (log.shouldLog(Log.DEBUG))
// log.debug("Got data for " + piece + " length " + size + " from: " + peer);
boolean done;
int chk = -1;
synchronized(state) {
if (state.isComplete())
return;
int len = is.available();
if (len != size) {
// probably fatal
if (log.shouldLog(Log.WARN))
log.warn("total_size " + size + " but avail data " + len);
}
peer.downloaded(len);
listener.downloaded(peer, len);
// this checks the size
done = state.saveChunk(piece, bs, bs.length - len, len);
if (log.shouldLog(Log.INFO))
log.info("Got chunk " + piece + " from " + peer);
@@ -290,11 +297,15 @@ abstract class ExtensionHandler {
}
}
private static void sendPiece(Peer peer, int piece, byte[] data) {
private static void sendPiece(Peer peer, int piece, byte[] data, int totalSize) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("msg_type", Integer.valueOf(TYPE_DATA));
map.put("piece", Integer.valueOf(piece));
map.put("total_size", Integer.valueOf(data.length));
// BEP 9
// "This key has the same semantics as the 'metadata_size' in the extension header"
// which apparently means the same value. Fixed in 0.9.21.
//map.put("total_size", Integer.valueOf(data.length));
map.put("total_size", Integer.valueOf(totalSize));
byte[] dict = BEncoder.bencode(map);
byte[] payload = new byte[dict.length + data.length];
System.arraycopy(dict, 0, payload, 0, dict.length);

View File

@@ -3,8 +3,8 @@ package org.klomp.snark;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -14,6 +14,7 @@ import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.I2PException;
import net.i2p.client.I2PClient;
import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException;
import net.i2p.client.streaming.I2PServerSocket;
@@ -74,8 +75,7 @@ public class I2PSnarkUtil {
private static final int EEPGET_CONNECT_TIMEOUT_SHORT = 5*1000;
public static final int DEFAULT_STARTUP_DELAY = 3;
public static final boolean DEFAULT_USE_OPENTRACKERS = true;
public static final int DEFAULT_MAX_UP_BW = 8; //KBps
public static final int MAX_CONNECTIONS = 16; // per torrent
public static final int MAX_CONNECTIONS = 24; // per torrent
public static final String PROP_MAX_BW = "i2cp.outboundBytesPerSecond";
public static final boolean DEFAULT_USE_DHT = true;
public static final String EEPGET_USER_AGENT = "I2PSnark";
@@ -97,7 +97,7 @@ public class I2PSnarkUtil {
setI2CPConfig("127.0.0.1", 7654, null);
_banlist = new ConcurrentHashSet<Hash>();
_maxUploaders = Snark.MAX_TOTAL_UPLOADERS;
_maxUpBW = DEFAULT_MAX_UP_BW;
_maxUpBW = SnarkManager.DEFAULT_MAX_UP_BW;
_maxConnections = MAX_CONNECTIONS;
_startupDelay = DEFAULT_STARTUP_DELAY;
_shouldUseOT = DEFAULT_USE_OPENTRACKERS;
@@ -106,8 +106,8 @@ public class I2PSnarkUtil {
// This is used for both announce replies and .torrent file downloads,
// so it must be available even if not connected to I2CP.
// so much for multiple instances
_tmpDir = new SecureDirectory(ctx.getTempDir(), baseName);
FileUtil.rmdir(_tmpDir, false);
_tmpDir = new SecureDirectory(ctx.getTempDir(), baseName + '-' + ctx.random().nextInt());
//FileUtil.rmdir(_tmpDir, false);
_tmpDir.mkdirs();
}
@@ -136,6 +136,7 @@ public class I2PSnarkUtil {
public boolean configured() { return _configured; }
@SuppressWarnings({"unchecked", "rawtypes"})
public void setI2CPConfig(String i2cpHost, int i2cpPort, Map opts) {
if (i2cpHost != null)
_i2cpHost = i2cpHost;
@@ -256,6 +257,8 @@ public class I2PSnarkUtil {
opts.setProperty("i2p.streaming.disableRejectLogging", "true");
if (opts.getProperty("i2p.streaming.answerPings") == null)
opts.setProperty("i2p.streaming.answerPings", "false");
if (opts.getProperty(I2PClient.PROP_SIGTYPE) == null)
opts.setProperty(I2PClient.PROP_SIGTYPE, "EdDSA_SHA512_Ed25519");
_manager = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, opts);
_connecting = false;
}
@@ -329,7 +332,7 @@ public class I2PSnarkUtil {
return rv;
} catch (I2PException ie) {
_banlist.add(dest);
_context.simpleScheduler().addEvent(new Unbanlist(dest), 10*60*1000);
_context.simpleTimer2().addEvent(new Unbanlist(dest), 10*60*1000);
IOException ioe = new IOException("Unable to reach the peer " + peer);
ioe.initCause(ie);
throw ioe;
@@ -457,7 +460,7 @@ public class I2PSnarkUtil {
return null;
}
String getOurIPString() {
public String getOurIPString() {
Destination dest = getMyDestination();
if (dest != null)
return dest.toBase64();
@@ -587,10 +590,10 @@ public class I2PSnarkUtil {
*/
public boolean isKnownOpenTracker(String url) {
try {
URL u = new URL(url);
URI u = new URI(url);
String host = u.getHost();
return host != null && SnarkManager.KNOWN_OPENTRACKERS.contains(host);
} catch (MalformedURLException mue) {
} catch (URISyntaxException use) {
return false;
}
}
@@ -658,7 +661,7 @@ public class I2PSnarkUtil {
* The {0} will be replaced by the parameter.
* Single quotes must be doubled, i.e. ' -> '' in the string.
* @param o parameter, not translated.
* To tranlslate parameter also, use _("foo {0} bar", _("baz"))
* To translate parameter also, use _t("foo {0} bar", _t("baz"))
* Do not double the single quotes in the parameter.
* Use autoboxing to call with ints, longs, floats, etc.
*/

View File

@@ -29,6 +29,9 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
private int _consec;
private int _consecNotRunning;
private boolean _isIdle;
private String _lastIn = "3";
private String _lastOut = "3";
private final Object _lock = new Object();
private static final long CHECK_TIME = 63*1000;
private static final int MAX_CONSEC_IDLE = 4;
@@ -46,16 +49,19 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
}
public void timeReached() {
synchronized (_lock) {
locked_timeReached();
}
}
private void locked_timeReached() {
if (_util.connected()) {
boolean torrentRunning = false;
boolean hasPeers = false;
int peerCount = 0;
for (PeerCoordinator pc : _pcs) {
if (!pc.halted()) {
torrentRunning = true;
if (pc.getPeers() > 0) {
hasPeers = true;
break;
}
peerCount += pc.getPeers();
}
}
@@ -73,19 +79,22 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
}
}
if (hasPeers) {
if (_isIdle)
restoreTunnels();
if (peerCount > 0) {
restoreTunnels(peerCount);
} else {
if (!_isIdle) {
if (_consec++ >= MAX_CONSEC_IDLE)
reduceTunnels();
else
restoreTunnels(1); // pretend we have one peer for now
}
}
} else {
_isIdle = false;
_consec = 0;
_consecNotRunning = 0;
_lastIn = "3";
_lastOut = "3";
}
schedule(CHECK_TIME);
}
@@ -101,26 +110,50 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
}
/**
* Restore tunnel count
* Restore or adjust tunnel count based on current peer count
* @param peerCount greater than zero
*/
private void restoreTunnels() {
_isIdle = false;
if (_log.shouldLog(Log.INFO))
private void restoreTunnels(int peerCount) {
if (_isIdle && _log.shouldLog(Log.INFO))
_log.info("Restoring tunnels on activity");
_isIdle = false;
Map<String, String> opts = _util.getI2CPOptions();
String i = opts.get("inbound.quantity");
if (i == null)
i = "3";
i = Integer.toString(SnarkManager.DEFAULT_TUNNEL_QUANTITY);
String o = opts.get("outbound.quantity");
if (o == null)
o = "3";
o = Integer.toString(SnarkManager.DEFAULT_TUNNEL_QUANTITY);
String ib = opts.get("inbound.backupQuantity");
if (ib == null)
ib = "0";
String ob= opts.get("outbound.backupQuantity");
if (ob == null)
ob = "0";
setTunnels(i, o, ib, ob);
// we don't need more tunnels than we have peers, reduce if so
// reduce to max(peerCount / 2, 2)
int in, out;
try {
in = Integer.parseInt(i);
} catch (NumberFormatException nfe) {
in = 3;
}
try {
out = Integer.parseInt(o);
} catch (NumberFormatException nfe) {
out = 3;
}
int target = Math.max(peerCount / 2, 2);
if (target < in && in > 2) {
in = target;
i = Integer.toString(in);
}
if (target < out && out > 2) {
out = target;
o = Integer.toString(out);
}
if (!(_lastIn.equals(i) && _lastOut.equals(o)))
setTunnels(i, o, ib, ob);
}
/**
@@ -132,12 +165,16 @@ class IdleChecker extends SimpleTimer2.TimedEvent {
if (mgr != null) {
I2PSession sess = mgr.getSession();
if (sess != null) {
if (_log.shouldLog(Log.INFO))
_log.info("New tunnel settings " + i + " / " + o + " / " + ib + " / " + ob);
Properties newProps = new Properties();
newProps.setProperty("inbound.quantity", i);
newProps.setProperty("outbound.quantity", o);
newProps.setProperty("inbound.backupQuantity", ib);
newProps.setProperty("outbound.backupQuantity", ob);
sess.updateOptions(newProps);
_lastIn = i;
_lastOut = o;
}
}
}

View File

@@ -55,11 +55,13 @@ class Message
byte type;
// Used for HAVE, REQUEST, PIECE and CANCEL messages.
// Also SUGGEST, REJECT, ALLOWED_FAST
// low byte used for EXTENSION message
// low two bytes used for PORT message
int piece;
// Used for REQUEST, PIECE and CANCEL messages.
// Also REJECT
int begin;
int length;
@@ -104,15 +106,18 @@ class Message
int datalen = 1;
// piece is 4 bytes.
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL)
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL ||
type == SUGGEST || type == REJECT || type == ALLOWED_FAST)
datalen += 4;
// begin/offset is 4 bytes
if (type == REQUEST || type == PIECE || type == CANCEL)
if (type == REQUEST || type == PIECE || type == CANCEL ||
type == REJECT)
datalen += 4;
// length is 4 bytes
if (type == REQUEST || type == CANCEL)
if (type == REQUEST || type == CANCEL ||
type == REJECT)
datalen += 4;
// msg type is 1 byte
@@ -131,15 +136,18 @@ class Message
dos.writeByte(type & 0xFF);
// Send additional info (piece number)
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL)
if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL ||
type == SUGGEST || type == REJECT || type == ALLOWED_FAST)
dos.writeInt(piece);
// Send additional info (begin/offset)
if (type == REQUEST || type == PIECE || type == CANCEL)
if (type == REQUEST || type == PIECE || type == CANCEL ||
type == REJECT)
dos.writeInt(begin);
// Send additional info (length); for PIECE this is implicit.
if (type == REQUEST || type == CANCEL)
if (type == REQUEST || type == CANCEL ||
type == REJECT)
dos.writeInt(length);
if (type == EXTENSION)
@@ -173,21 +181,32 @@ class Message
case UNINTERESTED:
return "UNINTERESTED";
case HAVE:
return "HAVE(" + piece + ")";
return "HAVE(" + piece + ')';
case BITFIELD:
return "BITFIELD";
case REQUEST:
return "REQUEST(" + piece + "," + begin + "," + length + ")";
return "REQUEST(" + piece + ',' + begin + ',' + length + ')';
case PIECE:
return "PIECE(" + piece + "," + begin + "," + length + ")";
return "PIECE(" + piece + ',' + begin + ',' + length + ')';
case CANCEL:
return "CANCEL(" + piece + "," + begin + "," + length + ")";
return "CANCEL(" + piece + ',' + begin + ',' + length + ')';
case PORT:
return "PORT(" + piece + ")";
return "PORT(" + piece + ')';
case EXTENSION:
return "EXTENSION(" + piece + ',' + data.length + ')';
// fast extensions below here
case SUGGEST:
return "SUGGEST(" + piece + ')';
case HAVE_ALL:
return "HAVE_ALL";
case HAVE_NONE:
return "HAVE_NONE";
case REJECT:
return "REJECT(" + piece + ',' + begin + ',' + length + ')';
case ALLOWED_FAST:
return "ALLOWED_FAST(" + piece + ')';
default:
return "<UNKNOWN>";
return "UNKNOWN (" + type + ')';
}
}
}

View File

@@ -74,10 +74,11 @@ public class MetaInfo
* @param files null for single-file torrent
* @param lengths null for single-file torrent
* @param announce_list may be null
* @param created_by may be null
*/
MetaInfo(String announce, String name, String name_utf8, List<List<String>> files, List<Long> lengths,
int piece_length, byte[] piece_hashes, long length, boolean privateTorrent,
List<List<String>> announce_list)
List<List<String>> announce_list, String created_by)
{
this.announce = announce;
this.name = name;
@@ -91,8 +92,8 @@ public class MetaInfo
this.privateTorrent = privateTorrent;
this.announce_list = announce_list;
this.comment = null;
this.created_by = null;
this.creation_date = 0;
this.created_by = created_by;
this.creation_date = I2PAppContext.getGlobalContext().clock().now();
// TODO if we add a parameter for other keys
//if (other != null) {
@@ -444,7 +445,7 @@ public class MetaInfo
/**
* The creation date (ms) or zero.
* Not available for locally-created torrents.
* As of 0.9.19, available for locally-created torrents.
* @since 0.9.7
*/
public long getCreationDate() {
@@ -595,6 +596,14 @@ public class MetaInfo
m.put("announce", announce);
if (announce_list != null)
m.put("announce-list", announce_list);
// misc. optional top-level stuff
if (comment != null)
m.put("comment", comment);
if (created_by != null)
m.put("created by", created_by);
if (creation_date != 0)
m.put("creation date", creation_date / 1000);
Map<String, BEValue> info = createInfoMap();
m.put("info", info);
// don't save this locally, we should only do this once

View File

@@ -108,7 +108,8 @@ class PartialPiece implements Comparable<PartialPiece> {
/**
* Convert this PartialPiece to a request for the next chunk.
* Used by PeerState only.
* Used by PeerState only. This depends on the downloaded value
* as set by setDownloaded() or read().
*/
public Request getRequest() {
@@ -128,14 +129,16 @@ class PartialPiece implements Comparable<PartialPiece> {
}
/**
* How many bytes are good - only valid by setDownloaded()
* How many bytes are good - as set by setDownloaded() or read()
*/
public int getDownloaded() {
return this.off;
}
/**
* Call this before returning a PartialPiece to the PeerCoordinator
* Call this if necessary before returning a PartialPiece to the PeerCoordinator.
* We do not use a bitmap to track individual chunks received.
* Any chunks after a 'hole' will be lost.
* @since 0.9.1
*/
public void setDownloaded(int offset) {
@@ -191,11 +194,20 @@ class PartialPiece implements Comparable<PartialPiece> {
/**
* Blocking.
* If offset matches the previous downloaded amount
* (as set by a previous call to read() or setDownlaoded()),
* the downloaded amount will be incremented by len.
*
* @since 0.9.1
*/
public void read(DataInputStream din, int off, int len) throws IOException {
public void read(DataInputStream din, int offset, int len) throws IOException {
if (bs != null) {
din.readFully(bs, off, len);
din.readFully(bs, offset, len);
synchronized (this) {
// only works for in-order chunks
if (this.off == offset)
this.off += len;
}
} else {
// read in fully before synching on raf
ByteArray ba;
@@ -211,8 +223,11 @@ class PartialPiece implements Comparable<PartialPiece> {
synchronized (this) {
if (raf == null)
createTemp();
raf.seek(off);
raf.seek(offset);
raf.write(tmp);
// only works for in-order chunks
if (this.off == offset)
this.off += len;
}
if (ba != null)
_cache.release(ba, false);

View File

@@ -62,7 +62,7 @@ public class Peer implements Comparable<Peer>
// Keeps state for in/out connections. Non-null when the handshake
// was successful, the connection setup and runs
PeerState state;
volatile PeerState state;
/** shared across all peers on this torrent */
MagnetState magnetState;
@@ -79,15 +79,15 @@ public class Peer implements Comparable<Peer>
private long uploaded_old[] = {-1,-1,-1};
private long downloaded_old[] = {-1,-1,-1};
// bytes per bt spec: 0011223344556677
static final long OPTION_EXTENSION = 0x0000000000100000l;
static final long OPTION_FAST = 0x0000000000000004l;
static final long OPTION_DHT = 0x0000000000000001l;
// bytes per bt spec: 0011223344556677
private static final long OPTION_EXTENSION = 0x0000000000100000l;
private static final long OPTION_FAST = 0x0000000000000004l;
//private static final long OPTION_DHT = 0x0000000000000001l;
/** we use a different bit since the compact format is different */
/* no, let's use an extension message
static final long OPTION_I2P_DHT = 0x0000000040000000l;
*/
static final long OPTION_AZMP = 0x1000000000000000l;
//private static final long OPTION_AZMP = 0x1000000000000000l;
private long options;
/**
@@ -194,6 +194,7 @@ public class Peer implements Comparable<Peer>
* Compares the PeerIDs.
* @deprecated unused?
*/
@Deprecated
public int compareTo(Peer p)
{
int rv = peerID.compareTo(p.peerID);
@@ -217,9 +218,11 @@ public class Peer implements Comparable<Peer>
*
* If the given BitField is non-null it is send to the peer as first
* message.
*
* @param uploadOnly if we are complete with skipped files, i.e. a partial seed
*/
public void runConnection(I2PSnarkUtil util, PeerListener listener, BitField bitfield, MagnetState mState)
{
public void runConnection(I2PSnarkUtil util, PeerListener listener, BitField bitfield,
MagnetState mState, boolean uploadOnly) {
if (state != null)
throw new IllegalStateException("Peer already started");
@@ -275,17 +278,9 @@ public class Peer implements Comparable<Peer>
int metasize = metainfo != null ? metainfo.getInfoBytes().length : -1;
boolean pexAndMetadata = metainfo == null || !metainfo.isPrivate();
boolean dht = util.getDHT() != null;
out.sendExtension(0, ExtensionHandler.getHandshake(metasize, pexAndMetadata, dht));
out.sendExtension(0, ExtensionHandler.getHandshake(metasize, pexAndMetadata, dht, uploadOnly));
}
// Old DHT PORT message
//if ((options & OPTION_I2P_DHT) != 0 && util.getDHT() != null) {
// if (_log.shouldLog(Log.DEBUG))
// _log.debug("Peer supports DHT, sending PORT message");
// int port = util.getDHT().getPort();
// out.sendPort(port);
//}
// Send our bitmap
if (bitfield != null)
s.out.sendBitfield(bitfield);
@@ -297,7 +292,7 @@ public class Peer implements Comparable<Peer>
if (_log.shouldLog(Log.DEBUG))
_log.debug("Start running the reader with " + toString());
// Use this thread for running the incomming connection.
// Use this thread for running the incoming connection.
// The outgoing connection creates its own Thread.
out.startup();
Thread.currentThread().setName("Snark reader from " + peerID);
@@ -338,6 +333,9 @@ public class Peer implements Comparable<Peer>
dout.write("BitTorrent protocol".getBytes("UTF-8"));
// Handshake write - options
long myOptions = OPTION_EXTENSION;
// we can't handle HAVE_ALL or HAVE_NONE if we don't know the number of pieces
if (metainfo != null)
myOptions |= OPTION_FAST;
// FIXME get util here somehow
//if (util.getDHT() != null)
// myOptions |= OPTION_I2P_DHT;
@@ -385,15 +383,15 @@ public class Peer implements Comparable<Peer>
if (options != 0) {
// send them something in runConnection() above
if (_log.shouldLog(Log.DEBUG))
_log.debug("Peer supports options 0x" + Long.toString(options, 16) + ": " + toString());
_log.debug("Peer supports options 0x" + Long.toHexString(options) + ": " + toString());
}
return bs;
}
/** @since 0.8.4 */
public long getOptions() {
return options;
/** @since 0.9.21 */
public boolean supportsFast() {
return (options & OPTION_FAST) != 0;
}
/** @since 0.8.4 */
@@ -534,6 +532,7 @@ public class Peer implements Comparable<Peer>
* @deprecated deadlocks
* @since 0.8.1
*/
@Deprecated
boolean isRequesting(int p) {
PeerState s = state;
return s != null && s.isRequesting(p);
@@ -566,6 +565,7 @@ public class Peer implements Comparable<Peer>
* us then we start downloading from it. Has no effect when not connected.
* @deprecated unused
*/
@Deprecated
public void setInteresting(boolean interest)
{
PeerState s = state;

View File

@@ -75,6 +75,8 @@ class PeerCheckerTask implements Runnable
List<Peer> removed = new ArrayList<Peer>();
int uploadLimit = coordinator.allowedUploaders();
boolean overBWLimit = coordinator.overUpBWLimit();
if (_log.shouldLog(Log.DEBUG))
_log.debug("peers: " + peerList.size() + " limit: " + uploadLimit + " overBW? " + overBWLimit);
DHT dht = _util.getDHT();
for (Peer peer : peerList) {
@@ -265,7 +267,23 @@ class PeerCheckerTask implements Runnable
// close out unused files, but we don't need to do it every time
Storage storage = coordinator.getStorage();
if (storage != null && (_runCount % 4) == 0) {
if (storage != null) {
// The more files a torrent has, the more often we call the cleaner,
// to keep from running out of FDs
int files = storage.getFileCount();
int skip;
if (files == 1)
skip = 6;
else if (files <= 4)
skip = 4;
else if (files <= 20)
skip = 3;
else if (files <= 50)
skip = 2;
else
skip = 1;
if ((_runCount % skip) == 0)
storage.cleanRAFs();
}

View File

@@ -39,7 +39,7 @@ class PeerConnectionIn implements Runnable
private static final int MAX_MSG_SIZE = Math.max(PeerState.PARTSIZE + 9,
MagnetState.CHUNK_SIZE + 100); // 100 for the ext msg dictionary
private Thread thread;
private volatile Thread thread;
private volatile boolean quit;
long lastRcvd;
@@ -75,9 +75,12 @@ class PeerConnectionIn implements Runnable
thread = Thread.currentThread();
try
{
PeerState ps = peer.state;
while (!quit && ps != null)
while (!quit)
{
final PeerState ps = peer.state;
if (ps == null)
break;
// Common variables used for some messages.
int piece;
int begin;
@@ -91,59 +94,64 @@ class PeerConnectionIn implements Runnable
if (i == 0)
{
ps.keepAliveMessage();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received keepalive from " + peer);
ps.keepAliveMessage();
continue;
}
byte b = din.readByte();
Message m = new Message();
m.type = b;
switch (b)
{
case 0:
ps.chokeMessage(true);
case Message.CHOKE:
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received choke from " + peer);
ps.chokeMessage(true);
break;
case 1:
ps.chokeMessage(false);
case Message.UNCHOKE:
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received unchoke from " + peer);
ps.chokeMessage(false);
break;
case 2:
ps.interestedMessage(true);
case Message.INTERESTED:
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received interested from " + peer);
ps.interestedMessage(true);
break;
case 3:
ps.interestedMessage(false);
case Message.UNINTERESTED:
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received not interested from " + peer);
ps.interestedMessage(false);
break;
case 4:
case Message.HAVE:
piece = din.readInt();
ps.haveMessage(piece);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received havePiece(" + piece + ") from " + peer);
ps.haveMessage(piece);
break;
case 5:
case Message.BITFIELD:
byte[] bitmap = new byte[i-1];
din.readFully(bitmap);
ps.bitfieldMessage(bitmap);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received bitmap from " + peer + ": size=" + (i-1) /* + ": " + ps.bitfield */ );
ps.bitfieldMessage(bitmap);
break;
case 6:
case Message.REQUEST:
piece = din.readInt();
begin = din.readInt();
len = din.readInt();
ps.requestMessage(piece, begin, len);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received request(" + piece + "," + begin + ") from " + peer);
ps.requestMessage(piece, begin, len);
break;
case 7:
case Message.PIECE:
piece = din.readInt();
begin = din.readInt();
len = i-9;
@@ -151,9 +159,9 @@ class PeerConnectionIn implements Runnable
if (req != null)
{
req.read(din);
ps.pieceMessage(req);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received data(" + piece + "," + begin + ") from " + peer);
ps.pieceMessage(req);
}
else
{
@@ -165,21 +173,24 @@ class PeerConnectionIn implements Runnable
_log.debug("Received UNWANTED data(" + piece + "," + begin + ") from " + peer);
}
break;
case 8:
case Message.CANCEL:
piece = din.readInt();
begin = din.readInt();
len = din.readInt();
ps.cancelMessage(piece, begin, len);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received cancel(" + piece + "," + begin + ") from " + peer);
ps.cancelMessage(piece, begin, len);
break;
case 9: // PORT message
case Message.PORT:
int port = din.readUnsignedShort();
ps.portMessage(port);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received port message from " + peer);
ps.portMessage(port);
break;
case 20: // Extension message
case Message.EXTENSION:
int id = din.readUnsignedByte();
byte[] payload = new byte[i-2];
din.readFully(payload);
@@ -187,6 +198,43 @@ class PeerConnectionIn implements Runnable
_log.debug("Received extension message from " + peer);
ps.extensionMessage(id, payload);
break;
// fast extensions below here
case Message.SUGGEST:
piece = din.readInt();
ps.suggestMessage(piece);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received suggest(" + piece + ") from " + peer);
break;
case Message.HAVE_ALL:
ps.haveMessage(true);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received have_all from " + peer);
break;
case Message.HAVE_NONE:
ps.haveMessage(false);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received have_none from " + peer);
break;
case Message.REJECT:
piece = din.readInt();
begin = din.readInt();
len = din.readInt();
ps.rejectMessage(piece, begin, len);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received reject(" + piece + ',' + begin + ',' + len + ") from " + peer);
break;
case Message.ALLOWED_FAST:
piece = din.readInt();
ps.allowedFastMessage(piece);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received allowed_fast(" + piece + ") from " + peer);
break;
default:
byte[] bs = new byte[i-1];
din.readFully(bs);
@@ -202,11 +250,9 @@ class PeerConnectionIn implements Runnable
if (_log.shouldLog(Log.INFO))
_log.info("IOError talking with " + peer, ioe);
}
catch (Throwable t)
catch (RuntimeException t)
{
_log.error("Error talking with " + peer, t);
if (t instanceof OutOfMemoryError)
throw (OutOfMemoryError)t;
}
finally
{

View File

@@ -22,15 +22,15 @@ package org.klomp.snark;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicLong;
import net.i2p.I2PAppContext;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
//import net.i2p.util.SimpleScheduler;
//import net.i2p.util.SimpleTimer;
class PeerConnectionOut implements Runnable
@@ -43,7 +43,7 @@ class PeerConnectionOut implements Runnable
private boolean quit;
// Contains Messages.
private final List<Message> sendQueue = new ArrayList<Message>();
private final BlockingQueue<Message> sendQueue = new LinkedBlockingQueue<Message>();
private static final AtomicLong __id = new AtomicLong();
private final long _id;
@@ -125,6 +125,16 @@ class PeerConnectionOut implements Runnable
if (state.choking) {
it.remove();
//SimpleTimer.getInstance().removeEvent(nm.expireEvent);
if (peer.supportsFast()) {
Message r = new Message();
r.type = Message.REJECT;
r.piece = nm.piece;
r.begin = nm.begin;
r.length = nm.length;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Send " + peer + ": " + r);
r.sendMessage(dout);
}
}
nm = null;
}
@@ -142,8 +152,8 @@ class PeerConnectionOut implements Runnable
it.remove();
}
}
if (m == null && !sendQueue.isEmpty()) {
m = sendQueue.remove(0);
if (m == null) {
m = sendQueue.poll();
//SimpleTimer.getInstance().removeEvent(m.expireEvent);
}
}
@@ -160,6 +170,8 @@ class PeerConnectionOut implements Runnable
lastSent = System.currentTimeMillis();
// Remove all piece messages after sending a choke message.
// FiXME this causes REJECT messages to be sent before sending the CHOKE;
// BEP 6 recommends sending them after.
if (m.type == Message.CHOKE)
removeMessage(Message.PIECE);
@@ -234,7 +246,7 @@ class PeerConnectionOut implements Runnable
{
synchronized(sendQueue)
{
sendQueue.add(m);
sendQueue.offer(m);
sendQueue.notifyAll();
}
}
@@ -278,11 +290,22 @@ class PeerConnectionOut implements Runnable
while (it.hasNext())
{
Message m = it.next();
if (m.type == type)
{
if (m.type == type) {
it.remove();
removed = true;
}
if (type == Message.PIECE && peer.supportsFast()) {
Message r = new Message();
r.type = Message.REJECT;
r.piece = m.piece;
r.begin = m.begin;
r.length = m.length;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Send " + peer + ": " + r);
try {
r.sendMessage(dout);
} catch (IOException ioe) {}
}
}
}
sendQueue.notifyAll();
}
@@ -297,7 +320,7 @@ class PeerConnectionOut implements Runnable
synchronized(sendQueue)
{
if(sendQueue.isEmpty())
sendQueue.add(m);
sendQueue.offer(m);
sendQueue.notifyAll();
}
}
@@ -350,12 +373,19 @@ class PeerConnectionOut implements Runnable
void sendBitfield(BitField bitfield)
{
Message m = new Message();
m.type = Message.BITFIELD;
m.data = bitfield.getFieldBytes();
m.off = 0;
m.len = m.data.length;
addMessage(m);
boolean fast = peer.supportsFast();
if (fast && bitfield.complete()) {
sendHaveAll();
} else if (fast && bitfield.count() <= 0) {
sendHaveNone();
} else {
Message m = new Message();
m.type = Message.BITFIELD;
m.data = bitfield.getFieldBytes();
m.off = 0;
m.len = m.data.length;
addMessage(m);
}
}
/** reransmit requests not received in 7m */
@@ -480,7 +510,6 @@ class PeerConnectionOut implements Runnable
m.len = length;
// since we have the data already loaded, queue a timeout to remove it
// no longer prefetched
//SimpleScheduler.getInstance().addEvent(new RemoveTooSlow(m), SEND_TIMEOUT);
addMessage(m);
}
@@ -511,7 +540,8 @@ class PeerConnectionOut implements Runnable
}
/**
* Remove all Request messages from the queue
* Remove all Request messages from the queue.
* Does not send a cancel message.
* @since 0.8.2
*/
void cancelRequestMessages() {
@@ -523,9 +553,12 @@ class PeerConnectionOut implements Runnable
}
}
// Called by the PeerState when the other side doesn't want this
// request to be handled anymore. Removes any pending Piece Message
// from out send queue.
/**
* Called by the PeerState when the other side doesn't want this
* request to be handled anymore. Removes any pending Piece Message
* from out send queue.
* Does not send a cancel message.
*/
void cancelRequest(int piece, int begin, int length)
{
synchronized (sendQueue)
@@ -561,4 +594,50 @@ class PeerConnectionOut implements Runnable
m.piece = port;
addMessage(m);
}
/**
* Unused
* @since 0.9.21
*/
void sendSuggest(int piece) {
Message m = new Message();
m.type = Message.SUGGEST;
m.piece = piece;
addMessage(m);
}
/** @since 0.9.21 */
private void sendHaveAll() {
Message m = new Message();
m.type = Message.HAVE_ALL;
addMessage(m);
}
/** @since 0.9.21 */
private void sendHaveNone() {
Message m = new Message();
m.type = Message.HAVE_NONE;
addMessage(m);
}
/** @since 0.9.21 */
void sendReject(int piece, int begin, int length) {
Message m = new Message();
m.type = Message.REJECT;
m.piece = piece;
m.begin = begin;
m.length = length;
addMessage(m);
}
/**
* Unused
* @since 0.9.21
*/
void sendAllowedFast(int piece) {
Message m = new Message();
m.type = Message.ALLOWED_FAST;
m.piece = piece;
addMessage(m);
}
}

View File

@@ -25,14 +25,15 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicLong;
import net.i2p.I2PAppContext;
import net.i2p.data.ByteArray;
@@ -52,7 +53,7 @@ import org.klomp.snark.dht.DHT;
*/
class PeerCoordinator implements PeerListener
{
private final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(PeerCoordinator.class);
private final Log _log;
/**
* External use by PeerMonitorTask only.
@@ -69,7 +70,7 @@ class PeerCoordinator implements PeerListener
// package local for access by CheckDownLoadersTask
final static long CHECK_PERIOD = 40*1000; // 40 seconds
final static int MAX_UPLOADERS = 6;
final static int MAX_UPLOADERS = 8;
public static final long MAX_INACTIVE = 8*60*1000;
/**
@@ -87,8 +88,8 @@ class PeerCoordinator implements PeerListener
// final static int MAX_DOWNLOADERS = MAX_CONNECTIONS;
// int downloaders = 0;
private long uploaded;
private long downloaded;
private final AtomicLong uploaded = new AtomicLong();
private final AtomicLong downloaded = new AtomicLong();
final static int RATE_DEPTH = 3; // make following arrays RATE_DEPTH long
private final long uploaded_old[] = {-1,-1,-1};
private final long downloaded_old[] = {-1,-1,-1};
@@ -98,7 +99,7 @@ class PeerCoordinator implements PeerListener
* This is a Queue, not a Set, because PeerCheckerTask keeps things in order for choking/unchoking.
* External use by PeerMonitorTask only.
*/
final Queue<Peer> peers;
final Deque<Peer> peers;
/**
* Peers we heard about via PEX
@@ -144,6 +145,7 @@ class PeerCoordinator implements PeerListener
{
_util = util;
_random = util.getContext().random();
_log = util.getContext().logManager().getLog(PeerCoordinator.class);
this.id = id;
this.infohash = infohash;
this.metainfo = metainfo;
@@ -154,7 +156,7 @@ class PeerCoordinator implements PeerListener
wantedPieces = new ArrayList<Piece>();
setWantedPieces();
partialPieces = new ArrayList<PartialPiece>(getMaxConnections() + 1);
peers = new LinkedBlockingQueue<Peer>();
peers = new LinkedBlockingDeque<Peer>();
magnetState = new MagnetState(infohash, metainfo);
pexPeers = new ConcurrentHashSet<PeerID>();
@@ -278,7 +280,7 @@ class PeerCoordinator implements PeerListener
*/
public long getUploaded()
{
return uploaded;
return uploaded.get();
}
/**
@@ -286,7 +288,7 @@ class PeerCoordinator implements PeerListener
* @since 0.9.15
*/
public void setUploaded(long up) {
uploaded = up;
uploaded.set(up);
}
/**
@@ -294,7 +296,7 @@ class PeerCoordinator implements PeerListener
*/
public long getDownloaded()
{
return downloaded;
return downloaded.get();
}
/**
@@ -320,16 +322,22 @@ class PeerCoordinator implements PeerListener
*/
public long getDownloadRate()
{
if (halted)
return 0;
return getRate(downloaded_old);
}
public long getUploadRate()
{
if (halted)
return 0;
return getRate(uploaded_old);
}
public long getCurrentUploadRate()
{
if (halted)
return 0;
// no need to synchronize, only one value
long r = uploaded_old[0];
if (r <= 0)
@@ -395,7 +403,7 @@ class PeerCoordinator implements PeerListener
* Formerly used to
* reduce max if huge pieces to keep from ooming when leeching
* but now we don't
* @return usually 16
* @return usually I2PSnarkUtil.MAX_CONNECTIONS
*/
private int getMaxConnections() {
if (metainfo == null)
@@ -522,7 +530,10 @@ class PeerCoordinator implements PeerListener
// Can't add to beginning since we converted from a List to a Queue
// We can do this in Java 6 with a Deque
//peers.add(0, peer);
peers.add(peer);
if (_util.getContext().random().nextInt(4) == 0)
peers.push(peer);
else
peers.add(peer);
peerCount = peers.size();
unchokePeer();
@@ -593,11 +604,13 @@ class PeerCoordinator implements PeerListener
bitfield = storage.getBitField();
else
bitfield = null;
// if we aren't a seed but we don't want any more
final boolean partialComplete = wantedBytes == 0 && bitfield != null && !bitfield.complete();
Runnable r = new Runnable()
{
public void run()
{
peer.runConnection(_util, listener, bitfield, magnetState);
peer.runConnection(_util, listener, bitfield, magnetState, partialComplete);
}
};
String threadName = "Snark peer " + peer.toString();
@@ -909,6 +922,7 @@ class PeerCoordinator implements PeerListener
* Returns a byte array containing the requested piece or null of
* the piece is unknown.
*
* @return bytes or null for errors such as not having the piece yet
* @throws RuntimeException on IOE getting the data
*/
public ByteArray gotRequest(Peer peer, int piece, int off, int len)
@@ -940,7 +954,7 @@ class PeerCoordinator implements PeerListener
*/
public void uploaded(Peer peer, int size)
{
uploaded += size;
uploaded.addAndGet(size);
//if (listener != null)
// listener.peerChange(this, peer);
@@ -951,7 +965,7 @@ class PeerCoordinator implements PeerListener
*/
public void downloaded(Peer peer, int size)
{
downloaded += size;
downloaded.addAndGet(size);
//if (listener != null)
// listener.peerChange(this, peer);
@@ -972,8 +986,9 @@ class PeerCoordinator implements PeerListener
}
int piece = pp.getPiece();
synchronized(wantedPieces)
{
// try/catch outside the synch to avoid deadlock in the catch
try {
synchronized(wantedPieces) {
Piece p = new Piece(piece);
if (!wantedPieces.contains(p))
{
@@ -989,8 +1004,7 @@ class PeerCoordinator implements PeerListener
}
}
try
{
// try/catch moved outside of synch
// this takes forever if complete, as it rechecks
if (storage.putPiece(pp))
{
@@ -999,26 +1013,38 @@ class PeerCoordinator implements PeerListener
}
else
{
// so we will try again
markUnrequested(peer, piece);
// just in case
removePartialPiece(piece);
// Oops. We didn't actually download this then... :(
downloaded -= metainfo.getPieceLength(piece);
_log.warn("Got BAD piece " + piece + "/" + metainfo.getPieces() + " from " + peer + " for " + metainfo.getName());
downloaded.addAndGet(0 - metainfo.getPieceLength(piece));
// Mark this peer as not having the piece. PeerState will update its bitfield.
for (Piece pc : wantedPieces) {
if (pc.getId() == piece) {
pc.removePeer(peer);
break;
}
}
if (_log.shouldWarn())
_log.warn("Got BAD piece " + piece + "/" + metainfo.getPieces() + " from " + peer + " for " + metainfo.getName());
return false; // No need to announce BAD piece to peers.
}
}
catch (IOException ioe)
{
wantedPieces.remove(p);
wantedBytes -= metainfo.getPieceLength(p.getId());
} // synch
} catch (IOException ioe) {
String msg = "Error writing storage (piece " + piece + ") for " + metainfo.getName() + ": " + ioe;
_log.error(msg, ioe);
if (listener != null) {
listener.addMessage(msg);
listener.addMessage("Fatal storage error: Stopping torrent " + metainfo.getName());
}
// deadlock was here
snark.stopTorrent();
throw new RuntimeException(msg, ioe);
}
wantedPieces.remove(p);
wantedBytes -= metainfo.getPieceLength(p.getId());
}
}
// just in case
removePartialPiece(piece);
@@ -1130,8 +1156,9 @@ class PeerCoordinator implements PeerListener
*
* Also mark the piece unrequested if this peer was the only one.
*
* @param peer partials, must include the zero-offset (empty) ones too
* No dup pieces, piece.setDownloaded() must be set
* @param peer partials, must include the zero-offset (empty) ones too.
* No dup pieces, piece.setDownloaded() must be set.
* len field in Requests is ignored.
* @since 0.8.2
*/
public void savePartialPieces(Peer peer, List<Request> partials)
@@ -1461,8 +1488,8 @@ class PeerCoordinator implements PeerListener
public int allowedUploaders()
{
if (listener != null && listener.overUploadLimit(uploaders)) {
// if (_log.shouldLog(Log.DEBUG))
// _log.debug("Over limit, uploaders was: " + uploaders);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Over limit, uploaders was: " + uploaders);
return uploaders - 1;
} else if (uploaders < MAX_UPLOADERS)
return uploaders + 1;

View File

@@ -196,6 +196,7 @@ public class PeerID implements Comparable<PeerID>
* Compares port, address and id.
* @deprecated unused? and will NPE now that address can be null?
*/
@Deprecated
public int compareTo(PeerID pid)
{
int result = port - pid.port;

View File

@@ -30,6 +30,7 @@ import net.i2p.data.DataHelper;
*
* @deprecated unused, for command line client only, commented out in Snark.java
*/
@Deprecated
class PeerMonitorTask implements Runnable
{
final static long MONITOR_PERIOD = 10 * 1000; // Ten seconds.

View File

@@ -21,6 +21,7 @@
package org.klomp.snark;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -36,7 +37,10 @@ class PeerState implements DataLoader
private final Peer peer;
/** Fixme, used by Peer.disconnect() to get to the coordinator */
final PeerListener listener;
/** Null before we have it. locking: this */
private MetaInfo metainfo;
/** Null unless needed. Contains -1 for all. locking: this */
private List<Integer> havesBeforeMetaInfo;
// Interesting and choking describes whether we are interested in or
// are choking the other side.
@@ -48,7 +52,7 @@ class PeerState implements DataLoader
volatile boolean interested;
volatile boolean choked = true;
/** the pieces the peer has */
/** the pieces the peer has. locking: this */
BitField bitfield;
// Package local for use by Peer.
@@ -65,6 +69,7 @@ class PeerState implements DataLoader
private final static int MAX_PIPELINE_BYTES = 128*1024; // this is for inbound requests
public final static int PARTSIZE = 16*1024; // outbound request
private final static int MAX_PARTSIZE = 64*1024; // Don't let anybody request more than this
private static final Integer PIECE_ALL = Integer.valueOf(-1);
/**
* @param metainfo null if in magnet mode
@@ -130,37 +135,73 @@ class PeerState implements DataLoader
{
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " rcv have(" + piece + ")");
// FIXME we will lose these until we get the metainfo
if (metainfo == null)
return;
// Sanity check
if (piece < 0 || piece >= metainfo.getPieces())
{
// XXX disconnect?
if (_log.shouldLog(Log.WARN))
if (piece < 0) {
if (_log.shouldWarn())
_log.warn("Got strange 'have: " + piece + "' message from " + peer);
return;
}
}
synchronized(this) {
if (metainfo == null) {
if (_log.shouldWarn())
_log.warn("Got HAVE " + piece + " before metainfo from " + peer);
if (bitfield != null) {
if (piece < bitfield.size())
bitfield.set(piece);
} else {
// note reception for later
if (havesBeforeMetaInfo == null) {
havesBeforeMetaInfo = new ArrayList<Integer>(8);
} else if (havesBeforeMetaInfo.size() > 1000) {
// don't blow up
if (_log.shouldWarn())
_log.warn("Got too many haves before metainfo from " + peer);
return;
}
havesBeforeMetaInfo.add(Integer.valueOf(piece));
}
return;
}
// Sanity check
if (piece >= metainfo.getPieces()) {
// XXX disconnect?
if (_log.shouldLog(Log.WARN))
_log.warn("Got strange 'have: " + piece + "' message from " + peer);
return;
}
synchronized(this)
{
// Can happen if the other side never send a bitfield message.
if (bitfield == null)
bitfield = new BitField(metainfo.getPieces());
bitfield = new BitField(metainfo.getPieces());
bitfield.set(piece);
}
}
if (listener.gotHave(peer, piece))
setInteresting(true);
}
void bitfieldMessage(byte[] bitmap)
{
synchronized(this)
{
if (_log.shouldLog(Log.DEBUG))
_log.debug(peer + " rcv bitfield");
void bitfieldMessage(byte[] bitmap) {
bitfieldMessage(bitmap, false);
}
/**
* @param bitmap null to use the isAll param
* @param isAll only if bitmap == null: true for have_all, false for have_none
* @since 0.9.21
*/
private void bitfieldMessage(byte[] bitmap, boolean isAll) {
if (_log.shouldLog(Log.DEBUG)) {
if (bitmap != null)
_log.debug(peer + " rcv bitfield bytes: " + bitmap.length);
else if (isAll)
_log.debug(peer + " rcv bitfield HAVE_ALL");
else
_log.debug(peer + " rcv bitfield HAVE_NONE");
}
synchronized(this) {
if (bitfield != null)
{
// XXX - Be liberal in what you accept?
@@ -170,15 +211,36 @@ class PeerState implements DataLoader
}
// XXX - Check for weird bitfield and disconnect?
// FIXME will have to regenerate the bitfield after we know exactly
// Will have to regenerate the bitfield after we know exactly
// how many pieces there are, as we don't know how many spare bits there are.
if (metainfo == null)
bitfield = new BitField(bitmap, bitmap.length * 8);
else
bitfield = new BitField(bitmap, metainfo.getPieces());
}
if (metainfo == null)
return;
// This happens in setMetaInfo() below.
if (metainfo == null) {
if (bitmap != null) {
bitfield = new BitField(bitmap, bitmap.length * 8);
} else {
if (_log.shouldLog(Log.WARN))
_log.warn("have_x w/o metainfo: " + isAll);
if (isAll) {
// note reception for later
if (havesBeforeMetaInfo == null)
havesBeforeMetaInfo = new ArrayList<Integer>(1);
else
havesBeforeMetaInfo.clear();
havesBeforeMetaInfo.add(PIECE_ALL);
} // else HAVE_NONE, ignore
}
return;
} else {
if (bitmap != null) {
bitfield = new BitField(bitmap, metainfo.getPieces());
} else {
bitfield = new BitField(metainfo.getPieces());
if (isAll)
bitfield.setAll();
}
}
} // synch
boolean interest = listener.gotBitField(peer, bitfield);
setInteresting(interest);
if (bitfield.complete() && !interest) {
@@ -198,14 +260,21 @@ class PeerState implements DataLoader
+ piece + ", " + begin + ", " + length + ") ");
if (metainfo == null)
return;
if (choking)
{
if (_log.shouldLog(Log.INFO))
_log.info("Request received, but choking " + peer);
if (choking) {
if (peer.supportsFast()) {
if (_log.shouldInfo())
_log.info("Request received, sending reject to choked " + peer);
out.sendReject(piece, begin, length);
} else {
if (_log.shouldInfo())
_log.info("Request received, but choking " + peer);
}
return;
}
}
// Sanity check
// There is no check here that we actually have the piece;
// this will be caught in loadData() below
if (piece < 0
|| piece >= metainfo.getPieces()
|| begin < 0
@@ -219,6 +288,8 @@ class PeerState implements DataLoader
+ ", " + begin
+ ", " + length
+ "' message from " + peer);
if (peer.supportsFast())
out.sendReject(piece, begin, length);
return;
}
@@ -227,8 +298,14 @@ class PeerState implements DataLoader
// Todo: limit number of requests also? (robert 64 x 4KB)
if (out.queuedBytes() + length > MAX_PIPELINE_BYTES)
{
if (_log.shouldLog(Log.WARN))
_log.warn("Discarding request over pipeline limit from " + peer);
if (peer.supportsFast()) {
if (_log.shouldWarn())
_log.warn("Rejecting request over pipeline limit from " + peer);
out.sendReject(piece, begin, length);
} else {
if (_log.shouldWarn())
_log.warn("Discarding request over pipeline limit from " + peer);
}
return;
}
@@ -243,7 +320,8 @@ class PeerState implements DataLoader
/**
* This is the callback that PeerConnectionOut calls
*
* @return bytes or null for errors
* @return bytes or null for errors such as not having the piece yet
* @throws RuntimeException on IOE getting the data
* @since 0.8.2
*/
public ByteArray loadData(int piece, int begin, int length) {
@@ -253,6 +331,8 @@ class PeerState implements DataLoader
// XXX - Protocol error-> diconnect?
if (_log.shouldLog(Log.WARN))
_log.warn("Got request for unknown piece: " + piece);
if (peer.supportsFast())
out.sendReject(piece, begin, length);
return null;
}
@@ -265,6 +345,8 @@ class PeerState implements DataLoader
+ ", " + begin
+ ", " + length
+ "' message from " + peer);
if (peer.supportsFast())
out.sendReject(piece, begin, length);
return null;
}
@@ -322,6 +404,11 @@ class PeerState implements DataLoader
{
if (_log.shouldLog(Log.WARN))
_log.warn("Got BAD " + req.getPiece() + " from " + peer);
synchronized(this) {
// so we don't ask again
if (bitfield != null)
bitfield.clear(req.getPiece());
}
}
}
@@ -455,7 +542,12 @@ class PeerState implements DataLoader
for (Integer p : pcs) {
Request req = getLowestOutstandingRequest(p.intValue());
if (req != null) {
req.getPartialPiece().setDownloaded(req.off);
PartialPiece pp = req.getPartialPiece();
synchronized(pp) {
int dl = pp.getDownloaded();
if (req.off != dl)
req = new Request(pp, dl, 1);
}
rv.add(req);
}
}
@@ -508,22 +600,43 @@ class PeerState implements DataLoader
* @param meta non-null
* @since 0.8.4
*/
public void setMetaInfo(MetaInfo meta) {
public synchronized void setMetaInfo(MetaInfo meta) {
if (metainfo != null)
return;
BitField oldBF = bitfield;
if (oldBF != null) {
if (oldBF.size() != meta.getPieces())
if (bitfield != null) {
if (bitfield.size() != meta.getPieces())
// fix bitfield, it was too big by 1-7 bits
bitfield = new BitField(oldBF.getFieldBytes(), meta.getPieces());
bitfield = new BitField(bitfield.getFieldBytes(), meta.getPieces());
// else no extra
} else if (havesBeforeMetaInfo != null) {
// initialize it now
bitfield = new BitField(meta.getPieces());
} else {
// it will be initialized later
//bitfield = new BitField(meta.getPieces());
}
metainfo = meta;
if (bitfield != null && bitfield.count() > 0)
setInteresting(true);
if (bitfield != null) {
if (havesBeforeMetaInfo != null) {
// set all 'haves' we got before the metainfo in the bitfield
for (Integer i : havesBeforeMetaInfo) {
if (i.equals(PIECE_ALL)) {
bitfield.setAll();
if (_log.shouldLog(Log.WARN))
_log.warn("set have_all after rcv metainfo");
break;
}
int piece = i.intValue();
if (piece >= 0 && piece < meta.getPieces())
bitfield.set(piece);
if (_log.shouldLog(Log.WARN))
_log.warn("set have " + piece + " after rcv metainfo");
}
havesBeforeMetaInfo = null;
}
if (bitfield.count() > 0)
setInteresting(true);
}
}
/**
@@ -536,6 +649,89 @@ class PeerState implements DataLoader
listener.gotPort(peer, port, port + 1);
}
/////////// fast message handlers /////////
/**
* BEP 6
* Treated as "have" for now
* @since 0.9.21
*/
void suggestMessage(int piece) {
if (_log.shouldInfo())
_log.info("Handling suggest as have(" + piece + ") from " + peer);
haveMessage(piece);
}
/**
* BEP 6
* @param isAll true for have_all, false for have_none
* @since 0.9.21
*/
void haveMessage(boolean isAll) {
bitfieldMessage(null, isAll);
}
/**
* BEP 6
* If the peer rejects lower chunks but not higher ones, thus creating holes,
* we won't figure it out and the piece will fail, since we don't currently
* keep a chunk bitmap in PartialPiece.
* As long as the peer rejects all the chunks, or rejects only the last chunks,
* no holes are created and we will be fine. The reject messages may be in any order,
* just don't make a hole when it's over.
*
* @since 0.9.21
*/
void rejectMessage(int piece, int begin, int length) {
if (_log.shouldInfo())
_log.info("Got reject(" + piece + ',' + begin + ',' + length + ") from " + peer);
out.cancelRequest(piece, begin, length);
synchronized(this) {
Request deletedRequest = null;
// for this piece only
boolean haveMoreRequests = false;
for (Iterator<Request> iter = outstandingRequests.iterator(); iter.hasNext(); ) {
Request req = iter.next();
if (req.getPiece() == piece) {
if (req.off == begin && req.len == length) {
iter.remove();
deletedRequest = req;
} else {
haveMoreRequests = true;
}
}
}
if (deletedRequest != null && !haveMoreRequests) {
// We must return the piece to the coordinator
// Create a new fake request so we can set the offset correctly
PartialPiece pp = deletedRequest.getPartialPiece();
int downloaded = pp.getDownloaded();
Request req;
if (deletedRequest.off == downloaded)
req = deletedRequest;
else
req = new Request(pp, downloaded, 1);
List<Request> pcs = Collections.singletonList(req);
listener.savePartialPieces(this.peer, pcs);
if (_log.shouldWarn())
_log.warn("Returned to coord. w/ offset " + pp.getDownloaded() + " due to reject(" + piece + ',' + begin + ',' + length + ") from " + peer);
}
if (lastRequest != null && lastRequest.getPiece() == piece &&
lastRequest.off == begin && lastRequest.len == length)
lastRequest = null;
}
}
/**
* BEP 6
* Ignored for now
* @since 0.9.21
*/
void allowedFastMessage(int piece) {
if (_log.shouldInfo())
_log.info("Ignoring allowed_fast(" + piece + ") from " + peer);
}
void unknownMessage(int type, byte[] bs)
{
if (_log.shouldLog(Log.WARN))
@@ -543,6 +739,8 @@ class PeerState implements DataLoader
+ " length: " + bs.length);
}
/////////// end message handlers /////////
/**
* We now have this piece.
* Tell the peer and cancel any requests for the piece.
@@ -601,6 +799,7 @@ class PeerState implements DataLoader
* @deprecated deadlocks
* @since 0.8.1
*/
@Deprecated
synchronized boolean isRequesting(int piece) {
if (pendingRequest != null && pendingRequest.getPiece() == piece)
return true;

View File

@@ -43,13 +43,13 @@ class Request
*/
Request(PartialPiece piece, int off, int len)
{
// Sanity check
if (off < 0 || len <= 0 || off + len > piece.getLength())
throw new IndexOutOfBoundsException("Illegal Request " + toString());
this.piece = piece;
this.off = off;
this.len = len;
// Sanity check
if (off < 0 || len <= 0 || off + len > piece.getLength())
throw new IndexOutOfBoundsException("Illegal Request " + toString());
}
/**

View File

@@ -290,6 +290,7 @@ public class Snark
/**
* multitorrent
* @throws RuntimeException via fatal()
*/
public Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
StorageListener slistener, CoordinatorListener clistener,
@@ -304,6 +305,7 @@ public class Snark
* multitorrent
*
* @param baseFile if null, use rootDir/torrentName; if non-null, use it instead
* @throws RuntimeException via fatal()
* @since 0.9.11
*/
public Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
@@ -478,6 +480,7 @@ public class Snark
* @param torrent a fake name for now (not a file name)
* @param ih 20-byte info hash
* @param trackerURL may be null
* @throws RuntimeException via fatal()
* @since 0.8.4
*/
public Snark(I2PSnarkUtil util, String torrent, byte[] ih, String trackerURL,
@@ -531,6 +534,8 @@ public class Snark
/**
* Start up contacting peers and querying the tracker.
* Blocks if tunnel is not yet open.
*
* @throws RuntimeException via fatal()
*/
public synchronized void startTorrent() {
starting = true;
@@ -612,7 +617,6 @@ public class Snark
* @since 0.9.1
*/
public synchronized void stopTorrent(boolean fast) {
stopped = true;
TrackerClient tc = trackerclient;
if (tc != null)
tc.halt(fast);
@@ -620,17 +624,28 @@ public class Snark
if (pc != null)
pc.halt();
Storage st = storage;
if (!fast)
// HACK: Needed a way to distinguish between user-stop and
// shutdown-stop. stopTorrent(true) is in stopAllTorrents().
// (#766)
stopped = true;
if (st != null) {
boolean changed = storage.isChanged() || getUploaded() != savedUploaded;
// TODO: Cache the config-in-mem to compare vs config-on-disk
// (needed for auto-save to not double-save in some cases)
//boolean changed = storage.isChanged() || getUploaded() != savedUploaded;
boolean changed = true;
if (changed && completeListener != null)
completeListener.updateStatus(this);
try {
storage.close();
} catch (IOException ioe) {
System.out.println("Error closing " + torrent);
ioe.printStackTrace();
}
if (changed && completeListener != null)
completeListener.updateStatus(this);
}
if (fast)
// HACK: See above if(!fast)
stopped = true;
if (pc != null && _peerCoordinatorSet != null)
_peerCoordinatorSet.remove(pc);
if (_peerCoordinatorSet == null)
@@ -730,6 +745,18 @@ public class Snark
return storage != null && storage.isChecking();
}
/**
* If checking is in progress, return completion 0.0 ... 1.0,
* else return 1.0.
* @since 0.9.23
*/
public double getCheckingProgress() {
if (storage != null && storage.isChecking())
return storage.getCheckingProgress();
else
return 1.0d;
}
/**
* Disk allocation (ballooning) in progress.
* @since 0.9.3
@@ -884,6 +911,30 @@ public class Snark
return -1;
}
/**
* Bytes not received and set to skipped.
* This is not the same as the total of all skipped files,
* since pieces may span multiple files.
*
* @return exact value. or 0 if no storage yet.
* @since 0.9.24
*/
public long getSkippedLength() {
PeerCoordinator coord = coordinator;
if (coord != null) {
// fast way
long r = getRemainingLength();
if (r <= 0)
return 0;
long n = coord.getNeededLength();
return r - n;
} else if (storage != null) {
// slow way
return storage.getSkippedLength();
}
return 0;
}
/**
* Does not account (i.e. includes) for skipped files.
* @return number of pieces still needed (magnet mode or not), or -1 if unknown
@@ -1249,7 +1300,8 @@ public class Snark
public void setWantedPieces(Storage storage)
{
coordinator.setWantedPieces();
if (coordinator != null)
coordinator.setWantedPieces();
}
///////////// End StorageListener methods
@@ -1258,7 +1310,7 @@ public class Snark
/** SnarkSnutdown callback unused */
public void shutdown()
{
// Should not be necessary since all non-deamon threads should
// Should not be necessary since all non-daemon threads should
// have died. But in reality this does not always happen.
//System.exit(0);
}
@@ -1277,7 +1329,7 @@ public class Snark
* coordinatorListener
*/
final static int MIN_TOTAL_UPLOADERS = 4;
final static int MAX_TOTAL_UPLOADERS = 10;
final static int MAX_TOTAL_UPLOADERS = 20;
public boolean overUploadLimit(int uploaders) {
if (_peerCoordinatorSet == null || uploaders <= 0)
@@ -1288,7 +1340,8 @@ public class Snark
totalUploaders += c.uploaders;
}
int limit = _util.getMaxUploaders();
// debug("Total uploaders: " + totalUploaders + " Limit: " + limit, Snark.DEBUG);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Total uploaders: " + totalUploaders + " Limit: " + limit);
return totalUploaders > limit;
}

File diff suppressed because it is too large Load Diff

View File

@@ -28,6 +28,7 @@ import net.i2p.util.I2PAppThread;
* Makes sure everything ends correctly when shutting down.
* @deprecated unused
*/
@Deprecated
public class SnarkShutdown extends I2PAppThread
{
private final Storage storage;

View File

@@ -20,6 +20,7 @@
package org.klomp.snark;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -38,6 +39,8 @@ import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import gnu.getopt.Getopt;
import net.i2p.I2PAppContext;
import net.i2p.crypto.SHA1;
import net.i2p.data.ByteArray;
@@ -50,7 +53,7 @@ import net.i2p.util.SystemVersion;
/**
* Maintains pieces on disk. Can be used to store and retrieve pieces.
*/
public class Storage
public class Storage implements Closeable
{
private final MetaInfo metainfo;
private final List<TorrentFile> _torrentFiles;
@@ -70,18 +73,20 @@ public class Storage
private boolean changed;
private volatile boolean _isChecking;
private final AtomicInteger _allocateCount = new AtomicInteger();
private final AtomicInteger _checkProgress = new AtomicInteger();
/** The default piece size. */
private static final int DEFAULT_PIECE_SIZE = 256*1024;
/** bigger than this will be rejected */
public static final int MAX_PIECE_SIZE = 8*1024*1024;
public static final int MAX_PIECE_SIZE = 16*1024*1024;
/** The maximum number of pieces in a torrent. */
public static final int MAX_PIECES = 10*1024;
public static final int MAX_PIECES = 32*1024;
public static final long MAX_TOTAL_SIZE = MAX_PIECE_SIZE * (long) MAX_PIECES;
private static final Map<String, String> _filterNameCache = new ConcurrentHashMap<String, String>();
private static final boolean _isWindows = SystemVersion.isWindows();
private static final boolean _isARM = SystemVersion.isARM();
private static final int BUFSIZE = PeerState.PARTSIZE;
private static final ByteCache _cache = ByteCache.getInstance(16, BUFSIZE);
@@ -122,10 +127,12 @@ public class Storage
*
* @param announce may be null
* @param listener may be null
* @param created_by may be null
* @throws IOException when creating and/or checking files fails.
*/
public Storage(I2PSnarkUtil util, File baseFile, String announce,
List<List<String>> announce_list,
String created_by,
boolean privateTorrent, StorageListener listener)
throws IOException
{
@@ -159,7 +166,7 @@ public class Storage
else
pc_size = DEFAULT_PIECE_SIZE;
int pcs = (int) ((total - 1)/pc_size) + 1;
while (pcs > (MAX_PIECES * 2 / 3) && pc_size < MAX_PIECE_SIZE)
while (pcs > (MAX_PIECES / 3) && pc_size < MAX_PIECE_SIZE)
{
pc_size *= 2;
pcs = (int) ((total - 1)/pc_size) +1;
@@ -194,7 +201,7 @@ public class Storage
byte[] piece_hashes = fast_digestCreate();
metainfo = new MetaInfo(announce, baseFile.getName(), null, files,
lengthsList, piece_size, piece_hashes, total, privateTorrent,
announce_list);
announce_list, created_by);
}
@@ -306,6 +313,18 @@ public class Storage
return _isChecking;
}
/**
* If checking is in progress, return completion 0.0 ... 1.0,
* else return 1.0.
* @since 0.9.23
*/
public double getCheckingProgress() {
if (_isChecking)
return _checkProgress.get() / (double) pieces;
else
return 1.0d;
}
/**
* Disk allocation (ballooning) in progress.
* Always false on Windows.
@@ -336,29 +355,28 @@ public class Storage
* @return number of bytes remaining; -1 if unknown file
* @since 0.7.14
*/
/****
public long remaining(int fileIndex) {
if (fileIndex < 0 || fileIndex >= _torrentFiles.size())
return -1;
if (complete())
return 0;
long bytes = 0;
for (int i = 0; i < _torrentFiles.size(); i++) {
TorrentFile tf = _torrentFiles.get(i);
if (i == fileIndex) {
File f = tf.RAFfile;
if (complete())
return 0;
int psz = piece_size;
long start = bytes;
long end = start + tf.length;
int pc = (int) (bytes / psz);
int pc = (int) (bytes / piece_size);
long rv = 0;
if (!bitfield.get(pc))
rv = Math.min(psz - (start % psz), tf.length);
for (int j = pc + 1; (((long)j) * psz) < end && j < pieces; j++) {
rv = Math.min(piece_size - (start % piece_size), tf.length);
for (int j = pc + 1; (((long)j) * piece_size) < end && j < pieces; j++) {
if (!bitfield.get(j)) {
if (((long)(j+1))*psz < end)
rv += psz;
if (((long)(j+1))*piece_size < end)
rv += piece_size;
else
rv += end - (((long)j) * psz);
rv += end - (((long)j) * piece_size);
}
}
return rv;
@@ -367,6 +385,40 @@ public class Storage
}
return -1;
}
****/
/**
* For efficiency, calculate remaining bytes for all files at once
*
* @return number of bytes remaining for each file, use indexOf() to get index for a file
* @since 0.9.23
*/
public long[] remaining() {
long[] rv = new long[_torrentFiles.size()];
if (complete())
return rv;
long bytes = 0;
for (int i = 0; i < _torrentFiles.size(); i++) {
TorrentFile tf = _torrentFiles.get(i);
long start = bytes;
long end = start + tf.length;
int pc = (int) (bytes / piece_size);
long rvi = 0;
if (!bitfield.get(pc))
rvi = Math.min(piece_size - (start % piece_size), tf.length);
for (int j = pc + 1; (((long)j) * piece_size) < end && j < pieces; j++) {
if (!bitfield.get(j)) {
if (((long)(j+1))*piece_size < end)
rvi += piece_size;
else
rvi += end - (((long)j) * piece_size);
}
}
rv[i] = rvi;
bytes += tf.length;
}
return rv;
}
/**
* @param fileIndex as obtained from indexOf
@@ -450,9 +502,8 @@ public class Storage
int file = 0;
long pcEnd = -1;
long fileEnd = _torrentFiles.get(0).length - 1;
int psz = piece_size;
for (int i = 0; i < rv.length; i++) {
pcEnd += psz;
pcEnd += piece_size;
int pri = _torrentFiles.get(file).priority;
while (fileEnd <= pcEnd && file < _torrentFiles.size() - 1) {
file++;
@@ -467,6 +518,31 @@ public class Storage
return rv;
}
/**
* Call setPriority() for all changed files first,
* then call this.
* The length of all the pieces that are not yet downloaded,
* and are set to skipped.
* This is not the same as the total of all skipped files,
* since pieces may span multiple files.
*
* @return 0 on error, if complete, or if only one file
* @since 0.9.24
*/
public long getSkippedLength() {
int[] pri = getPiecePriorities();
if (pri == null)
return 0;
long rv = 0;
final int end = pri.length - 1;
for (int i = 0; i <= end; i++) {
if (pri[i] <= -9 && !bitfield.get(i)) {
rv += (i != end) ? piece_size : metainfo.getPieceLength(i);
}
}
return rv;
}
/**
* The BitField that tells which pieces this storage contains.
* Do not change this since this is the current state of the storage.
@@ -496,6 +572,9 @@ public class Storage
/**
* Creates (and/or checks) all files from the metainfo file list.
* Only call this once, and only after the constructor with the metainfo.
* Use recheck() to check again later.
*
* @throws IllegalStateException if called more than once
*/
public void check() throws IOException
{
@@ -506,6 +585,9 @@ public class Storage
* Creates (and/or checks) all files from the metainfo file list.
* Use a saved bitfield and timestamp from a config file.
* Only call this once, and only after the constructor with the metainfo.
* Use recheck() to check again later.
*
* @throws IllegalStateException if called more than once
*/
public void check(long savedTime, BitField savedBitField) throws IOException
{
@@ -691,7 +773,7 @@ public class Storage
}
rv = repl;
}
} catch (Exception ex) {
} catch (RuntimeException ex) {
ex.printStackTrace();
}
}
@@ -763,6 +845,14 @@ public class Storage
return rv;
}
/**
* Does not include directories.
* @since 0.9.23
*/
public int getFileCount() {
return _torrentFiles.size();
}
/**
* Includes the base for a multi-file torrent.
* Sorted bottom-up for easy deletion.
@@ -784,6 +874,24 @@ public class Storage
return rv;
}
/**
* Blocking. Holds lock.
* Recommend running only when stopped.
* Caller should thread.
* Calls listener.setWantedPieces() on completion if anything changed.
*
* @return true if anything changed, false otherwise
* @since 0.9.23
*/
public boolean recheck() throws IOException {
int previousNeeded = needed;
checkCreateFiles(true);
boolean changed = previousNeeded != needed;
if (listener != null && changed)
listener.setWantedPieces(this);
return changed;
}
/**
* This is called at the beginning, and at presumed completion,
* so we have to be careful about locking.
@@ -808,6 +916,7 @@ public class Storage
private void locked_checkCreateFiles(boolean recheck) throws IOException
{
_checkProgress.set(0);
// Whether we are resuming or not,
// if any of the files already exists we assume we are resuming.
boolean resume = false;
@@ -824,13 +933,16 @@ public class Storage
// Make sure all files are available and of correct length
// The files should all exist as they have been created with zero length by createFilesFromNames()
long lengthProgress = 0;
for (TorrentFile tf : _torrentFiles)
{
long length = tf.RAFfile.length();
lengthProgress += tf.length;
if(tf.RAFfile.exists() && length == tf.length)
{
if (listener != null)
listener.storageAllocated(this, length);
_checkProgress.set(0);
resume = true; // XXX Could dynamicly check
}
else if (length == 0) {
@@ -842,6 +954,8 @@ public class Storage
tf.closeRAF();
} catch (IOException ioe) {}
}
if (!resume)
_checkProgress.set((int) (pieces * lengthProgress / total_length));
} else {
String msg = "File '" + tf.name + "' exists, but has wrong length (expected " +
tf.length + " but found " + length + ") - repairing corruption";
@@ -850,6 +964,7 @@ public class Storage
_log.error(msg);
changed = true;
resume = true;
_checkProgress.set(0);
_probablyComplete = false; // to force RW
synchronized(tf) {
RandomAccessFile raf = tf.checkRAF();
@@ -870,17 +985,16 @@ public class Storage
long pieceEnd = 0;
for (int i = 0; i < pieces; i++)
{
_checkProgress.set(i);
int length = getUncheckedPiece(i, piece);
boolean correctHash = metainfo.checkPiece(i, piece, 0, length);
// close as we go so we don't run out of file descriptors
pieceEnd += length;
while (fileEnd <= pieceEnd) {
TorrentFile tf = _torrentFiles.get(file);
synchronized(tf) {
try {
tf.closeRAF();
} catch (IOException ioe) {}
}
try {
tf.closeRAF();
} catch (IOException ioe) {}
if (++file >= _torrentFiles.size())
break;
fileEnd += _torrentFiles.get(file).length;
@@ -896,6 +1010,7 @@ public class Storage
}
}
_checkProgress.set(pieces);
_probablyComplete = complete();
// close all the files so we don't end up with a zillion open ones;
// we will reopen as needed
@@ -952,9 +1067,7 @@ public class Storage
for (TorrentFile tf : _torrentFiles)
{
try {
synchronized(tf) {
tf.closeRAF();
}
} catch (IOException ioe) {
_log.error("Error closing " + tf, ioe);
// gobble gobble
@@ -1179,17 +1292,15 @@ public class Storage
return length;
}
private static final long RAFCloseDelay = 4*60*1000;
private static final long RAF_CLOSE_DELAY = 4*60*1000;
/**
* Close unused RAFs - call periodically
*/
public void cleanRAFs() {
long cutoff = System.currentTimeMillis() - RAFCloseDelay;
long cutoff = System.currentTimeMillis() - RAF_CLOSE_DELAY;
for (TorrentFile tf : _torrentFiles) {
synchronized(tf) {
tf.closeRAF(cutoff);
}
}
}
@@ -1315,7 +1426,9 @@ public class Storage
// Windows will zero-fill up to the point of the write, which
// will make the file fairly unfragmented, on average, at least until
// near the end where it will get exponentially more fragmented.
if (!_isWindows)
// Also don't ballon on ARM, as a proxy for solid state disk, where fragmentation doesn't matter too much.
// Actual detection of SSD is almost impossible.
if (!_isWindows && !_isARM)
isSparse = true;
}
@@ -1372,18 +1485,44 @@ public class Storage
* @since 0.9.4
*/
public static void main(String[] args) {
if (args.length < 1 || args.length > 2) {
System.err.println("Usage: Storage file-or-dir [announceURL]");
boolean error = false;
String created_by = null;
String announce = null;
Getopt g = new Getopt("Storage", args, "a:c:");
try {
int c;
while ((c = g.getopt()) != -1) {
switch (c) {
case 'a':
announce = g.getOptarg();
break;
case 'c':
created_by = g.getOptarg();
break;
case '?':
case ':':
default:
error = true;
break;
} // switch
} // while
} catch (RuntimeException e) {
e.printStackTrace();
error = true;
}
if (error || args.length - g.getOptind() != 1) {
System.err.println("Usage: Storage [-a announceURL] [-c created-by] file-or-dir");
System.exit(1);
}
File base = new File(args[0]);
String announce = args.length == 2 ? args[1] : null;
File base = new File(args[g.getOptind()]);
I2PAppContext ctx = I2PAppContext.getGlobalContext();
I2PSnarkUtil util = new I2PSnarkUtil(ctx);
File file = null;
FileOutputStream out = null;
try {
Storage storage = new Storage(util, base, announce, null, false, null);
Storage storage = new Storage(util, base, announce, null, created_by, false, null);
MetaInfo meta = storage.getMetaInfo();
file = new File(storage.getBaseName() + ".torrent");
out = new FileOutputStream(file);

View File

@@ -23,8 +23,8 @@ package org.klomp.snark;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -36,7 +36,9 @@ import java.util.Locale;
import java.util.Random;
import java.util.Set;
import net.i2p.crypto.SigType;
import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.util.ConvertToHash;
import net.i2p.util.I2PAppThread;
@@ -88,6 +90,8 @@ public class TrackerClient implements Runnable {
private static final int DHT_ANNOUNCE_PEERS = 4;
public static final int PORT = 6881;
private static final int MAX_TRACKERS = 12;
// tracker.welterde.i2p
private static final Hash DSA_ONLY_TRACKER = ConvertToHash.getHash("cfmqlafjfmgkzbt4r3jsfyhgsr5abgxryl6fnz3d3y5a365di5aa.b32.i2p");
private final I2PSnarkUtil _util;
private final MetaInfo meta;
@@ -156,6 +160,7 @@ public class TrackerClient implements Runnable {
consecutiveFails = 0;
runStarted = false;
_fastUnannounce = false;
snark.setTrackerProblems(null);
_thread = new I2PAppThread(this, _threadName + " #" + (++_runCount), true);
_thread.start();
started = true;
@@ -362,12 +367,21 @@ public class TrackerClient implements Runnable {
if (h == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("Bad announce URL: [" + ann + ']');
return false;
return false;
}
// comment this out if tracker.welterde.i2p upgrades
if (h.equals(DSA_ONLY_TRACKER)) {
Destination dest = _util.getMyDestination();
if (dest != null && dest.getSigType() != SigType.DSA_SHA1) {
if (_log.shouldLog(Log.WARN))
_log.warn("Skipping incompatible tracker: " + ann);
return false;
}
}
if (existing.size() >= MAX_TRACKERS) {
if (_log.shouldLog(Log.INFO))
_log.info("Not using announce URL, we have enough: [" + ann + ']');
return false;
return false;
}
boolean rv = existing.add(h);
if (!rv) {
@@ -528,9 +542,9 @@ public class TrackerClient implements Runnable {
!snark.isChecking() &&
info.getSeedCount() > 100 &&
coordinator.getPeerCount() <= 0 &&
_util.getContext().clock().now() > _startedOn + 2*60*60*1000 &&
_util.getContext().clock().now() > _startedOn + 30*60*1000 &&
snark.getTotalLength() > 0 &&
uploaded >= snark.getTotalLength() * 5 / 4) {
uploaded >= snark.getTotalLength() / 2) {
if (_log.shouldLog(Log.WARN))
_log.warn("Auto stopping " + snark.getBaseName());
snark.setAutoStoppable(false);
@@ -861,18 +875,20 @@ public class TrackerClient implements Runnable {
}
/**
* @param ann an announce URL
* @param ann an announce URL, may be null, returns false if null
* @return true for i2p hosts only
* @since 0.7.12
*/
public static boolean isValidAnnounce(String ann) {
URL url;
if (ann == null)
return false;
URI url;
try {
url = new URL(ann);
} catch (MalformedURLException mue) {
return false;
url = new URI(ann);
} catch (URISyntaxException use) {
return false;
}
return url.getProtocol().equals("http") &&
return "http".equals(url.getScheme()) && url.getHost() != null &&
(url.getHost().endsWith(".i2p") || url.getHost().equals("i2p"));
}
@@ -882,15 +898,17 @@ public class TrackerClient implements Runnable {
* @since 0.9.5
*/
private static Hash getHostHash(String ann) {
URL url;
URI url;
try {
url = new URL(ann);
} catch (MalformedURLException mue) {
url = new URI(ann);
} catch (URISyntaxException use) {
return null;
}
if (!url.getProtocol().equals("http"))
if (!"http".equals(url.getScheme()))
return null;
String host = url.getHost();
if (host == null)
return null;
if (host.endsWith(".i2p"))
return ConvertToHash.getHash(host);
if (host.equals("i2p")) {
@@ -898,7 +916,7 @@ public class TrackerClient implements Runnable {
if (path == null || path.length() < 517 ||
!path.startsWith("/"))
return null;
String[] parts = path.substring(1).split("/?&;", 2);
String[] parts = DataHelper.split(path.substring(1), "[/\\?&;]", 2);
return ConvertToHash.getHash(parts[0]);
}
return null;

View File

@@ -25,6 +25,7 @@ import java.util.List;
import java.util.Map;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
/**
* Holds different types that a bencoded byte array can represent.
@@ -208,7 +209,7 @@ public class BEValue
} else if (bin) {
buf.append(bs.length).append(" bytes: ").append(Base64.encode(bs));
} else {
buf.append('"').append(new String(bs)).append('"');
buf.append('"').append(DataHelper.getUTF8(bs)).append('"');
}
valueString = buf.toString();
} else

View File

@@ -106,6 +106,7 @@ class DHTTracker {
* @param noSeeds true if we do not want seeds in the result
* @return list or empty list (never null)
*/
@SuppressWarnings({"unchecked", "rawtypes"})
List<Hash> getPeers(InfoHash ih, int max, boolean noSeeds) {
Peers peers = _torrents.get(ih);
if (peers == null || max <= 0)

View File

@@ -40,6 +40,7 @@ import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer2;
import org.klomp.snark.I2PSnarkUtil;
import org.klomp.snark.SnarkManager;
import org.klomp.snark.TrackerClient;
import org.klomp.snark.bencode.BDecoder;
@@ -128,8 +129,10 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
/** Max number of nodes to return. BEP 5 says 8 */
private static final int K = 8;
/** Max number of peers to return. BEP 5 doesn't say. We'll use the same as I2PSnarkUtil.MAX_CONNECTIONS */
private static final int MAX_WANT = 16;
/** Max number of peers to return. BEP 5 doesn't say.
* We'll use more than I2PSnarkUtil.MAX_CONNECTIONS since lots could be old.
*/
private static final int MAX_WANT = I2PSnarkUtil.MAX_CONNECTIONS * 3 / 2;
/** overloads error codes which start with 201 */
private static final int REPLY_NONE = 0;
@@ -243,6 +246,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
* @param maxWait how long to wait for each to reply (not total) must be > 0
* @param parallel how many outstanding at once (unimplemented, always 1)
*/
@SuppressWarnings("unchecked")
private void explore(NID target, int maxNodes, long maxWait, int parallel) {
List<NodeInfo> nodes = _knownNodes.findClosest(target, maxNodes);
if (nodes.isEmpty()) {
@@ -327,6 +331,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
* @param noSeeds true if we do not want seeds in the result
* @return possibly empty (never null)
*/
@SuppressWarnings("unchecked")
public Collection<Hash> getPeersAndAnnounce(byte[] ih, int max, long maxWait,
int annMax, long annMaxWait,
boolean isSeed, boolean noSeeds) {
@@ -842,7 +847,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
if (_log.shouldLog(Log.INFO))
_log.info("Sending error " + msg + " to: " + nInfo);
Map<String, Object> map = new HashMap<String, Object>(4);
List<Object> error = new ArrayList(2);
List<Object> error = new ArrayList<Object>(2);
error.add(Integer.valueOf(err));
error.add(msg);
map.put("e", error);
@@ -858,6 +863,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
* @param repliable true for all but announce
* @return null on error
*/
@SuppressWarnings("unchecked")
private ReplyWaiter sendQuery(NodeInfo nInfo, Map<String, Object> map, boolean repliable) {
if (nInfo.equals(_myNodeInfo))
throw new IllegalArgumentException("wtf don't send to ourselves");
@@ -907,6 +913,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
* @param toPort the query port, we will increment here
* @return success
*/
@SuppressWarnings("unchecked")
private boolean sendResponse(NodeInfo nInfo, MsgID msgID, Map<String, Object> map) {
if (nInfo.equals(_myNodeInfo))
throw new IllegalArgumentException("wtf don't send to ourselves");
@@ -1294,7 +1301,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
} else {
List<byte[]> hashes;
if (peers.isEmpty()) {
hashes = Collections.EMPTY_LIST;
hashes = Collections.emptyList();
} else {
hashes = new ArrayList<byte[]>(peers.size());
for (Hash peer : peers) {
@@ -1408,7 +1415,7 @@ public class KRPC implements I2PSessionMuxedListener, DHT {
private List<Hash> receivePeers(NodeInfo nInfo, List<BEValue> peers) throws InvalidBEncodingException {
if (_log.shouldLog(Log.INFO))
_log.info("Rcvd peers from: " + nInfo);
int max = Math.min(MAX_WANT, peers.size());
int max = Math.min(MAX_WANT * 2, peers.size());
List<Hash> rv = new ArrayList<Hash>(max);
for (BEValue bev : peers) {
byte[] b = bev.getBytes();

View File

@@ -102,7 +102,7 @@ class NodeInfo extends SimpleDataStructure {
*/
public NodeInfo(String s) throws DataFormatException {
super();
String[] parts = s.split(":", 4);
String[] parts = DataHelper.split(s, ":", 4);
if (parts.length != 4)
throw new DataFormatException("Bad format");
byte[] nid = Base64.decode(parts[0]);
@@ -225,7 +225,7 @@ class NodeInfo extends SimpleDataStructure {
NodeInfo ni = (NodeInfo) o;
// assume dest matches, ignore it
return this.hash.equals(ni.hash) && nID.equals(ni.nID) && port == ni.port;
} catch (Exception e) {
} catch (RuntimeException e) {
return false;
}
}

View File

@@ -87,6 +87,8 @@ abstract class PersistDHT {
out.println(ni.toPersistentString());
count++;
}
if (out.checkError())
throw new IOException("Failed write to " + file);
} catch (IOException ioe) {
if (log.shouldLog(Log.WARN))
log.warn("Error writing the DHT File", ioe);

View File

@@ -0,0 +1,8 @@
<html>
<body>
<p>
I2P version of the snark bittorrent client, imported in 2005 and heavily enhanced
to add a web UI, DHT support, and other features.
</p>
</body>
</html>

View File

@@ -358,6 +358,7 @@ class BasicServlet extends HttpServlet
writeHeaders(response, content, content_length);
response.setStatus(416);
response.setHeader("Content-Range", InclusiveByteRange.to416HeaderRangeString(content_length));
in.close();
return;
}
@@ -377,7 +378,7 @@ class BasicServlet extends HttpServlet
{
if (content.getContentType()!=null && response.getContentType()==null)
response.setContentType(content.getContentType());
response.setHeader("X-Content-Type-Options", "nosniff");
long lml = content.getLastModified();
if (lml > 0)
response.setDateHeader("Last-Modified",lml);
@@ -393,7 +394,6 @@ class BasicServlet extends HttpServlet
long ct = content.getCacheTime();
if (ct>=0)
response.setHeader("Cache-Control", "public, max-age=" + ct);
}
/* ------------------------------------------------------------ */

View File

@@ -77,7 +77,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
_log = ctx.logManager().getLog(FetchAndAdd.class);
_mgr = mgr;
_url = url;
_name = _("Download torrent file from {0}", url);
_name = _t("Download torrent file from {0}", url);
_dataDir = dataDir;
byte[] fake = null;
try {
@@ -90,7 +90,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
* Set off by startTorrent()
*/
public void run() {
_mgr.addMessageNoEscape(_("Fetching {0}", urlify(_url)));
_mgr.addMessageNoEscape(_t("Fetching {0}", urlify(_url)));
File file = get();
if (!_isRunning) // stopped?
return;
@@ -100,7 +100,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
_mgr.deleteMagnet(this);
add(file);
} else {
_mgr.addMessageNoEscape(_("Torrent was not retrieved from {0}", urlify(_url)) +
_mgr.addMessageNoEscape(_t("Torrent was not retrieved from {0}", urlify(_url)) +
((_failCause != null) ? (": " + DataHelper.stripHTML(_failCause)) : ""));
}
if (file != null)
@@ -127,7 +127,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
out.deleteOnExit();
if (!_mgr.util().connected()) {
_mgr.addMessage(_("Opening the I2P tunnel"));
_mgr.addMessage(_t("Opening the I2P tunnel"));
if (!_mgr.util().connect())
return null;
}
@@ -154,7 +154,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
* This Snark may then be deleted.
*/
private void add(File file) {
_mgr.addMessageNoEscape(_("Torrent fetched from {0}", urlify(_url)));
_mgr.addMessageNoEscape(_t("Torrent fetched from {0}", urlify(_url)));
FileInputStream in = null;
try {
in = new FileInputStream(file);
@@ -163,7 +163,7 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
try { in.close(); } catch (IOException ioe) {}
Snark snark = _mgr.getTorrentByInfoHash(fileInfoHash);
if (snark != null) {
_mgr.addMessage(_("Torrent with this info hash is already running: {0}", snark.getBaseName()));
_mgr.addMessage(_t("Torrent with this info hash is already running: {0}", snark.getBaseName()));
return;
}
@@ -175,9 +175,9 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
if (torrentFile.exists()) {
if (_mgr.getTorrent(canonical) != null)
_mgr.addMessage(_("Torrent already running: {0}", name));
_mgr.addMessage(_t("Torrent already running: {0}", name));
else
_mgr.addMessage(_("Torrent already in the queue: {0}", name));
_mgr.addMessage(_t("Torrent already in the queue: {0}", name));
} else {
// This may take a LONG time to create the storage.
_mgr.copyAndAddTorrent(file, canonical, _dataDir);
@@ -188,9 +188,9 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
throw new IOException("Unknown error - check logs");
}
} catch (IOException ioe) {
_mgr.addMessageNoEscape(_("Torrent at {0} was not valid", urlify(_url)) + ": " + DataHelper.stripHTML(ioe.getMessage()));
_mgr.addMessageNoEscape(_t("Torrent at {0} was not valid", urlify(_url)) + ": " + DataHelper.stripHTML(ioe.getMessage()));
} catch (OutOfMemoryError oom) {
_mgr.addMessageNoEscape(_("ERROR - Out of memory, cannot create torrent from {0}", urlify(_url)) + ": " + DataHelper.stripHTML(oom.getMessage()));
_mgr.addMessageNoEscape(_t("ERROR - Out of memory, cannot create torrent from {0}", urlify(_url)) + ": " + DataHelper.stripHTML(oom.getMessage()));
} finally {
try { if (in != null) in.close(); } catch (IOException ioe) {}
}
@@ -345,11 +345,11 @@ public class FetchAndAdd extends Snark implements EepGet.StatusListener, Runnabl
// End of EepGet status listeners
private String _(String s) {
private String _t(String s) {
return _mgr.util().getString(s);
}
private String _(String s, String o) {
private String _t(String s, String o) {
return _mgr.util().getString(s, o);
}

View File

@@ -10,6 +10,7 @@ import net.i2p.util.FileUtil;
/**
* @deprecated does not work
*/
@Deprecated
public class RunStandalone {
/****
static {

View File

@@ -6,6 +6,8 @@ import java.text.Collator;
import java.util.Collections;
import java.util.Comparator;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.klomp.snark.MetaInfo;
import org.klomp.snark.Snark;
@@ -18,6 +20,13 @@ import org.klomp.snark.Storage;
*/
class Sorters {
/**
* See below
*/
private static final Pattern PATTERN_DE, PATTERN_EN, PATTERN_ES, PATTERN_FR,
PATTERN_IT, PATTERN_NL, PATTERN_PT;
private static Pattern _pattern;
/**
* Negative is reverse
*
@@ -113,8 +122,8 @@ class Sorters {
/**
* Sort alphabetically in current locale, ignore case, ignore leading "the "
* (I guess this is worth it, a lot of torrents start with "The "
* Sort alphabetically in current locale, ignore case, ignore leading
* articles such as "the" if the pattern is set by setPattern()
* @since 0.7.14
*/
private static class TorrentNameComparator implements Comparator<Snark>, Serializable {
@@ -130,13 +139,16 @@ class Sorters {
if (l.getStorage() != null && r.getStorage() == null)
return 1;
String ls = l.getBaseName();
String llc = ls.toLowerCase(Locale.US);
if (llc.startsWith("the ") || llc.startsWith("the.") || llc.startsWith("the_"))
ls = ls.substring(4);
String rs = r.getBaseName();
String rlc = rs.toLowerCase(Locale.US);
if (rlc.startsWith("the ") || rlc.startsWith("the.") || rlc.startsWith("the_"))
rs = rs.substring(4);
Pattern p = _pattern;
if (p != null) {
Matcher m = p.matcher(ls);
if (m.matches())
ls = ls.substring(m.group(1).length());
m = p.matcher(rs);
if (m.matches())
rs = rs.substring(m.group(1).length());
}
return Collator.getInstance().compare(ls, rs);
}
}
@@ -356,13 +368,14 @@ class Sorters {
/**
* @param storage may be null
* @param remainingArray precomputed, non-null iff storage is non-null
*/
public FileAndIndex(File file, Storage storage) {
public FileAndIndex(File file, Storage storage, long[] remainingArray) {
this.file = file;
index = storage != null ? storage.indexOf(file) : -1;
if (index >= 0) {
isDirectory = false;
remaining = storage.remaining(index);
remaining = remainingArray[index];
priority = storage.getPriority(index);
} else {
isDirectory = file.isDirectory();
@@ -527,4 +540,104 @@ class Sorters {
return r.priority - l.priority;
}
}
/*
* Match an indefinite or definite article in the language,
* followed by one or more whitespace, '.', or '_'.
* Does not match "partitive" articles.
*
* https://en.wikipedia.org/wiki/Article_%28grammar%29
* http://www.loc.gov/marc/bibliographic/bdapndxf.html
*/
static {
PATTERN_DE = Pattern.compile(
// can't make the non-capturing innner group work
//"^((?:" +
"^((" +
"der|die|das|des|dem|den|ein|eine|einer|eines|einem|einen" +
")[\\s\\._]+).*",
Pattern.CASE_INSENSITIVE);
PATTERN_EN = Pattern.compile(
"^((" +
"a|an|the" +
")[\\s\\._]+).*",
Pattern.CASE_INSENSITIVE);
PATTERN_ES = Pattern.compile(
"^((" +
"el|la|lo|los|las|un|una|unos|unas" +
")[\\s\\._]+).*",
Pattern.CASE_INSENSITIVE);
PATTERN_FR = Pattern.compile(
// note l' doesn't require whitespace after
"^(l'|((" +
"le|la|les|un|une|des" +
")[\\s\\._]+)).*",
Pattern.CASE_INSENSITIVE);
PATTERN_IT = Pattern.compile(
// note l' and un' don't require whitespace after
"^(l'|un'|((" +
"il|lo|la|i|gli|le|uno|una|un" +
")[\\s\\._]+)).*",
Pattern.CASE_INSENSITIVE);
PATTERN_NL = Pattern.compile(
"^((" +
"de|het|het'n|een|een'n" +
")[\\s\\._]+).*",
Pattern.CASE_INSENSITIVE);
PATTERN_PT = Pattern.compile(
"^((" +
"o|a|os|as|um|uma|uns|umas" +
")[\\s\\._]+).*",
Pattern.CASE_INSENSITIVE);
}
/**
* Sets static field, oh well
* @param lang null for none
* @since 0.9.23
*/
public static void setPattern(String lang) {
Pattern p;
if (lang == null)
p = null;
else if (lang.equals("de"))
p = PATTERN_DE;
else if (lang.equals("en"))
p = PATTERN_EN;
else if (lang.equals("es"))
p = PATTERN_ES;
else if (lang.equals("fr"))
p = PATTERN_FR;
else if (lang.equals("it"))
p = PATTERN_IT;
else if (lang.equals("nl"))
p = PATTERN_NL;
else if (lang.equals("pt"))
p = PATTERN_PT;
else
p = null;
_pattern = p;
}
/****
public static final void main(String[] args) {
if (args.length != 2) {
System.out.println("Usage: Sorters lang 'string'");
System.exit(1);
}
String lang = args[0];
setPattern(lang);
if (_pattern == null) {
System.out.println("Unsupported " + lang);
System.exit(1);
}
String s = args[1];
Matcher m = _pattern.matcher(s);
if (m.matches()) {
System.out.println("Match is \"" + m.group(1) + '"');
} else {
System.out.println("No match for \"" + s + '"');
}
}
****/
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
The i2psnark user interface, implemented as a webapp in i2psnark.war.
</p>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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