diff --git a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java index 7c9d101214838a4549684b83d95ef78bcd0e9652..a570de2d06a28cc6e3a67fcbf8e29de0f747b90a 100644 --- a/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java +++ b/apps/i2psnark/java/src/org/klomp/snark/web/I2PSnarkServlet.java @@ -329,14 +329,18 @@ public class I2PSnarkServlet extends Default { // Opera and text-mode browsers: no   and no input type=image values submitted String ua = req.getHeader("User-Agent"); - boolean isDegraded = ua != null && (ua.startsWith("Opera") || ua.startsWith("Lynx") || + boolean isDegraded = ua != null && (ua.startsWith("Lynx") || ua.startsWith("ELinks") || ua.startsWith("Dillo")); + boolean noThinsp = isDegraded || ua.startsWith("Opera"); if (_manager.util().connected()) { if (isDegraded) out.write("<a href=\"/i2psnark/?action=StopAll&nonce=" + _nonce + "\"><img title=\""); - else - out.write("<input type=\"image\" name=\"action\" value=\"StopAll\" title=\""); + else { + // http://www.onenaught.com/posts/382/firefox-4-change-input-type-image-only-submits-x-and-y-not-name + //out.write("<input type=\"image\" name=\"action\" value=\"StopAll\" title=\""); + out.write("<input type=\"image\" name=\"action_StopAll\" value=\"foo\" title=\""); + } out.write(_("Stop all torrents and the I2P tunnel")); out.write("\" src=\"" + _imgPath + "stop_all.png\" alt=\""); out.write(_("Stop All")); @@ -347,7 +351,7 @@ public class I2PSnarkServlet extends Default { if (isDegraded) out.write("<a href=\"/i2psnark/?action=StartAll&nonce=" + _nonce + "\"><img title=\""); else - out.write("<input type=\"image\" name=\"action\" value=\"StartAll\" title=\""); + out.write("<input type=\"image\" name=\"action_StartAll\" value=\"foo\" title=\""); out.write(_("Start all torrents and the I2P tunnel")); out.write("\" src=\"" + _imgPath + "start_all.png\" alt=\""); out.write(_("Start All")); @@ -362,7 +366,7 @@ public class I2PSnarkServlet extends Default { Snark snark = (Snark)snarks.get(i); boolean showDebug = "2".equals(peerParam); boolean showPeers = showDebug || "1".equals(peerParam) || Base64.encode(snark.meta.getInfoHash()).equals(peerParam); - displaySnark(out, snark, uri, i, stats, showPeers, isDegraded, showDebug); + displaySnark(out, snark, uri, i, stats, showPeers, isDegraded, noThinsp, showDebug); } if (snarks.isEmpty()) { @@ -404,8 +408,19 @@ public class I2PSnarkServlet extends Default { private void processRequest(HttpServletRequest req) { String action = req.getParameter("action"); if (action == null) { - _manager.addMessage("No action specified"); - return; + // http://www.onenaught.com/posts/382/firefox-4-change-input-type-image-only-submits-x-and-y-not-name + Map params = req.getParameterMap(); + for (Object o : params.keySet()) { + String key = (String) o; + if (key.startsWith("action_") && key.endsWith(".x")) { + action = key.substring(0, key.length() - 2).substring(7); + break; + } + } + if (action == null) { + _manager.addMessage("No action specified"); + return; + } } // sadly, Opera doesn't send value with input type=image, so we have to use GET there //if (!"POST".equals(req.getMethod())) { @@ -698,7 +713,7 @@ public class I2PSnarkServlet extends Default { private static final int MAX_DISPLAYED_FILENAME_LENGTH = 50; private static final int MAX_DISPLAYED_ERROR_LENGTH = 43; private void displaySnark(PrintWriter out, Snark snark, String uri, int row, long stats[], boolean showPeers, - boolean isDegraded, boolean showDebug) throws IOException { + boolean isDegraded, boolean noThinsp, boolean showDebug) throws IOException { String filename = snark.torrent; File f = new File(filename); filename = f.getName(); // the torrent may be the canonical name, so lets just grab the local name @@ -758,11 +773,11 @@ public class I2PSnarkServlet extends Default { if (isRunning && curPeers > 0 && !showPeers) statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "trackererror.png\" title=\"" + err + "\"></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Tracker Error") + ": <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" + - curPeers + thinsp(isDegraded) + + curPeers + thinsp(noThinsp) + ngettext("1 peer", "{0} peers", knownPeers) + "</a>"; else if (isRunning) statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "trackererror.png\" title=\"" + err + "\"></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Tracker Error") + - ": " + curPeers + thinsp(isDegraded) + + ": " + curPeers + thinsp(noThinsp) + ngettext("1 peer", "{0} peers", knownPeers); else { if (err.length() > MAX_DISPLAYED_ERROR_LENGTH) @@ -774,11 +789,11 @@ public class I2PSnarkServlet extends Default { if (isRunning && curPeers > 0 && !showPeers) statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "seeding.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Seeding") + ": <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" + - curPeers + thinsp(isDegraded) + + curPeers + thinsp(noThinsp) + ngettext("1 peer", "{0} peers", knownPeers) + "</a>"; else if (isRunning) statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "seeding.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Seeding") + - ": " + curPeers + thinsp(isDegraded) + + ": " + curPeers + thinsp(noThinsp) + ngettext("1 peer", "{0} peers", knownPeers); else statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "complete.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Complete"); @@ -786,24 +801,24 @@ public class I2PSnarkServlet extends Default { if (isRunning && curPeers > 0 && downBps > 0 && !showPeers) statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "downloading.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("OK") + ": <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" + - curPeers + thinsp(isDegraded) + + curPeers + thinsp(noThinsp) + ngettext("1 peer", "{0} peers", knownPeers) + "</a>"; else if (isRunning && curPeers > 0 && downBps > 0) statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "downloading.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("OK") + - ": " + curPeers + thinsp(isDegraded) + + ": " + curPeers + thinsp(noThinsp) + ngettext("1 peer", "{0} peers", knownPeers); else if (isRunning && curPeers > 0 && !showPeers) statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "stalled.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Stalled") + ": <a href=\"" + uri + "?p=" + Base64.encode(snark.meta.getInfoHash()) + "\">" + - curPeers + thinsp(isDegraded) + + curPeers + thinsp(noThinsp) + ngettext("1 peer", "{0} peers", knownPeers) + "</a>"; else if (isRunning && curPeers > 0) statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "stalled.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("Stalled") + - ": " + curPeers + thinsp(isDegraded) + + ": " + curPeers + thinsp(noThinsp) + ngettext("1 peer", "{0} peers", knownPeers); else if (isRunning && knownPeers > 0) statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "nopeers.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("No Peers") + - ": 0" + thinsp(isDegraded) + knownPeers ; + ": 0" + thinsp(noThinsp) + knownPeers ; else if (isRunning) statusString = "<img alt=\"\" border=\"0\" src=\"" + _imgPath + "nopeers.png\" ></td><td class=\"snarkTorrentStatus " + rowClass + "\">" + _("No Peers"); else @@ -880,7 +895,7 @@ public class I2PSnarkServlet extends Default { out.write("</td>\n\t"); out.write("<td align=\"right\" class=\"snarkTorrentDownloaded " + rowClass + "\">"); if (remaining > 0) - out.write(formatSize(total-remaining) + thinsp(isDegraded) + formatSize(total)); + out.write(formatSize(total-remaining) + thinsp(noThinsp) + formatSize(total)); else out.write(formatSize(total)); // 3GB out.write("</td>\n\t"); @@ -905,7 +920,7 @@ public class I2PSnarkServlet extends Default { if (isDegraded) out.write("<a href=\"/i2psnark/?action=Stop_" + b64 + "&nonce=" + _nonce + "\"><img title=\""); else - out.write("<input type=\"image\" name=\"action\" value=\"Stop_" + b64 + "\" title=\""); + out.write("<input type=\"image\" name=\"action_Stop_" + b64 + "\" value=\"foo\" title=\""); out.write(_("Stop the torrent")); out.write("\" src=\"" + _imgPath + "stop.png\" alt=\""); out.write(_("Stop")); @@ -917,7 +932,7 @@ public class I2PSnarkServlet extends Default { if (isDegraded) out.write("<a href=\"/i2psnark/?action=Start_" + b64 + "&nonce=" + _nonce + "\"><img title=\""); else - out.write("<input type=\"image\" name=\"action\" value=\"Start_" + b64 + "\" title=\""); + out.write("<input type=\"image\" name=\"action_Start_" + b64 + "\" value=\"foo\" title=\""); out.write(_("Start the torrent")); out.write("\" src=\"" + _imgPath + "start.png\" alt=\""); out.write(_("Start")); @@ -929,7 +944,7 @@ public class I2PSnarkServlet extends Default { if (isDegraded) out.write("<a href=\"/i2psnark/?action=Remove_" + b64 + "&nonce=" + _nonce + "\"><img title=\""); else - out.write("<input type=\"image\" name=\"action\" value=\"Remove_" + b64 + "\" title=\""); + 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")); out.write("\" onclick=\"if (!confirm('"); // Can't figure out how to escape double quotes inside the onclick string. @@ -946,7 +961,7 @@ public class I2PSnarkServlet extends Default { if (isDegraded) out.write("<a href=\"/i2psnark/?action=Delete_" + b64 + "&nonce=" + _nonce + "\"><img title=\""); else - out.write("<input type=\"image\" name=\"action\" value=\"Delete_" + b64 + "\" title=\""); + out.write("<input type=\"image\" name=\"action_Delete_" + b64 + "\" value=\"foo\" title=\""); out.write(_("Delete the .torrent file and the associated data file(s)")); out.write("\" onclick=\"if (!confirm('"); // Can't figure out how to escape double quotes inside the onclick string. @@ -1384,7 +1399,7 @@ public class I2PSnarkServlet extends Default { private static String urlify(String s) { StringBuilder buf = new StringBuilder(256); // browsers seem to work without doing this but let's be strict - String link = s.replace("&", "&"); + String link = s.replace("&", "&").replace(" ", "%20"); buf.append("<a href=\"").append(link).append("\">").append(link).append("</a>"); return buf.toString(); } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java index 4299b6d4788e9aa028858c666fd4981fa80ce12b..1f1f9e971d376c7843b3d08efca0cf9c37d8ba7b 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/TunnelController.java @@ -259,10 +259,9 @@ public class TunnelController implements Logging { /* * Streamr client is a UDP server, use the listenPort field for targetPort - * and the listenOnInterface field for the targetHost */ private void startStreamrClient() { - String targetHost = getListenOnInterface(); + String targetHost = getTargetHost(); String targetPort = getListenPort(); String dest = getTargetDestination(); _tunnel.runStreamrClient(new String[] { targetHost, targetPort, dest }, this); @@ -270,10 +269,9 @@ public class TunnelController implements Logging { /** * Streamr server is a UDP client, use the targetPort field for listenPort - * and the targetHost field for the listenOnInterface */ private void startStreamrServer() { - String listenOn = getTargetHost(); + String listenOn = getListenOnInterface(); if ( (listenOn != null) && (listenOn.length() > 0) ) { _tunnel.runListenOn(new String[] { listenOn }, this); } diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java index 87beb689cb3022bf97268321896537c59db2cbef..2184b434f1f02aa968cc9f698e47b84b53a8b758 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/EditBean.java @@ -12,6 +12,7 @@ import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.StringTokenizer; +import java.util.Set; import net.i2p.data.Base64; import net.i2p.data.Destination; @@ -22,6 +23,7 @@ import net.i2p.i2ptunnel.I2PTunnelHTTPClient; import net.i2p.i2ptunnel.I2PTunnelHTTPClientBase; import net.i2p.i2ptunnel.TunnelController; import net.i2p.i2ptunnel.TunnelControllerGroup; +import net.i2p.util.Addresses; /** * Ugly little accessor for the edit page @@ -314,6 +316,11 @@ public class EditBean extends IndexBean { return _context.isRouterContext(); } + /** @since 0.8.3 */ + public Set<String> interfaceSet() { + return Addresses.getAllAddresses(); + } + public String getI2CPHost(int tunnel) { if (_context.isRouterContext()) return _("internal"); diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java index bb1a339c294140da226b3711aa5066c6265eda61..9787078894a7998b4fd3ba6a234ebc497690291b 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -63,7 +63,6 @@ public class IndexBean { private String _proxyList; private String _port; private String _reachableBy; - private String _reachableByOther; private String _targetDestination; private String _targetHost; private String _targetPort; @@ -432,10 +431,13 @@ public class IndexBean { public String getClientInterface(int tunnel) { TunnelController tun = getController(tunnel); - if (tun != null) - return tun.getListenOnInterface(); - else - return ""; + if (tun != null) { + if ("streamrclient".equals(tun.getType())) + return tun.getTargetHost(); + else + return tun.getListenOnInterface(); + } else + return "127.0.0.1"; } public int getTunnelStatus(int tunnel) { @@ -478,11 +480,38 @@ public class IndexBean { return rv != null ? rv : ""; } + /** + * Call this to see if it is ok to linkify getServerTarget() + * @since 0.8.3 + */ + public boolean isServerTargetLinkValid(int tunnel) { + TunnelController tun = getController(tunnel); + return tun != null && + "httpserver".equals(tun.getType()) && + tun.getTargetHost() != null && + tun.getTargetPort() != null; + } + + /** + * @return valid host:port only if isServerTargetLinkValid() is true + */ public String getServerTarget(int tunnel) { TunnelController tun = getController(tunnel); - if (tun != null) - return tun.getTargetHost() + ':' + tun.getTargetPort(); - else + if (tun != null) { + String host; + if ("streamrserver".equals(tun.getType())) + host = tun.getListenOnInterface(); + else + host = tun.getTargetHost(); + String port = tun.getTargetPort(); + if (host == null) + host = "<font color=\"red\">" + _("Host not set") + "</font>"; + else if (host.indexOf(':') >= 0) + host = '[' + host + ']'; + if (port == null) + port = "<font color=\"red\">" + _("Port not set") + "</font>"; + return host + ':' + port; + } else return ""; } @@ -575,19 +604,11 @@ public class IndexBean { _port = (port != null ? port.trim() : null); } /** - * what interface should this client/httpclient/ircclient listen on (unless - * overridden by the setReachableByOther() field) + * what interface should this client/httpclient/ircclient listen on */ public void setReachableBy(String reachableBy) { _reachableBy = (reachableBy != null ? reachableBy.trim() : null); } - /** - * If specified, defines the exact IP interface to listen for requests - * on (in the case of client/httpclient/ircclient tunnels) - */ - public void setReachableByOther(String reachableByOther) { - _reachableByOther = (reachableByOther != null ? reachableByOther.trim() : null); - } /** What peer does this client tunnel point at */ public void setTargetDestination(String dest) { _targetDestination = (dest != null ? dest.trim() : null); @@ -891,17 +912,22 @@ public class IndexBean { Properties config = new Properties(); updateConfigGeneric(config); - if (isClient(_type)) { - // generic client stuff - if (_port != null) - config.setProperty("listenPort", _port); - if (_reachableByOther != null) - config.setProperty("interface", _reachableByOther); - else if (_reachableBy != null) + if ((isClient(_type) && !"streamrclient".equals(_type)) || "streamrserver".equals(_type)) { + // streamrserver uses interface + if (_reachableBy != null) config.setProperty("interface", _reachableBy); else config.setProperty("interface", ""); + } else { + // streamrclient uses targetHost + if (_targetHost != null) + config.setProperty("targetHost", _targetHost); + } + if (isClient(_type)) { + // generic client stuff + if (_port != null) + config.setProperty("listenPort", _port); config.setProperty("sharedClient", _sharedClient + ""); for (String p : _booleanClientOpts) config.setProperty("option." + p, "" + _booleanOptions.contains(p)); @@ -910,8 +936,6 @@ public class IndexBean { config.setProperty("option." + p, _otherOptions.get(p)); } else { // generic server stuff - if (_targetHost != null) - config.setProperty("targetHost", _targetHost); if (_targetPort != null) config.setProperty("targetPort", _targetPort); for (String p : _booleanServerOpts) @@ -940,9 +964,7 @@ public class IndexBean { if ("httpbidirserver".equals(_type)) { if (_port != null) config.setProperty("listenPort", _port); - if (_reachableByOther != null) - config.setProperty("interface", _reachableByOther); - else if (_reachableBy != null) + if (_reachableBy != null) config.setProperty("interface", _reachableBy); else if (_targetHost != null) config.setProperty("interface", _targetHost); diff --git a/apps/i2ptunnel/jsp/editClient.jsp b/apps/i2ptunnel/jsp/editClient.jsp index f2b427542ba1c87d60310e99174a8692fb3634b3..cabfb2946479df5f7aba8cd4862c856e7d96cf7d 100644 --- a/apps/i2ptunnel/jsp/editClient.jsp +++ b/apps/i2ptunnel/jsp/editClient.jsp @@ -80,7 +80,7 @@ <label><%=intl._("Target")%>:</label> <% } else { %> <label><%=intl._("Access Point")%>:</label> - <% } %> + <% } /* streamrclient */ %> </div> <div id="portField" class="rowItem"> <label for="port" accesskey="P"> @@ -95,46 +95,41 @@ </label> <input type="text" size="6" maxlength="5" id="port" name="port" title="Access Port Number" value="<%=editBean.getClientPort(curTunnel)%>" class="freetext" /> </div> - <% String otherInterface = ""; - String clientInterface = editBean.getClientInterface(curTunnel); - if ("streamrclient".equals(tunnelType)) { - otherInterface = clientInterface; - } else { %> <div id="reachField" class="rowItem"> <label for="reachableBy" accesskey="r"> - <%=intl._("Reachable by")%>(<span class="accessKey">R</span>): - </label> - <select id="reachableBy" name="reachableBy" title="Valid IP for Client Access" class="selectbox"> - <% if (!("127.0.0.1".equals(clientInterface)) && - !("0.0.0.0".equals(clientInterface)) && - (clientInterface != null) && - (clientInterface.trim().length() > 0)) { - otherInterface = clientInterface; - } - %><option value="127.0.0.1"<%=("127.0.0.1".equals(clientInterface) ? " selected=\"selected\"" : "")%>><%=intl._("Locally (127.0.0.1)")%></option> - <option value="0.0.0.0"<%=("0.0.0.0".equals(clientInterface) ? " selected=\"selected\"" : "")%>><%=intl._("Everyone (0.0.0.0)")%></option> - <option value="other"<%=(!("".equals(otherInterface)) ? " selected=\"selected\"" : "")%>><%=intl._("LAN Hosts (Please specify your LAN address)")%></option> - </select> - </div> - <% } // streamrclient %> - <div id="otherField" class="rowItem"> - <label for="reachableByOther" accesskey="O"> - <% if ("streamrclient".equals(tunnelType)) { %> - Host: - <% String vvv = otherInterface; - if (vvv == null || "".equals(vvv.trim())) { + <% + if ("streamrclient".equals(tunnelType)) { + out.write("Host:"); + String targetHost = editBean.getTargetHost(curTunnel); + if (targetHost == null || "".equals(targetHost.trim())) { out.write(" <font color=\"red\">("); out.write(intl._("required")); out.write(")</font>"); } - %> + %> + </label> + <input type="text" size="20" id="targetHost" name="targetHost" title="Target Hostname or IP" value="<%=targetHost%>" class="freetext" /> <% } else { %> - <%=intl._("Other")%>(<span class="accessKey">O</span>): - <% } %> + <%=intl._("Reachable by")%>(<span class="accessKey">R</span>): </label> - <input type="text" size="20" id="reachableByOther" name="reachableByOther" title="Alternative IP for Client Access" value="<%=otherInterface%>" class="freetext" /> - </div> - + <select id="reachableBy" name="reachableBy" title="IP for Client Access" class="selectbox"> + <% + String clientInterface = editBean.getClientInterface(curTunnel); + for (String ifc : editBean.interfaceSet()) { + out.write("<option value=\""); + out.write(ifc); + out.write('\"'); + if (ifc.equals(clientInterface)) + out.write(" selected=\"selected\""); + out.write('>'); + out.write(ifc); + out.write("</option>\n"); + } + %> + </select> + <% } /* streamrclient */ %> + </div> + <div class="subdivider"> <hr /> </div> diff --git a/apps/i2ptunnel/jsp/editServer.jsp b/apps/i2ptunnel/jsp/editServer.jsp index b0f870fb7aa5797730ea6a1bc6f38ff480b17ac2..4f45b86672c9f70d3d2c7ee8cd132175b713d92a 100644 --- a/apps/i2ptunnel/jsp/editServer.jsp +++ b/apps/i2ptunnel/jsp/editServer.jsp @@ -89,16 +89,14 @@ <label><%=intl._("Target")%>:</label> <% } %> </div> + <% if (!"streamrserver".equals(tunnelType)) { %> <div id="hostField" class="rowItem"> <label for="targetHost" accesskey="H"> - <% if ("streamrserver".equals(tunnelType)) { %> - <%=intl._("Reachable by")%>(<span class="accessKey">R</span>): - <% } else { %> <%=intl._("Host")%>(<span class="accessKey">H</span>): - <% } %> </label> <input type="text" size="20" id="targetHost" name="targetHost" title="Target Hostname or IP" value="<%=editBean.getTargetHost(curTunnel)%>" class="freetext" /> </div> + <% } /* !streamrserver */ %> <div id="portField" class="rowItem"> <label for="targetPort" accesskey="P"> <%=intl._("Port")%>(<span class="accessKey">P</span>): @@ -113,8 +111,7 @@ <input type="text" size="6" maxlength="5" id="targetPort" name="targetPort" title="Target Port Number" value="<%=editBean.getTargetPort(curTunnel)%>" class="freetext" /> </div> - <% if ("httpbidirserver".equals(tunnelType)) { - %> + <% if ("httpbidirserver".equals(tunnelType)) { %> <div class="subdivider"> <hr /> </div> @@ -134,32 +131,30 @@ </label> <input type="text" size="6" maxlength="5" id="port" name="port" title="Access Port Number" value="<%=editBean.getClientPort(curTunnel)%>" class="freetext" /> </div> - <% String otherInterface = ""; - String clientInterface = editBean.getClientInterface(curTunnel); - %> + <% } /* httpbidirserver */ %> + <% if ("httpbidirserver".equals(tunnelType) || "streamrserver".equals(tunnelType)) { %> <div id="reachField" class="rowItem"> <label for="reachableBy" accesskey="r"> <%=intl._("Reachable by")%>(<span class="accessKey">R</span>): </label> - <select id="reachableBy" name="reachableBy" title="Valid IP for Client Access" class="selectbox"> - <% if (!("127.0.0.1".equals(clientInterface)) && - !("0.0.0.0".equals(clientInterface)) && - (clientInterface != null) && - (clientInterface.trim().length() > 0)) { - otherInterface = clientInterface; - } - %><option value="127.0.0.1"<%=("127.0.0.1".equals(clientInterface) ? " selected=\"selected\"" : "")%>><%=intl._("Locally (127.0.0.1)")%></option> - <option value="0.0.0.0"<%=("0.0.0.0".equals(clientInterface) ? " selected=\"selected\"" : "")%>><%=intl._("Everyone (0.0.0.0)")%></option> - <option value="other"<%=(!("".equals(otherInterface)) ? " selected=\"selected\"" : "")%>><%=intl._("LAN Hosts (Please specify your LAN address)")%></option> + <select id="reachableBy" name="reachableBy" title="IP for Client Access" class="selectbox"> + <% + String clientInterface = editBean.getClientInterface(curTunnel); + for (String ifc : editBean.interfaceSet()) { + out.write("<option value=\""); + out.write(ifc); + out.write('\"'); + if (ifc.equals(clientInterface)) + out.write(" selected=\"selected\""); + out.write('>'); + out.write(ifc); + out.write("</option>\n"); + } + %> </select> - </div> - <div id="otherField" class="rowItem"> - <label for="reachableByOther" accesskey="O"> - <%=intl._("Other")%>(<span class="accessKey">O</span>): - </label> - <input type="text" size="20" id="reachableByOther" name="reachableByOther" title="Alternative IP for Client Access" value="<%=otherInterface%>" class="freetext" /> </div> - <% } %> + <% } /* httpbidirserver || streamrserver */ %> + <div class="subdivider"> <hr /> </div> @@ -302,7 +297,7 @@ <div class="subdivider"> <hr /> </div> - <% } // !streamrserver %> + <% } /* !streamrserver */ %> <div id="optionsField" class="rowItem"> <label><%=intl._("Router I2CP Address")%>:</label> diff --git a/apps/i2ptunnel/jsp/index.jsp b/apps/i2ptunnel/jsp/index.jsp index cf0d0067c39cf4551a8fe37a1dae821fa2cca46d..faf904de034f52e0ea2e26bd2e26291e9a754273 100644 --- a/apps/i2ptunnel/jsp/index.jsp +++ b/apps/i2ptunnel/jsp/index.jsp @@ -95,7 +95,7 @@ <label><%=intl._("Points at")%>:</label> <span class="text"> <% - if ("httpserver".equals(indexBean.getInternalType(curServer))) { + if (indexBean.isServerTargetLinkValid(curServer)) { %> <a href="http://<%=indexBean.getServerTarget(curServer)%>/" title="Test HTTP server, bypassing I2P"><%=indexBean.getServerTarget(curServer)%></a> <% @@ -213,7 +213,18 @@ </div> <div class="portField rowItem"> <label><%=intl._("Port")%>:</label> - <span class="text"><%=indexBean.getClientPort(curClient)%></span> + <span class="text"> + <% + String cPort= indexBean.getClientPort(curClient); + if ("".equals(cPort)) { + out.write("<font color=\"red\">"); + out.write(intl._("Port not set")); + out.write("</font>"); + } else { + out.write(cPort); + } + %> + </span> </div> <div class="typeField rowItem"> <label><%=intl._("Type")%>:</label> @@ -221,7 +232,19 @@ </div> <div class="interfaceField rowItem"> <label><%=intl._("Interface")%>:</label> - <span class="text"><%=indexBean.getClientInterface(curClient)%></span> + <span class="text"> + <% + /* should only happen for streamr client */ + String cHost= indexBean.getClientInterface(curClient); + if ("".equals(cHost)) { + out.write("<font color=\"red\">"); + out.write(intl._("Hort not set")); + out.write("</font>"); + } else { + out.write(cHost); + } + %> + </span> </div> <div class="statusField rowItem"> <label><%=intl._("Status")%>:</label> diff --git a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java index 11010f3124c775242ff24875ebefd604542f4b30..6160caf5b7a9243401ac5b6a166340649fcccb67 100644 --- a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java +++ b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java @@ -327,11 +327,12 @@ public class ElGamalAESEngine { //_log.debug("len: " + len); if ((len < 0) || (len > decrypted.length - cur - Hash.HASH_LENGTH - 1)) throw new Exception("Invalid size of payload (" + len + ", remaining " + (decrypted.length-cur) +")"); - byte hashval[] = new byte[Hash.HASH_LENGTH]; - System.arraycopy(decrypted, cur, hashval, 0, Hash.HASH_LENGTH); + //byte hashval[] = new byte[Hash.HASH_LENGTH]; + //System.arraycopy(decrypted, cur, hashval, 0, Hash.HASH_LENGTH); + //readHash = new Hash(); + //readHash.setData(hashval); + readHash = Hash.create(decrypted, cur); cur += Hash.HASH_LENGTH; - readHash = new Hash(); - readHash.setData(hashval); byte flag = decrypted[cur++]; if (flag == 0x01) { byte rekeyVal[] = new byte[SessionKey.KEYSIZE_BYTES]; diff --git a/core/java/src/net/i2p/crypto/ElGamalEngine.java b/core/java/src/net/i2p/crypto/ElGamalEngine.java index ae3b0eb3addbe476498b019bbca8d75dc8d3bf30..b311d3c07915efa516ef6b4ea9040fcef8abc2a9 100644 --- a/core/java/src/net/i2p/crypto/ElGamalEngine.java +++ b/core/java/src/net/i2p/crypto/ElGamalEngine.java @@ -202,9 +202,10 @@ public class ElGamalEngine { } //ByteArrayInputStream bais = new ByteArrayInputStream(val, i, val.length - i); - byte hashData[] = new byte[Hash.HASH_LENGTH]; - System.arraycopy(val, i + 1, hashData, 0, Hash.HASH_LENGTH); - Hash hash = new Hash(hashData); + //byte hashData[] = new byte[Hash.HASH_LENGTH]; + //System.arraycopy(val, i + 1, hashData, 0, Hash.HASH_LENGTH); + //Hash hash = new Hash(hashData); + Hash hash = Hash.create(val, i + 1); byte rv[] = new byte[payloadLen]; System.arraycopy(val, i + 1 + Hash.HASH_LENGTH, rv, 0, rv.length); diff --git a/core/java/src/net/i2p/crypto/SHA256Generator.java b/core/java/src/net/i2p/crypto/SHA256Generator.java index aa4d041f004ab9d2aa4983db3ecab84243e1a780..8d5b6957b5e2743e83e29429c732236aa475bb23 100644 --- a/core/java/src/net/i2p/crypto/SHA256Generator.java +++ b/core/java/src/net/i2p/crypto/SHA256Generator.java @@ -35,7 +35,8 @@ public final class SHA256Generator { digest.update(source, start, len); byte rv[] = digest.digest(); releaseGnu(digest); - return new Hash(rv); + //return new Hash(rv); + return Hash.create(rv); } public final void calculateHash(byte[] source, int start, int len, byte out[], int outOffset) { diff --git a/core/java/src/net/i2p/data/Hash.java b/core/java/src/net/i2p/data/Hash.java index 96c07c9978eed7d335bb1e8816f63a99d1630057..7512f52f48390fad99b667f65399bda8e5fd9ebf 100644 --- a/core/java/src/net/i2p/data/Hash.java +++ b/core/java/src/net/i2p/data/Hash.java @@ -25,7 +25,34 @@ public class Hash extends SimpleDataStructure { public final static int HASH_LENGTH = 32; public final static Hash FAKE_HASH = new Hash(new byte[HASH_LENGTH]); + private static final int CACHE_SIZE = 2048; + private static final SDSCache<Hash> _cache = new SDSCache(Hash.class, HASH_LENGTH, CACHE_SIZE); + + /** + * Pull from cache or return new + * @since 0.8.3 + */ + public static Hash create(byte[] data) { + return _cache.get(data); + } + + /** + * Pull from cache or return new + * @since 0.8.3 + */ + public static Hash create(byte[] data, int off) { + return _cache.get(data, off); + } + + /** + * Pull from cache or return new + * @since 0.8.3 + */ + public static Hash create(InputStream in) throws IOException { + return _cache.get(in); + } + public Hash() { super(); } diff --git a/core/java/src/net/i2p/data/KeysAndCert.java b/core/java/src/net/i2p/data/KeysAndCert.java index d27d4bcf455ca97f3b1f8178673da5bb48062acc..ef772880b97c72ad3fc4fe189fe859d212569fbd 100644 --- a/core/java/src/net/i2p/data/KeysAndCert.java +++ b/core/java/src/net/i2p/data/KeysAndCert.java @@ -61,10 +61,12 @@ public class KeysAndCert extends DataStructureImpl { } public void readBytes(InputStream in) throws DataFormatException, IOException { - _publicKey = new PublicKey(); - _publicKey.readBytes(in); - _signingKey = new SigningPublicKey(); - _signingKey.readBytes(in); + //_publicKey = new PublicKey(); + //_publicKey.readBytes(in); + _publicKey = PublicKey.create(in); + //_signingKey = new SigningPublicKey(); + //_signingKey.readBytes(in); + _signingKey = SigningPublicKey.create(in); //_certificate = new Certificate(); //_certificate.readBytes(in); _certificate = Certificate.create(in); diff --git a/core/java/src/net/i2p/data/Lease.java b/core/java/src/net/i2p/data/Lease.java index 17abdc92b1898c26c9127357d4772c91359ff7a9..283e2aa7c232e3462a4a4dbd170f5d9ce692030b 100644 --- a/core/java/src/net/i2p/data/Lease.java +++ b/core/java/src/net/i2p/data/Lease.java @@ -110,8 +110,9 @@ public class Lease extends DataStructureImpl { } public void readBytes(InputStream in) throws DataFormatException, IOException { - _gateway = new Hash(); - _gateway.readBytes(in); + //_gateway = new Hash(); + //_gateway.readBytes(in); + _gateway = Hash.create(in); _tunnelId = new TunnelId(); _tunnelId.readBytes(in); _end = DataHelper.readDate(in); diff --git a/core/java/src/net/i2p/data/PublicKey.java b/core/java/src/net/i2p/data/PublicKey.java index cf6b014346e94ce761299de76b78ea233fc33b97..13b0217f5f371826f3c58058e9c5d3ac3587e293 100644 --- a/core/java/src/net/i2p/data/PublicKey.java +++ b/core/java/src/net/i2p/data/PublicKey.java @@ -9,6 +9,9 @@ package net.i2p.data; * */ +import java.io.InputStream; +import java.io.IOException; + /** * Defines the PublicKey as defined by the I2P data structure spec. * A public key is 256byte Integer. The public key represents only the @@ -18,6 +21,17 @@ package net.i2p.data; */ public class PublicKey extends SimpleDataStructure { public final static int KEYSIZE_BYTES = 256; + private static final int CACHE_SIZE = 256; + + private static final SDSCache<PublicKey> _cache = new SDSCache(PublicKey.class, KEYSIZE_BYTES, CACHE_SIZE); + + /** + * Pull from cache or return new + * @since 0.8.3 + */ + public static PublicKey create(InputStream in) throws IOException { + return _cache.get(in); + } public PublicKey() { super(); diff --git a/core/java/src/net/i2p/data/SDSCache.java b/core/java/src/net/i2p/data/SDSCache.java new file mode 100644 index 0000000000000000000000000000000000000000..562c49c79ae0ca8d880e0022fed1c619f8392d8f --- /dev/null +++ b/core/java/src/net/i2p/data/SDSCache.java @@ -0,0 +1,175 @@ +package net.i2p.data; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.LinkedHashMap; +import java.util.Map; + +import net.i2p.I2PAppContext; +import net.i2p.util.Log; +import net.i2p.util.SimpleByteCache; + +/** + * A least recently used cache with a max size, for SimpleDataStructures. + * The index to the cache is the first 4 bytes of the data, so + * the data must be sufficiently random. + * + * This caches the SDS objects, and also uses SimpleByteCache to cache + * the unused byte arrays themselves + * + * Following is sample usage: + * <pre> + + private static final SDSCache<Foo> _cache = new SDSCache(Foo.class, LENGTH, 1024); + + public static Foo create(byte[] data) { + return _cache.get(data); + } + + public static Foo create(byte[] data, int off) { + return _cache.get(data, off); + } + + public static Foo create(InputStream in) throws IOException { + return _cache.get(in); + } + + * </pre> + * @since 0.8.3 + * @author zzz + */ +public class SDSCache<V extends SimpleDataStructure> { + private static final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(SDSCache.class); + + private static final Class[] conArg = new Class[] { byte[].class }; + private static final double MIN_FACTOR = 0.25; + private static final double MAX_FACTOR = 3.0; + private static final double FACTOR; + static { + long maxMemory = Runtime.getRuntime().maxMemory(); + FACTOR = Math.max(MIN_FACTOR, Math.min(MAX_FACTOR, maxMemory / (128*1024*1024d))); + } + + /** the LRU cache */ + private final Map<Integer, V> _cache; + /** the byte array length for the class we are caching */ + private final int _datalen; + /** the constructor for the class we are caching */ + private final Constructor<V> _rvCon; + private final String _statName; + + /** + * @param rvClass the class that we are storing, i.e. an extension of SimpleDataStructure + * @param len the length of the byte array in the SimpleDataStructure + * @param max maximum size of the cache assuming 128MB of mem. + * The actual max size will be scaled based on available memory. + */ + public SDSCache(Class<V> rvClass, int len, int max) { + int size = (int) (max * FACTOR); + _cache = new LHM(size); + _datalen = len; + try { + _rvCon = rvClass.getConstructor(conArg); + } catch (NoSuchMethodException e) { + throw new RuntimeException("SDSCache init error", e); + } + _statName = "SDSCache." + rvClass.getSimpleName(); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("New SDSCache for " + rvClass + " data size: " + len + + " max: " + size + " max mem: " + (len * size)); + I2PAppContext.getGlobalContext().statManager().createRateStat(_statName, "Hit rate", "Router", new long[] { 10*60*1000 }); + } + + /** + * @param data non-null, the byte array for the SimpleDataStructure + * @return the cached value if available, otherwise + * makes a new object and returns it + * @throws IllegalArgumentException if data is not the correct number of bytes + * @throws NPE + */ + public V get(byte[] data) { + if (data == null) + throw new NullPointerException("Don't pull null data from the cache"); + int found; + V rv; + Integer key = hashCodeOf(data); + synchronized(_cache) { + rv = _cache.get(key); + if (rv != null && DataHelper.eq(data, rv.getData())) { + // found it, we don't need the data passed in any more + SimpleByteCache.release(data); + found = 1; + } else { + // make a new one + try { + rv = _rvCon.newInstance(new Object[] { data } ); + } catch (InstantiationException e) { + throw new RuntimeException("SDSCache error", e); + } catch (IllegalAccessException e) { + throw new RuntimeException("SDSCache error", e); + } catch (InvocationTargetException e) { + throw new RuntimeException("SDSCache error", e); + } + _cache.put(key, rv); + found = 0; + } + } + I2PAppContext.getGlobalContext().statManager().addRateData(_statName, found, 0); + return rv; + } + + /* + * @param b non-null byte array containing the data, data will be copied to not hold the reference + * @param off offset in the array to start reading from + * @return the cached value if available, otherwise + * makes a new object and returns it + * @throws AIOOBE if not enough bytes + * @throws NPE + */ + public V get(byte[] b, int off) { + byte[] data = SimpleByteCache.acquire(_datalen); + System.arraycopy(b, off, data, 0, _datalen); + return get(data); + } + + /* + * @param in a stream from which the bytes will be read + * @return the cached value if available, otherwise + * makes a new object and returns it + * @throws IOException if not enough bytes + */ + public V get(InputStream in) throws IOException { + byte[] data = SimpleByteCache.acquire(_datalen); + int read = DataHelper.read(in, data); + if (read != _datalen) + throw new EOFException("Not enough bytes to read the data"); + return get(data); + } + + /** + * We assume the data has enough randomness in it, so use the first 4 bytes for speed. + */ + private static Integer hashCodeOf(byte[] data) { + int rv = data[0]; + for (int i = 1; i < 4; i++) + rv ^= (data[i] << (i*8)); + return Integer.valueOf(rv); + } + + private static class LHM<K, V> extends LinkedHashMap<K, V> { + private final int _max; + + public LHM(int max) { + super(max, 0.75f, true); + _max = max; + } + + @Override + protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { + return size() > _max; + } + } +} diff --git a/core/java/src/net/i2p/data/SigningPublicKey.java b/core/java/src/net/i2p/data/SigningPublicKey.java index 0e11ad081d159745443f03c9b0ad82e190b78b60..8187b1c185aa37808e69b79ffbfe6aeaed909e28 100644 --- a/core/java/src/net/i2p/data/SigningPublicKey.java +++ b/core/java/src/net/i2p/data/SigningPublicKey.java @@ -9,6 +9,9 @@ package net.i2p.data; * */ +import java.io.InputStream; +import java.io.IOException; + /** * Defines the SigningPublicKey as defined by the I2P data structure spec. * A public key is 256byte Integer. The public key represents only the @@ -19,6 +22,17 @@ package net.i2p.data; */ public class SigningPublicKey extends SimpleDataStructure { public final static int KEYSIZE_BYTES = 128; + private static final int CACHE_SIZE = 256; + + private static final SDSCache<SigningPublicKey> _cache = new SDSCache(SigningPublicKey.class, KEYSIZE_BYTES, CACHE_SIZE); + + /** + * Pull from cache or return new + * @since 0.8.3 + */ + public static SigningPublicKey create(InputStream in) throws IOException { + return _cache.get(in); + } public SigningPublicKey() { super(); diff --git a/core/java/src/net/i2p/data/i2cp/DestLookupMessage.java b/core/java/src/net/i2p/data/i2cp/DestLookupMessage.java index 6ffea95678f7a49c64731088ef89c6fceae0496b..b635936cd64f842366f242f69a1730a12b559837 100644 --- a/core/java/src/net/i2p/data/i2cp/DestLookupMessage.java +++ b/core/java/src/net/i2p/data/i2cp/DestLookupMessage.java @@ -33,13 +33,15 @@ public class DestLookupMessage extends I2CPMessageImpl { } protected void doReadMessage(InputStream in, int size) throws I2CPMessageException, IOException { - Hash h = new Hash(); + //Hash h = new Hash(); try { - h.readBytes(in); - } catch (DataFormatException dfe) { + //h.readBytes(in); + _hash = Hash.create(in); + //} catch (DataFormatException dfe) { + } catch (IllegalArgumentException dfe) { throw new I2CPMessageException("Unable to load the hash", dfe); } - _hash = h; + //_hash = h; } protected byte[] doWriteMessage() throws I2CPMessageException, IOException { diff --git a/core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java b/core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java index 32241e833861de22d05ad4fa36b6ba09e98673c8..e459d65da1acdf11f41a4afc294bb235412af1bc 100644 --- a/core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java +++ b/core/java/src/net/i2p/data/i2cp/RequestLeaseSetMessage.java @@ -86,8 +86,9 @@ public class RequestLeaseSetMessage extends I2CPMessageImpl { int numTunnels = (int) DataHelper.readLong(in, 1); _endpoints.clear(); for (int i = 0; i < numTunnels; i++) { - Hash router = new Hash(); - router.readBytes(in); + //Hash router = new Hash(); + //router.readBytes(in); + Hash router = Hash.create(in); TunnelId tunnel = new TunnelId(); tunnel.readBytes(in); _endpoints.add(new TunnelEndpoint(router, tunnel)); diff --git a/core/java/src/net/i2p/util/ByteCache.java b/core/java/src/net/i2p/util/ByteCache.java index a95b33de1f50eeafd314d7e0df3aa2fd8cab7b39..3ef72ca10c3ca55d352223eee1868403269e0730 100644 --- a/core/java/src/net/i2p/util/ByteCache.java +++ b/core/java/src/net/i2p/util/ByteCache.java @@ -1,9 +1,9 @@ package net.i2p.util; import java.util.Arrays; -import java.util.HashMap; import java.util.Map; import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; import net.i2p.I2PAppContext; @@ -57,7 +57,8 @@ import net.i2p.data.ByteArray; */ public final class ByteCache { - private static final Map<Integer, ByteCache> _caches = new HashMap(16); + private static final Log _log = I2PAppContext.getGlobalContext().logManager().getLog(ByteCache.class); + private static final Map<Integer, ByteCache> _caches = new ConcurrentHashMap(16); /** * max size in bytes of each cache @@ -74,8 +75,9 @@ public final class ByteCache { /** * Get a cache responsible for objects of the given size * - * @param cacheSize how large we want the cache to grow before using on - * demand allocation + * @param cacheSize how large we want the cache to grow + * (number of objects, NOT memory size) + * before discarding released objects. * Since 0.7.14, a limit of 1MB / size is enforced * for the typical 128MB max memory JVM * @param size how large should the objects cached be? @@ -84,12 +86,11 @@ public final class ByteCache { if (cacheSize * size > MAX_CACHE) cacheSize = MAX_CACHE / size; Integer sz = Integer.valueOf(size); - ByteCache cache = null; - synchronized (_caches) { - if (!_caches.containsKey(sz)) - _caches.put(sz, new ByteCache(cacheSize, size)); - cache = _caches.get(sz); - } + ByteCache cache = _caches.get(sz); + if (cache == null) { + cache = new ByteCache(cacheSize, size); + _caches.put(sz, cache); +; } cache.resize(cacheSize); //I2PAppContext.getGlobalContext().logManager().getLog(ByteCache.class).error("ByteCache size: " + size + " max: " + cacheSize, new Exception("from")); return cache; @@ -102,10 +103,9 @@ public final class ByteCache { public static void clearAll() { for (ByteCache bc : _caches.values()) bc.clear(); - I2PAppContext.getGlobalContext().logManager().getLog(ByteCache.class).warn("WARNING: Low memory, clearing byte caches"); + _log.warn("WARNING: Low memory, clearing byte caches"); } - private Log _log; /** list of available and available entries */ private Queue<ByteArray> _available; private int _maxCached; @@ -116,7 +116,7 @@ public final class ByteCache { private static final boolean _cache = true; /** how often do we cleanup the cache */ - private static final int CLEANUP_FREQUENCY = 30*1000; + private static final int CLEANUP_FREQUENCY = 33*1000; /** if we haven't exceeded the cache size in 2 minutes, cut our cache in half */ private static final long EXPIRE_PERIOD = 2*60*1000; @@ -126,9 +126,8 @@ public final class ByteCache { _maxCached = maxCachedEntries; _entrySize = entrySize; _lastOverflow = -1; - SimpleScheduler.getInstance().addPeriodicEvent(new Cleanup(), CLEANUP_FREQUENCY); - _log = I2PAppContext.getGlobalContext().logManager().getLog(ByteCache.class); - I2PAppContext.getGlobalContext().statManager().createRateStat("byteCache.memory." + entrySize, "Memory usage (B)", "Router", new long[] { 60*1000 }); + SimpleScheduler.getInstance().addPeriodicEvent(new Cleanup(), CLEANUP_FREQUENCY + (entrySize % 7)); //stagger + I2PAppContext.getGlobalContext().statManager().createRateStat("byteCache.memory." + entrySize, "Memory usage (B)", "Router", new long[] { 10*60*1000 }); } private void resize(int maxCachedEntries) { diff --git a/core/java/src/net/i2p/util/SimpleByteCache.java b/core/java/src/net/i2p/util/SimpleByteCache.java new file mode 100644 index 0000000000000000000000000000000000000000..50242e8fd3127e015140bf0de2850c98e21a2dd9 --- /dev/null +++ b/core/java/src/net/i2p/util/SimpleByteCache.java @@ -0,0 +1,125 @@ +package net.i2p.util; + +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingQueue; + +import net.i2p.I2PAppContext; + +/** + * Like ByteCache but works directly with byte arrays, not ByteArrays. + * These are designed to be small caches, so there's no cleaner task + * like there is in ByteCache. And we don't zero out the arrays here. + * Only the static methods are public here. + * + * @since 0.8.3 + */ +public final class SimpleByteCache { + + private static final Map<Integer, SimpleByteCache> _caches = new ConcurrentHashMap(8); + + private static final int DEFAULT_SIZE = 16; + + /** + * Get a cache responsible for arrays of the given size + * + * @param size how large should the objects cached be? + */ + public static SimpleByteCache getInstance(int size) { + return getInstance(DEFAULT_SIZE, size); + } + + /** + * Get a cache responsible for objects of the given size + * + * @param cacheSize how large we want the cache to grow + * (number of objects, NOT memory size) + * before discarding released objects. + * @param size how large should the objects cached be? + */ + public static SimpleByteCache getInstance(int cacheSize, int size) { + Integer sz = Integer.valueOf(size); + SimpleByteCache cache = _caches.get(sz); + if (cache == null) { + cache = new SimpleByteCache(cacheSize, size); + _caches.put(sz, cache); + } + cache.resize(cacheSize); + return cache; + } + + /** + * Clear everything (memory pressure) + */ + public static void clearAll() { + for (SimpleByteCache bc : _caches.values()) + bc.clear(); + } + + /** list of available and available entries */ + private Queue<byte[]> _available; + private int _maxCached; + private int _entrySize; + + private SimpleByteCache(int maxCachedEntries, int entrySize) { + _available = new LinkedBlockingQueue(maxCachedEntries); + _maxCached = maxCachedEntries; + _entrySize = entrySize; + } + + private void resize(int maxCachedEntries) { + if (_maxCached >= maxCachedEntries) return; + _maxCached = maxCachedEntries; + // make a bigger one, move the cached items over + Queue<byte[]> newLBQ = new LinkedBlockingQueue(maxCachedEntries); + byte[] ba; + while ((ba = _available.poll()) != null) + newLBQ.offer(ba); + _available = newLBQ; + } + + /** + * Get the next available array, either from the cache or a brand new one + */ + public static byte[] acquire(int size) { + return getInstance(size).acquire(); + } + + /** + * Get the next available array, either from the cache or a brand new one + */ + private byte[] acquire() { + byte[] rv = _available.poll(); + if (rv == null) + rv = new byte[_entrySize]; + return rv; + } + + /** + * Put this array back onto the available cache for reuse + */ + public static void release(byte[] entry) { + SimpleByteCache cache = _caches.get(entry.length); + if (cache != null) + cache.releaseIt(entry); + } + + /** + * Put this array back onto the available cache for reuse + */ + private void releaseIt(byte[] entry) { + if (entry == null || entry.length != _entrySize) + return; + // should be safe without this + //Arrays.fill(entry, (byte) 0); + _available.offer(entry); + } + + /** + * Clear everything (memory pressure) + */ + private void clear() { + _available.clear(); + } +} diff --git a/history.txt b/history.txt index 81b496b5899765399184c49aca6f66e03dd1383a..63449fd25f3628193244ef3bf869863446c865ff 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,15 @@ +2010-12-30 zzz + * Data Structures: + - New SDSCache for SimpleDataStructures + - New SimpleByteCache for byte[] + - Cache Hash, PublicKey, and SigningPublicKey + - Remove global lock in ByteCache + * I2CP: Missing piece of parallel naming lookup + * i2psnark: Fix buttons on Firefox 4.0b + * i2ptunnel: + - Use dropdown box to select interface for clients + - Warn on index page if required fields not set + 2010-12-29 zzz * Console: Add 500 error page * DSAEngine: Restore variants of methods using a Hash argument, @@ -42,7 +54,9 @@ - Add support for specifying the timeout for DestLookups (can only be smaller than the router timeout for now) - Extend dest lookup router timeout from 10s to 15s - * i2psnark: Backport TrackerClient NPE fix + * i2psnark: + - Backport TrackerClient NPE fix + - Fix last piece length calculation for torrents > 2GB (ticket #361) * i2ptunnel: - Get Log from the logManager instead of instantiating, so we may adjust the levels on the fly diff --git a/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java b/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java index 667d7fb18477372c529b95b37054cfadcabef810..cb44498aaab8649b7bbff915c870ee78c358596e 100644 --- a/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java +++ b/router/java/src/net/i2p/data/i2np/BuildRequestRecord.java @@ -93,9 +93,10 @@ public class BuildRequestRecord { * the gateway to which the reply should be sent. */ public Hash readNextIdentity() { - byte rv[] = new byte[Hash.HASH_LENGTH]; - System.arraycopy(_data.getData(), _data.getOffset() + OFF_SEND_IDENT, rv, 0, Hash.HASH_LENGTH); - return new Hash(rv); + //byte rv[] = new byte[Hash.HASH_LENGTH]; + //System.arraycopy(_data.getData(), _data.getOffset() + OFF_SEND_IDENT, rv, 0, Hash.HASH_LENGTH); + //return new Hash(rv); + return Hash.create(_data.getData(), _data.getOffset() + OFF_SEND_IDENT); } /** * Tunnel layer encryption key that the current hop should use diff --git a/router/java/src/net/i2p/data/i2np/DatabaseLookupMessage.java b/router/java/src/net/i2p/data/i2np/DatabaseLookupMessage.java index 80aaa889182b5924add0ad029234814e1c0ba259..18bc54b49c53c58f83688fdbe9e08bafec4f6970 100644 --- a/router/java/src/net/i2p/data/i2np/DatabaseLookupMessage.java +++ b/router/java/src/net/i2p/data/i2np/DatabaseLookupMessage.java @@ -133,15 +133,17 @@ public class DatabaseLookupMessage extends I2NPMessageImpl { if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message"); int curIndex = offset; - byte keyData[] = new byte[Hash.HASH_LENGTH]; - System.arraycopy(data, curIndex, keyData, 0, Hash.HASH_LENGTH); + //byte keyData[] = new byte[Hash.HASH_LENGTH]; + //System.arraycopy(data, curIndex, keyData, 0, Hash.HASH_LENGTH); + _key = Hash.create(data, curIndex); curIndex += Hash.HASH_LENGTH; - _key = new Hash(keyData); + //_key = new Hash(keyData); - byte fromData[] = new byte[Hash.HASH_LENGTH]; - System.arraycopy(data, curIndex, fromData, 0, Hash.HASH_LENGTH); + //byte fromData[] = new byte[Hash.HASH_LENGTH]; + //System.arraycopy(data, curIndex, fromData, 0, Hash.HASH_LENGTH); + _fromHash = Hash.create(data, curIndex); curIndex += Hash.HASH_LENGTH; - _fromHash = new Hash(fromData); + //_fromHash = new Hash(fromData); boolean tunnelSpecified = false; switch (data[curIndex]) { @@ -168,10 +170,11 @@ public class DatabaseLookupMessage extends I2NPMessageImpl { throw new I2NPMessageException("Invalid number of peers - " + numPeers); Set<Hash> peers = new HashSet(numPeers); for (int i = 0; i < numPeers; i++) { - byte peer[] = new byte[Hash.HASH_LENGTH]; - System.arraycopy(data, curIndex, peer, 0, Hash.HASH_LENGTH); + //byte peer[] = new byte[Hash.HASH_LENGTH]; + //System.arraycopy(data, curIndex, peer, 0, Hash.HASH_LENGTH); + Hash p = Hash.create(data, curIndex); curIndex += Hash.HASH_LENGTH; - peers.add(new Hash(peer)); + peers.add(p); } _dontIncludePeers = peers; } diff --git a/router/java/src/net/i2p/data/i2np/DatabaseSearchReplyMessage.java b/router/java/src/net/i2p/data/i2np/DatabaseSearchReplyMessage.java index 08f5511dfdeb6dc13da4b44fa903ca9b2028ba66..44ee294e6a9e9eb89d85ac916d1057ccf09cbde8 100644 --- a/router/java/src/net/i2p/data/i2np/DatabaseSearchReplyMessage.java +++ b/router/java/src/net/i2p/data/i2np/DatabaseSearchReplyMessage.java @@ -55,26 +55,29 @@ public class DatabaseSearchReplyMessage extends I2NPMessageImpl { if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message"); int curIndex = offset; - byte keyData[] = new byte[Hash.HASH_LENGTH]; - System.arraycopy(data, curIndex, keyData, 0, Hash.HASH_LENGTH); + //byte keyData[] = new byte[Hash.HASH_LENGTH]; + //System.arraycopy(data, curIndex, keyData, 0, Hash.HASH_LENGTH); + _key = Hash.create(data, curIndex); curIndex += Hash.HASH_LENGTH; - _key = new Hash(keyData); + //_key = new Hash(keyData); int num = (int)DataHelper.fromLong(data, curIndex, 1); curIndex++; _peerHashes.clear(); for (int i = 0; i < num; i++) { - byte peer[] = new byte[Hash.HASH_LENGTH]; - System.arraycopy(data, curIndex, peer, 0, Hash.HASH_LENGTH); + //byte peer[] = new byte[Hash.HASH_LENGTH]; + //System.arraycopy(data, curIndex, peer, 0, Hash.HASH_LENGTH); + Hash p = Hash.create(data, curIndex); curIndex += Hash.HASH_LENGTH; - addReply(new Hash(peer)); + addReply(p); } - byte from[] = new byte[Hash.HASH_LENGTH]; - System.arraycopy(data, curIndex, from, 0, Hash.HASH_LENGTH); + //byte from[] = new byte[Hash.HASH_LENGTH]; + //System.arraycopy(data, curIndex, from, 0, Hash.HASH_LENGTH); + _from = Hash.create(data, curIndex); curIndex += Hash.HASH_LENGTH; - _from = new Hash(from); + //_from = new Hash(from); //_context.statManager().addRateData("netDb.searchReplyMessageReceive", num*32 + 64, 1); } diff --git a/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java b/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java index f85ecce23772e15fdd2c6a1cb21d6fe8d080b4bc..0a2ee498b7c163c13292cbe7cda582c0044bfe92 100644 --- a/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java +++ b/router/java/src/net/i2p/data/i2np/DatabaseStoreMessage.java @@ -113,10 +113,11 @@ public class DatabaseStoreMessage extends I2NPMessageImpl { if (type != MESSAGE_TYPE) throw new I2NPMessageException("Message type is incorrect for this message"); int curIndex = offset; - byte keyData[] = new byte[Hash.HASH_LENGTH]; - System.arraycopy(data, curIndex, keyData, 0, Hash.HASH_LENGTH); + //byte keyData[] = new byte[Hash.HASH_LENGTH]; + //System.arraycopy(data, curIndex, keyData, 0, Hash.HASH_LENGTH); + _key = Hash.create(data, curIndex); curIndex += Hash.HASH_LENGTH; - _key = new Hash(keyData); + //_key = new Hash(keyData); _type = (int)DataHelper.fromLong(data, curIndex, 1); curIndex++; @@ -130,10 +131,11 @@ public class DatabaseStoreMessage extends I2NPMessageImpl { _replyTunnel = new TunnelId(tunnel); curIndex += 4; - byte gw[] = new byte[Hash.HASH_LENGTH]; - System.arraycopy(data, curIndex, gw, 0, Hash.HASH_LENGTH); + //byte gw[] = new byte[Hash.HASH_LENGTH]; + //System.arraycopy(data, curIndex, gw, 0, Hash.HASH_LENGTH); + _replyGateway = Hash.create(data, curIndex); curIndex += Hash.HASH_LENGTH; - _replyGateway = new Hash(gw); + //_replyGateway = new Hash(gw); } else { _replyTunnel = null; _replyGateway = null; diff --git a/router/java/src/net/i2p/data/i2np/DeliveryInstructions.java b/router/java/src/net/i2p/data/i2np/DeliveryInstructions.java index 6c9ddb154da78e54d4f493cc20b89e616ebae08b..d086521f528c9decb7eb8212c8b824e704de5912 100644 --- a/router/java/src/net/i2p/data/i2np/DeliveryInstructions.java +++ b/router/java/src/net/i2p/data/i2np/DeliveryInstructions.java @@ -90,18 +90,21 @@ public class DeliveryInstructions extends DataStructureImpl { case FLAG_MODE_LOCAL: break; case FLAG_MODE_DESTINATION: - Hash destHash = new Hash(); - destHash.readBytes(in); + //Hash destHash = new Hash(); + //destHash.readBytes(in); + Hash destHash = Hash.create(in); setDestination(destHash); break; case FLAG_MODE_ROUTER: - Hash routerHash = new Hash(); - routerHash.readBytes(in); + //Hash routerHash = new Hash(); + //routerHash.readBytes(in); + Hash routerHash = Hash.create(in); setRouter(routerHash); break; case FLAG_MODE_TUNNEL: - Hash tunnelRouterHash = new Hash(); - tunnelRouterHash.readBytes(in); + //Hash tunnelRouterHash = new Hash(); + //tunnelRouterHash.readBytes(in); + Hash tunnelRouterHash = Hash.create(in); setRouter(tunnelRouterHash); TunnelId id = new TunnelId(); id.readBytes(in); @@ -140,22 +143,25 @@ public class DeliveryInstructions extends DataStructureImpl { case FLAG_MODE_LOCAL: break; case FLAG_MODE_DESTINATION: - byte destHash[] = new byte[Hash.HASH_LENGTH]; - System.arraycopy(data, cur, destHash, 0, Hash.HASH_LENGTH); + //byte destHash[] = new byte[Hash.HASH_LENGTH]; + //System.arraycopy(data, cur, destHash, 0, Hash.HASH_LENGTH); + Hash dh = Hash.create(data, cur); cur += Hash.HASH_LENGTH; - setDestination(new Hash(destHash)); + setDestination(dh); break; case FLAG_MODE_ROUTER: - byte routerHash[] = new byte[Hash.HASH_LENGTH]; - System.arraycopy(data, cur, routerHash, 0, Hash.HASH_LENGTH); + //byte routerHash[] = new byte[Hash.HASH_LENGTH]; + //System.arraycopy(data, cur, routerHash, 0, Hash.HASH_LENGTH); + Hash rh = Hash.create(data, cur); cur += Hash.HASH_LENGTH; - setRouter(new Hash(routerHash)); + setRouter(rh); break; case FLAG_MODE_TUNNEL: - byte tunnelRouterHash[] = new byte[Hash.HASH_LENGTH]; - System.arraycopy(data, cur, tunnelRouterHash, 0, Hash.HASH_LENGTH); + //byte tunnelRouterHash[] = new byte[Hash.HASH_LENGTH]; + //System.arraycopy(data, cur, tunnelRouterHash, 0, Hash.HASH_LENGTH); + Hash trh = Hash.create(data, cur); cur += Hash.HASH_LENGTH; - setRouter(new Hash(tunnelRouterHash)); + setRouter(trh); setTunnelId(new TunnelId(DataHelper.fromLong(data, cur, 4))); cur += 4; break; diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index f6e6df3a5c0d10fbf1efc2f7069c342c2fe16f4e..4e28a2c8d2676ff98ee569a93ae883ef407d9461 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 4; + public final static long BUILD = 5; /** for example "-test" */ public final static String EXTRA = ""; diff --git a/router/java/src/net/i2p/router/client/LookupDestJob.java b/router/java/src/net/i2p/router/client/LookupDestJob.java index 68edbcaa06f5a7a85e269121d265f522b1bf62fe..7db546e91a11489fbe951781eb9c9e8451d36a4f 100644 --- a/router/java/src/net/i2p/router/client/LookupDestJob.java +++ b/router/java/src/net/i2p/router/client/LookupDestJob.java @@ -28,7 +28,8 @@ class LookupDestJob extends JobImpl { public String getName() { return "LeaseSet Lookup for Client"; } public void runJob() { DoneJob done = new DoneJob(getContext()); - getContext().netDb().lookupLeaseSet(_hash, done, done, 10*1000); + // TODO add support for specifying the timeout in the lookup message + getContext().netDb().lookupLeaseSet(_hash, done, done, 15*1000); } private class DoneJob extends JobImpl { @@ -41,7 +42,7 @@ class LookupDestJob extends JobImpl { if (ls != null) returnDest(ls.getDestination()); else - returnDest(null); + returnHash(_hash); } } @@ -51,4 +52,15 @@ class LookupDestJob extends JobImpl { _runner.doSend(msg); } catch (I2CPMessageException ime) {} } + + /** + * Return the failed hash so the client can correlate replies with requests + * @since 0.8.3 + */ + private void returnHash(Hash h) { + DestReplyMessage msg = new DestReplyMessage(h); + try { + _runner.doSend(msg); + } catch (I2CPMessageException ime) {} + } } diff --git a/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java b/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java index 610dfec4f8a26eb01f07a781b483bf180105a43b..e012fab4906c9a19694a6bd84710429f0028a6df 100644 --- a/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java +++ b/router/java/src/net/i2p/router/peermanager/ProfilePersistenceHelper.java @@ -13,6 +13,7 @@ import java.util.Set; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; +import net.i2p.data.Base64; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Hash; @@ -290,15 +291,19 @@ class ProfilePersistenceHelper { _log.warn("Error loading properties from " + file.getName(), ioe); } } - + private Hash getHash(String name) { String key = name.substring("profile-".length()); key = key.substring(0, key.length() - ".dat".length()); - Hash h = new Hash(); + //Hash h = new Hash(); try { - h.fromBase64(key); + //h.fromBase64(key); + byte[] b = Base64.decode(key); + if (b == null) + return null; + Hash h = Hash.create(b); return h; - } catch (DataFormatException dfe) { + } catch (Exception dfe) { _log.warn("Invalid base64 [" + key + "]", dfe); return null; } diff --git a/router/java/src/net/i2p/router/tunnel/FragmentHandler.java b/router/java/src/net/i2p/router/tunnel/FragmentHandler.java index 18a06f0362dfa1f4ad68bba6e2d054e2a4f1cc66..7948c5387b7ac4917df0d89841dc5afab4014331 100644 --- a/router/java/src/net/i2p/router/tunnel/FragmentHandler.java +++ b/router/java/src/net/i2p/router/tunnel/FragmentHandler.java @@ -327,11 +327,12 @@ public class FragmentHandler { offset += 4; } if ( (type == TYPE_ROUTER) || (type == TYPE_TUNNEL) ) { - byte h[] = new byte[Hash.HASH_LENGTH]; if (offset + Hash.HASH_LENGTH >= preprocessed.length) return -1; - System.arraycopy(preprocessed, offset, h, 0, Hash.HASH_LENGTH); - router = new Hash(h); + //byte h[] = new byte[Hash.HASH_LENGTH]; + //System.arraycopy(preprocessed, offset, h, 0, Hash.HASH_LENGTH); + //router = new Hash(h); + router = Hash.create(preprocessed, offset); offset += Hash.HASH_LENGTH; } if (fragmented) {