propagate from branch 'i2p.i2p' (head 60a9a2297abeaf042645e3f0bc8d106f1ff585bf)
to branch 'i2p.i2p.zzz.test2' (head 6ff6f0bcee835d32aad62449a37f5171afde915a)
@@ -100,15 +100,15 @@
|
||||
<target name="war" depends="jar, bundle, warUpToDate, listChangedFiles" unless="war.uptodate" >
|
||||
<!-- set if unset -->
|
||||
<property name="workspace.changes.tr" value="" />
|
||||
<copy todir="build/icons/.icons" >
|
||||
<fileset dir="../icons/" />
|
||||
<copy todir="build/resources/.resources" >
|
||||
<fileset dir="../resources/" />
|
||||
</copy>
|
||||
<!-- mime.properties must be in with the classes -->
|
||||
<copy file="../mime.properties" todir="build/obj/org/klomp/snark/web" />
|
||||
<war destfile="../i2psnark.war" webxml="../web.xml" >
|
||||
<!-- include only the web stuff, as of 0.7.12 the router will add i2psnark.jar to the classpath for the war -->
|
||||
<classes dir="./build/obj" includes="**/web/*" />
|
||||
<fileset dir="build/icons/" />
|
||||
<fileset dir="build/resources/" />
|
||||
<manifest>
|
||||
<attribute name="Implementation-Version" value="${full.version}" />
|
||||
<attribute name="Built-By" value="${build.built-by}" />
|
||||
@@ -121,7 +121,7 @@
|
||||
|
||||
<target name="warUpToDate">
|
||||
<uptodate property="war.uptodate" targetfile="../i2psnark.war" >
|
||||
<srcfiles dir= "." includes="build/obj/org/klomp/snark/web/*.class ../icons/* ../web.xml" />
|
||||
<srcfiles dir= "." includes="build/obj/org/klomp/snark/web/*.class ../resources/**/* ../web.xml" />
|
||||
</uptodate>
|
||||
</target>
|
||||
|
||||
|
||||
@@ -57,8 +57,8 @@ public class Peer implements Comparable<Peer>
|
||||
private DataOutputStream dout;
|
||||
|
||||
/** running counters */
|
||||
private long downloaded;
|
||||
private long uploaded;
|
||||
private final AtomicLong downloaded = new AtomicLong();
|
||||
private final AtomicLong uploaded = new AtomicLong();
|
||||
|
||||
// Keeps state for in/out connections. Non-null when the handshake
|
||||
// was successful, the connection setup and runs
|
||||
@@ -618,7 +618,7 @@ public class Peer implements Comparable<Peer>
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public void downloaded(int size) {
|
||||
downloaded += size;
|
||||
downloaded.addAndGet(size);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -626,7 +626,7 @@ public class Peer implements Comparable<Peer>
|
||||
* @since 0.8.4
|
||||
*/
|
||||
public void uploaded(int size) {
|
||||
uploaded += size;
|
||||
uploaded.addAndGet(size);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -635,7 +635,7 @@ public class Peer implements Comparable<Peer>
|
||||
*/
|
||||
public long getDownloaded()
|
||||
{
|
||||
return downloaded;
|
||||
return downloaded.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -644,7 +644,7 @@ public class Peer implements Comparable<Peer>
|
||||
*/
|
||||
public long getUploaded()
|
||||
{
|
||||
return uploaded;
|
||||
return uploaded.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -652,8 +652,8 @@ public class Peer implements Comparable<Peer>
|
||||
*/
|
||||
public void resetCounters()
|
||||
{
|
||||
downloaded = 0;
|
||||
uploaded = 0;
|
||||
downloaded.set(0);
|
||||
uploaded.set(0);
|
||||
}
|
||||
|
||||
public long getInactiveTime() {
|
||||
|
||||
@@ -27,7 +27,6 @@ import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
@@ -245,16 +244,19 @@ public class Snark
|
||||
*
|
||||
* @deprecated unused
|
||||
*/
|
||||
/****
|
||||
Snark(I2PSnarkUtil util, String torrent, String ip, int user_port,
|
||||
StorageListener slistener, CoordinatorListener clistener) {
|
||||
this(util, torrent, ip, user_port, slistener, clistener, null, null, null, true, ".");
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* single torrent - via router
|
||||
*
|
||||
* @deprecated unused
|
||||
*/
|
||||
/****
|
||||
public Snark(I2PAppContext ctx, Properties opts, String torrent,
|
||||
StorageListener slistener, boolean start, String rootDir) {
|
||||
this(new I2PSnarkUtil(ctx), torrent, null, -1, slistener, null, null, null, null, false, rootDir);
|
||||
@@ -284,6 +286,7 @@ public class Snark
|
||||
if (start)
|
||||
this.startTorrent();
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* multitorrent
|
||||
@@ -515,18 +518,13 @@ public class Snark
|
||||
|
||||
// Create a new ID and fill it with something random. First nine
|
||||
// zeros bytes, then three bytes filled with snark and then
|
||||
// sixteen random bytes.
|
||||
// eight random bytes.
|
||||
byte snark = (((3 + 7 + 10) * (1000 - 8)) / 992) - 17;
|
||||
byte[] rv = new byte[20];
|
||||
Random random = I2PAppContext.getGlobalContext().random();
|
||||
int i;
|
||||
for (i = 0; i < 9; i++)
|
||||
rv[i] = 0;
|
||||
rv[i++] = snark;
|
||||
rv[i++] = snark;
|
||||
rv[i++] = snark;
|
||||
while (i < 20)
|
||||
rv[i++] = (byte)random.nextInt(256);
|
||||
rv[9] = snark;
|
||||
rv[10] = snark;
|
||||
rv[11] = snark;
|
||||
I2PAppContext.getGlobalContext().random().nextBytes(rv, 12, 8);
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -958,6 +956,7 @@ public class Snark
|
||||
* non-valid argument list. The given listeners will be
|
||||
* passed to all components that take one.
|
||||
*/
|
||||
/****
|
||||
private static Snark parseArguments(String[] args,
|
||||
StorageListener slistener,
|
||||
CoordinatorListener clistener)
|
||||
@@ -972,6 +971,7 @@ public class Snark
|
||||
int i = 0;
|
||||
while (i < args.length)
|
||||
{
|
||||
****/
|
||||
/*
|
||||
if (args[i].equals("--debug"))
|
||||
{
|
||||
@@ -993,7 +993,9 @@ public class Snark
|
||||
catch (NumberFormatException nfe) { }
|
||||
}
|
||||
}
|
||||
else */ if (args[i].equals("--port"))
|
||||
else */
|
||||
/****
|
||||
if (args[i].equals("--port"))
|
||||
{
|
||||
if (args.length - 1 < i + 1)
|
||||
usage("--port needs port number to listen on");
|
||||
@@ -1099,6 +1101,7 @@ public class Snark
|
||||
System.out.println
|
||||
(" \tor (with --share) a file to share.");
|
||||
}
|
||||
****/
|
||||
|
||||
/**
|
||||
* Aborts program abnormally.
|
||||
|
||||
@@ -61,7 +61,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
|
||||
private static final String DEFAULT_NAME = "i2psnark";
|
||||
public static final String PROP_CONFIG_FILE = "i2psnark.configFile";
|
||||
private static final String WARBASE = "/.icons/";
|
||||
private static final String WARBASE = "/.resources/";
|
||||
private static final char HELLIP = '\u2026';
|
||||
|
||||
public I2PSnarkServlet() {
|
||||
@@ -194,22 +194,8 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
resp.setHeader("Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'");
|
||||
resp.setHeader("X-XSS-Protection", "1; mode=block");
|
||||
|
||||
String peerParam = req.getParameter("p");
|
||||
String stParam = req.getParameter("st");
|
||||
String peerString;
|
||||
if (peerParam == null || (!_manager.util().connected()) ||
|
||||
peerParam.replaceAll("[a-zA-Z0-9~=-]", "").length() > 0) { // XSS
|
||||
peerString = "";
|
||||
} else {
|
||||
peerString = "?p=" + DataHelper.stripHTML(peerParam);
|
||||
}
|
||||
if (stParam != null && !stParam.equals("0")) {
|
||||
stParam = DataHelper.stripHTML(stParam);
|
||||
if (peerString.length() > 0)
|
||||
peerString += "&st=" + stParam;
|
||||
else
|
||||
peerString = "?st="+ stParam;
|
||||
}
|
||||
String pOverride = _manager.util().connected() ? null : "";
|
||||
String peerString = getQueryString(req, pOverride, null, null);
|
||||
|
||||
// AJAX for mainsection
|
||||
if ("/.ajax/xhr1.html".equals(path)) {
|
||||
@@ -292,6 +278,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
out.write(_("Configuration"));
|
||||
else
|
||||
out.write(_("Anonymous BitTorrent Client"));
|
||||
String peerParam = req.getParameter("p");
|
||||
if ("2".equals(peerParam))
|
||||
out.write(" | Debug Mode");
|
||||
out.write("</title>\n");
|
||||
@@ -413,13 +400,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
boolean isForm = _manager.util().connected() || !snarks.isEmpty();
|
||||
if (isForm) {
|
||||
out.write("<form action=\"_post\" method=\"POST\">\n");
|
||||
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" >\n");
|
||||
// don't lose peer setting
|
||||
if (peerParam != null)
|
||||
out.write("<input type=\"hidden\" name=\"p\" value=\"" + peerParam + "\" >\n");
|
||||
// ...or st setting
|
||||
if (stParam != null)
|
||||
out.write("<input type=\"hidden\" name=\"st\" value=\"" + stParam + "\" >\n");
|
||||
writeHiddenInputs(out, req, null);
|
||||
}
|
||||
out.write(TABLE_HEADER);
|
||||
|
||||
@@ -441,18 +422,29 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
}
|
||||
int pageSize = Math.max(_manager.getPageSize(), 5);
|
||||
|
||||
out.write("<tr><th><img border=\"0\" src=\"" + _imgPath + "status.png\" title=\"");
|
||||
String currentSort = req.getParameter("sort");
|
||||
boolean showSort = total > 1;
|
||||
out.write("<tr><th>");
|
||||
String sort = ("2".equals(currentSort)) ? "-2" : "2";
|
||||
if (showSort) {
|
||||
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
|
||||
out.write("\">");
|
||||
}
|
||||
out.write("<img border=\"0\" src=\"" + _imgPath + "status.png\" title=\"");
|
||||
if (showSort)
|
||||
out.write(_("Sort by {0}", _("Status")));
|
||||
else
|
||||
out.write(_("Status"));
|
||||
out.write("\" alt=\"");
|
||||
out.write(_("Status"));
|
||||
out.write("\"></th>\n<th>");
|
||||
out.write("\">");
|
||||
if (showSort)
|
||||
out.write("</a>");
|
||||
out.write("</th>\n<th>");
|
||||
if (_manager.util().connected() && !snarks.isEmpty()) {
|
||||
out.write(" <a href=\"" + _contextPath + '/');
|
||||
if (peerParam != null) {
|
||||
if (stParam != null) {
|
||||
out.write("?st=");
|
||||
out.write(stParam);
|
||||
}
|
||||
// disable peer view
|
||||
out.write("\">");
|
||||
out.write("<img border=\"0\" src=\"" + _imgPath + "hidepeers.png\" title=\"");
|
||||
out.write(_("Hide Peers"));
|
||||
@@ -460,11 +452,8 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
out.write(_("Hide Peers"));
|
||||
out.write("\">");
|
||||
} else {
|
||||
out.write("?p=1");
|
||||
if (stParam != null) {
|
||||
out.write("&st=");
|
||||
out.write(stParam);
|
||||
}
|
||||
// enable peer view
|
||||
out.write(getQueryString(req, "1", null, null));
|
||||
out.write("\">");
|
||||
out.write("<img border=\"0\" src=\"" + _imgPath + "showpeers.png\" title=\"");
|
||||
out.write(_("Show Peers"));
|
||||
@@ -475,56 +464,158 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
out.write("</a><br>\n");
|
||||
}
|
||||
out.write("</th>\n<th colspan=\"2\" align=\"left\">");
|
||||
// cycle through sort by name or type
|
||||
boolean isTypeSort = false;
|
||||
if (showSort) {
|
||||
if (currentSort == null || "0".equals(currentSort) || "1".equals(currentSort)) {
|
||||
sort = "-1";
|
||||
} else if ("-1".equals(currentSort)) {
|
||||
sort = "12";
|
||||
isTypeSort = true;
|
||||
} else if ("12".equals(currentSort)) {
|
||||
sort = "-12";
|
||||
isTypeSort = true;
|
||||
} else {
|
||||
sort = "";
|
||||
}
|
||||
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
|
||||
out.write("\">");
|
||||
}
|
||||
out.write("<img border=\"0\" src=\"" + _imgPath + "torrent.png\" title=\"");
|
||||
if (showSort)
|
||||
out.write(_("Sort by {0}", (isTypeSort ? _("File type") : _("Torrent"))));
|
||||
else
|
||||
out.write(_("Torrent"));
|
||||
out.write("\" alt=\"");
|
||||
out.write(_("Torrent"));
|
||||
out.write("\"></th>\n<th align=\"center\">");
|
||||
out.write("\">");
|
||||
if (showSort)
|
||||
out.write("</a>");
|
||||
out.write("</th>\n<th align=\"center\">");
|
||||
if (total > 0 && (start > 0 || total > pageSize)) {
|
||||
writePageNav(out, start, pageSize, total, peerParam, noThinsp);
|
||||
writePageNav(out, req, start, pageSize, total, noThinsp);
|
||||
}
|
||||
out.write("</th>\n<th align=\"right\">");
|
||||
if (_manager.util().connected() && !snarks.isEmpty()) {
|
||||
if (showSort) {
|
||||
sort = ("4".equals(currentSort)) ? "-4" : "4";
|
||||
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
|
||||
out.write("\">");
|
||||
}
|
||||
out.write("<img border=\"0\" src=\"" + _imgPath + "eta.png\" title=\"");
|
||||
if (showSort)
|
||||
out.write(_("Sort by {0}", _("Estimated time remaining")));
|
||||
else
|
||||
out.write(_("Estimated time remaining"));
|
||||
out.write("\" alt=\"");
|
||||
// Translators: Please keep short or translate as " "
|
||||
out.write(_("ETA"));
|
||||
out.write("\">");
|
||||
if (showSort)
|
||||
out.write("</a>");
|
||||
}
|
||||
out.write("</th>\n<th align=\"right\">");
|
||||
// cycle through sort by size or downloaded
|
||||
boolean isDlSort = false;
|
||||
if (showSort) {
|
||||
if ("5".equals(currentSort)) {
|
||||
sort = "-5";
|
||||
} else if ("-5".equals(currentSort)) {
|
||||
sort = "6";
|
||||
isDlSort = true;
|
||||
} else if ("6".equals(currentSort)) {
|
||||
sort = "-6";
|
||||
isDlSort = true;
|
||||
} else {
|
||||
sort = "5";
|
||||
}
|
||||
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
|
||||
out.write("\">");
|
||||
}
|
||||
out.write("<img border=\"0\" src=\"" + _imgPath + "head_rx.png\" title=\"");
|
||||
if (showSort)
|
||||
out.write(_("Sort by {0}", (isDlSort ? _("Downloaded") : _("Size"))));
|
||||
else
|
||||
out.write(_("Downloaded"));
|
||||
out.write("\" alt=\"");
|
||||
// Translators: Please keep short or translate as " "
|
||||
out.write(_("RX"));
|
||||
out.write("\">");
|
||||
if (showSort)
|
||||
out.write("</a>");
|
||||
out.write("</th>\n<th align=\"right\">");
|
||||
boolean isRatSort = false;
|
||||
if (!snarks.isEmpty()) {
|
||||
// cycle through sort by uploaded or ratio
|
||||
boolean nextRatSort = false;
|
||||
if (showSort) {
|
||||
if ("7".equals(currentSort)) {
|
||||
sort = "-7";
|
||||
} else if ("-7".equals(currentSort)) {
|
||||
sort = "11";
|
||||
nextRatSort = true;
|
||||
} else if ("11".equals(currentSort)) {
|
||||
sort = "-11";
|
||||
nextRatSort = true;
|
||||
isRatSort = true;
|
||||
} else if ("-11".equals(currentSort)) {
|
||||
sort = "7";
|
||||
isRatSort = true;
|
||||
} else {
|
||||
sort = "7";
|
||||
}
|
||||
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
|
||||
out.write("\">");
|
||||
}
|
||||
out.write("<img border=\"0\" src=\"" + _imgPath + "head_tx.png\" title=\"");
|
||||
if (showSort)
|
||||
out.write(_("Sort by {0}", (nextRatSort ? _("Upload ratio") : _("Uploaded"))));
|
||||
else
|
||||
out.write(_("Uploaded"));
|
||||
out.write("\" alt=\"");
|
||||
// Translators: Please keep short or translate as " "
|
||||
out.write(_("TX"));
|
||||
out.write("\">");
|
||||
if (showSort)
|
||||
out.write("</a>");
|
||||
}
|
||||
out.write("</th>\n<th align=\"right\">");
|
||||
if (_manager.util().connected() && !snarks.isEmpty()) {
|
||||
if (showSort) {
|
||||
sort = ("8".equals(currentSort)) ? "-8" : "8";
|
||||
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
|
||||
out.write("\">");
|
||||
}
|
||||
out.write("<img border=\"0\" src=\"" + _imgPath + "head_rxspeed.png\" title=\"");
|
||||
if (showSort)
|
||||
out.write(_("Sort by {0}", _("Down Rate")));
|
||||
else
|
||||
out.write(_("Down Rate"));
|
||||
out.write("\" alt=\"");
|
||||
// Translators: Please keep short or translate as " "
|
||||
out.write(_("RX Rate"));
|
||||
out.write(" \">");
|
||||
out.write("\">");
|
||||
if (showSort)
|
||||
out.write("</a>");
|
||||
}
|
||||
out.write("</th>\n<th align=\"right\">");
|
||||
if (_manager.util().connected() && !snarks.isEmpty()) {
|
||||
if (showSort) {
|
||||
sort = ("9".equals(currentSort)) ? "-9" : "9";
|
||||
out.write("<a href=\"" + _contextPath + '/' + getQueryString(req, null, null, sort));
|
||||
out.write("\">");
|
||||
}
|
||||
out.write("<img border=\"0\" src=\"" + _imgPath + "head_txspeed.png\" title=\"");
|
||||
if (showSort)
|
||||
out.write(_("Sort by {0}", _("Up Rate")));
|
||||
else
|
||||
out.write(_("Up Rate"));
|
||||
out.write("\" alt=\"");
|
||||
// Translators: Please keep short or translate as " "
|
||||
out.write(_("TX Rate"));
|
||||
out.write(" \">");
|
||||
out.write("\">");
|
||||
if (showSort)
|
||||
out.write("</a>");
|
||||
}
|
||||
out.write("</th>\n<th align=\"center\">");
|
||||
|
||||
@@ -580,12 +671,11 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
String uri = _contextPath + '/';
|
||||
boolean showDebug = "2".equals(peerParam);
|
||||
|
||||
String stParamStr = stParam == null ? "" : "&st=" + stParam;
|
||||
for (int i = 0; i < total; i++) {
|
||||
Snark snark = snarks.get(i);
|
||||
boolean showPeers = showDebug || "1".equals(peerParam) || Base64.encode(snark.getInfoHash()).equals(peerParam);
|
||||
boolean hide = i < start || i >= start + pageSize;
|
||||
displaySnark(out, snark, uri, i, stats, showPeers, isDegraded, noThinsp, showDebug, hide, stParamStr);
|
||||
displaySnark(out, req, snark, uri, i, stats, showPeers, isDegraded, noThinsp, showDebug, hide, isRatSort);
|
||||
}
|
||||
|
||||
if (total == 0) {
|
||||
@@ -636,17 +726,105 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
return start == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* hidden inputs for nonce and paramters p, st, and sort
|
||||
*
|
||||
* @param out writes to it
|
||||
* @param action if non-null, add it as the action
|
||||
* @since 0.9.16
|
||||
*/
|
||||
private void writeHiddenInputs(PrintWriter out, HttpServletRequest req, String action) {
|
||||
StringBuilder buf = new StringBuilder(256);
|
||||
writeHiddenInputs(buf, req, action);
|
||||
out.write(buf.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* hidden inputs for nonce and paramters p, st, and sort
|
||||
*
|
||||
* @param out appends to it
|
||||
* @param action if non-null, add it as the action
|
||||
* @since 0.9.16
|
||||
*/
|
||||
private void writeHiddenInputs(StringBuilder buf, HttpServletRequest req, String action) {
|
||||
buf.append("<input type=\"hidden\" name=\"nonce\" value=\"")
|
||||
.append(_nonce).append("\" >\n");
|
||||
String peerParam = req.getParameter("p");
|
||||
if (peerParam != null) {
|
||||
buf.append("<input type=\"hidden\" name=\"p\" value=\"")
|
||||
.append(DataHelper.stripHTML(peerParam)).append("\" >\n");
|
||||
}
|
||||
String stParam = req.getParameter("st");
|
||||
if (stParam != null) {
|
||||
buf.append("<input type=\"hidden\" name=\"st\" value=\"")
|
||||
.append(DataHelper.stripHTML(stParam)).append("\" >\n");
|
||||
}
|
||||
String soParam = req.getParameter("sort");
|
||||
if (soParam != null) {
|
||||
buf.append("<input type=\"hidden\" name=\"sort\" value=\"")
|
||||
.append(DataHelper.stripHTML(soParam)).append("\" >\n");
|
||||
}
|
||||
if (action != null) {
|
||||
buf.append("<input type=\"hidden\" name=\"action\" value=\"")
|
||||
.append(action).append("\" >\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build HTML-escaped and stripped query string
|
||||
*
|
||||
* @param p override or "" for default or null to keep the same as in req
|
||||
* @param st override or "" for default or null to keep the same as in req
|
||||
* @param so override or "" for default or null to keep the same as in req
|
||||
* @return non-null, possibly empty
|
||||
* @since 0.9.16
|
||||
*/
|
||||
private static String getQueryString(HttpServletRequest req, String p, String st, String so) {
|
||||
StringBuilder buf = new StringBuilder(64);
|
||||
if (p == null) {
|
||||
p = req.getParameter("p");
|
||||
if (p != null)
|
||||
p = DataHelper.stripHTML(p);
|
||||
}
|
||||
if (p != null && !p.equals(""))
|
||||
buf.append("?p=").append(p);
|
||||
if (so == null) {
|
||||
so = req.getParameter("sort");
|
||||
if (so != null)
|
||||
so = DataHelper.stripHTML(so);
|
||||
}
|
||||
if (so != null && !so.equals("")) {
|
||||
if (buf.length() <= 0)
|
||||
buf.append("?sort=");
|
||||
else
|
||||
buf.append("&sort=");
|
||||
buf.append(so);
|
||||
}
|
||||
if (st == null) {
|
||||
st = req.getParameter("st");
|
||||
if (st != null)
|
||||
st = DataHelper.stripHTML(st);
|
||||
}
|
||||
if (st != null && !st.equals("")) {
|
||||
if (buf.length() <= 0)
|
||||
buf.append("?st=");
|
||||
else
|
||||
buf.append("&st=");
|
||||
buf.append(st);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.6
|
||||
*/
|
||||
private void writePageNav(PrintWriter out, int start, int pageSize, int total,
|
||||
String peerParam, boolean noThinsp) {
|
||||
private void writePageNav(PrintWriter out, HttpServletRequest req, int start, int pageSize, int total,
|
||||
boolean noThinsp) {
|
||||
// Page nav
|
||||
if (start > 0) {
|
||||
// First
|
||||
out.write("<a href=\"" + _contextPath);
|
||||
if (peerParam != null)
|
||||
out.write("?p=" + peerParam);
|
||||
out.write(getQueryString(req, null, "", null));
|
||||
out.write("\">" +
|
||||
"<img alt=\"" + _("First") + "\" title=\"" + _("First page") + "\" border=\"0\" src=\"" +
|
||||
_imgPath + "control_rewind_blue.png\">" +
|
||||
@@ -655,9 +833,9 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
//if (prev > 0) {
|
||||
if (true) {
|
||||
// Back
|
||||
out.write(" <a href=\"" + _contextPath + "?st=" + prev);
|
||||
if (peerParam != null)
|
||||
out.write("&p=" + peerParam);
|
||||
out.write(" <a href=\"" + _contextPath);
|
||||
String sprev = (prev > 0) ? Integer.toString(prev) : "";
|
||||
out.write(getQueryString(req, null, sprev, null));
|
||||
out.write("\">" +
|
||||
"<img alt=\"" + _("Prev") + "\" title=\"" + _("Previous page") + "\" border=\"0\" src=\"" +
|
||||
_imgPath + "control_back_blue.png\">" +
|
||||
@@ -690,9 +868,8 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
//if (next + pageSize < total) {
|
||||
if (true) {
|
||||
// Next
|
||||
out.write(" <a href=\"" + _contextPath + "?st=" + next);
|
||||
if (peerParam != null)
|
||||
out.write("&p=" + peerParam);
|
||||
out.write(" <a href=\"" + _contextPath);
|
||||
out.write(getQueryString(req, null, Integer.toString(next), null));
|
||||
out.write("\">" +
|
||||
"<img alt=\"" + _("Next") + "\" title=\"" + _("Next page") + "\" border=\"0\" src=\"" +
|
||||
_imgPath + "control_play_blue.png\">" +
|
||||
@@ -700,9 +877,8 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
}
|
||||
// Last
|
||||
int last = ((total - 1) / pageSize) * pageSize;
|
||||
out.write(" <a href=\"" + _contextPath + "?st=" + last);
|
||||
if (peerParam != null)
|
||||
out.write("&p=" + peerParam);
|
||||
out.write(" <a href=\"" + _contextPath);
|
||||
out.write(getQueryString(req, null, Integer.toString(last), null));
|
||||
out.write("\">" +
|
||||
"<img alt=\"" + _("Last") + "\" title=\"" + _("Last page") + "\" border=\"0\" src=\"" +
|
||||
_imgPath + "control_fastforward_blue.png\">" +
|
||||
@@ -1190,34 +1366,22 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort alphabetically in current locale, ignore case, ignore leading "the "
|
||||
* (I guess this is worth it, a lot of torrents start with "The "
|
||||
* @since 0.7.14
|
||||
*/
|
||||
private static class TorrentNameComparator implements Comparator<Snark>, Serializable {
|
||||
|
||||
public int compare(Snark l, Snark r) {
|
||||
// put downloads and magnets first
|
||||
if (l.getStorage() == null && r.getStorage() != null)
|
||||
return -1;
|
||||
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);
|
||||
return Collator.getInstance().compare(ls, rs);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Snark> getSortedSnarks(HttpServletRequest req) {
|
||||
ArrayList<Snark> rv = new ArrayList<Snark>(_manager.getTorrents());
|
||||
Collections.sort(rv, new TorrentNameComparator());
|
||||
if (rv.size() > 1) {
|
||||
int sort = 0;
|
||||
String ssort = req.getParameter("sort");
|
||||
if (ssort != null) {
|
||||
try {
|
||||
sort = Integer.parseInt(ssort);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
try {
|
||||
Collections.sort(rv, Sorters.getComparator(sort, this));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
// Java 7 TimSort - may be unstable
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -1229,11 +1393,11 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
*
|
||||
* @param stats in/out param (totals)
|
||||
* @param statsOnly if true, output nothing, update stats only
|
||||
* @param stParam non null; empty or e.g. &st=10
|
||||
*/
|
||||
private void displaySnark(PrintWriter out, Snark snark, String uri, int row, long stats[], boolean showPeers,
|
||||
private void displaySnark(PrintWriter out, HttpServletRequest req,
|
||||
Snark snark, String uri, int row, long stats[], boolean showPeers,
|
||||
boolean isDegraded, boolean noThinsp, boolean showDebug, boolean statsOnly,
|
||||
String stParam) throws IOException {
|
||||
boolean showRatios) throws IOException {
|
||||
// stats
|
||||
long uploaded = snark.getUploaded();
|
||||
stats[0] += snark.getDownloaded();
|
||||
@@ -1335,7 +1499,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
if (curPeers > 0 && !showPeers)
|
||||
statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + img + ".png\" title=\"" + txt + "\"></td>" +
|
||||
"<td class=\"snarkTorrentStatus\">" + txt +
|
||||
": <a href=\"" + uri + "?p=" + Base64.encode(snark.getInfoHash()) + stParam + "\">" +
|
||||
": <a href=\"" + uri + getQueryString(req, Base64.encode(snark.getInfoHash()), null, null) + "\">" +
|
||||
curPeers + thinsp(noThinsp) +
|
||||
ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
|
||||
else
|
||||
@@ -1351,7 +1515,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
if (isRunning && curPeers > 0 && downBps > 0 && !showPeers)
|
||||
statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "downloading.png\" title=\"" + _("OK") + "\"></td>" +
|
||||
"<td class=\"snarkTorrentStatus\">" + _("OK") +
|
||||
": <a href=\"" + uri + "?p=" + Base64.encode(snark.getInfoHash()) + stParam + "\">" +
|
||||
": <a href=\"" + uri + getQueryString(req, Base64.encode(snark.getInfoHash()), null, null) + "\">" +
|
||||
curPeers + thinsp(noThinsp) +
|
||||
ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
|
||||
else if (isRunning && curPeers > 0 && downBps > 0)
|
||||
@@ -1362,7 +1526,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
else if (isRunning && curPeers > 0 && !showPeers)
|
||||
statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "stalled.png\" title=\"" + _("Stalled") + "\"></td>" +
|
||||
"<td class=\"snarkTorrentStatus\">" + _("Stalled") +
|
||||
": <a href=\"" + uri + "?p=" + Base64.encode(snark.getInfoHash()) + stParam + "\">" +
|
||||
": <a href=\"" + uri + getQueryString(req, Base64.encode(snark.getInfoHash()), null, null) + "\">" +
|
||||
curPeers + thinsp(noThinsp) +
|
||||
ngettext("1 peer", "{0} peers", knownPeers) + "</a>";
|
||||
else if (isRunning && curPeers > 0)
|
||||
@@ -1466,8 +1630,17 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
// out.write("??"); // no meta size yet
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td align=\"right\" class=\"snarkTorrentUploaded\">");
|
||||
if (isValid && uploaded > 0)
|
||||
if (isValid) {
|
||||
if (showRatios) {
|
||||
if (total > 0) {
|
||||
double ratio = uploaded / ((double) total);
|
||||
out.write((new DecimalFormat("0.000")).format(ratio));
|
||||
out.write(" x");
|
||||
}
|
||||
} else if (uploaded > 0) {
|
||||
out.write(formatSize(uploaded));
|
||||
}
|
||||
}
|
||||
out.write("</td>\n\t");
|
||||
out.write("<td align=\"right\" class=\"snarkTorrentRateDown\">");
|
||||
if (isRunning && needed > 0)
|
||||
@@ -1484,7 +1657,8 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
} else if (isRunning) {
|
||||
// Stop Button
|
||||
if (isDegraded)
|
||||
out.write("<a href=\"" + _contextPath + "/?action=Stop_" + b64 + "&nonce=" + _nonce + stParam + "\"><img title=\"");
|
||||
out.write("<a href=\"" + _contextPath + "/?action=Stop_" + b64 + "&nonce=" + _nonce +
|
||||
getQueryString(req, "", null, null).replace("?", "&") + "\"><img title=\"");
|
||||
else
|
||||
out.write("<input type=\"image\" name=\"action_Stop_" + b64 + "\" value=\"foo\" title=\"");
|
||||
out.write(_("Stop the torrent"));
|
||||
@@ -1498,7 +1672,8 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
// Start Button
|
||||
// This works in Opera but it's displayed a little differently, so use noThinsp here too so all 3 icons are consistent
|
||||
if (noThinsp)
|
||||
out.write("<a href=\"" + _contextPath + "/?action=Start_" + b64 + "&nonce=" + _nonce + stParam + "\"><img title=\"");
|
||||
out.write("<a href=\"" + _contextPath + "/?action=Start_" + b64 + "&nonce=" + _nonce +
|
||||
getQueryString(req, "", null, null).replace("?", "&") + "\"><img title=\"");
|
||||
else
|
||||
out.write("<input type=\"image\" name=\"action_Start_" + b64 + "\" value=\"foo\" title=\"");
|
||||
out.write(_("Start the torrent"));
|
||||
@@ -1512,7 +1687,8 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
// Remove Button
|
||||
// Doesnt work with Opera so use noThinsp instead of isDegraded
|
||||
if (noThinsp)
|
||||
out.write("<a href=\"" + _contextPath + "/?action=Remove_" + b64 + "&nonce=" + _nonce + stParam + "\"><img title=\"");
|
||||
out.write("<a href=\"" + _contextPath + "/?action=Remove_" + b64 + "&nonce=" + _nonce +
|
||||
getQueryString(req, "", null, null).replace("?", "&") + "\"><img title=\"");
|
||||
else
|
||||
out.write("<input type=\"image\" name=\"action_Remove_" + b64 + "\" value=\"foo\" title=\"");
|
||||
out.write(_("Remove the torrent from the active list, deleting the .torrent file"));
|
||||
@@ -1533,7 +1709,8 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
// Delete Button
|
||||
// Doesnt work with Opera so use noThinsp instead of isDegraded
|
||||
if (noThinsp)
|
||||
out.write("<a href=\"" + _contextPath + "/?action=Delete_" + b64 + "&nonce=" + _nonce + stParam + "\"><img title=\"");
|
||||
out.write("<a href=\"" + _contextPath + "/?action=Delete_" + b64 + "&nonce=" + _nonce +
|
||||
getQueryString(req, "", null, null).replace("?", "&") + "\"><img title=\"");
|
||||
else
|
||||
out.write("<input type=\"image\" name=\"action_Delete_" + b64 + "\" value=\"foo\" title=\"");
|
||||
out.write(_("Delete the .torrent file and the associated data file(s)"));
|
||||
@@ -1842,12 +2019,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
out.write("<div class=\"snarkNewTorrent\">\n");
|
||||
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
|
||||
out.write("<form action=\"_post\" method=\"POST\">\n");
|
||||
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" >\n");
|
||||
out.write("<input type=\"hidden\" name=\"action\" value=\"Add\" >\n");
|
||||
// don't lose peer setting
|
||||
String peerParam = req.getParameter("p");
|
||||
if (peerParam != null)
|
||||
out.write("<input type=\"hidden\" name=\"p\" value=\"" + DataHelper.stripHTML(peerParam) + "\" >\n");
|
||||
writeHiddenInputs(out, req, "Add");
|
||||
out.write("<div class=\"addtorrentsection\"><span class=\"snarkConfigTitle\">");
|
||||
out.write("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "add.png\"> ");
|
||||
out.write(_("Add Torrent"));
|
||||
@@ -1874,12 +2046,7 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
out.write("<a name=\"add\"></a><div class=\"newtorrentsection\"><div class=\"snarkNewTorrent\">\n");
|
||||
// *not* enctype="multipart/form-data", so that the input type=file sends the filename, not the file
|
||||
out.write("<form action=\"_post\" method=\"POST\">\n");
|
||||
out.write("<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" >\n");
|
||||
out.write("<input type=\"hidden\" name=\"action\" value=\"Create\" >\n");
|
||||
// don't lose peer setting
|
||||
String peerParam = req.getParameter("p");
|
||||
if (peerParam != null)
|
||||
out.write("<input type=\"hidden\" name=\"p\" value=\"" + DataHelper.stripHTML(peerParam) + "\" >\n");
|
||||
writeHiddenInputs(out, req, "Create");
|
||||
out.write("<span class=\"snarkConfigTitle\">");
|
||||
out.write("<img alt=\"\" border=\"0\" src=\"" + _imgPath + "create.png\"> ");
|
||||
out.write(_("Create Torrent"));
|
||||
@@ -1945,10 +2112,9 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
//int seedPct = 0;
|
||||
|
||||
out.write("<form action=\"" + _contextPath + "/configure\" method=\"POST\">\n" +
|
||||
"<div class=\"configsectionpanel\"><div class=\"snarkConfig\">\n" +
|
||||
"<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" >\n" +
|
||||
"<input type=\"hidden\" name=\"action\" value=\"Save\" >\n" +
|
||||
"<span class=\"snarkConfigTitle\">" +
|
||||
"<div class=\"configsectionpanel\"><div class=\"snarkConfig\">\n");
|
||||
writeHiddenInputs(out, req, "Save");
|
||||
out.write("<span class=\"snarkConfigTitle\">" +
|
||||
"<img alt=\"\" border=\"0\" src=\"" + _imgPath + "config.png\"> ");
|
||||
out.write(_("Configuration"));
|
||||
out.write("</span><hr>\n" +
|
||||
@@ -2131,10 +2297,9 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
private void writeTrackerForm(PrintWriter out, HttpServletRequest req) throws IOException {
|
||||
StringBuilder buf = new StringBuilder(1024);
|
||||
buf.append("<form action=\"" + _contextPath + "/configure\" method=\"POST\">\n" +
|
||||
"<div class=\"configsectionpanel\"><div class=\"snarkConfig\">\n" +
|
||||
"<input type=\"hidden\" name=\"nonce\" value=\"" + _nonce + "\" >\n" +
|
||||
"<input type=\"hidden\" name=\"action\" value=\"Save2\" >\n" +
|
||||
"<span class=\"snarkConfigTitle\">" +
|
||||
"<div class=\"configsectionpanel\"><div class=\"snarkConfig\">\n");
|
||||
writeHiddenInputs(buf, req, "Save2");
|
||||
buf.append("<span class=\"snarkConfigTitle\">" +
|
||||
"<img alt=\"\" border=\"0\" src=\"" + _imgPath + "config.png\"> ");
|
||||
buf.append(_("Trackers"));
|
||||
buf.append("</span><hr>\n" +
|
||||
@@ -2435,6 +2600,10 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
// dummy
|
||||
r = new File("");
|
||||
}
|
||||
|
||||
boolean showPriority = snark != null && snark.getStorage() != null && !snark.getStorage().complete() &&
|
||||
r.isDirectory();
|
||||
|
||||
StringBuilder buf=new StringBuilder(4096);
|
||||
buf.append(DOCTYPE).append("<HTML><HEAD><TITLE>");
|
||||
if (title.endsWith("/"))
|
||||
@@ -2442,8 +2611,14 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
String directory = title;
|
||||
title = _("Torrent") + ": " + DataHelper.escapeHTML(title);
|
||||
buf.append(title);
|
||||
buf.append("</TITLE>").append(HEADER_A).append(_themePath).append(HEADER_B).append("<link rel=\"shortcut icon\" href=\"" + _themePath + "favicon.ico\">" +
|
||||
"</HEAD><BODY>\n<center><div class=\"snarknavbar\"><a href=\"").append(_contextPath).append("/\" title=\"Torrents\"");
|
||||
buf.append("</TITLE>\n").append(HEADER_A).append(_themePath).append(HEADER_B)
|
||||
.append("<link rel=\"shortcut icon\" href=\"" + _themePath + "favicon.ico\">\n");
|
||||
if (showPriority)
|
||||
buf.append("<script src=\"").append(_contextPath).append(WARBASE + "js/folder.js\" type=\"text/javascript\"></script>\n");
|
||||
buf.append("</HEAD><BODY");
|
||||
if (showPriority)
|
||||
buf.append(" onload=\"setupbuttons()\"");
|
||||
buf.append(">\n<center><div class=\"snarknavbar\"><a href=\"").append(_contextPath).append("/\" title=\"Torrents\"");
|
||||
buf.append(" class=\"snarkRefresh\"><img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("arrow_refresh.png\"> ");
|
||||
if (_contextName.equals(DEFAULT_NAME))
|
||||
buf.append(_("I2PSnark"));
|
||||
@@ -2453,8 +2628,6 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
|
||||
if (parent) // always true
|
||||
buf.append("<div class=\"page\"><div class=\"mainsection\">");
|
||||
boolean showPriority = snark != null && snark.getStorage() != null && !snark.getStorage().complete() &&
|
||||
r.isDirectory();
|
||||
if (showPriority) {
|
||||
buf.append("<form action=\"").append(base).append("\" method=\"POST\">\n");
|
||||
buf.append("<input type=\"hidden\" name=\"nonce\" value=\"").append(_nonce).append("\" >\n");
|
||||
@@ -2594,9 +2767,20 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
.append(":</b> ")
|
||||
.append((new DecimalFormat("0.00%")).format(completion));
|
||||
else
|
||||
buf.append(" <img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_rx.png\" > ")
|
||||
.append(_("Complete"));
|
||||
// else unknown
|
||||
buf.append(" <img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_rx.png\" > <b>")
|
||||
.append(_("Complete")).append("</b>");
|
||||
// up ratio
|
||||
buf.append(" <img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_tx.png\" > <b>")
|
||||
.append(_("Upload ratio"))
|
||||
.append(":</b> ");
|
||||
long uploaded = snark.getUploaded();
|
||||
if (uploaded > 0) {
|
||||
double ratio = uploaded / ((double) snark.getTotalLength());
|
||||
buf.append((new DecimalFormat("0.000")).format(ratio));
|
||||
buf.append(" x");
|
||||
} else {
|
||||
buf.append('0');
|
||||
}
|
||||
long needed = snark.getNeededLength();
|
||||
if (needed > 0)
|
||||
buf.append(" <img alt=\"\" border=\"0\" src=\"").append(_imgPath).append("head_rx.png\" > <b>")
|
||||
@@ -2781,19 +2965,19 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
if (showPriority) {
|
||||
buf.append("<td class=\"priority\">");
|
||||
if ((!complete) && (!item.isDirectory())) {
|
||||
buf.append("<input type=\"radio\" value=\"5\" name=\"pri.").append(fileIndex).append("\" ");
|
||||
buf.append("\n<input type=\"radio\" onclick=\"priorityclicked();\" class=\"prihigh\" value=\"5\" name=\"pri.").append(fileIndex).append("\" ");
|
||||
if (priority > 0)
|
||||
buf.append("checked=\"true\"");
|
||||
buf.append("checked=\"checked\"");
|
||||
buf.append('>').append(_("High"));
|
||||
|
||||
buf.append("<input type=\"radio\" value=\"0\" name=\"pri.").append(fileIndex).append("\" ");
|
||||
buf.append("\n<input type=\"radio\" onclick=\"priorityclicked();\" class=\"prinorm\" value=\"0\" name=\"pri.").append(fileIndex).append("\" ");
|
||||
if (priority == 0)
|
||||
buf.append("checked=\"true\"");
|
||||
buf.append("checked=\"checked\"");
|
||||
buf.append('>').append(_("Normal"));
|
||||
|
||||
buf.append("<input type=\"radio\" value=\"-9\" name=\"pri.").append(fileIndex).append("\" ");
|
||||
buf.append("\n<input type=\"radio\" onclick=\"priorityclicked();\" class=\"priskip\" value=\"-9\" name=\"pri.").append(fileIndex).append("\" ");
|
||||
if (priority < 0)
|
||||
buf.append("checked=\"true\"");
|
||||
buf.append("checked=\"checked\"");
|
||||
buf.append('>').append(_("Skip"));
|
||||
showSaveButton = true;
|
||||
}
|
||||
@@ -2802,9 +2986,16 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
buf.append("</TR>\n");
|
||||
}
|
||||
if (showSaveButton) {
|
||||
buf.append("<thead><tr><th colspan=\"4\"> </th><th class=\"headerpriority\"><input type=\"submit\" value=\"");
|
||||
buf.append(_("Save priorities"));
|
||||
buf.append("\" name=\"foo\" ></th></tr></thead>\n");
|
||||
buf.append("<thead><tr><th colspan=\"4\"> </th><th class=\"headerpriority\">" +
|
||||
"<a class=\"control\" id=\"setallhigh\" href=\"javascript:void(null);\" onclick=\"setallhigh();\">")
|
||||
.append(toImg("clock_red")).append(_("Set all high")).append("</a>\n" +
|
||||
"<a class=\"control\" id=\"setallnorm\" href=\"javascript:void(null);\" onclick=\"setallnorm();\">")
|
||||
.append(toImg("clock")).append(_("Set all normal")).append("</a>\n" +
|
||||
"<a class=\"control\" id=\"setallskip\" href=\"javascript:void(null);\" onclick=\"setallskip();\">")
|
||||
.append(toImg("cancel")).append(_("Skip all")).append("</a>\n" +
|
||||
"<br><br><input type=\"submit\" class=\"accept\" value=\"").append(_("Save priorities"))
|
||||
.append("\" name=\"savepri\" >\n" +
|
||||
"</th></tr></thead>\n");
|
||||
}
|
||||
buf.append("</table>\n");
|
||||
if (showPriority)
|
||||
@@ -2814,7 +3005,12 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/** @since 0.7.14 */
|
||||
/**
|
||||
* Pick an icon; try to catch the common types in an i2p environment.
|
||||
*
|
||||
* @return file name not including ".png"
|
||||
* @since 0.7.14
|
||||
*/
|
||||
private String toIcon(File item) {
|
||||
if (item.isDirectory())
|
||||
return "folder";
|
||||
@@ -2823,10 +3019,12 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
|
||||
/**
|
||||
* Pick an icon; try to catch the common types in an i2p environment
|
||||
* Pkg private for FileTypeSorter.
|
||||
*
|
||||
* @return file name not including ".png"
|
||||
* @since 0.7.14
|
||||
*/
|
||||
private String toIcon(String path) {
|
||||
String toIcon(String path) {
|
||||
String icon;
|
||||
// Note that for this to work well, our custom mime.properties file must be loaded.
|
||||
String plc = path.toLowerCase(Locale.US);
|
||||
@@ -2873,12 +3071,12 @@ public class I2PSnarkServlet extends BasicServlet {
|
||||
|
||||
/** @since 0.7.14 */
|
||||
private String toImg(String icon) {
|
||||
return "<img alt=\"\" height=\"16\" width=\"16\" src=\"" + _contextPath + "/.icons/" + icon + ".png\">";
|
||||
return toImg(icon, "");
|
||||
}
|
||||
|
||||
/** @since 0.8.2 */
|
||||
private String toImg(String icon, String altText) {
|
||||
return "<img alt=\"" + altText + "\" height=\"16\" width=\"16\" src=\"" + _contextPath + "/.icons/" + icon + ".png\">";
|
||||
return "<img alt=\"" + altText + "\" height=\"16\" width=\"16\" src=\"" + _contextPath + WARBASE + "icons/" + icon + ".png\">";
|
||||
}
|
||||
|
||||
/** @since 0.8.1 */
|
||||
|
||||
341
apps/i2psnark/java/src/org/klomp/snark/web/Sorters.java
Normal file
@@ -0,0 +1,341 @@
|
||||
package org.klomp.snark.web;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.text.Collator;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.klomp.snark.MetaInfo;
|
||||
import org.klomp.snark.Snark;
|
||||
|
||||
/**
|
||||
* Comparators for various columns
|
||||
*
|
||||
* @since 0.9.16 from TorrentNameComparator, moved from I2PSnarkservlet
|
||||
*/
|
||||
class Sorters {
|
||||
|
||||
/**
|
||||
* Negative is reverse
|
||||
*
|
||||
*<ul>
|
||||
*<li>0, 1: Name
|
||||
*<li>2: Status
|
||||
*<li>3: Peers
|
||||
*<li>4: ETA
|
||||
*<li>5: Size
|
||||
*<li>6: Downloaded
|
||||
*<li>7: Uploaded
|
||||
*<li>8: Down rate
|
||||
*<li>9: Up rate
|
||||
*<li>10: Remaining (needed)
|
||||
*<li>11: Upload ratio
|
||||
*<li>11: File type
|
||||
*</ul>
|
||||
*
|
||||
* @param servlet for file type callback only
|
||||
*/
|
||||
public static Comparator<Snark> getComparator(int type, I2PSnarkServlet servlet) {
|
||||
boolean rev = type < 0;
|
||||
Comparator<Snark> rv;
|
||||
switch (type) {
|
||||
|
||||
case -1:
|
||||
case 0:
|
||||
case 1:
|
||||
default:
|
||||
rv = new TorrentNameComparator();
|
||||
if (rev)
|
||||
rv = Collections.reverseOrder(rv);
|
||||
break;
|
||||
|
||||
case -2:
|
||||
case 2:
|
||||
rv = new StatusComparator(rev);
|
||||
break;
|
||||
|
||||
case -3:
|
||||
case 3:
|
||||
rv = new PeersComparator(rev);
|
||||
break;
|
||||
|
||||
case -4:
|
||||
case 4:
|
||||
rv = new ETAComparator(rev);
|
||||
break;
|
||||
|
||||
case -5:
|
||||
case 5:
|
||||
rv = new SizeComparator(rev);
|
||||
break;
|
||||
|
||||
case -6:
|
||||
case 6:
|
||||
rv = new DownloadedComparator(rev);
|
||||
break;
|
||||
|
||||
case -7:
|
||||
case 7:
|
||||
rv = new UploadedComparator(rev);
|
||||
break;
|
||||
|
||||
case -8:
|
||||
case 8:
|
||||
rv = new DownRateComparator(rev);
|
||||
break;
|
||||
|
||||
case -9:
|
||||
case 9:
|
||||
rv = new UpRateComparator(rev);
|
||||
break;
|
||||
|
||||
case -10:
|
||||
case 10:
|
||||
rv = new RemainingComparator(rev);
|
||||
break;
|
||||
|
||||
case -11:
|
||||
case 11:
|
||||
rv = new RatioComparator(rev);
|
||||
break;
|
||||
|
||||
case -12:
|
||||
case 12:
|
||||
rv = new FileTypeComparator(rev, servlet);
|
||||
break;
|
||||
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sort alphabetically in current locale, ignore case, ignore leading "the "
|
||||
* (I guess this is worth it, a lot of torrents start with "The "
|
||||
* @since 0.7.14
|
||||
*/
|
||||
private static class TorrentNameComparator implements Comparator<Snark>, Serializable {
|
||||
|
||||
public int compare(Snark l, Snark r) {
|
||||
return comp(l, r);
|
||||
}
|
||||
|
||||
public static int comp(Snark l, Snark r) {
|
||||
// put downloads and magnets first
|
||||
if (l.getStorage() == null && r.getStorage() != null)
|
||||
return -1;
|
||||
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);
|
||||
return Collator.getInstance().compare(ls, rs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward or reverse sort, but the fallback is always forward
|
||||
*/
|
||||
private static abstract class Sort implements Comparator<Snark>, Serializable {
|
||||
|
||||
private final boolean _rev;
|
||||
|
||||
public Sort(boolean rev) {
|
||||
_rev = rev;
|
||||
}
|
||||
|
||||
public int compare(Snark l, Snark r) {
|
||||
int rv = compareIt(l, r);
|
||||
if (rv != 0)
|
||||
return _rev ? 0 - rv : rv;
|
||||
return TorrentNameComparator.comp(l, r);
|
||||
}
|
||||
|
||||
protected abstract int compareIt(Snark l, Snark r);
|
||||
|
||||
protected static int compLong(long l, long r) {
|
||||
if (l < r)
|
||||
return -1;
|
||||
if (l > r)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class StatusComparator extends Sort {
|
||||
|
||||
private StatusComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
int rv = getStatus(l) - getStatus(r);
|
||||
if (rv != 0)
|
||||
return rv;
|
||||
// use reverse remaining as first tie break
|
||||
return compLong(r.getNeededLength(), l.getNeededLength());
|
||||
}
|
||||
|
||||
private static int getStatus(Snark snark) {
|
||||
long remaining = snark.getRemainingLength();
|
||||
long needed = snark.getNeededLength();
|
||||
if (snark.isStopped()) {
|
||||
if (remaining < 0)
|
||||
return 0;
|
||||
if (remaining > 0)
|
||||
return 5;
|
||||
return 10;
|
||||
}
|
||||
if (snark.isStarting())
|
||||
return 15;
|
||||
if (snark.isAllocating())
|
||||
return 20;
|
||||
if (remaining < 0)
|
||||
return 15; // magnet
|
||||
if (remaining == 0)
|
||||
return 100;
|
||||
if (snark.isChecking())
|
||||
return 95;
|
||||
if (snark.getNeededLength() <= 0)
|
||||
return 90;
|
||||
if (snark.getPeerCount() <= 0)
|
||||
return 40;
|
||||
if (snark.getDownloadRate() <= 0)
|
||||
return 50;
|
||||
return 60;
|
||||
}
|
||||
}
|
||||
|
||||
private static class PeersComparator extends Sort {
|
||||
|
||||
public PeersComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
return l.getPeerCount() - r.getPeerCount();
|
||||
}
|
||||
}
|
||||
|
||||
private static class RemainingComparator extends Sort {
|
||||
|
||||
public RemainingComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
return compLong(l.getNeededLength(), r.getNeededLength());
|
||||
}
|
||||
}
|
||||
|
||||
private static class ETAComparator extends Sort {
|
||||
|
||||
public ETAComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
return compLong(eta(l), eta(r));
|
||||
}
|
||||
|
||||
private static long eta(Snark snark) {
|
||||
long needed = snark.getNeededLength();
|
||||
if (needed <= 0)
|
||||
return 0;
|
||||
long total = snark.getTotalLength();
|
||||
if (needed > total)
|
||||
needed = total;
|
||||
long downBps = snark.getDownloadRate();
|
||||
if (downBps > 0)
|
||||
return needed / downBps;
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SizeComparator extends Sort {
|
||||
|
||||
public SizeComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
return compLong(l.getTotalLength(), r.getTotalLength());
|
||||
}
|
||||
}
|
||||
|
||||
private static class DownloadedComparator extends Sort {
|
||||
|
||||
public DownloadedComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
long ld = l.getTotalLength() - l.getRemainingLength();
|
||||
long rd = r.getTotalLength() - r.getRemainingLength();
|
||||
return compLong(ld, rd);
|
||||
}
|
||||
}
|
||||
|
||||
private static class UploadedComparator extends Sort {
|
||||
|
||||
public UploadedComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
return compLong(l.getUploaded(), r.getUploaded());
|
||||
}
|
||||
}
|
||||
|
||||
private static class DownRateComparator extends Sort {
|
||||
|
||||
public DownRateComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
return compLong(l.getDownloadRate(), r.getDownloadRate());
|
||||
}
|
||||
}
|
||||
|
||||
private static class UpRateComparator extends Sort {
|
||||
|
||||
public UpRateComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
return compLong(l.getUploadRate(), r.getUploadRate());
|
||||
}
|
||||
}
|
||||
|
||||
private static class RatioComparator extends Sort {
|
||||
|
||||
private static final long M = 128 * 1024 * 1024;
|
||||
|
||||
public RatioComparator(boolean rev) { super(rev); }
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
long lt = l.getTotalLength();
|
||||
long ld = lt > 0 ? ((M * l.getUploaded()) / lt) : 0;
|
||||
long rt = r.getTotalLength();
|
||||
long rd = rt > 0 ? ((M * r.getUploaded()) / rt) : 0;
|
||||
return compLong(ld, rd);
|
||||
}
|
||||
}
|
||||
|
||||
private static class FileTypeComparator extends Sort {
|
||||
|
||||
private final I2PSnarkServlet servlet;
|
||||
|
||||
public FileTypeComparator(boolean rev, I2PSnarkServlet servlet) {
|
||||
super(rev);
|
||||
this.servlet = servlet;
|
||||
}
|
||||
|
||||
public int compareIt(Snark l, Snark r) {
|
||||
String ls = toName(l);
|
||||
String rs = toName(r);
|
||||
return ls.compareTo(rs);
|
||||
}
|
||||
|
||||
private String toName(Snark snark) {
|
||||
MetaInfo meta = snark.getMetaInfo();
|
||||
if (meta == null)
|
||||
return "0";
|
||||
if (meta.getFiles() != null)
|
||||
return "1";
|
||||
// arbitrary sort based on icon name
|
||||
return servlet.toIcon(meta.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ epub = application/epub+zip
|
||||
flac = audio/flac
|
||||
flv = video/x-flv
|
||||
iso = application/x-iso9660-image
|
||||
js = text/javascript
|
||||
m4a = audio/mp4a-latm
|
||||
m4v = video/x-m4v
|
||||
mkv = video/x-matroska
|
||||
|
||||
|
Before Width: | Height: | Size: 464 B After Width: | Height: | Size: 464 B |
|
Before Width: | Height: | Size: 733 B After Width: | Height: | Size: 733 B |
|
Before Width: | Height: | Size: 587 B After Width: | Height: | Size: 587 B |
|
Before Width: | Height: | Size: 673 B After Width: | Height: | Size: 673 B |
|
Before Width: | Height: | Size: 882 B After Width: | Height: | Size: 882 B |
|
Before Width: | Height: | Size: 889 B After Width: | Height: | Size: 889 B |
|
Before Width: | Height: | Size: 766 B After Width: | Height: | Size: 766 B |
|
Before Width: | Height: | Size: 653 B After Width: | Height: | Size: 653 B |
|
Before Width: | Height: | Size: 537 B After Width: | Height: | Size: 537 B |
|
Before Width: | Height: | Size: 578 B After Width: | Height: | Size: 578 B |
|
Before Width: | Height: | Size: 591 B After Width: | Height: | Size: 591 B |
|
Before Width: | Height: | Size: 385 B After Width: | Height: | Size: 385 B |
|
Before Width: | Height: | Size: 853 B After Width: | Height: | Size: 853 B |
|
Before Width: | Height: | Size: 635 B After Width: | Height: | Size: 635 B |
|
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 294 B |
|
Before Width: | Height: | Size: 591 B After Width: | Height: | Size: 591 B |
|
Before Width: | Height: | Size: 589 B After Width: | Height: | Size: 589 B |
|
Before Width: | Height: | Size: 591 B After Width: | Height: | Size: 591 B |
|
Before Width: | Height: | Size: 537 B After Width: | Height: | Size: 537 B |
93
apps/i2psnark/resources/js/folder.js
Normal file
@@ -0,0 +1,93 @@
|
||||
function setupbuttons() {
|
||||
updatesetallbuttons();
|
||||
var form = document.forms[0];
|
||||
form.savepri.disabled = true;
|
||||
form.savepri.className = 'disabled';
|
||||
}
|
||||
|
||||
function priorityclicked() {
|
||||
updatesetallbuttons();
|
||||
var form = document.forms[0];
|
||||
form.savepri.disabled = false;
|
||||
form.savepri.className = 'accept';
|
||||
}
|
||||
|
||||
function updatesetallbuttons() {
|
||||
var notNorm = false;
|
||||
var notHigh = false;
|
||||
var notSkip = false;
|
||||
var form = document.forms[0];
|
||||
for(i = 0; i < form.elements.length; i++) {
|
||||
var elem = form.elements[i];
|
||||
if (elem.type == 'radio') {
|
||||
if (!elem.checked) {
|
||||
if (elem.className == 'prinorm')
|
||||
notNorm = true;
|
||||
else if (elem.className == 'prihigh')
|
||||
notHigh = true;
|
||||
else
|
||||
notSkip = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (notNorm)
|
||||
document.getElementById('setallnorm').className = 'control';
|
||||
else
|
||||
document.getElementById('setallnorm').className = 'controld';
|
||||
if (notHigh)
|
||||
document.getElementById('setallhigh').className = 'control';
|
||||
else
|
||||
document.getElementById('setallhigh').className = 'controld';
|
||||
if (notSkip)
|
||||
document.getElementById('setallskip').className = 'control';
|
||||
else
|
||||
document.getElementById('setallskip').className = 'controld';
|
||||
}
|
||||
|
||||
function setallnorm() {
|
||||
var form = document.forms[0];
|
||||
for(i = 0; i < form.elements.length; i++) {
|
||||
var elem = form.elements[i];
|
||||
if (elem.type == 'radio') {
|
||||
if (elem.className === 'prinorm')
|
||||
elem.checked = true;
|
||||
}
|
||||
}
|
||||
document.getElementById('setallnorm').className = 'controld';
|
||||
document.getElementById('setallhigh').className = 'control';
|
||||
document.getElementById('setallskip').className = 'control';
|
||||
form.savepri.disabled = false;
|
||||
form.savepri.className = 'accept';
|
||||
}
|
||||
|
||||
function setallhigh() {
|
||||
var form = document.forms[0];
|
||||
for(i = 0; i < form.elements.length; i++) {
|
||||
var elem = form.elements[i];
|
||||
if (elem.type == 'radio') {
|
||||
if (elem.className === 'prihigh')
|
||||
elem.checked = true;
|
||||
}
|
||||
}
|
||||
document.getElementById('setallnorm').className = 'control';
|
||||
document.getElementById('setallhigh').className = 'controld';
|
||||
document.getElementById('setallskip').className = 'control';
|
||||
form.savepri.disabled = false;
|
||||
form.savepri.className = 'accept';
|
||||
}
|
||||
|
||||
function setallskip() {
|
||||
var form = document.forms[0];
|
||||
for(i = 0; i < form.elements.length; i++) {
|
||||
var elem = form.elements[i];
|
||||
if (elem.type == 'radio') {
|
||||
if (elem.className === 'priskip')
|
||||
elem.checked = true;
|
||||
}
|
||||
}
|
||||
document.getElementById('setallnorm').className = 'control';
|
||||
document.getElementById('setallhigh').className = 'control';
|
||||
document.getElementById('setallskip').className = 'controld';
|
||||
form.savepri.disabled = false;
|
||||
form.savepri.className = 'accept';
|
||||
}
|
||||
@@ -601,9 +601,12 @@ public abstract class I2PTunnelHTTPClientBase extends I2PTunnelClientBase implem
|
||||
return;
|
||||
int status = ise != null ? ise.getStatus() : -1;
|
||||
String error;
|
||||
//TODO MessageStatusMessage.STATUS_SEND_FAILURE_UNSUPPORTED_ENCRYPTION
|
||||
if (status == MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET) {
|
||||
// We won't get this one unless it is treated as a hard failure
|
||||
// in streaming. See PacketQueue.java
|
||||
error = usingWWWProxy ? "nolsp" : "nols";
|
||||
} else if (status == MessageStatusMessage.STATUS_SEND_FAILURE_UNSUPPORTED_ENCRYPTION) {
|
||||
error = usingWWWProxy ? "encp" : "enc";
|
||||
} else {
|
||||
error = usingWWWProxy ? "dnfp" : "dnf";
|
||||
}
|
||||
|
||||
@@ -188,8 +188,7 @@ public class EditBean extends IndexBean {
|
||||
|
||||
/** @since 0.9.12 */
|
||||
public boolean isSigTypeAvailable(int code) {
|
||||
SigType type = SigType.getByCode(code);
|
||||
return type != null && type.isAvailable();
|
||||
return SigType.isAvailable(code);
|
||||
}
|
||||
|
||||
/** @since 0.8.9 */
|
||||
|
||||
@@ -5,7 +5,7 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.transport.TransportManager;
|
||||
|
||||
@@ -29,8 +29,8 @@ import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.Lease;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.TunnelPoolSettings;
|
||||
import net.i2p.router.util.HashDistance; // debug
|
||||
@@ -415,7 +415,9 @@ public class NetDbRenderer {
|
||||
// shouldnt happen
|
||||
buf.append("<b>" + _("Published") + ":</b> in ").append(DataHelper.formatDuration2(0-age)).append("???<br>\n");
|
||||
}
|
||||
buf.append("<b>" + _("Address(es)") + ":</b> ");
|
||||
buf.append("<b>").append(_("Signing Key")).append(":</b> ")
|
||||
.append(info.getIdentity().getSigningPublicKey().getType().toString());
|
||||
buf.append("<br>\n<b>" + _("Address(es)") + ":</b> ");
|
||||
String country = _context.commSystem().getCountry(info.getIdentity().getHash());
|
||||
if(country != null) {
|
||||
buf.append("<img height=\"11\" width=\"16\" alt=\"").append(country.toUpperCase(Locale.US)).append('\"');
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.TreeSet;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.router.peermanager.DBHistory;
|
||||
import net.i2p.router.peermanager.PeerProfile;
|
||||
|
||||
@@ -3,8 +3,8 @@ package net.i2p.router.web;
|
||||
import java.util.Date;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.Signature;
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,8 +15,8 @@ import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
@@ -11,7 +11,7 @@ import java.util.Map;
|
||||
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
@@ -44,7 +44,7 @@ class PacketQueue implements SendMessageStatusListener {
|
||||
private static final int FINAL_TAGS_TO_SEND = 4;
|
||||
private static final int FINAL_TAG_THRESHOLD = 2;
|
||||
private static final long REMOVE_EXPIRED_TIME = 67*1000;
|
||||
private static final boolean ENABLE_STATUS_LISTEN = false;
|
||||
private static final boolean ENABLE_STATUS_LISTEN = true;
|
||||
|
||||
public PacketQueue(I2PAppContext context, I2PSession session, ConnectionManager mgr) {
|
||||
_context = context;
|
||||
@@ -267,6 +267,20 @@ class PacketQueue implements SendMessageStatusListener {
|
||||
_messageStatusMap.remove(id);
|
||||
break;
|
||||
|
||||
case MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET:
|
||||
// Ideally we would like to make this a hard failure,
|
||||
// but it caused far too many fast-fails that were then
|
||||
// resolved by the user clicking reload in his browser.
|
||||
// Until the LS fetch is faster and more reliable,
|
||||
// or we increase the timeout for it,
|
||||
// we can't treat this one as a hard fail.
|
||||
// Let the streaming retransmission paper over the problem.
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("LS lookup (soft) failure for msg " + msgId + " on " + con);
|
||||
_messageStatusMap.remove(id);
|
||||
break;
|
||||
|
||||
|
||||
case MessageStatusMessage.STATUS_SEND_FAILURE_LOCAL:
|
||||
case MessageStatusMessage.STATUS_SEND_FAILURE_ROUTER:
|
||||
case MessageStatusMessage.STATUS_SEND_FAILURE_NETWORK:
|
||||
@@ -280,7 +294,6 @@ class PacketQueue implements SendMessageStatusListener {
|
||||
case MessageStatusMessage.STATUS_SEND_FAILURE_DESTINATION:
|
||||
case MessageStatusMessage.STATUS_SEND_FAILURE_BAD_LEASESET:
|
||||
case MessageStatusMessage.STATUS_SEND_FAILURE_EXPIRED_LEASESET:
|
||||
case MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET:
|
||||
case SendMessageStatusListener.STATUS_CANCELLED:
|
||||
if (con.getHighestAckedThrough() >= 0) {
|
||||
// a retxed SYN succeeded before the first SYN failed
|
||||
|
||||
@@ -548,7 +548,7 @@
|
||||
windowtitle="I2P Anonymous Network - Java Documentation - Version ${release.number}">
|
||||
<group title="Core SDK (i2p.jar)" packages="net.i2p:net.i2p.*:net.i2p.client:net.i2p.client.*:net.i2p.internal:net.i2p.internal.*:freenet.support.CPUInformation:org.bouncycastle.oldcrypto:org.bouncycastle.oldcrypto.*:gnu.crypto.*:gnu.getopt:gnu.gettext:com.nettgryppa.security:net.metanotion:net.metanotion.*" />
|
||||
<group title="Streaming Library" packages="net.i2p.client.streaming:net.i2p.client.streaming.impl" />
|
||||
<group title="Router" packages="net.i2p.router:net.i2p.router.*:net.i2p.data.i2np:org.cybergarage.*:org.freenetproject:org.xlattice.crypto.filters" />
|
||||
<group title="Router" packages="net.i2p.router:net.i2p.router.*:net.i2p.data.i2np:net.i2p.data.router:org.cybergarage.*:org.freenetproject:org.xlattice.crypto.filters" />
|
||||
<group title="Router Console" packages="net.i2p.router.web:net.i2p.router.update" />
|
||||
<!-- apps and bridges starting here, alphabetical please -->
|
||||
<group title="Addressbook Application" packages="net.i2p.addressbook" />
|
||||
|
||||
135
core/java/src/net/i2p/crypto/ECUtil.java
Normal file
@@ -0,0 +1,135 @@
|
||||
package net.i2p.crypto;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.spec.ECField;
|
||||
import java.security.spec.ECFieldFp;
|
||||
import java.security.spec.ECPoint;
|
||||
import java.security.spec.EllipticCurve;
|
||||
|
||||
import net.i2p.util.NativeBigInteger;
|
||||
|
||||
/**
|
||||
* Used by KeyGenerator.getSigningPublicKey()
|
||||
*
|
||||
* Modified from
|
||||
* http://stackoverflow.com/questions/15727147/scalar-multiplication-of-point-over-elliptic-curve
|
||||
* Apparently public domain.
|
||||
* Supported P-192 only.
|
||||
* Added curve parameters to support all curves.
|
||||
*
|
||||
* @since 0.9.16
|
||||
*/
|
||||
class ECUtil {
|
||||
|
||||
private static final BigInteger TWO = new BigInteger("2");
|
||||
private static final BigInteger THREE = new BigInteger("3");
|
||||
|
||||
public static ECPoint scalarMult(ECPoint p, BigInteger kin, EllipticCurve curve) {
|
||||
ECPoint r = ECPoint.POINT_INFINITY;
|
||||
BigInteger prime = ((ECFieldFp) curve.getField()).getP();
|
||||
BigInteger k = kin.mod(prime);
|
||||
int length = k.bitLength();
|
||||
byte[] binarray = new byte[length];
|
||||
for (int i = 0; i <= length-1; i++) {
|
||||
binarray[i] = k.mod(TWO).byteValue();
|
||||
k = k.divide(TWO);
|
||||
}
|
||||
|
||||
for (int i = length-1; i >= 0; i--) {
|
||||
// i should start at length-1 not -2 because the MSB of binarry may not be 1
|
||||
r = doublePoint(r, curve);
|
||||
if (binarray[i] == 1)
|
||||
r = addPoint(r, p, curve);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
private static ECPoint addPoint(ECPoint r, ECPoint s, EllipticCurve curve) {
|
||||
if (r.equals(s))
|
||||
return doublePoint(r, curve);
|
||||
else if (r.equals(ECPoint.POINT_INFINITY))
|
||||
return s;
|
||||
else if (s.equals(ECPoint.POINT_INFINITY))
|
||||
return r;
|
||||
BigInteger prime = ((ECFieldFp) curve.getField()).getP();
|
||||
BigInteger slope = (r.getAffineY().subtract(s.getAffineY())).multiply(r.getAffineX().subtract(s.getAffineX()).modInverse(prime)).mod(prime);
|
||||
slope = new NativeBigInteger(slope);
|
||||
BigInteger xOut = (slope.modPow(TWO, prime).subtract(r.getAffineX())).subtract(s.getAffineX()).mod(prime);
|
||||
BigInteger yOut = s.getAffineY().negate().mod(prime);
|
||||
yOut = yOut.add(slope.multiply(s.getAffineX().subtract(xOut))).mod(prime);
|
||||
ECPoint out = new ECPoint(xOut, yOut);
|
||||
return out;
|
||||
}
|
||||
|
||||
private static ECPoint doublePoint(ECPoint r, EllipticCurve curve) {
|
||||
if (r.equals(ECPoint.POINT_INFINITY))
|
||||
return r;
|
||||
BigInteger slope = (r.getAffineX().pow(2)).multiply(THREE);
|
||||
slope = slope.add(curve.getA());
|
||||
BigInteger prime = ((ECFieldFp) curve.getField()).getP();
|
||||
slope = slope.multiply((r.getAffineY().multiply(TWO)).modInverse(prime));
|
||||
BigInteger xOut = slope.pow(2).subtract(r.getAffineX().multiply(TWO)).mod(prime);
|
||||
BigInteger yOut = (r.getAffineY().negate()).add(slope.multiply(r.getAffineX().subtract(xOut))).mod(prime);
|
||||
ECPoint out = new ECPoint(xOut, yOut);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* P-192 test only.
|
||||
* See KeyGenerator.main() for a test of all supported curves.
|
||||
*/
|
||||
/****
|
||||
public static void main(String[] args) {
|
||||
EllipticCurve P192 = ECConstants.P192_SPEC.getCurve();
|
||||
BigInteger xs = new BigInteger("d458e7d127ae671b0c330266d246769353a012073e97acf8", 16);
|
||||
BigInteger ys = new BigInteger("325930500d851f336bddc050cf7fb11b5673a1645086df3b", 16);
|
||||
BigInteger xt = new BigInteger("f22c4395213e9ebe67ddecdd87fdbd01be16fb059b9753a4", 16);
|
||||
BigInteger yt = new BigInteger("264424096af2b3597796db48f8dfb41fa9cecc97691a9c79", 16);
|
||||
ECPoint S = new ECPoint(xs,ys);
|
||||
ECPoint T = new ECPoint(xt,yt);
|
||||
|
||||
// Verifying addition
|
||||
ECPoint Rst = addPoint(S, T, P192);
|
||||
BigInteger xst = new BigInteger("48e1e4096b9b8e5ca9d0f1f077b8abf58e843894de4d0290", 16); // Specified value of x of point R for addition in NIST Routine example
|
||||
System.out.println("x-coordinate of point Rst is : " + Rst.getAffineX());
|
||||
System.out.println("y-coordinate of point Rst is : " + Rst.getAffineY());
|
||||
if (Rst.getAffineX().equals(xst))
|
||||
System.out.println("Adding is correct");
|
||||
else
|
||||
System.out.println("Adding FAIL");
|
||||
|
||||
//Verifying Doubling
|
||||
BigInteger xr = new BigInteger("30c5bc6b8c7da25354b373dc14dd8a0eba42d25a3f6e6962", 16); // Specified value of x of point R for doubling in NIST Routine example
|
||||
BigInteger yr = new BigInteger("0dde14bc4249a721c407aedbf011e2ddbbcb2968c9d889cf", 16);
|
||||
ECPoint R2s = new ECPoint(xr, yr); // Specified value of y of point R for doubling in NIST Routine example
|
||||
System.out.println("x-coordinate of point R2s is : " + R2s.getAffineX());
|
||||
System.out.println("y-coordinate of point R2s is : " + R2s.getAffineY());
|
||||
System.out.println("x-coordinate of calculated point is : " + doublePoint(S, P192).getAffineX());
|
||||
System.out.println("y-coordinate of calculated point is : " + doublePoint(S, P192).getAffineY());
|
||||
if (R2s.getAffineX().equals(doublePoint(S, P192).getAffineX()) &&
|
||||
R2s.getAffineY().equals(doublePoint(S, P192).getAffineY()))
|
||||
System.out.println("Doubling is correct");
|
||||
else
|
||||
System.out.println("Doubling FAIL");
|
||||
|
||||
xr = new BigInteger("1faee4205a4f669d2d0a8f25e3bcec9a62a6952965bf6d31", 16); // Specified value of x of point R for scalar Multiplication in NIST Routine example
|
||||
yr = new BigInteger("5ff2cdfa508a2581892367087c696f179e7a4d7e8260fb06", 16); // Specified value of y of point R for scalar Multiplication in NIST Routine example
|
||||
ECPoint Rds = new ECPoint(xr, yr);
|
||||
BigInteger d = new BigInteger("a78a236d60baec0c5dd41b33a542463a8255391af64c74ee", 16);
|
||||
|
||||
ECPoint Rs = scalarMult(S, d, P192);
|
||||
|
||||
System.out.println("x-coordinate of point Rds is : " + Rds.getAffineX());
|
||||
System.out.println("y-coordinate of point Rds is : " + Rds.getAffineY());
|
||||
System.out.println("x-coordinate of calculated point is : " + Rs.getAffineX());
|
||||
System.out.println("y-coordinate of calculated point is : " + Rs.getAffineY());
|
||||
|
||||
|
||||
if (Rds.getAffineX().equals(Rs.getAffineX()) &&
|
||||
Rds.getAffineY().equals(Rs.getAffineY()))
|
||||
System.out.println("Scalar Multiplication is correct");
|
||||
else
|
||||
System.out.println("Scalar Multiplication FAIL");
|
||||
}
|
||||
****/
|
||||
}
|
||||
@@ -12,11 +12,25 @@ package net.i2p.crypto;
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.ProviderException;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.ECParameterSpec;
|
||||
import java.security.spec.ECPoint;
|
||||
import java.security.spec.ECPublicKeySpec;
|
||||
import java.security.spec.EllipticCurve;
|
||||
import java.security.spec.RSAKeyGenParameterSpec;
|
||||
import java.security.spec.RSAPublicKeySpec;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
@@ -268,24 +282,56 @@ public class KeyGenerator {
|
||||
}
|
||||
|
||||
/** Convert a SigningPrivateKey to a SigningPublicKey.
|
||||
* DSA-SHA1 only.
|
||||
* As of 0.9.16, supports all key types.
|
||||
*
|
||||
* @param priv a SigningPrivateKey object
|
||||
* @return a SigningPublicKey object
|
||||
* @throws IllegalArgumentException on bad key
|
||||
* @throws IllegalArgumentException on bad key or unknown type
|
||||
*/
|
||||
public static SigningPublicKey getSigningPublicKey(SigningPrivateKey priv) {
|
||||
if (priv.getType() != SigType.DSA_SHA1)
|
||||
throw new IllegalArgumentException();
|
||||
SigType type = priv.getType();
|
||||
if (type == null)
|
||||
throw new IllegalArgumentException("Unknown type");
|
||||
try {
|
||||
switch (type.getBaseAlgorithm()) {
|
||||
case DSA:
|
||||
BigInteger x = new NativeBigInteger(1, priv.toByteArray());
|
||||
BigInteger y = CryptoConstants.dsag.modPow(x, CryptoConstants.dsap);
|
||||
SigningPublicKey pub = new SigningPublicKey();
|
||||
try {
|
||||
pub.setData(SigUtil.rectify(y, SigningPublicKey.KEYSIZE_BYTES));
|
||||
} catch (InvalidKeyException ike) {
|
||||
throw new IllegalArgumentException(ike);
|
||||
}
|
||||
return pub;
|
||||
|
||||
case EC:
|
||||
ECPrivateKey ecpriv = SigUtil.toJavaECKey(priv);
|
||||
BigInteger s = ecpriv.getS();
|
||||
ECParameterSpec spec = (ECParameterSpec) type.getParams();
|
||||
EllipticCurve curve = spec.getCurve();
|
||||
ECPoint g = spec.getGenerator();
|
||||
ECPoint w = ECUtil.scalarMult(g, s, curve);
|
||||
ECPublicKeySpec ecks = new ECPublicKeySpec(w, ecpriv.getParams());
|
||||
KeyFactory eckf = KeyFactory.getInstance("EC");
|
||||
ECPublicKey ecpub = (ECPublicKey) eckf.generatePublic(ecks);
|
||||
return SigUtil.fromJavaKey(ecpub, type);
|
||||
|
||||
case RSA:
|
||||
RSAPrivateKey rsapriv = SigUtil.toJavaRSAKey(priv);
|
||||
BigInteger exp = ((RSAKeyGenParameterSpec)type.getParams()).getPublicExponent();
|
||||
RSAPublicKeySpec rsaks = new RSAPublicKeySpec(rsapriv.getModulus(), exp);
|
||||
KeyFactory rsakf = KeyFactory.getInstance("RSA");
|
||||
RSAPublicKey rsapub = (RSAPublicKey) rsakf.generatePublic(rsaks);
|
||||
return SigUtil.fromJavaKey(rsapub, type);
|
||||
|
||||
case EdDSA:
|
||||
EdDSAPrivateKey epriv = SigUtil.toJavaEdDSAKey(priv);
|
||||
EdDSAPublicKey epub = new EdDSAPublicKey(new EdDSAPublicKeySpec(epriv.getA(), epriv.getParams()));
|
||||
return SigUtil.fromJavaKey(epub, type);
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported algorithm");
|
||||
}
|
||||
} catch (GeneralSecurityException gse) {
|
||||
throw new IllegalArgumentException("Conversion failed", gse);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
@@ -322,14 +368,20 @@ public class KeyGenerator {
|
||||
long stime = 0;
|
||||
long vtime = 0;
|
||||
SimpleDataStructure keys[] = KeyGenerator.getInstance().generateSigningKeys(type);
|
||||
//System.out.println("pubkey " + keys[0]);
|
||||
SigningPublicKey pubkey = (SigningPublicKey) keys[0];
|
||||
SigningPrivateKey privkey = (SigningPrivateKey) keys[1];
|
||||
SigningPublicKey pubkey2 = getSigningPublicKey(privkey);
|
||||
if (pubkey.equals(pubkey2))
|
||||
System.out.println(type + " private-to-public test PASSED");
|
||||
else
|
||||
System.out.println(type + " private-to-public test FAILED");
|
||||
//System.out.println("privkey " + keys[1]);
|
||||
for (int i = 0; i < runs; i++) {
|
||||
RandomSource.getInstance().nextBytes(src);
|
||||
long start = System.nanoTime();
|
||||
Signature sig = DSAEngine.getInstance().sign(src, (SigningPrivateKey) keys[1]);
|
||||
Signature sig = DSAEngine.getInstance().sign(src, privkey);
|
||||
long mid = System.nanoTime();
|
||||
boolean ok = DSAEngine.getInstance().verifySignature(sig, src, (SigningPublicKey) keys[0]);
|
||||
boolean ok = DSAEngine.getInstance().verifySignature(sig, src, pubkey);
|
||||
long end = System.nanoTime();
|
||||
stime += mid - start;
|
||||
vtime += end - mid;
|
||||
|
||||
@@ -331,7 +331,7 @@ public class SigUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated unused
|
||||
*
|
||||
*/
|
||||
public static RSAPrivateKey toJavaRSAKey(SigningPrivateKey pk)
|
||||
throws GeneralSecurityException {
|
||||
@@ -344,7 +344,7 @@ public class SigUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated unused
|
||||
*
|
||||
*/
|
||||
public static SigningPublicKey fromJavaKey(RSAPublicKey pk, SigType type)
|
||||
throws GeneralSecurityException {
|
||||
|
||||
@@ -24,20 +24,15 @@ import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Serializable;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
@@ -638,13 +633,17 @@ public class DataHelper {
|
||||
* Integers are a fixed number of bytes (numBytes), stored as unsigned integers in network byte order.
|
||||
* @param value value to write out, non-negative
|
||||
* @param rawStream stream to write to
|
||||
* @param numBytes number of bytes to write the number into (padding as necessary)
|
||||
* @throws DataFormatException if value is negative
|
||||
* @param numBytes number of bytes to write the number into, 1-8 (padding as necessary)
|
||||
* @throws DataFormatException if value is negative or if numBytes not 1-8
|
||||
* @throws IOException if there is an IO error writing to the stream
|
||||
*/
|
||||
public static void writeLong(OutputStream rawStream, int numBytes, long value)
|
||||
throws DataFormatException, IOException {
|
||||
if (value < 0) throw new DataFormatException("Value is negative (" + value + ")");
|
||||
if (numBytes <= 0 || numBytes > 8)
|
||||
// probably got the args backwards
|
||||
throw new DataFormatException("Bad byte count " + numBytes);
|
||||
if (value < 0)
|
||||
throw new DataFormatException("Value is negative (" + value + ")");
|
||||
for (int i = (numBytes - 1) * 8; i >= 0; i -= 8) {
|
||||
byte cur = (byte) (value >> i);
|
||||
rawStream.write(cur);
|
||||
@@ -1425,58 +1424,6 @@ public class DataHelper {
|
||||
out.write(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort based on the Hash of the DataStructure.
|
||||
* Warning - relatively slow.
|
||||
* WARNING - this sort order must be consistent network-wide, so while the order is arbitrary,
|
||||
* it cannot be changed.
|
||||
* Why? Just because it has to be consistent so signing will work.
|
||||
* How to spec as returning the same type as the param?
|
||||
* DEPRECATED - Only used by RouterInfo.
|
||||
*
|
||||
* @return a new list
|
||||
*/
|
||||
public static List<? extends DataStructure> sortStructures(Collection<? extends DataStructure> dataStructures) {
|
||||
if (dataStructures == null) return Collections.emptyList();
|
||||
|
||||
// This used to use Hash.toString(), which is insane, since a change to toString()
|
||||
// would break the whole network. Now use Hash.toBase64().
|
||||
// Note that the Base64 sort order is NOT the same as the raw byte sort order,
|
||||
// despite what you may read elsewhere.
|
||||
|
||||
//ArrayList<DataStructure> rv = new ArrayList(dataStructures.size());
|
||||
//TreeMap<String, DataStructure> tm = new TreeMap();
|
||||
//for (DataStructure struct : dataStructures) {
|
||||
// tm.put(struct.calculateHash().toString(), struct);
|
||||
//}
|
||||
//for (DataStructure struct : tm.values()) {
|
||||
// rv.add(struct);
|
||||
//}
|
||||
ArrayList<DataStructure> rv = new ArrayList<DataStructure>(dataStructures);
|
||||
sortStructureList(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* See above.
|
||||
* DEPRECATED - Only used by RouterInfo.
|
||||
*
|
||||
* @since 0.9
|
||||
*/
|
||||
static void sortStructureList(List<? extends DataStructure> dataStructures) {
|
||||
Collections.sort(dataStructures, new DataStructureComparator());
|
||||
}
|
||||
|
||||
/**
|
||||
* See sortStructures() comments.
|
||||
* @since 0.8.3
|
||||
*/
|
||||
private static class DataStructureComparator implements Comparator<DataStructure>, Serializable {
|
||||
public int compare(DataStructure l, DataStructure r) {
|
||||
return l.calculateHash().toBase64().compareTo(r.calculateHash().toBase64());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: formatDuration2() recommended in most cases for readability
|
||||
*/
|
||||
|
||||
@@ -77,6 +77,13 @@ public class KeysAndCert extends DataStructureImpl {
|
||||
_signingKey = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public byte[] getPadding() {
|
||||
return _padding;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalStateException if was already set
|
||||
* @since 0.9.12
|
||||
@@ -114,6 +121,8 @@ public class KeysAndCert extends DataStructureImpl {
|
||||
_publicKey.writeBytes(out);
|
||||
if (_padding != null)
|
||||
out.write(_padding);
|
||||
else if (_signingKey.length() < SigningPublicKey.KEYSIZE_BYTES)
|
||||
throw new DataFormatException("No padding set");
|
||||
_signingKey.writeTruncatedBytes(out);
|
||||
_certificate.writeBytes(out);
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ public class PrivateKey extends SimpleDataStructure {
|
||||
/** derives a new PublicKey object derived from the secret contents
|
||||
* of this PrivateKey
|
||||
* @return a PublicKey object
|
||||
* @throws IllegalArgumentException on bad key
|
||||
*/
|
||||
public PublicKey toPublic() {
|
||||
return KeyGenerator.getPublicKey(this);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package net.i2p.data;
|
||||
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@@ -24,6 +26,7 @@ import net.i2p.crypto.DSAEngine;
|
||||
import net.i2p.crypto.KeyGenerator;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.util.RandomSource;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
|
||||
/**
|
||||
* This helper class reads and writes files in the
|
||||
@@ -48,11 +51,11 @@ public class PrivateKeyFile {
|
||||
|
||||
private static final int HASH_EFFORT = VerifiedDestination.MIN_HASHCASH_EFFORT;
|
||||
|
||||
private final File file;
|
||||
protected final File file;
|
||||
private final I2PClient client;
|
||||
private Destination dest;
|
||||
private PrivateKey privKey;
|
||||
private SigningPrivateKey signingPrivKey;
|
||||
protected Destination dest;
|
||||
protected PrivateKey privKey;
|
||||
protected SigningPrivateKey signingPrivKey;
|
||||
|
||||
/**
|
||||
* Create a new PrivateKeyFile, or modify an existing one, with various
|
||||
@@ -224,6 +227,16 @@ public class PrivateKeyFile {
|
||||
*/
|
||||
public PrivateKeyFile(File file, PublicKey pubkey, SigningPublicKey spubkey, Certificate cert,
|
||||
PrivateKey pk, SigningPrivateKey spk) {
|
||||
this(file, pubkey, spubkey, cert, pk, spk, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param padding null OK, must be non-null if spubkey length < 128
|
||||
* @throws IllegalArgumentException on mismatch of spubkey and spk types
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public PrivateKeyFile(File file, PublicKey pubkey, SigningPublicKey spubkey, Certificate cert,
|
||||
PrivateKey pk, SigningPrivateKey spk, byte[] padding) {
|
||||
if (spubkey.getType() != spk.getType())
|
||||
throw new IllegalArgumentException("Signing key type mismatch");
|
||||
this.file = file;
|
||||
@@ -232,6 +245,8 @@ public class PrivateKeyFile {
|
||||
this.dest.setPublicKey(pubkey);
|
||||
this.dest.setSigningPublicKey(spubkey);
|
||||
this.dest.setCertificate(cert);
|
||||
if (padding != null)
|
||||
this.dest.setPadding(padding);
|
||||
this.privKey = pk;
|
||||
this.signingPrivKey = spk;
|
||||
}
|
||||
@@ -241,9 +256,9 @@ public class PrivateKeyFile {
|
||||
*/
|
||||
public Destination createIfAbsent() throws I2PException, IOException, DataFormatException {
|
||||
if(!this.file.exists()) {
|
||||
FileOutputStream out = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(this.file);
|
||||
out = new SecureFileOutputStream(this.file);
|
||||
if (this.client != null)
|
||||
this.client.createDestination(out);
|
||||
else
|
||||
@@ -257,7 +272,10 @@ public class PrivateKeyFile {
|
||||
return getDestination();
|
||||
}
|
||||
|
||||
/** Also sets the local privKey and signingPrivKey */
|
||||
/**
|
||||
* If the destination is not set, read it in from the file.
|
||||
* Also sets the local privKey and signingPrivKey.
|
||||
*/
|
||||
public Destination getDestination() throws I2PSessionException, IOException, DataFormatException {
|
||||
if (dest == null) {
|
||||
I2PSession s = open();
|
||||
@@ -408,9 +426,9 @@ public class PrivateKeyFile {
|
||||
}
|
||||
|
||||
public I2PSession open(Properties opts) throws I2PSessionException, IOException {
|
||||
FileInputStream in = null;
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(this.file);
|
||||
in = new BufferedInputStream(new FileInputStream(this.file));
|
||||
I2PSession s = this.client.createSession(in, opts);
|
||||
return s;
|
||||
} finally {
|
||||
@@ -424,13 +442,12 @@ public class PrivateKeyFile {
|
||||
* Copied from I2PClientImpl.createDestination()
|
||||
*/
|
||||
public void write() throws IOException, DataFormatException {
|
||||
FileOutputStream out = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(this.file);
|
||||
out = new SecureFileOutputStream(this.file);
|
||||
this.dest.writeBytes(out);
|
||||
this.privKey.writeBytes(out);
|
||||
this.signingPrivKey.writeBytes(out);
|
||||
out.flush();
|
||||
} finally {
|
||||
if (out != null) {
|
||||
try { out.close(); } catch (IOException ioe) {}
|
||||
@@ -438,6 +455,23 @@ public class PrivateKeyFile {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the PublicKey matches the PrivateKey, and
|
||||
* the SigningPublicKey matches the SigningPrivateKey.
|
||||
*
|
||||
* @return success
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public boolean validateKeyPairs() {
|
||||
try {
|
||||
if (!dest.getPublicKey().equals(KeyGenerator.getPublicKey(privKey)))
|
||||
return false;
|
||||
return dest.getSigningPublicKey().equals(KeyGenerator.getSigningPublicKey(signingPrivKey));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder s = new StringBuilder(128);
|
||||
|
||||
@@ -75,8 +75,12 @@ public class SigningPrivateKey extends SimpleDataStructure {
|
||||
return _type;
|
||||
}
|
||||
|
||||
/** converts this signing private key to its public equivalent
|
||||
/**
|
||||
* Converts this signing private key to its public equivalent.
|
||||
* As of 0.9.16, supports all key types.
|
||||
*
|
||||
* @return a SigningPublicKey object derived from this private key
|
||||
* @throws IllegalArgumentException on bad key or unknown or unsupported type
|
||||
*/
|
||||
public SigningPublicKey toPublic() {
|
||||
return KeyGenerator.getSigningPublicKey(this);
|
||||
|
||||
24
installer/resources/proxy/enc-header.ht
Normal file
@@ -0,0 +1,24 @@
|
||||
HTTP/1.1 504 Gateway Timeout
|
||||
Content-Type: text/html; charset=UTF-8
|
||||
Cache-control: no-cache
|
||||
Connection: close
|
||||
Proxy-Connection: close
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html><head>
|
||||
<title>_("Warning: Eepsite Unreachable")</title>
|
||||
<link rel="shortcut icon" href="http://proxy.i2p/themes/console/images/favicon.ico">
|
||||
<link href="http://proxy.i2p/themes/console/default/console.css" rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="logo">
|
||||
<a href="http://127.0.0.1:7657/" title="_("Router Console")"><img src="http://proxy.i2p/themes/console/images/i2plogo.png" alt="_("I2P Router Console")" border="0"></a><hr>
|
||||
<a href="http://127.0.0.1:7657/config.jsp">_("Configuration")</a> <a href="http://127.0.0.1:7657/help.jsp">_("Help")</a> <a href="http://127.0.0.1:7657/susidns/index">_("Addressbook")</a>
|
||||
</div>
|
||||
<div class="warning" id="warning">
|
||||
<h3>_("Warning: Eepsite Unreachable")</h3>
|
||||
<p>
|
||||
_("The eepsite was not reachable, because it uses encryption options that are not supported by your I2P or Java version.")
|
||||
<hr>
|
||||
<p><b>_("Could not connect to the following destination:")</b>
|
||||
</p>
|
||||
25
installer/resources/proxy/encp-header.ht
Normal file
@@ -0,0 +1,25 @@
|
||||
HTTP/1.1 504 Gateway Timeout
|
||||
Content-Type: text/html; charset=UTF-8
|
||||
Cache-control: no-cache
|
||||
Connection: close
|
||||
Proxy-Connection: close
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html><head>
|
||||
<title>_("Warning: Outproxy Unreachable")</title>
|
||||
<link rel="shortcut icon" href="http://proxy.i2p/themes/console/images/favicon.ico">
|
||||
<link href="http://proxy.i2p/themes/console/default/console.css" rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="logo">
|
||||
<a href="http://127.0.0.1:7657/" title="_("Router Console")"><img src="http://proxy.i2p/themes/console/images/i2plogo.png" alt="_("I2P Router Console")" border="0"></a><hr>
|
||||
<a href="http://127.0.0.1:7657/config.jsp">_("Configuration")</a> <a href="http://127.0.0.1:7657/help.jsp">_("Help")</a> <a href="http://127.0.0.1:7657/susidns/index">_("Addressbook")</a>
|
||||
</div>
|
||||
<div class="warning" id="warning">
|
||||
<h3>_("Warning: Outproxy Unreachable")</h3>
|
||||
<p>
|
||||
_("The HTTP outproxy was not reachable, because it uses encryption options that are not supported by your I2P or Java version.")
|
||||
_("You may want to {0}retry{1} as this will randomly reselect an outproxy from the pool you have defined {2}here{3} (if you have more than one configured).", "<a href=\"javascript:parent.window.location.reload()\">", "</a>", "<a href=\"http://127.0.0.1:7657/i2ptunnel/index.jsp\">", "</a>")
|
||||
_("If you continue to have trouble you may want to edit your outproxy list {0}here{1}.", "<a href=\"http://127.0.0.1:7657/i2ptunnel/edit.jsp?tunnel=0\">", "</a>")
|
||||
</p>
|
||||
<hr><p><b>_("Could not connect to the following destination:")</b></p>
|
||||
@@ -526,6 +526,40 @@ a:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
a.control, a.controld {
|
||||
background: #989;
|
||||
border: 1px inset #bbb;
|
||||
border-radius: 4px;
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
margin: 2px 4px;
|
||||
padding: 2px;
|
||||
text-shadow: 0px 0px #410;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
a.controld {
|
||||
color: #444;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
a.controld img {
|
||||
display: none;
|
||||
}
|
||||
|
||||
a.control:hover {
|
||||
background-color: #f60;
|
||||
border: 1px outset #bbb;
|
||||
color: #fff;
|
||||
text-shadow: 0px 1px 5px #f00;
|
||||
}
|
||||
|
||||
a.control:active {
|
||||
background: #000 !important;
|
||||
color: #f60 !important;
|
||||
text-shadow: 0 !important;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 8.5pt;
|
||||
font-weight: bold;
|
||||
@@ -598,6 +632,14 @@ input[type=radio] {
|
||||
|
||||
input.default { width: 1px; height: 1px; visibility: hidden; }
|
||||
|
||||
input.disabled, input.disabled:hover {
|
||||
background-color: #989;
|
||||
border: 1px inset #bbb;
|
||||
color: #444;
|
||||
font-weight: normal;
|
||||
text-shadow: 0px 0px 0px #444;
|
||||
}
|
||||
|
||||
input.accept {
|
||||
background: #989 url('../../console/images/accept.png') no-repeat 2px center;
|
||||
padding: 2px 3px 2px 20px !important;
|
||||
|
||||
@@ -146,10 +146,10 @@ public class BuildRequestRecord {
|
||||
return (_data.getData()[_data.getOffset() + OFF_FLAG] & FLAG_OUTBOUND_ENDPOINT) != 0;
|
||||
}
|
||||
/**
|
||||
* Time that the request was sent, truncated to the nearest hour
|
||||
* Time that the request was sent (ms), truncated to the nearest hour
|
||||
*/
|
||||
public long readRequestTime() {
|
||||
return DataHelper.fromLong(_data.getData(), _data.getOffset() + OFF_REQ_TIME, 4) * 60l * 60l * 1000l;
|
||||
return DataHelper.fromLong(_data.getData(), _data.getOffset() + OFF_REQ_TIME, 4) * (60 * 60 * 1000L);
|
||||
}
|
||||
/**
|
||||
* What message ID should we send the request to the next hop with. If this is the outbound tunnel endpoint,
|
||||
@@ -250,6 +250,8 @@ public class BuildRequestRecord {
|
||||
else if (isOutEndpoint)
|
||||
buf[OFF_FLAG] |= FLAG_OUTBOUND_ENDPOINT;
|
||||
long truncatedHour = ctx.clock().now();
|
||||
// prevent hop identification at top of the hour
|
||||
truncatedHour -= ctx.random().nextInt(90*1000);
|
||||
truncatedHour /= (60l*60l*1000l);
|
||||
DataHelper.toLong(buf, OFF_REQ_TIME, 4, truncatedHour);
|
||||
DataHelper.toLong(buf, OFF_SEND_MSG_ID, 4, nextMsgId);
|
||||
|
||||
@@ -17,7 +17,7 @@ import java.util.Set;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.SessionTag;
|
||||
import net.i2p.data.TunnelId;
|
||||
|
||||
@@ -18,7 +18,7 @@ import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.TunnelId;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.i2p.data;
|
||||
package net.i2p.data.router;
|
||||
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
@@ -17,6 +17,9 @@ import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.DataStructureImpl;
|
||||
import net.i2p.util.Addresses;
|
||||
import net.i2p.util.OrderedProperties;
|
||||
|
||||
@@ -36,6 +39,7 @@ import net.i2p.util.OrderedProperties;
|
||||
* several releases for the change to propagate as it is backwards-incompatible.
|
||||
* Restored as of 0.9.12.
|
||||
*
|
||||
* @since 0.9.16 moved from net.i2p.data
|
||||
* @author jrandom
|
||||
*/
|
||||
public class RouterAddress extends DataStructureImpl {
|
||||
@@ -1,4 +1,7 @@
|
||||
package net.i2p.data;
|
||||
package net.i2p.data.router;
|
||||
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.KeysAndCert;
|
||||
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
@@ -16,6 +19,7 @@ package net.i2p.data;
|
||||
* As of 0.9.9 this data structure is immutable after the two keys and the certificate
|
||||
* are set; attempts to change them will throw an IllegalStateException.
|
||||
*
|
||||
* @since 0.9.16 moved from net.i2p.data
|
||||
* @author jrandom
|
||||
*/
|
||||
public class RouterIdentity extends KeysAndCert {
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.i2p.data;
|
||||
package net.i2p.data.router;
|
||||
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
@@ -31,6 +31,13 @@ import net.i2p.crypto.SHA1;
|
||||
import net.i2p.crypto.SHA1Hash;
|
||||
import net.i2p.crypto.SHA256Generator;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.KeysAndCert;
|
||||
import net.i2p.data.Signature;
|
||||
import net.i2p.data.SimpleDataStructure;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.OrderedProperties;
|
||||
@@ -47,6 +54,7 @@ import net.i2p.util.SystemVersion;
|
||||
* To ensure integrity of the RouterInfo, methods that change an element of the
|
||||
* RouterInfo will throw an IllegalStateException after the RouterInfo is signed.
|
||||
*
|
||||
* @since 0.9.16 moved from net.i2p.data
|
||||
* @author jrandom
|
||||
*/
|
||||
public class RouterInfo extends DatabaseEntry {
|
||||
@@ -190,7 +198,7 @@ public class RouterInfo extends DatabaseEntry {
|
||||
// WARNING this sort algorithm cannot be changed, as it must be consistent
|
||||
// network-wide. The signature is not checked at readin time, but only
|
||||
// later, and the addresses are stored in a Set, not a List.
|
||||
DataHelper.sortStructureList(_addresses);
|
||||
SortHelper.sortStructureList(_addresses);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,7 +316,7 @@ public class RouterInfo extends DatabaseEntry {
|
||||
// WARNING this sort algorithm cannot be changed, as it must be consistent
|
||||
// network-wide. The signature is not checked at readin time, but only
|
||||
// later, and the hashes are stored in a Set, not a List.
|
||||
peers = (Collection<Hash>) DataHelper.sortStructures(peers);
|
||||
peers = (Collection<Hash>) SortHelper.sortStructures(peers);
|
||||
for (Hash peerHash : peers) {
|
||||
peerHash.writeBytes(out);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package net.i2p.data.router;
|
||||
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PrivateKeyFile;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
|
||||
/**
|
||||
* Same format as super, simply adds a method to
|
||||
* treat it as a RouterIdentity instead of a Destination.
|
||||
*
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public class RouterPrivateKeyFile extends PrivateKeyFile {
|
||||
|
||||
public RouterPrivateKeyFile(File file) {
|
||||
super(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read it in from the file.
|
||||
* Also sets the local privKey and signingPrivKey.
|
||||
*/
|
||||
public RouterIdentity getRouterIdentity() throws IOException, DataFormatException {
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = new BufferedInputStream(new FileInputStream(this.file));
|
||||
RouterIdentity ri = new RouterIdentity();
|
||||
ri.readBytes(in);
|
||||
privKey = new PrivateKey();
|
||||
privKey.readBytes(in);
|
||||
SigType type = ri.getSigningPublicKey().getType();
|
||||
if (type == null)
|
||||
throw new DataFormatException("Unknown sig type");
|
||||
signingPrivKey = new SigningPrivateKey(type);
|
||||
signingPrivKey.readBytes(in);
|
||||
|
||||
// set it a Destination, so we may call validateKeyPairs()
|
||||
// or other methods
|
||||
dest = new Destination();
|
||||
dest.setPublicKey(ri.getPublicKey());
|
||||
dest.setSigningPublicKey(ri.getSigningPublicKey());
|
||||
dest.setCertificate(ri.getCertificate());
|
||||
dest.setPadding(ri.getPadding());
|
||||
|
||||
return ri;
|
||||
} finally {
|
||||
if (in != null) {
|
||||
try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
79
router/java/src/net/i2p/data/router/SortHelper.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package net.i2p.data.router;
|
||||
|
||||
/*
|
||||
* free (adj.): unencumbered; not under the control of others
|
||||
* Written by jrandom in 2003 and released into the public domain
|
||||
* with no warranty of any kind, either expressed or implied.
|
||||
* It probably won't make your computer catch on fire, or eat
|
||||
* your children, but it might. Use at your own risk.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.data.DataStructure;
|
||||
|
||||
/**
|
||||
* The sorting of addresses in RIs
|
||||
*
|
||||
* @since 0.9.16 moved from DataHelper
|
||||
*/
|
||||
class SortHelper {
|
||||
|
||||
/**
|
||||
* Sort based on the Hash of the DataStructure.
|
||||
* Warning - relatively slow.
|
||||
* WARNING - this sort order must be consistent network-wide, so while the order is arbitrary,
|
||||
* it cannot be changed.
|
||||
* Why? Just because it has to be consistent so signing will work.
|
||||
* How to spec as returning the same type as the param?
|
||||
* DEPRECATED - Only used by RouterInfo.
|
||||
*
|
||||
* @return a new list
|
||||
*/
|
||||
public static List<? extends DataStructure> sortStructures(Collection<? extends DataStructure> dataStructures) {
|
||||
if (dataStructures == null) return Collections.emptyList();
|
||||
|
||||
// This used to use Hash.toString(), which is insane, since a change to toString()
|
||||
// would break the whole network. Now use Hash.toBase64().
|
||||
// Note that the Base64 sort order is NOT the same as the raw byte sort order,
|
||||
// despite what you may read elsewhere.
|
||||
|
||||
//ArrayList<DataStructure> rv = new ArrayList(dataStructures.size());
|
||||
//TreeMap<String, DataStructure> tm = new TreeMap();
|
||||
//for (DataStructure struct : dataStructures) {
|
||||
// tm.put(struct.calculateHash().toString(), struct);
|
||||
//}
|
||||
//for (DataStructure struct : tm.values()) {
|
||||
// rv.add(struct);
|
||||
//}
|
||||
ArrayList<DataStructure> rv = new ArrayList<DataStructure>(dataStructures);
|
||||
sortStructureList(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* See above.
|
||||
* DEPRECATED - Only used by RouterInfo.
|
||||
*
|
||||
* @since 0.9
|
||||
*/
|
||||
static void sortStructureList(List<? extends DataStructure> dataStructures) {
|
||||
Collections.sort(dataStructures, new DataStructureComparator());
|
||||
}
|
||||
|
||||
/**
|
||||
* See sortStructures() comments.
|
||||
* @since 0.8.3
|
||||
*/
|
||||
private static class DataStructureComparator implements Comparator<DataStructure>, Serializable {
|
||||
public int compare(DataStructure l, DataStructure r) {
|
||||
return l.calculateHash().toBase64().compareTo(r.calculateHash().toBase64());
|
||||
}
|
||||
}
|
||||
}
|
||||
7
router/java/src/net/i2p/data/router/package.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>
|
||||
Classes formerly in net.i2p.data but moved here as they are only used by the router.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -28,8 +28,8 @@ import java.util.TreeSet;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
|
||||
import net.i2p.util.Addresses;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.io.Writer;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
|
||||
/**
|
||||
* Manages the communication subsystem between peers, including connections,
|
||||
|
||||
@@ -9,7 +9,7 @@ package net.i2p.router;
|
||||
*/
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,7 +14,7 @@ import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
|
||||
import net.i2p.data.i2np.DeliveryStatusMessage;
|
||||
|
||||
@@ -18,6 +18,7 @@ import java.io.OutputStream;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataStructure;
|
||||
import net.i2p.data.Destination;
|
||||
@@ -26,6 +27,7 @@ import net.i2p.data.PrivateKey;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.SigningPublicKey;
|
||||
import net.i2p.router.startup.CreateRouterInfoJob;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SecureDirectory;
|
||||
import net.i2p.util.SecureFileOutputStream;
|
||||
@@ -47,10 +49,10 @@ public class KeyManager {
|
||||
|
||||
public final static String PROP_KEYDIR = "router.keyBackupDir";
|
||||
public final static String DEFAULT_KEYDIR = "keyBackup";
|
||||
private final static String KEYFILE_PRIVATE_ENC = "privateEncryption.key";
|
||||
private final static String KEYFILE_PUBLIC_ENC = "publicEncryption.key";
|
||||
private final static String KEYFILE_PRIVATE_SIGNING = "privateSigning.key";
|
||||
private final static String KEYFILE_PUBLIC_SIGNING = "publicSigning.key";
|
||||
public final static String KEYFILE_PRIVATE_ENC = "privateEncryption.key";
|
||||
public final static String KEYFILE_PUBLIC_ENC = "publicEncryption.key";
|
||||
public final static String KEYFILE_PRIVATE_SIGNING = "privateSigning.key";
|
||||
public final static String KEYFILE_PUBLIC_SIGNING = "publicSigning.key";
|
||||
|
||||
public KeyManager(RouterContext context) {
|
||||
_context = context;
|
||||
@@ -151,8 +153,9 @@ public class KeyManager {
|
||||
private void syncKeys(File keyDir) {
|
||||
syncPrivateKey(keyDir);
|
||||
syncPublicKey(keyDir);
|
||||
syncSigningKey(keyDir);
|
||||
syncVerificationKey(keyDir);
|
||||
SigType type = CreateRouterInfoJob.getSigTypeConfig(getContext());
|
||||
syncSigningKey(keyDir, type);
|
||||
syncVerificationKey(keyDir, type);
|
||||
}
|
||||
|
||||
private void syncPrivateKey(File keyDir) {
|
||||
@@ -181,27 +184,33 @@ public class KeyManager {
|
||||
_publicKey = (PublicKey) readin;
|
||||
}
|
||||
|
||||
private void syncSigningKey(File keyDir) {
|
||||
/**
|
||||
* @param type the SigType to expect on read-in, ignored on write
|
||||
*/
|
||||
private void syncSigningKey(File keyDir, SigType type) {
|
||||
DataStructure ds;
|
||||
File keyFile = new File(keyDir, KEYFILE_PRIVATE_SIGNING);
|
||||
boolean exists = (_signingPrivateKey != null);
|
||||
if (exists)
|
||||
ds = _signingPrivateKey;
|
||||
else
|
||||
ds = new SigningPrivateKey();
|
||||
ds = new SigningPrivateKey(type);
|
||||
DataStructure readin = syncKey(keyFile, ds, exists);
|
||||
if (readin != null && !exists)
|
||||
_signingPrivateKey = (SigningPrivateKey) readin;
|
||||
}
|
||||
|
||||
private void syncVerificationKey(File keyDir) {
|
||||
/**
|
||||
* @param type the SigType to expect on read-in, ignored on write
|
||||
*/
|
||||
private void syncVerificationKey(File keyDir, SigType type) {
|
||||
DataStructure ds;
|
||||
File keyFile = new File(keyDir, KEYFILE_PUBLIC_SIGNING);
|
||||
boolean exists = (_signingPublicKey != null);
|
||||
if (exists)
|
||||
ds = _signingPublicKey;
|
||||
else
|
||||
ds = new SigningPublicKey();
|
||||
ds = new SigningPublicKey(type);
|
||||
DataStructure readin = syncKey(keyFile, ds, exists);
|
||||
if (readin != null && !exists)
|
||||
_signingPublicKey = (SigningPublicKey) readin;
|
||||
|
||||
@@ -40,7 +40,7 @@ public class LeaseSetKeys {
|
||||
/**
|
||||
* Key with which a LeaseSet can be revoked (by republishing it with no Leases)
|
||||
*
|
||||
* @deprecated unused
|
||||
* Deprecated, unused
|
||||
*/
|
||||
public SigningPrivateKey getRevocationKey() { return _revocationKey; }
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.Scanner;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.Router;
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,9 +14,10 @@ import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.networkdb.reseed.ReseedChecker;
|
||||
|
||||
/**
|
||||
@@ -51,18 +52,51 @@ public abstract class NetworkDatabaseFacade implements Service {
|
||||
public abstract LeaseSet lookupLeaseSetLocally(Hash key);
|
||||
public abstract void lookupRouterInfo(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs);
|
||||
public abstract RouterInfo lookupRouterInfoLocally(Hash key);
|
||||
|
||||
/**
|
||||
* return the leaseSet if another leaseSet already existed at that key
|
||||
* Lookup using the client's tunnels
|
||||
* Succeeds even if LS validation fails due to unsupported sig type
|
||||
*
|
||||
* @param fromLocalDest use these tunnels for the lookup, or null for exploratory
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public abstract void lookupDestination(Hash key, Job onFinishedJob, long timeoutMs, Hash fromLocalDest);
|
||||
|
||||
/**
|
||||
* Lookup locally in netDB and in badDest cache
|
||||
* Succeeds even if LS validation failed due to unsupported sig type
|
||||
*
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public abstract Destination lookupDestinationLocally(Hash key);
|
||||
|
||||
/**
|
||||
* @return the leaseSet if another leaseSet already existed at that key
|
||||
*
|
||||
* @throws IllegalArgumentException if the data is not valid
|
||||
*/
|
||||
public abstract LeaseSet store(Hash key, LeaseSet leaseSet) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* return the routerInfo if another router already existed at that key
|
||||
* @return the routerInfo if another router already existed at that key
|
||||
*
|
||||
* @throws IllegalArgumentException if the data is not valid
|
||||
*/
|
||||
public abstract RouterInfo store(Hash key, RouterInfo routerInfo) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* @return the old entry if it already existed at that key
|
||||
* @throws IllegalArgumentException if the data is not valid
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public DatabaseEntry store(Hash key, DatabaseEntry entry) throws IllegalArgumentException {
|
||||
if (entry.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO)
|
||||
return store(key, (RouterInfo) entry);
|
||||
if (entry.getType() == DatabaseEntry.KEY_TYPE_LEASESET)
|
||||
return store(key, (LeaseSet) entry);
|
||||
throw new IllegalArgumentException("unknown type");
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if the local router is not valid
|
||||
*/
|
||||
@@ -101,4 +135,12 @@ public abstract class NetworkDatabaseFacade implements Service {
|
||||
* @since IPv6
|
||||
*/
|
||||
public boolean floodfillEnabled() { return false; };
|
||||
|
||||
/**
|
||||
* Is it permanently negative cached?
|
||||
*
|
||||
* @param key only for Destinations; for RouterIdentities, see Banlist
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public boolean isNegativeCachedForever(Hash key) { return false; }
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.util.CDPQEntry;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
@@ -70,9 +70,8 @@ public class PersistentKeyRing extends KeyRing {
|
||||
Hash h = e.getKey();
|
||||
buf.append(h.toBase64().substring(0, 6)).append("…");
|
||||
buf.append("<td>");
|
||||
LeaseSet ls = _ctx.netDb().lookupLeaseSetLocally(h);
|
||||
if (ls != null) {
|
||||
Destination dest = ls.getDestination();
|
||||
Destination dest = _ctx.netDb().lookupDestinationLocally(h);
|
||||
if (dest != null) {
|
||||
if (_ctx.clientManager().isLocal(dest)) {
|
||||
TunnelPoolSettings in = _ctx.tunnelManager().getInboundSettings(h);
|
||||
if (in != null && in.getDestinationNickname() != null)
|
||||
|
||||
@@ -29,11 +29,12 @@ import net.i2p.data.Certificate;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.data.i2np.GarlicMessage;
|
||||
import net.i2p.router.message.GarlicMessageHandler;
|
||||
import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade;
|
||||
import net.i2p.router.startup.CreateRouterInfoJob;
|
||||
import net.i2p.router.startup.StartupJob;
|
||||
import net.i2p.router.startup.WorkingDir;
|
||||
import net.i2p.router.tasks.*;
|
||||
@@ -98,10 +99,6 @@ public class Router implements RouterClock.ClockShiftListener {
|
||||
/** this does not put an 'H' in your routerInfo **/
|
||||
public final static String PROP_HIDDEN_HIDDEN = "router.isHidden";
|
||||
public final static String PROP_DYNAMIC_KEYS = "router.dynamicKeys";
|
||||
public final static String PROP_INFO_FILENAME = "router.info.location";
|
||||
public final static String PROP_INFO_FILENAME_DEFAULT = "router.info";
|
||||
public final static String PROP_KEYS_FILENAME = "router.keys.location";
|
||||
public final static String PROP_KEYS_FILENAME_DEFAULT = "router.keys";
|
||||
public final static String PROP_SHUTDOWN_IN_PROGRESS = "__shutdownInProgress";
|
||||
public final static String DNS_CACHE_TIME = "" + (5*60);
|
||||
private static final String EVENTLOG = "eventlog.txt";
|
||||
@@ -673,20 +670,6 @@ public class Router implements RouterClock.ClockShiftListener {
|
||||
return _context.commSystem().isInBadCountry();
|
||||
}
|
||||
|
||||
/**
|
||||
* Only called at startup via LoadRouterInfoJob and RebuildRouterInfoJob.
|
||||
* Not called by periodic RepublishLocalRouterInfoJob.
|
||||
* We don't want to change the cert on the fly as it changes the router hash.
|
||||
* RouterInfo.isHidden() checks the capability, but RouterIdentity.isHidden() checks the cert.
|
||||
* There's no reason to ever add a hidden cert?
|
||||
* @return the certificate for a new RouterInfo - probably a null cert.
|
||||
*/
|
||||
public Certificate createCertificate() {
|
||||
if (_context.getBooleanProperty(PROP_HIDDEN))
|
||||
return new Certificate(Certificate.CERTIFICATE_TYPE_HIDDEN, null);
|
||||
return Certificate.NULL_CERT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.3
|
||||
*/
|
||||
@@ -698,14 +681,16 @@ public class Router implements RouterClock.ClockShiftListener {
|
||||
* Ugly list of files that we need to kill if we are building a new identity
|
||||
*
|
||||
*/
|
||||
private static final String _rebuildFiles[] = new String[] { "router.info",
|
||||
"router.keys",
|
||||
private static final String _rebuildFiles[] = new String[] {
|
||||
CreateRouterInfoJob.INFO_FILENAME,
|
||||
CreateRouterInfoJob.KEYS_FILENAME,
|
||||
CreateRouterInfoJob.KEYS2_FILENAME,
|
||||
"netDb/my.info", // no longer used
|
||||
"connectionTag.keys", // never used?
|
||||
"keyBackup/privateEncryption.key",
|
||||
"keyBackup/privateSigning.key",
|
||||
"keyBackup/publicEncryption.key",
|
||||
"keyBackup/publicSigning.key",
|
||||
KeyManager.DEFAULT_KEYDIR + '/' + KeyManager.KEYFILE_PRIVATE_ENC,
|
||||
KeyManager.DEFAULT_KEYDIR + '/' + KeyManager.KEYFILE_PUBLIC_ENC,
|
||||
KeyManager.DEFAULT_KEYDIR + '/' + KeyManager.KEYFILE_PRIVATE_SIGNING,
|
||||
KeyManager.DEFAULT_KEYDIR + '/' + KeyManager.KEYFILE_PUBLIC_SIGNING,
|
||||
"sessionKeys.dat" // no longer used
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.app.ClientAppManager;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.internal.InternalClientManager;
|
||||
import net.i2p.router.client.ClientManagerFacadeImpl;
|
||||
import net.i2p.router.crypto.TransientSessionKeyManager;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package net.i2p.router;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.peermanager.TunnelHistory;
|
||||
import net.i2p.stat.Rate;
|
||||
import net.i2p.stat.RateAverages;
|
||||
|
||||
@@ -12,8 +12,10 @@ import java.util.Properties;
|
||||
|
||||
import net.i2p.CoreVersion;
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.Payload;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.i2cp.BandwidthLimitsMessage;
|
||||
import net.i2p.data.i2cp.CreateLeaseSetMessage;
|
||||
import net.i2p.data.i2cp.CreateSessionMessage;
|
||||
@@ -37,6 +39,7 @@ import net.i2p.data.i2cp.SessionId;
|
||||
import net.i2p.data.i2cp.SessionStatusMessage;
|
||||
import net.i2p.data.i2cp.SetDateMessage;
|
||||
import net.i2p.router.ClientTunnelSettings;
|
||||
import net.i2p.router.LeaseSetKeys;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.PasswordManager;
|
||||
@@ -81,8 +84,8 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
_log.debug("Message received: \n" + message);
|
||||
int type = message.getType();
|
||||
if (!_authorized) {
|
||||
// TODO change to default true
|
||||
boolean strict = _context.getBooleanProperty(PROP_AUTH_STRICT);
|
||||
// Default true as of 0.9.16
|
||||
boolean strict = _context.getBooleanPropertyDefaultTrue(PROP_AUTH_STRICT);
|
||||
if ((strict && type != GetDateMessage.MESSAGE_TYPE) ||
|
||||
(type != CreateSessionMessage.MESSAGE_TYPE &&
|
||||
type != GetDateMessage.MESSAGE_TYPE &&
|
||||
@@ -367,8 +370,41 @@ class ClientMessageEventListener implements I2CPMessageReader.I2CPMessageEventLi
|
||||
_runner.disconnectClient("Invalid CreateLeaseSetMessage");
|
||||
return;
|
||||
}
|
||||
|
||||
_context.keyManager().registerKeys(message.getLeaseSet().getDestination(), message.getSigningPrivateKey(), message.getPrivateKey());
|
||||
Destination dest = _runner.getConfig().getDestination();
|
||||
Destination ndest = message.getLeaseSet().getDestination();
|
||||
if (!dest.equals(ndest)) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Different destination in LS");
|
||||
_runner.disconnectClient("Different destination in LS");
|
||||
return;
|
||||
}
|
||||
LeaseSetKeys keys = _context.keyManager().getKeys(dest);
|
||||
if (keys == null ||
|
||||
!message.getPrivateKey().equals(keys.getDecryptionKey())) {
|
||||
// Verify and register crypto keys if new or if changed
|
||||
// Private crypto key should never change, and if it does,
|
||||
// one of the checks below will fail
|
||||
PublicKey pk;
|
||||
try {
|
||||
pk = message.getPrivateKey().toPublic();
|
||||
} catch (IllegalArgumentException iae) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Bad private key in LS");
|
||||
_runner.disconnectClient("Bad private key in LS");
|
||||
return;
|
||||
}
|
||||
if (!pk.equals(message.getLeaseSet().getEncryptionKey())) {
|
||||
if (_log.shouldLog(Log.ERROR))
|
||||
_log.error("Private/public crypto key mismatch in LS");
|
||||
_runner.disconnectClient("Private/public crypto key mismatch in LS");
|
||||
return;
|
||||
}
|
||||
// just register new SPK, don't verify, unused
|
||||
_context.keyManager().registerKeys(dest, message.getSigningPrivateKey(), message.getPrivateKey());
|
||||
} else if (!message.getSigningPrivateKey().equals(keys.getRevocationKey())) {
|
||||
// just register new SPK, don't verify, unused
|
||||
_context.keyManager().registerKeys(dest, message.getSigningPrivateKey(), message.getPrivateKey());
|
||||
}
|
||||
try {
|
||||
_context.netDb().publish(message.getLeaseSet());
|
||||
} catch (IllegalArgumentException iae) {
|
||||
|
||||
@@ -38,7 +38,11 @@ class LookupDestJob extends JobImpl {
|
||||
}
|
||||
|
||||
/**
|
||||
* One of h or name non-null
|
||||
* One of h or name non-null.
|
||||
*
|
||||
* For hash or b32 name, the dest will be returned if the LS can be found,
|
||||
* even if the dest uses unsupported crypto.
|
||||
*
|
||||
* @param reqID must be >= 0 if name != null
|
||||
* @param sessID must non-null if reqID >= 0
|
||||
* @param fromLocalDest use these tunnels for the lookup, or null for exploratory
|
||||
@@ -88,7 +92,7 @@ class LookupDestJob extends JobImpl {
|
||||
returnFail();
|
||||
} else {
|
||||
DoneJob done = new DoneJob(getContext());
|
||||
getContext().netDb().lookupLeaseSet(_hash, done, done, _timeout, _fromLocalDest);
|
||||
getContext().netDb().lookupDestination(_hash, done, _timeout, _fromLocalDest);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,9 +102,9 @@ class LookupDestJob extends JobImpl {
|
||||
}
|
||||
public String getName() { return "LeaseSet Lookup Reply to Client"; }
|
||||
public void runJob() {
|
||||
LeaseSet ls = getContext().netDb().lookupLeaseSetLocally(_hash);
|
||||
if (ls != null)
|
||||
returnDest(ls.getDestination());
|
||||
Destination dest = getContext().netDb().lookupDestinationLocally(_hash);
|
||||
if (dest != null)
|
||||
returnDest(dest);
|
||||
else
|
||||
returnFail();
|
||||
}
|
||||
|
||||
@@ -15,16 +15,17 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.NetworkDatabaseFacade;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
public class DummyNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
private Map<Hash, RouterInfo> _routers;
|
||||
private RouterContext _context;
|
||||
private final Map<Hash, RouterInfo> _routers;
|
||||
private final RouterContext _context;
|
||||
|
||||
public DummyNetworkDatabaseFacade(RouterContext ctx) {
|
||||
_routers = Collections.synchronizedMap(new HashMap<Hash, RouterInfo>());
|
||||
@@ -42,6 +43,11 @@ public class DummyNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) {}
|
||||
public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs, Hash fromLocalDest) {}
|
||||
public LeaseSet lookupLeaseSetLocally(Hash key) { return null; }
|
||||
|
||||
public void lookupDestination(Hash key, Job onFinishedJob, long timeoutMs, Hash fromLocalDest) {}
|
||||
|
||||
public Destination lookupDestinationLocally(Hash key) { return null; }
|
||||
|
||||
public void lookupRouterInfo(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) {
|
||||
RouterInfo info = lookupRouterInfoLocally(key);
|
||||
if (info == null)
|
||||
@@ -50,13 +56,16 @@ public class DummyNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
_context.jobQueue().addJob(onFindJob);
|
||||
}
|
||||
public RouterInfo lookupRouterInfoLocally(Hash key) { return _routers.get(key); }
|
||||
|
||||
public void publish(LeaseSet localLeaseSet) {}
|
||||
public void publish(RouterInfo localRouterInfo) {}
|
||||
|
||||
public LeaseSet store(Hash key, LeaseSet leaseSet) { return leaseSet; }
|
||||
public RouterInfo store(Hash key, RouterInfo routerInfo) {
|
||||
RouterInfo rv = _routers.put(key, routerInfo);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public void unpublish(LeaseSet localLeaseSet) {}
|
||||
public void fail(Hash dbEntry) {
|
||||
_routers.remove(dbEntry);
|
||||
|
||||
@@ -14,7 +14,7 @@ import java.util.List;
|
||||
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.i2np.DeliveryInstructions;
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,7 +9,7 @@ package net.i2p.router.message;
|
||||
*/
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.i2np.GarlicMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.HandlerJobBuilder;
|
||||
|
||||
@@ -9,7 +9,7 @@ package net.i2p.router.message;
|
||||
*/
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.i2np.DeliveryInstructions;
|
||||
import net.i2p.data.i2np.GarlicMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
|
||||
@@ -18,7 +18,7 @@ import net.i2p.data.Lease;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.Payload;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.SessionTag;
|
||||
import net.i2p.data.i2cp.MessageId;
|
||||
@@ -425,12 +425,19 @@ public class OutboundClientMessageOneShotJob extends JobImpl {
|
||||
getContext().statManager().addRateData("client.leaseSetFailedRemoteTime", lookupTime);
|
||||
}
|
||||
|
||||
//if (_finished == Result.NONE) {
|
||||
|
||||
int cause;
|
||||
if (getContext().netDb().isNegativeCachedForever(_to.calculateHash())) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unable to send to " + _toString + " because the sig type is unsupported");
|
||||
cause = MessageStatusMessage.STATUS_SEND_FAILURE_UNSUPPORTED_ENCRYPTION;
|
||||
} else {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unable to send to " + _toString + " because we couldn't find their leaseSet");
|
||||
//}
|
||||
cause = MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET;
|
||||
}
|
||||
|
||||
dieFatal(MessageStatusMessage.STATUS_SEND_FAILURE_NO_LEASESET);
|
||||
dieFatal(cause);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ package net.i2p.router.message;
|
||||
import java.util.Date;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.JobImpl;
|
||||
|
||||
@@ -14,8 +14,8 @@ import java.util.Set;
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.util.Date;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.SigningPrivateKey;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.Router;
|
||||
|
||||
@@ -25,8 +25,8 @@ import gnu.getopt.Getopt;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.transport.BadCountries;
|
||||
import net.i2p.router.transport.GeoIP;
|
||||
import net.i2p.util.FileUtil;
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.util.Set;
|
||||
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
@@ -15,6 +15,8 @@ import java.util.Set;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.kademlia.KBucketSet;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
@@ -71,15 +73,15 @@ class ExploreJob extends SearchJob {
|
||||
* and PeerSelector doesn't include the floodfill peers,
|
||||
* so we add the ff peers ourselves and then use the regular PeerSelector.
|
||||
*
|
||||
* TODO should we encrypt this also like we do for normal lookups?
|
||||
* Could the OBEP capture it and reply with a reference to a hostile peer?
|
||||
*
|
||||
* @param replyTunnelId tunnel to receive replies through
|
||||
* @param replyGateway gateway for the reply tunnel
|
||||
* @param expiration when the search should stop
|
||||
* @param peer the peer to send it to
|
||||
*
|
||||
* @return a DatabaseLookupMessage or GarlicMessage
|
||||
*/
|
||||
@Override
|
||||
protected DatabaseLookupMessage buildMessage(TunnelId replyTunnelId, Hash replyGateway, long expiration) {
|
||||
protected I2NPMessage buildMessage(TunnelId replyTunnelId, Hash replyGateway, long expiration, RouterInfo peer) {
|
||||
DatabaseLookupMessage msg = new DatabaseLookupMessage(getContext(), true);
|
||||
msg.setSearchKey(getState().getTarget());
|
||||
msg.setFrom(replyGateway);
|
||||
@@ -127,7 +129,27 @@ class ExploreJob extends SearchJob {
|
||||
_log.debug("Peers we don't want to hear about: " + dontIncludePeers);
|
||||
|
||||
msg.setDontIncludePeers(dontIncludePeers);
|
||||
return msg;
|
||||
|
||||
// Now encrypt if we can
|
||||
I2NPMessage outMsg;
|
||||
if (getContext().getProperty(IterativeSearchJob.PROP_ENCRYPT_RI, IterativeSearchJob.DEFAULT_ENCRYPT_RI)) {
|
||||
// request encrypted reply?
|
||||
if (DatabaseLookupMessage.supportsEncryptedReplies(peer)) {
|
||||
MessageWrapper.OneTimeSession sess;
|
||||
sess = MessageWrapper.generateSession(getContext());
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info(getJobId() + ": Requesting encrypted reply from " + peer.getIdentity().calculateHash() +
|
||||
' ' + sess.key + ' ' + sess.tag);
|
||||
msg.setReplySession(sess.key, sess.tag);
|
||||
}
|
||||
outMsg = MessageWrapper.wrap(getContext(), msg, peer);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(getJobId() + ": Encrypted exploratory DLM for " + getState().getTarget() + " to " +
|
||||
peer.getIdentity().calculateHash());
|
||||
} else {
|
||||
outMsg = msg;
|
||||
}
|
||||
return outMsg;
|
||||
}
|
||||
|
||||
/** max # of concurrent searches */
|
||||
|
||||
@@ -2,10 +2,10 @@ package net.i2p.router.networkdb.kademlia;
|
||||
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.ReplyJob;
|
||||
import net.i2p.router.RouterContext;
|
||||
@@ -62,6 +62,9 @@ class FloodOnlyLookupMatchJob extends JobImpl implements ReplyJob {
|
||||
} else {
|
||||
getContext().netDb().store(dsm.getKey(), (RouterInfo) dsm.getEntry());
|
||||
}
|
||||
} catch (UnsupportedCryptoException uce) {
|
||||
_search.failed();
|
||||
return;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn(_search.getJobId() + ": Received an invalid store reply", iae);
|
||||
|
||||
@@ -9,7 +9,7 @@ package net.i2p.router.networkdb.kademlia;
|
||||
*/
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.HandlerJobBuilder;
|
||||
|
||||
@@ -9,7 +9,7 @@ package net.i2p.router.networkdb.kademlia;
|
||||
*/
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.router.HandlerJobBuilder;
|
||||
|
||||
@@ -3,8 +3,8 @@ package net.i2p.router.networkdb.kademlia;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
@@ -7,11 +7,12 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.Job;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
@@ -31,7 +32,6 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
|
||||
private final Set<Hash> _verifiesInProgress;
|
||||
private FloodThrottler _floodThrottler;
|
||||
private LookupThrottler _lookupThrottler;
|
||||
private NegativeLookupCache _negativeCache;
|
||||
|
||||
/**
|
||||
* This is the flood redundancy. Entries are
|
||||
@@ -65,7 +65,6 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
|
||||
_context.statManager().createRateStat("netDb.searchReplyNotValidated", "How many search replies we get that we are NOT able to validate (fetch)", "NetworkDatabase", new long[] { 5*60*1000l, 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
|
||||
_context.statManager().createRateStat("netDb.searchReplyValidationSkipped", "How many search replies we get from unreliable peers that we skip?", "NetworkDatabase", new long[] { 5*60*1000l, 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
|
||||
_context.statManager().createRateStat("netDb.republishQuantity", "How many peers do we need to send a found leaseSet to?", "NetworkDatabase", new long[] { 10*60*1000l, 60*60*1000l, 3*60*60*1000l, 24*60*60*1000l });
|
||||
_context.statManager().createRateStat("netDb.negativeCache", "Aborted lookup, already cached", "NetworkDatabase", new long[] { 60*60*1000l });
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,7 +72,6 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
|
||||
super.startup();
|
||||
_context.jobQueue().addJob(new FloodfillMonitorJob(_context, this));
|
||||
_lookupThrottler = new LookupThrottler();
|
||||
_negativeCache = new NegativeLookupCache();
|
||||
|
||||
// refresh old routers
|
||||
Job rrj = new RefreshRoutersJob(_context, this);
|
||||
@@ -171,25 +169,6 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
|
||||
return _lookupThrottler.shouldThrottle(from, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment in the negative lookup cache
|
||||
* @since 0.9.4
|
||||
*/
|
||||
void lookupFailed(Hash key) {
|
||||
_negativeCache.lookupFailed(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the key in the negative lookup cache?
|
||||
* @since 0.9.4
|
||||
*/
|
||||
boolean isNegativeCached(Hash key) {
|
||||
boolean rv = _negativeCache.isCached(key);
|
||||
if (rv)
|
||||
_context.statManager().addRateData("netDb.negativeCache", 1);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send to a subset of all floodfill peers.
|
||||
* We do this to implement Kademlia within the floodfills, i.e.
|
||||
@@ -301,7 +280,9 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup using exploratory tunnels
|
||||
* Lookup using exploratory tunnels.
|
||||
*
|
||||
* Caller should check negative cache and/or banlist before calling.
|
||||
*
|
||||
* Begin a kademlia style search for the key specified, which can take up to timeoutMs and
|
||||
* will fire the appropriate jobs on success or timeout (or if the kademlia search completes
|
||||
@@ -315,7 +296,10 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup using the client's tunnels
|
||||
* Lookup using the client's tunnels.
|
||||
*
|
||||
* Caller should check negative cache and/or banlist before calling.
|
||||
*
|
||||
* @param fromLocalDest use these tunnels for the lookup, or null for exploratory
|
||||
* @return null always
|
||||
* @since 0.9.10
|
||||
@@ -473,6 +457,7 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
|
||||
// should we skip the search?
|
||||
if (_floodfillEnabled ||
|
||||
_context.jobQueue().getMaxLag() > 500 ||
|
||||
_context.banlist().isBanlistedForever(peer) ||
|
||||
getKBucketSetSize() > MAX_DB_BEFORE_SKIPPING_SEARCH) {
|
||||
// don't try to overload ourselves (e.g. failing 3000 router refs at
|
||||
// once, and then firing off 3000 netDb lookup tasks)
|
||||
|
||||
@@ -18,8 +18,8 @@ import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.kademlia.KBucketSet;
|
||||
import net.i2p.kademlia.SelectionCollector;
|
||||
import net.i2p.kademlia.XORComparator;
|
||||
|
||||
@@ -6,9 +6,10 @@ import java.util.Set;
|
||||
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
@@ -173,9 +174,9 @@ class FloodfillVerifyStoreJob extends JobImpl {
|
||||
FloodfillPeerSelector sel = (FloodfillPeerSelector)_facade.getPeerSelector();
|
||||
Certificate keyCert = null;
|
||||
if (!_isRouterInfo) {
|
||||
LeaseSet ls = _facade.lookupLeaseSetLocally(_key);
|
||||
if (ls != null) {
|
||||
Certificate cert = ls.getDestination().getCertificate();
|
||||
Destination dest = _facade.lookupDestinationLocally(_key);
|
||||
if (dest != null) {
|
||||
Certificate cert = dest.getCertificate();
|
||||
if (cert.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY)
|
||||
keyCert = cert;
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ package net.i2p.router.networkdb.kademlia;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.TunnelId;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
|
||||
@@ -14,9 +14,9 @@ import java.util.Date;
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterIdentity;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.i2np.DeliveryStatusMessage;
|
||||
import net.i2p.router.JobImpl;
|
||||
@@ -51,6 +51,8 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
|
||||
long recvBegin = System.currentTimeMillis();
|
||||
|
||||
String invalidMessage = null;
|
||||
// set if invalid store but not his fault
|
||||
boolean dontBlamePeer = false;
|
||||
boolean wasNew = false;
|
||||
RouterInfo prevNetDb = null;
|
||||
Hash key = _message.getKey();
|
||||
@@ -72,6 +74,7 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
|
||||
if (getContext().clientManager().isLocal(key)) {
|
||||
//getContext().statManager().addRateData("netDb.storeLocalLeaseSetAttempt", 1, 0);
|
||||
// throw rather than return, so that we send the ack below (prevent easy attack)
|
||||
dontBlamePeer = true;
|
||||
throw new IllegalArgumentException("Peer attempted to store local leaseSet: " +
|
||||
key.toBase64().substring(0, 4));
|
||||
}
|
||||
@@ -114,6 +117,9 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
|
||||
//if (!ls.getReceivedAsReply())
|
||||
// match.setReceivedAsPublished(true);
|
||||
}
|
||||
} catch (UnsupportedCryptoException uce) {
|
||||
invalidMessage = uce.getMessage();
|
||||
dontBlamePeer = true;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
invalidMessage = iae.getMessage();
|
||||
}
|
||||
@@ -131,8 +137,10 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
|
||||
if (getContext().routerHash().equals(key)) {
|
||||
//getContext().statManager().addRateData("netDb.storeLocalRouterInfoAttempt", 1, 0);
|
||||
// throw rather than return, so that we send the ack below (prevent easy attack)
|
||||
dontBlamePeer = true;
|
||||
throw new IllegalArgumentException("Peer attempted to store our RouterInfo");
|
||||
}
|
||||
getContext().profileManager().heardAbout(key);
|
||||
prevNetDb = getContext().netDb().store(key, ri);
|
||||
wasNew = ((null == prevNetDb) || (prevNetDb.getPublished() < ri.getPublished()));
|
||||
// Check new routerinfo address against blocklist
|
||||
@@ -152,7 +160,9 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
|
||||
_log.warn("New address received, Blocklisting old peer " + key + ' ' + ri);
|
||||
}
|
||||
}
|
||||
getContext().profileManager().heardAbout(key);
|
||||
} catch (UnsupportedCryptoException uce) {
|
||||
invalidMessage = uce.getMessage();
|
||||
dontBlamePeer = true;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
invalidMessage = iae.getMessage();
|
||||
}
|
||||
@@ -165,6 +175,8 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
|
||||
long recvEnd = System.currentTimeMillis();
|
||||
getContext().statManager().addRateData("netDb.storeRecvTime", recvEnd-recvBegin);
|
||||
|
||||
// ack even if invalid or unsupported
|
||||
// TODO any cases where we shouldn't?
|
||||
if (_message.getReplyToken() > 0)
|
||||
sendAck();
|
||||
long ackEnd = System.currentTimeMillis();
|
||||
@@ -172,7 +184,7 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
|
||||
if (_from != null)
|
||||
_fromHash = _from.getHash();
|
||||
if (_fromHash != null) {
|
||||
if (invalidMessage == null) {
|
||||
if (invalidMessage == null || dontBlamePeer) {
|
||||
getContext().profileManager().dbStoreReceived(_fromHash, wasNew);
|
||||
getContext().statManager().addRateData("netDb.storeHandled", ackEnd-recvEnd);
|
||||
} else {
|
||||
@@ -180,7 +192,7 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Peer " + _fromHash.toBase64() + " sent bad data: " + invalidMessage);
|
||||
}
|
||||
} else if (invalidMessage != null) {
|
||||
} else if (invalidMessage != null && !dontBlamePeer) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unknown peer sent bad data: " + invalidMessage);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.OutNetMessage;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package net.i2p.router.networkdb.kademlia;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.router.JobImpl;
|
||||
|
||||
@@ -13,9 +13,9 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.kademlia.KBucketSet;
|
||||
import net.i2p.kademlia.XORComparator;
|
||||
import net.i2p.router.CommSystemFacade;
|
||||
@@ -28,6 +28,8 @@ import net.i2p.router.TunnelInfo;
|
||||
import net.i2p.router.TunnelManagerFacade;
|
||||
import net.i2p.router.util.RandomIterator;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.NativeBigInteger;
|
||||
import net.i2p.util.SystemVersion;
|
||||
|
||||
/**
|
||||
* A traditional Kademlia search that continues to search
|
||||
@@ -88,8 +90,13 @@ class IterativeSearchJob extends FloodSearchJob {
|
||||
*/
|
||||
private static final int MAX_CONCURRENT = 1;
|
||||
|
||||
/** testing */
|
||||
private static final String PROP_ENCRYPT_RI = "router.encryptRouterLookups";
|
||||
public static final String PROP_ENCRYPT_RI = "router.encryptRouterLookups";
|
||||
|
||||
/** only on fast boxes, for now */
|
||||
public static final boolean DEFAULT_ENCRYPT_RI =
|
||||
SystemVersion.isX86() && SystemVersion.is64Bit() &&
|
||||
!SystemVersion.isApache() && !SystemVersion.isGNU() &&
|
||||
NativeBigInteger.isNative();
|
||||
|
||||
/**
|
||||
* Lookup using exploratory tunnels
|
||||
@@ -315,7 +322,7 @@ class IterativeSearchJob extends FloodSearchJob {
|
||||
_sentTime.put(peer, Long.valueOf(now));
|
||||
|
||||
I2NPMessage outMsg = null;
|
||||
if (_isLease || getContext().getBooleanProperty(PROP_ENCRYPT_RI)) {
|
||||
if (_isLease || getContext().getProperty(PROP_ENCRYPT_RI, DEFAULT_ENCRYPT_RI)) {
|
||||
// Full ElG is fairly expensive so only do it for LS lookups
|
||||
// if we have the ff RI, garlic encrypt it
|
||||
RouterInfo ri = getContext().netDb().lookupRouterInfoLocally(peer);
|
||||
|
||||
@@ -19,14 +19,20 @@ import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.crypto.SigType;
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.KeyCertificate;
|
||||
import net.i2p.data.LeaseSet;
|
||||
import net.i2p.data.RouterAddress;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.i2np.DatabaseLookupMessage;
|
||||
import net.i2p.data.i2np.DatabaseStoreMessage;
|
||||
import net.i2p.data.router.RouterAddress;
|
||||
import net.i2p.data.router.RouterIdentity;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.kademlia.KBucketSet;
|
||||
import net.i2p.kademlia.RejectTrimmer;
|
||||
import net.i2p.kademlia.SelectionCollector;
|
||||
@@ -63,6 +69,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
protected final RouterContext _context;
|
||||
private final ReseedChecker _reseedChecker;
|
||||
private volatile long _lastRIPublishTime;
|
||||
private NegativeLookupCache _negativeCache;
|
||||
|
||||
/**
|
||||
* Map of Hash to RepublishLeaseSetJob for leases we'realready managing.
|
||||
@@ -155,6 +162,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
_reseedChecker = new ReseedChecker(context);
|
||||
context.statManager().createRateStat("netDb.lookupDeferred", "how many lookups are deferred?", "NetworkDatabase", new long[] { 60*60*1000 });
|
||||
context.statManager().createRateStat("netDb.exploreKeySet", "how many keys are queued for exploration?", "NetworkDatabase", new long[] { 60*60*1000 });
|
||||
context.statManager().createRateStat("netDb.negativeCache", "Aborted lookup, already cached", "NetworkDatabase", new long[] { 60*60*1000l });
|
||||
// following are for StoreJob
|
||||
context.statManager().createRateStat("netDb.storeRouterInfoSent", "How many routerInfo store messages have we sent?", "NetworkDatabase", new long[] { 60*60*1000l });
|
||||
context.statManager().createRateStat("netDb.storeLeaseSetSent", "How many leaseSet store messages have we sent?", "NetworkDatabase", new long[] { 60*60*1000l });
|
||||
@@ -223,6 +231,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
//_ds = null;
|
||||
_exploreKeys.clear(); // hope this doesn't cause an explosion, it shouldn't.
|
||||
// _exploreKeys = null;
|
||||
_negativeCache.clear();
|
||||
}
|
||||
|
||||
public synchronized void restart() {
|
||||
@@ -262,6 +271,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
//_ds = new TransientDataStore();
|
||||
// _exploreKeys = new HashSet(64);
|
||||
_dbDir = dbDir;
|
||||
_negativeCache = new NegativeLookupCache();
|
||||
|
||||
createHandlers();
|
||||
|
||||
@@ -480,7 +490,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup using exploratory tunnels
|
||||
* Lookup using exploratory tunnels.
|
||||
* Use lookupDestination() if you don't need the LS or need it validated.
|
||||
*/
|
||||
public void lookupLeaseSet(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) {
|
||||
lookupLeaseSet(key, onFindJob, onFailedLookupJob, timeoutMs, null);
|
||||
@@ -488,6 +499,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
|
||||
/**
|
||||
* Lookup using the client's tunnels
|
||||
* Use lookupDestination() if you don't need the LS or need it validated.
|
||||
*
|
||||
* @param fromLocalDest use these tunnels for the lookup, or null for exploratory
|
||||
* @since 0.9.10
|
||||
*/
|
||||
@@ -500,6 +513,11 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
_log.debug("leaseSet found locally, firing " + onFindJob);
|
||||
if (onFindJob != null)
|
||||
_context.jobQueue().addJob(onFindJob);
|
||||
} else if (isNegativeCached(key)) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Negative cached, not searching: " + key);
|
||||
if (onFailedLookupJob != null)
|
||||
_context.jobQueue().addJob(onFailedLookupJob);
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("leaseSet not found locally, running search");
|
||||
@@ -509,6 +527,9 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
_log.debug("after lookupLeaseSet");
|
||||
}
|
||||
|
||||
/**
|
||||
* Use lookupDestination() if you don't need the LS or need it validated.
|
||||
*/
|
||||
public LeaseSet lookupLeaseSetLocally(Hash key) {
|
||||
if (!_initialized) return null;
|
||||
DatabaseEntry ds = _ds.get(key);
|
||||
@@ -532,12 +553,56 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup using the client's tunnels
|
||||
* Succeeds even if LS validation and store fails due to unsupported sig type, expired, etc.
|
||||
*
|
||||
* Note that there are not separate success and fail jobs. Caller must call
|
||||
* lookupDestinationLocally() in the job to determine success.
|
||||
*
|
||||
* @param onFinishedJob non-null
|
||||
* @param fromLocalDest use these tunnels for the lookup, or null for exploratory
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public void lookupDestination(Hash key, Job onFinishedJob, long timeoutMs, Hash fromLocalDest) {
|
||||
if (!_initialized) return;
|
||||
Destination d = lookupDestinationLocally(key);
|
||||
if (d != null) {
|
||||
_context.jobQueue().addJob(onFinishedJob);
|
||||
} else {
|
||||
search(key, onFinishedJob, onFinishedJob, timeoutMs, true, fromLocalDest);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup locally in netDB and in badDest cache
|
||||
* Succeeds even if LS validation fails due to unsupported sig type, expired, etc.
|
||||
*
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public Destination lookupDestinationLocally(Hash key) {
|
||||
if (!_initialized) return null;
|
||||
DatabaseEntry ds = _ds.get(key);
|
||||
if (ds != null) {
|
||||
if (ds.getType() == DatabaseEntry.KEY_TYPE_LEASESET) {
|
||||
LeaseSet ls = (LeaseSet)ds;
|
||||
return ls.getDestination();
|
||||
}
|
||||
} else {
|
||||
return _negativeCache.getBadDest(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void lookupRouterInfo(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs) {
|
||||
if (!_initialized) return;
|
||||
RouterInfo ri = lookupRouterInfoLocally(key);
|
||||
if (ri != null) {
|
||||
if (onFindJob != null)
|
||||
_context.jobQueue().addJob(onFindJob);
|
||||
} else if (_context.banlist().isBanlistedForever(key)) {
|
||||
if (onFailedLookupJob != null)
|
||||
_context.jobQueue().addJob(onFailedLookupJob);
|
||||
} else {
|
||||
search(key, onFindJob, onFailedLookupJob, timeoutMs, false);
|
||||
}
|
||||
@@ -694,9 +759,10 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
* Unlike for RouterInfos, this is only called once, when stored.
|
||||
* After that, LeaseSet.isCurrent() is used.
|
||||
*
|
||||
* @throws UnsupportedCryptoException if that's why it failed.
|
||||
* @return reason why the entry is not valid, or null if it is valid
|
||||
*/
|
||||
private String validate(Hash key, LeaseSet leaseSet) {
|
||||
private String validate(Hash key, LeaseSet leaseSet) throws UnsupportedCryptoException {
|
||||
if (!key.equals(leaseSet.getDestination().calculateHash())) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid store attempt! key does not match leaseSet.destination! key = "
|
||||
@@ -704,9 +770,11 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
return "Key does not match leaseSet.destination - " + key.toBase64();
|
||||
}
|
||||
if (!leaseSet.verifySignature()) {
|
||||
// throws UnsupportedCryptoException
|
||||
processStoreFailure(key, leaseSet);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid leaseSet signature! leaseSet = " + leaseSet);
|
||||
return "Invalid leaseSet signature on " + leaseSet.getDestination().calculateHash().toBase64();
|
||||
_log.warn("Invalid leaseSet signature! " + leaseSet);
|
||||
return "Invalid leaseSet signature on " + key;
|
||||
}
|
||||
long earliest = leaseSet.getEarliestLeaseDate();
|
||||
long latest = leaseSet.getLatestLeaseDate();
|
||||
@@ -722,7 +790,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
+ " first exp. " + new Date(earliest)
|
||||
+ " last exp. " + new Date(latest),
|
||||
new Exception("Rejecting store"));
|
||||
return "Expired leaseSet for " + leaseSet.getDestination().calculateHash().toBase64()
|
||||
return "Expired leaseSet for " + leaseSet.getDestination().calculateHash()
|
||||
+ " expired " + DataHelper.formatDuration(age) + " ago";
|
||||
}
|
||||
if (latest > now + (Router.CLOCK_FUDGE_FACTOR + MAX_LEASE_FUTURE)) {
|
||||
@@ -739,9 +807,13 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the leaseSet
|
||||
* Store the leaseSet.
|
||||
*
|
||||
* If the store fails due to unsupported crypto, it will negative cache
|
||||
* the hash until restart.
|
||||
*
|
||||
* @throws IllegalArgumentException if the leaseSet is not valid
|
||||
* @throws UnsupportedCryptoException if that's why it failed.
|
||||
* @return previous entry or null
|
||||
*/
|
||||
public LeaseSet store(Hash key, LeaseSet leaseSet) throws IllegalArgumentException {
|
||||
@@ -798,6 +870,10 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
*
|
||||
* Call this only on first store, to check the key and signature once
|
||||
*
|
||||
* If the store fails due to unsupported crypto, it will banlist
|
||||
* the router hash until restart and then throw UnsupportedCrytpoException.
|
||||
*
|
||||
* @throws UnsupportedCryptoException if that's why it failed.
|
||||
* @return reason why the entry is not valid, or null if it is valid
|
||||
*/
|
||||
private String validate(Hash key, RouterInfo routerInfo) throws IllegalArgumentException {
|
||||
@@ -807,6 +883,8 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
return "Key does not match routerInfo.identity";
|
||||
}
|
||||
if (!routerInfo.isValid()) {
|
||||
// throws UnsupportedCryptoException
|
||||
processStoreFailure(key, routerInfo);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid routerInfo signature! forged router structure! router = " + routerInfo);
|
||||
return "Invalid routerInfo signature";
|
||||
@@ -892,15 +970,29 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
}
|
||||
|
||||
/**
|
||||
* store the routerInfo
|
||||
* Store the routerInfo.
|
||||
*
|
||||
* If the store fails due to unsupported crypto, it will banlist
|
||||
* the router hash until restart and then throw UnsupportedCrytpoException.
|
||||
*
|
||||
* @throws IllegalArgumentException if the routerInfo is not valid
|
||||
* @throws UnsupportedCryptoException if that's why it failed.
|
||||
* @return previous entry or null
|
||||
*/
|
||||
public RouterInfo store(Hash key, RouterInfo routerInfo) throws IllegalArgumentException {
|
||||
return store(key, routerInfo, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the routerInfo.
|
||||
*
|
||||
* If the store fails due to unsupported crypto, it will banlist
|
||||
* the router hash until restart and then throw UnsupportedCrytpoException.
|
||||
*
|
||||
* @throws IllegalArgumentException if the routerInfo is not valid
|
||||
* @throws UnsupportedCryptoException if that's why it failed.
|
||||
* @return previous entry or null
|
||||
*/
|
||||
RouterInfo store(Hash key, RouterInfo routerInfo, boolean persist) throws IllegalArgumentException {
|
||||
if (!_initialized) return null;
|
||||
|
||||
@@ -935,6 +1027,59 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the validate fails, call this
|
||||
* to determine if it was because of unsupported crypto.
|
||||
*
|
||||
* If so, this will banlist-forever the router hash or permanently negative cache the dest hash,
|
||||
* and then throw the exception. Otherwise it does nothing.
|
||||
*
|
||||
* @throws UnsupportedCryptoException if that's why it failed.
|
||||
* @since 0.9.16
|
||||
*/
|
||||
private void processStoreFailure(Hash h, DatabaseEntry entry) throws UnsupportedCryptoException {
|
||||
if (entry.getHash().equals(h)) {
|
||||
if (entry.getType() == DatabaseEntry.KEY_TYPE_LEASESET) {
|
||||
LeaseSet ls = (LeaseSet) entry;
|
||||
Destination d = ls.getDestination();
|
||||
Certificate c = d.getCertificate();
|
||||
if (c.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) {
|
||||
try {
|
||||
KeyCertificate kc = c.toKeyCertificate();
|
||||
SigType type = kc.getSigType();
|
||||
if (type == null || !type.isAvailable()) {
|
||||
failPermanently(d);
|
||||
String stype = (type != null) ? type.toString() : Integer.toString(kc.getSigTypeCode());
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unsupported sig type " + stype + " for destination " + h);
|
||||
throw new UnsupportedCryptoException("Sig type " + stype);
|
||||
}
|
||||
} catch (DataFormatException dfe) {}
|
||||
}
|
||||
} else if (entry.getType() == DatabaseEntry.KEY_TYPE_ROUTERINFO) {
|
||||
RouterInfo ri = (RouterInfo) entry;
|
||||
RouterIdentity id = ri.getIdentity();
|
||||
Certificate c = id.getCertificate();
|
||||
if (c.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) {
|
||||
try {
|
||||
KeyCertificate kc = c.toKeyCertificate();
|
||||
SigType type = kc.getSigType();
|
||||
if (type == null || !type.isAvailable()) {
|
||||
String stype = (type != null) ? type.toString() : Integer.toString(kc.getSigTypeCode());
|
||||
_context.banlist().banlistRouterForever(h, "Unsupported signature type " + stype);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Unsupported sig type " + stype + " for router " + h);
|
||||
throw new UnsupportedCryptoException("Sig type " + stype);
|
||||
}
|
||||
} catch (DataFormatException dfe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Verify fail, cause unknown: " + entry);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Final remove for a leaseset.
|
||||
* For a router info, will look up in the network before dropping.
|
||||
@@ -1005,8 +1150,12 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
* without any match)
|
||||
*
|
||||
* Unused - called only by FNDF.searchFull() from FloodSearchJob which is overridden - don't use this.
|
||||
*
|
||||
* @throws UnsupportedOperationException always
|
||||
*/
|
||||
SearchJob search(Hash key, Job onFindJob, Job onFailedLookupJob, long timeoutMs, boolean isLease) {
|
||||
throw new UnsupportedOperationException();
|
||||
/****
|
||||
if (!_initialized) return null;
|
||||
boolean isNew = true;
|
||||
SearchJob searchJob = null;
|
||||
@@ -1031,6 +1180,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
_context.statManager().addRateData("netDb.lookupDeferred", deferred, searchJob.getExpiration()-_context.clock().now());
|
||||
}
|
||||
return searchJob;
|
||||
****/
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1102,6 +1252,47 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
|
||||
_context.jobQueue().addJob(new StoreJob(_context, this, key, ds, onSuccess, onFailure, sendTimeout, toIgnore));
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment in the negative lookup cache
|
||||
*
|
||||
* @param key for Destinations or RouterIdentities
|
||||
* @since 0.9.4 moved from FNDF to KNDF in 0.9.16
|
||||
*/
|
||||
void lookupFailed(Hash key) {
|
||||
_negativeCache.lookupFailed(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the key in the negative lookup cache?
|
||||
*&
|
||||
* @param key for Destinations or RouterIdentities
|
||||
* @since 0.9.4 moved from FNDF to KNDF in 0.9.16
|
||||
*/
|
||||
boolean isNegativeCached(Hash key) {
|
||||
boolean rv = _negativeCache.isCached(key);
|
||||
if (rv)
|
||||
_context.statManager().addRateData("netDb.negativeCache", 1);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Negative cache until restart
|
||||
* @since 0.9.16
|
||||
*/
|
||||
void failPermanently(Destination dest) {
|
||||
_negativeCache.failPermanently(dest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is it permanently negative cached?
|
||||
*
|
||||
* @param key only for Destinations; for RouterIdentities, see Banlist
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public boolean isNegativeCachedForever(Hash key) {
|
||||
return _negativeCache.getBadDest(key) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug info, HTML formatted
|
||||
* @since 0.9.10
|
||||
|
||||
@@ -8,7 +8,7 @@ import net.i2p.crypto.TagSetHandle;
|
||||
import net.i2p.data.Certificate;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.SessionTag;
|
||||
import net.i2p.data.i2np.DeliveryInstructions;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package net.i2p.router.networkdb.kademlia;
|
||||
|
||||
import java.util.Map;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.util.LHMCache;
|
||||
import net.i2p.util.ObjectCounter;
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
@@ -12,11 +15,15 @@ import net.i2p.util.SimpleTimer;
|
||||
*/
|
||||
class NegativeLookupCache {
|
||||
private final ObjectCounter<Hash> counter;
|
||||
private final Map<Hash, Destination> badDests;
|
||||
|
||||
private static final int MAX_FAILS = 3;
|
||||
private static final int MAX_BAD_DESTS = 128;
|
||||
private static final long CLEAN_TIME = 2*60*1000;
|
||||
|
||||
public NegativeLookupCache() {
|
||||
this.counter = new ObjectCounter<Hash>();
|
||||
this.badDests = new LHMCache<Hash, Destination>(MAX_BAD_DESTS);
|
||||
SimpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), CLEAN_TIME);
|
||||
}
|
||||
|
||||
@@ -25,7 +32,46 @@ class NegativeLookupCache {
|
||||
}
|
||||
|
||||
public boolean isCached(Hash h) {
|
||||
return this.counter.count(h) >= MAX_FAILS;
|
||||
if (counter.count(h) >= MAX_FAILS)
|
||||
return true;
|
||||
synchronized(badDests) {
|
||||
return badDests.get(h) != null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Negative cache the hash until restart,
|
||||
* but cache the destination.
|
||||
*
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public void failPermanently(Destination dest) {
|
||||
Hash h = dest.calculateHash();
|
||||
synchronized(badDests) {
|
||||
badDests.put(h, dest);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an unsupported but cached Destination
|
||||
*
|
||||
* @return dest or null if not cached
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public Destination getBadDest(Hash h) {
|
||||
synchronized(badDests) {
|
||||
return badDests.get(h);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.9.16
|
||||
*/
|
||||
public void clear() {
|
||||
counter.clear();
|
||||
synchronized(badDests) {
|
||||
badDests.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private class Cleaner implements SimpleTimer.TimedEvent {
|
||||
|
||||
@@ -16,7 +16,7 @@ import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.kademlia.KBucketSet;
|
||||
import net.i2p.kademlia.SelectionCollector;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
@@ -29,7 +29,7 @@ import net.i2p.data.Base64;
|
||||
import net.i2p.data.DatabaseEntry;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.Router;
|
||||
import net.i2p.router.RouterContext;
|
||||
|
||||
@@ -5,7 +5,7 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
import net.i2p.router.JobImpl;
|
||||
import net.i2p.router.RouterContext;
|
||||
import net.i2p.util.Log;
|
||||
|
||||