diff --git a/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java index 959b8b797486d9a119fcf3a963ac5710cd213dc7..00b9afa935daa92b1590536eb44b4b30f7631b89 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/LogsHelper.java @@ -12,7 +12,19 @@ public class LogsHelper extends HelperBase { /** @since 0.8.11 */ public String getJettyVersion() { - return Version.getImplVersion(); + return jettyVersion(); + } + + /** @since 0.8.13 */ + static String jettyVersion() { + try { + String rv = Version.getImplVersion(); + if (rv.startsWith("Jetty/")) + rv = rv.substring(6); + return rv; + } catch (Throwable t) { + return "unknown"; + } } public String getLogs() { diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java index 7e44cd6e83b895504f2e92b04391897c301aa282..1a207763a155efe33203be5126012cae472f0b85 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/NetDbRenderer.java @@ -376,9 +376,8 @@ public class NetDbRenderer { int cost = addr.getCost(); if (!((style.equals("SSU") && cost == 5) || (style.equals("NTCP") && cost == 10))) buf.append('[').append(_("cost")).append('=').append("" + cost).append("] "); - Properties p = new OrderedProperties(); - p.putAll(addr.getOptions()); - for (Map.Entry e : p.entrySet()) { + Map p = addr.getOptionsMap(); + for (Map.Entry e : (Set<Map.Entry>) p.entrySet()) { String name = (String) e.getKey(); String val = (String) e.getValue(); buf.append('[').append(_(DataHelper.stripHTML(name))).append('=').append(DataHelper.stripHTML(val)).append("] "); @@ -387,9 +386,10 @@ public class NetDbRenderer { buf.append("</td></tr>\n"); if (full) { buf.append("<tr><td>" + _("Stats") + ": <br><code>"); - for (Iterator iter = info.getOptions().keySet().iterator(); iter.hasNext(); ) { - String key = (String)iter.next(); - String val = info.getOption(key); + Map p = info.getOptionsMap(); + for (Map.Entry e : (Set<Map.Entry>) p.entrySet()) { + String key = (String) e.getKey(); + String val = (String) e.getValue(); buf.append(DataHelper.stripHTML(key)).append(" = ").append(DataHelper.stripHTML(val)).append("<br>\n"); } buf.append("</code></td></tr>\n"); @@ -412,7 +412,7 @@ public class NetDbRenderer { if (style.equals("NTCP")) { rv |= NTCP; } else if (style.equals("SSU")) { - if (addr.getOptions().getProperty("iport0") != null) + if (addr.getOption("iport0") != null) rv |= SSUI; else rv |= SSU; diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java index d1835a32065f46168517b8eec9ec6cd46e0efc4b..6be4b1cd7ee034d23a7e953f0e7eb7431e630a98 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginStarter.java @@ -17,6 +17,7 @@ import java.util.Properties; import java.util.StringTokenizer; import java.util.concurrent.ConcurrentHashMap; +import net.i2p.CoreVersion; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.router.Job; @@ -27,6 +28,7 @@ import net.i2p.util.ConcurrentHashSet; import net.i2p.util.FileUtil; import net.i2p.util.Log; import net.i2p.util.Translate; +import net.i2p.util.VersionComparator; import org.mortbay.jetty.Server; @@ -95,6 +97,41 @@ public class PluginStarter implements Runnable { log.error("Cannot start nonexistent plugin: " + appName); return false; } + + Properties props = pluginProperties(ctx, appName); + String minVersion = ConfigClientsHelper.stripHTML(props, "min-i2p-version"); + if (minVersion != null && + (new VersionComparator()).compare(CoreVersion.VERSION, minVersion) < 0) { + String foo = "Plugin " + appName + " requires I2P version " + minVersion + " or higher"; + log.error(foo); + throw new Exception(foo); + } + + minVersion = ConfigClientsHelper.stripHTML(props, "min-java-version"); + if (minVersion != null && + (new VersionComparator()).compare(System.getProperty("java.version"), minVersion) < 0) { + String foo = "Plugin " + appName + " requires Java version " + minVersion + " or higher"; + log.error(foo); + throw new Exception(foo); + } + + String jVersion = LogsHelper.jettyVersion(); + minVersion = ConfigClientsHelper.stripHTML(props, "min-jetty-version"); + if (minVersion != null && + (new VersionComparator()).compare(minVersion, jVersion) > 0) { + String foo = "Plugin " + appName + " requires Jetty version " + minVersion + " or higher"; + log.error(foo); + throw new Exception(foo); + } + + String maxVersion = ConfigClientsHelper.stripHTML(props, "max-jetty-version"); + if (maxVersion != null && + (new VersionComparator()).compare(maxVersion, jVersion) < 0) { + String foo = "Plugin " + appName + " requires Jetty version " + maxVersion + " or lower"; + log.error(foo); + throw new Exception(foo); + } + if (log.shouldLog(Log.INFO)) log.info("Starting plugin: " + appName); @@ -113,8 +150,8 @@ public class PluginStarter implements Runnable { // load and start things in clients.config File clientConfig = new File(pluginDir, "clients.config"); if (clientConfig.exists()) { - Properties props = new Properties(); - DataHelper.loadProps(props, clientConfig); + Properties cprops = new Properties(); + DataHelper.loadProps(cprops, clientConfig); List<ClientAppConfig> clients = ClientAppConfig.getClientApps(clientConfig); runClientApps(ctx, pluginDir, clients, "start"); } @@ -123,7 +160,7 @@ public class PluginStarter implements Runnable { Server server = WebAppStarter.getConsoleServer(); if (server != null) { File consoleDir = new File(pluginDir, "console"); - Properties props = RouterConsoleRunner.webAppProperties(consoleDir.getAbsolutePath()); + Properties wprops = RouterConsoleRunner.webAppProperties(consoleDir.getAbsolutePath()); File webappDir = new File(consoleDir, "webapps"); String fileNames[] = webappDir.list(RouterConsoleRunner.WarFilenameFilter.instance()); if (fileNames != null) { @@ -138,7 +175,7 @@ public class PluginStarter implements Runnable { log.error("Skipping duplicate webapp " + warName + " in plugin " + appName); continue; } - String enabled = props.getProperty(RouterConsoleRunner.PREFIX + warName + ENABLED); + String enabled = wprops.getProperty(RouterConsoleRunner.PREFIX + warName + ENABLED); if (! "false".equals(enabled)) { if (log.shouldLog(Log.INFO)) log.info("Starting webapp: " + warName); @@ -181,7 +218,6 @@ public class PluginStarter implements Runnable { } // add summary bar link - Properties props = pluginProperties(ctx, appName); String name = ConfigClientsHelper.stripHTML(props, "consoleLinkName_" + Messages.getLanguage(ctx)); if (name == null) name = ConfigClientsHelper.stripHTML(props, "consoleLinkName"); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateHandler.java index 884a20bb5fcbf1dbd92871b41a59747310f77a3f..511bfb0cd075de25f00352148c67a4a16c89a9d2 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/PluginUpdateHandler.java @@ -334,6 +334,21 @@ public class PluginUpdateHandler extends UpdateHandler { statusDone("<b>" + _("Plugin update requires installed plugin version {0} or lower", maxVersion) + "</b>"); return; } + oldVersion = LogsHelper.jettyVersion(); + minVersion = ConfigClientsHelper.stripHTML(props, "min-jetty-version"); + if (minVersion != null && + (new VersionComparator()).compare(minVersion, oldVersion) > 0) { + to.delete(); + statusDone("<b>" + _("Plugin requires Jetty version {0} or higher", minVersion) + "</b>"); + return; + } + maxVersion = ConfigClientsHelper.stripHTML(props, "max-jetty-version"); + if (maxVersion != null && + (new VersionComparator()).compare(maxVersion, oldVersion) < 0) { + to.delete(); + statusDone("<b>" + _("Plugin requires Jetty version {0} or lower", maxVersion) + "</b>"); + return; + } // check if it is running first? try { diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java index 95dd1875d21bdc38e31f14b548b1bb419a317061..420775065ad354581693816733318023a4630c9f 100644 --- a/core/java/src/net/i2p/data/DataHelper.java +++ b/core/java/src/net/i2p/data/DataHelper.java @@ -118,6 +118,17 @@ public class DataHelper { public static Properties readProperties(InputStream rawStream) throws DataFormatException, IOException { Properties props = new OrderedProperties(); + readProperties(rawStream, props); + return props; + } + + /** + * Ditto, load into an existing properties + * @param props the Properties to load into + * @since 0.8.13 + */ + public static Properties readProperties(InputStream rawStream, Properties props) + throws DataFormatException, IOException { long size = readLong(rawStream, 2); byte data[] = new byte[(int) size]; int read = read(rawStream, data); @@ -1268,6 +1279,8 @@ public class DataHelper { * 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.EMPTY_LIST; diff --git a/core/java/src/net/i2p/data/DatabaseEntry.java b/core/java/src/net/i2p/data/DatabaseEntry.java index 3c7b665ca2763eba2f9ab3f3766ad1b476d2aa2f..943fdbd733972d35da754890ef739bdaec0c1cf6 100644 --- a/core/java/src/net/i2p/data/DatabaseEntry.java +++ b/core/java/src/net/i2p/data/DatabaseEntry.java @@ -124,16 +124,22 @@ public abstract class DatabaseEntry extends DataStructureImpl { /** * Configure the proof that the entity stands behind the info here * + * @throws IllegalStateException if already signed */ public void setSignature(Signature signature) { + if (_signature != null) + throw new IllegalStateException(); _signature = signature; } /** * Sign the structure using the supplied signing key * + * @throws IllegalStateException if already signed */ public void sign(SigningPrivateKey key) throws DataFormatException { + if (_signature != null) + throw new IllegalStateException(); byte[] bytes = getBytes(); if (bytes == null) throw new DataFormatException("Not enough data to sign"); // now sign with the key diff --git a/core/java/src/net/i2p/data/RouterAddress.java b/core/java/src/net/i2p/data/RouterAddress.java index 91a05e6d4e9c5dfec522ae5cfbb6aee260efde90..012ef0194eea9fe33601eafe3683d4a61953c0de 100644 --- a/core/java/src/net/i2p/data/RouterAddress.java +++ b/core/java/src/net/i2p/data/RouterAddress.java @@ -12,6 +12,7 @@ package net.i2p.data; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.Map; @@ -22,16 +23,24 @@ import net.i2p.util.OrderedProperties; /** * Defines a method of communicating with a router * + * For efficiency, the options methods and structures here are unsynchronized. + * Initialize the structure with readBytes(), or call the setOptions(). + * Don't change it after that. + * + * To ensure integrity of the RouterInfo, methods that change an element of the + * RouterInfo will throw an IllegalStateException after the RouterInfo is signed. + * * @author jrandom */ public class RouterAddress extends DataStructureImpl { private int _cost; private Date _expiration; private String _transportStyle; - private Properties _options; + private final Properties _options; public RouterAddress() { _cost = -1; + _options = new OrderedProperties(); } /** @@ -85,28 +94,59 @@ public class RouterAddress extends DataStructureImpl { /** * Configure the type of transport that must be used to communicate on this address * + * @throws IllegalStateException if was already set */ public void setTransportStyle(String transportStyle) { + if (_transportStyle != null) + throw new IllegalStateException(); _transportStyle = transportStyle; } /** * Retrieve the transport specific options necessary for communication * + * @deprecated use getOptionsMap() + * @return sorted, non-null, NOT a copy, do not modify */ public Properties getOptions() { return _options; } /** - * Specify the transport specific options necessary for communication + * Retrieve the transport specific options necessary for communication * + * @return an unmodifiable view, non-null, sorted + * @since 0.8.13 + */ + public Map getOptionsMap() { + return Collections.unmodifiableMap(_options); + } + + /** + * @since 0.8.13 + */ + public String getOption(String opt) { + return _options.getProperty(opt); + } + + /** + * Specify the transport specific options necessary for communication. + * Makes a copy. + * @param options non-null + * @throws IllegalStateException if was already set */ public void setOptions(Properties options) { - _options = options; + if (!_options.isEmpty()) + throw new IllegalStateException(); + _options.putAll(options); } + /** + * @throws IllegalStateException if was already read in + */ public void readBytes(InputStream in) throws DataFormatException, IOException { + if (_transportStyle != null) + throw new IllegalStateException(); _cost = (int) DataHelper.readLong(in, 1); _expiration = DataHelper.readDate(in); _transportStyle = DataHelper.readString(in); @@ -115,11 +155,11 @@ public class RouterAddress extends DataStructureImpl { _transportStyle = "SSU"; else if (_transportStyle.equals("NTCP")) _transportStyle = "NTCP"; - _options = DataHelper.readProperties(in); + DataHelper.readProperties(in, _options); } public void writeBytes(OutputStream out) throws DataFormatException, IOException { - if ((_cost < 0) || (_transportStyle == null) || (_options == null)) + if ((_cost < 0) || (_transportStyle == null)) throw new DataFormatException("Not enough data to write a router address"); DataHelper.writeLong(out, 1, _cost); DataHelper.writeDate(out, _expiration); @@ -131,11 +171,12 @@ public class RouterAddress extends DataStructureImpl { public boolean equals(Object object) { if ((object == null) || !(object instanceof RouterAddress)) return false; RouterAddress addr = (RouterAddress) object; + // let's keep this fast as we are putting an address into the RouterInfo set frequently return _cost == addr._cost && - DataHelper.eq(_transportStyle, addr._transportStyle) && - DataHelper.eq(_options, addr._options) && - DataHelper.eq(_expiration, addr._expiration); + DataHelper.eq(_transportStyle, addr._transportStyle); + //DataHelper.eq(_options, addr._options) && + //DataHelper.eq(_expiration, addr._expiration); } /** @@ -161,9 +202,7 @@ public class RouterAddress extends DataStructureImpl { buf.append("\n\tExpiration: ").append(_expiration); if (_options != null) { buf.append("\n\tOptions: #: ").append(_options.size()); - Properties p = new OrderedProperties(); - p.putAll(_options); - for (Map.Entry e : p.entrySet()) { + for (Map.Entry e : _options.entrySet()) { String key = (String) e.getKey(); String val = (String) e.getValue(); buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]"); diff --git a/core/java/src/net/i2p/data/RouterInfo.java b/core/java/src/net/i2p/data/RouterInfo.java index ca4420403039506798af37815218097cd4201fe0..6c34ad2023989aed04caff0b47d662e346296e0a 100644 --- a/core/java/src/net/i2p/data/RouterInfo.java +++ b/core/java/src/net/i2p/data/RouterInfo.java @@ -19,6 +19,7 @@ import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.Vector; @@ -32,6 +33,13 @@ import net.i2p.util.OrderedProperties; * Defines the data that a router either publishes to the global routing table or * provides to trusted peers. * + * For efficiency, the methods and structures here are now unsynchronized. + * Initialize the RI with readBytes(), or call the setters and then sign() in a single thread. + * Don't change it after that. + * + * To ensure integrity of the RouterInfo, methods that change an element of the + * RouterInfo will throw an IllegalStateException after the RouterInfo is signed. + * * @author jrandom */ public class RouterInfo extends DatabaseEntry { @@ -41,7 +49,7 @@ public class RouterInfo extends DatabaseEntry { private final Set<RouterAddress> _addresses; /** may be null to save memory, no longer final */ private Set<Hash> _peers; - private /* FIXME final FIXME */ Properties _options; + private final Properties _options; private volatile boolean _validated; private volatile boolean _isValid; private volatile String _stringified; @@ -67,14 +75,19 @@ public class RouterInfo extends DatabaseEntry { _options = new OrderedProperties(); } + /** + * Used only by Router and PublishLocalRouterInfoJob. + * Copies ONLY the identity and peers. + * Does not copy published, addresses, options, or signature. + */ public RouterInfo(RouterInfo old) { this(); setIdentity(old.getIdentity()); - setPublished(old.getPublished()); - setAddresses(old.getAddresses()); + //setPublished(old.getPublished()); + //setAddresses(old.getAddresses()); setPeers(old.getPeers()); - setOptions(old.getOptions()); - setSignature(old.getSignature()); + //setOptions(old.getOptions()); + //setSignature(old.getSignature()); // copy over _byteified? } @@ -90,12 +103,6 @@ public class RouterInfo extends DatabaseEntry { return KEY_TYPE_ROUTERINFO; } - private void resetCache() { - _stringified = null; - _byteified = null; - _hashCodeInitialized = false; - } - /** * Retrieve the identity of the router represented * @@ -107,10 +114,12 @@ public class RouterInfo extends DatabaseEntry { /** * Configure the identity of the router represented * + * @throws IllegalStateException if RouterInfo is already signed */ public void setIdentity(RouterIdentity ident) { + if (_signature != null) + throw new IllegalStateException(); _identity = ident; - resetCache(); // We only want to cache the bytes for our own RI, which is frequently written. // To cache for all RIs doubles the RI memory usage. // setIdentity() is only called when we are creating our own RI. @@ -133,34 +142,35 @@ public class RouterInfo extends DatabaseEntry { /** * Date on which it was published, in milliseconds since Midnight GMT on Jan 01, 1970 * + * @throws IllegalStateException if RouterInfo is already signed */ public void setPublished(long published) { + if (_signature != null) + throw new IllegalStateException(); _published = published; - resetCache(); } /** * Retrieve the set of RouterAddress structures at which this * router can be contacted. * + * @return unmodifiable view, non-null */ public Set<RouterAddress> getAddresses() { - synchronized (_addresses) { - return new HashSet(_addresses); - } + return Collections.unmodifiableSet(_addresses); } /** * Specify a set of RouterAddress structures at which this router * can be contacted. * + * @throws IllegalStateException if RouterInfo is already signed */ public void setAddresses(Set<RouterAddress> addresses) { - synchronized (_addresses) { - _addresses.clear(); - if (addresses != null) _addresses.addAll(addresses); - } - resetCache(); + if (_signature != null) + throw new IllegalStateException(); + _addresses.clear(); + if (addresses != null) _addresses.addAll(addresses); } /** @@ -180,8 +190,11 @@ public class RouterInfo extends DatabaseEntry { * this router can be reached through. * * @deprecated Implemented here but unused elsewhere + * @throws IllegalStateException if RouterInfo is already signed */ public void setPeers(Set<Hash> peers) { + if (_signature != null) + throw new IllegalStateException(); if (peers == null || peers.isEmpty()) { _peers = null; return; @@ -192,37 +205,46 @@ public class RouterInfo extends DatabaseEntry { _peers.clear(); _peers.addAll(peers); } - resetCache(); } /** - * Retrieve a set of options or statistics that the router can expose + * Retrieve a set of options or statistics that the router can expose. * + * @deprecated use getOptionsMap() + * @return sorted, non-null, NOT a copy, do not modify!!! */ public Properties getOptions() { - if (_options == null) return new Properties(); - synchronized (_options) { - return (Properties) _options.clone(); - } + return _options; } + + /** + * Retrieve a set of options or statistics that the router can expose. + * + * @return an unmodifiable view, non-null, sorted + * @since 0.8.13 + */ + public Map getOptionsMap() { + return Collections.unmodifiableMap(_options); + } + public String getOption(String opt) { - if (_options == null) return null; - synchronized (_options) { - return _options.getProperty(opt); - } + return _options.getProperty(opt); } /** - * Configure a set of options or statistics that the router can expose + * Configure a set of options or statistics that the router can expose. + * Makes a copy. + * * @param options if null, clears current options + * @throws IllegalStateException if RouterInfo is already signed */ public void setOptions(Properties options) { - synchronized (_options) { - _options.clear(); - if (options != null) - _options.putAll(options); - } - resetCache(); + if (_signature != null) + throw new IllegalStateException(); + + _options.clear(); + if (options != null) + _options.putAll(options); } /** @@ -234,14 +256,14 @@ public class RouterInfo extends DatabaseEntry { protected byte[] getBytes() throws DataFormatException { if (_byteified != null) return _byteified; if (_identity == null) throw new DataFormatException("Router identity isn't set? wtf!"); - if (_addresses == null) throw new DataFormatException("Router addressess isn't set? wtf!"); - if (_options == null) throw new DataFormatException("Router options isn't set? wtf!"); //long before = Clock.getInstance().now(); - ByteArrayOutputStream out = new ByteArrayOutputStream(6*1024); + ByteArrayOutputStream out = new ByteArrayOutputStream(2*1024); try { _identity.writeBytes(out); - DataHelper.writeDate(out, new Date(_published)); + // avoid thrashing objects + //DataHelper.writeDate(out, new Date(_published)); + DataHelper.writeLong(out, 8, _published); int sz = _addresses.size(); if (sz <= 0 || isHidden()) { // Do not send IP address to peers in hidden mode @@ -249,11 +271,12 @@ public class RouterInfo extends DatabaseEntry { } else { DataHelper.writeLong(out, 1, sz); Collection<RouterAddress> addresses = _addresses; - if (sz > 1) + if (sz > 1) { // 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. addresses = (Collection<RouterAddress>) DataHelper.sortStructures(addresses); + } for (RouterAddress addr : addresses) { addr.writeBytes(out); } @@ -293,7 +316,7 @@ public class RouterInfo extends DatabaseEntry { * Determine whether this router info is authorized with a valid signature * */ - public synchronized boolean isValid() { + public boolean isValid() { if (!_validated) doValidate(); return _isValid; } @@ -304,11 +327,7 @@ public class RouterInfo extends DatabaseEntry { * @return -1 if unknown */ public int getNetworkId() { - if (_options == null) return -1; - String id = null; - synchronized (_options) { - id = _options.getProperty(PROP_NETWORK_ID); - } + String id = _options.getProperty(PROP_NETWORK_ID); if (id != null) { try { return Integer.parseInt(id); @@ -322,11 +341,7 @@ public class RouterInfo extends DatabaseEntry { * @return non-null, empty string if none */ public String getCapabilities() { - if (_options == null) return ""; - String capabilities = null; - synchronized (_options) { - capabilities = _options.getProperty(PROP_CAPABILITIES); - } + String capabilities = _options.getProperty(PROP_CAPABILITIES); if (capabilities != null) return capabilities; else @@ -358,20 +373,27 @@ public class RouterInfo extends DatabaseEntry { return (bwTier); } + /** + * @throws IllegalStateException if RouterInfo is already signed + */ public void addCapability(char cap) { - if (_options == null) _options = new OrderedProperties(); - synchronized (_options) { + if (_signature != null) + throw new IllegalStateException(); + String caps = _options.getProperty(PROP_CAPABILITIES); if (caps == null) _options.setProperty(PROP_CAPABILITIES, ""+cap); else if (caps.indexOf(cap) == -1) _options.setProperty(PROP_CAPABILITIES, caps + cap); - } } + /** + * @throws IllegalStateException if RouterInfo is already signed + */ public void delCapability(char cap) { - if (_options == null) return; - synchronized (_options) { + if (_signature != null) + throw new IllegalStateException(); + String caps = _options.getProperty(PROP_CAPABILITIES); int idx; if (caps == null) { @@ -384,7 +406,6 @@ public class RouterInfo extends DatabaseEntry { buf.deleteCharAt(idx); _options.setProperty(PROP_CAPABILITIES, buf.toString()); } - } } /** @@ -409,12 +430,9 @@ public class RouterInfo extends DatabaseEntry { * */ public RouterAddress getTargetAddress(String transportStyle) { - synchronized (_addresses) { - for (Iterator iter = _addresses.iterator(); iter.hasNext(); ) { - RouterAddress addr = (RouterAddress)iter.next(); - if (addr.getTransportStyle().equals(transportStyle)) - return addr; - } + for (RouterAddress addr : _addresses) { + if (addr.getTransportStyle().equals(transportStyle)) + return addr; } return null; } @@ -425,12 +443,9 @@ public class RouterInfo extends DatabaseEntry { */ public List<RouterAddress> getTargetAddresses(String transportStyle) { List<RouterAddress> ret = new Vector<RouterAddress>(); - synchronized(this._addresses) { - for(Object o : this._addresses) { - RouterAddress addr = (RouterAddress)o; - if(addr.getTransportStyle().equals(transportStyle)) - ret.add(addr); - } + for (RouterAddress addr : _addresses) { + if(addr.getTransportStyle().equals(transportStyle)) + ret.add(addr); } return ret; } @@ -438,9 +453,9 @@ public class RouterInfo extends DatabaseEntry { /** * Actually validate the signature */ - private synchronized void doValidate() { - _validated = true; + private void doValidate() { _isValid = super.verifySignature(); + _validated = true; if (!_isValid) { byte data[] = null; @@ -459,15 +474,21 @@ public class RouterInfo extends DatabaseEntry { /** * This does NOT validate the signature + * + * @throws IllegalStateException if RouterInfo was already read in */ - public synchronized void readBytes(InputStream in) throws DataFormatException, IOException { + public void readBytes(InputStream in) throws DataFormatException, IOException { + if (_signature != null) + throw new IllegalStateException(); _identity = new RouterIdentity(); _identity.readBytes(in); - Date when = DataHelper.readDate(in); - if (when == null) - _published = 0; - else - _published = when.getTime(); + // avoid thrashing objects + //Date when = DataHelper.readDate(in); + //if (when == null) + // _published = 0; + //else + // _published = when.getTime(); + _published = DataHelper.readLong(in, 8); int numAddresses = (int) DataHelper.readLong(in, 1); for (int i = 0; i < numAddresses; i++) { RouterAddress address = new RouterAddress(); @@ -485,11 +506,10 @@ public class RouterInfo extends DatabaseEntry { _peers.add(peerIdentityHash); } } - _options = DataHelper.readProperties(in); + DataHelper.readProperties(in, _options); _signature = new Signature(); _signature.readBytes(in); - resetCache(); //_log.debug("Read routerInfo: " + toString()); } @@ -497,13 +517,13 @@ public class RouterInfo extends DatabaseEntry { /** * This does NOT validate the signature */ - public synchronized void writeBytes(OutputStream out) throws DataFormatException, IOException { + public void writeBytes(OutputStream out) throws DataFormatException, IOException { if (_identity == null) throw new DataFormatException("Missing identity"); if (_published < 0) throw new DataFormatException("Invalid published date: " + _published); if (_signature == null) throw new DataFormatException("Signature is null"); //if (!isValid()) // throw new DataFormatException("Data is not valid"); - ByteArrayOutputStream baos = new ByteArrayOutputStream(512); + ByteArrayOutputStream baos = new ByteArrayOutputStream(2048); baos.write(getBytes()); _signature.writeBytes(baos); @@ -518,10 +538,11 @@ public class RouterInfo extends DatabaseEntry { RouterInfo info = (RouterInfo) object; return DataHelper.eq(_identity, info.getIdentity()) && DataHelper.eq(_signature, info.getSignature()) - && _published == info.getPublished() - && DataHelper.eq(_addresses, info.getAddresses()) - && DataHelper.eq(_options, info.getOptions()) - && DataHelper.eq(getPeers(), info.getPeers()); + && _published == info.getPublished(); + // Let's speed up the NetDB + //&& DataHelper.eq(_addresses, info.getAddresses()) + //&& DataHelper.eq(_options, info.getOptions()) + //&& DataHelper.eq(getPeers(), info.getPeers()); } @Override @@ -541,23 +562,19 @@ public class RouterInfo extends DatabaseEntry { buf.append("\n\tIdentity: ").append(_identity); buf.append("\n\tSignature: ").append(_signature); buf.append("\n\tPublished on: ").append(new Date(_published)); - Set addresses = _addresses; // getAddresses() - buf.append("\n\tAddresses: #: ").append(addresses.size()); - for (Iterator iter = addresses.iterator(); iter.hasNext();) { - RouterAddress addr = (RouterAddress) iter.next(); + buf.append("\n\tAddresses: #: ").append(_addresses.size()); + for (RouterAddress addr : _addresses) { buf.append("\n\t\tAddress: ").append(addr); } - Set peers = getPeers(); + Set<Hash> peers = getPeers(); buf.append("\n\tPeers: #: ").append(peers.size()); - for (Iterator iter = peers.iterator(); iter.hasNext();) { - Hash hash = (Hash) iter.next(); + for (Hash hash : peers) { buf.append("\n\t\tPeer hash: ").append(hash); } - Properties options = _options; // getOptions(); - buf.append("\n\tOptions: #: ").append(options.size()); - for (Iterator iter = options.keySet().iterator(); iter.hasNext();) { - String key = (String) iter.next(); - String val = options.getProperty(key); + buf.append("\n\tOptions: #: ").append(_options.size()); + for (Map.Entry e : _options.entrySet()) { + String key = (String) e.getKey(); + String val = (String) e.getValue(); buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]"); } buf.append("]"); diff --git a/router/java/src/net/i2p/router/Blocklist.java b/router/java/src/net/i2p/router/Blocklist.java index f76e6eb49961f558c6c239fdcaff31d06eba318d..153b14e7c928064182255abb0e51f209f9764946 100644 --- a/router/java/src/net/i2p/router/Blocklist.java +++ b/router/java/src/net/i2p/router/Blocklist.java @@ -488,9 +488,7 @@ public class Blocklist { for (int j = 0; j < paddr.size(); j++) { RouterAddress pa = (RouterAddress) pladdr.get(j); if (pa == null) continue; - Properties pprops = pa.getOptions(); - if (pprops == null) continue; - String phost = pprops.getProperty("host"); + String phost = pa.getOption("host"); if (phost == null) continue; if (oldphost != null && oldphost.equals(phost)) continue; oldphost = phost; diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index 08ea93bd8c3a5984ea157335ec7a10f4ab727184..e7172550f88194f6b1c9ebacbaedf37277e74242 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -60,7 +60,7 @@ import net.i2p.util.SimpleScheduler; * */ public class Router implements RouterClock.ClockShiftListener { - private final Log _log; + private Log _log; private final RouterContext _context; private final Map<String, String> _config; /** full path */ @@ -77,9 +77,9 @@ public class Router implements RouterClock.ClockShiftListener { private ShutdownHook _shutdownHook; /** non-cancellable shutdown has begun */ private volatile boolean _shutdownInProgress; - private final I2PThread _gracefulShutdownDetector; - private final RouterWatchdog _watchdog; - private final Thread _watchdogThread; + private I2PThread _gracefulShutdownDetector; + private RouterWatchdog _watchdog; + private Thread _watchdogThread; public final static String PROP_CONFIG_FILE = "router.configLocation"; @@ -128,9 +128,17 @@ public class Router implements RouterClock.ClockShiftListener { System.setProperty("Dorg.mortbay.util.FileResource.checkAliases", "true"); } + /** + * Instantiation only. Starts no threads. Does not install updates. + * RouterContext is created but not initialized. + * You must call runRouter() after any constructor to start things up. + */ public Router() { this(null, null); } + public Router(Properties envProps) { this(null, envProps); } + public Router(String configFilename) { this(configFilename, null); } + public Router(String configFilename, Properties envProps) { _gracefulExitCode = -1; _config = new ConcurrentHashMap(); @@ -235,14 +243,16 @@ public class Router implements RouterClock.ClockShiftListener { _config.put("router.updateLastInstalled", now); saveConfig(); } + // ********* Start no threads before here ********* // + } - // This is here so that we can get the directory location from the context - // for the zip file and the base location to unzip to. - // If it does an update, it never returns. - // I guess it's better to have the other-router check above this, we don't want to - // overwrite an existing running router's jar files. Other than ours. - installUpdates(); - + /** + * Initializes the RouterContext. + * Starts some threads. Does not install updates. + * All this was in the constructor. + * @since 0.8.12 + */ + private void startupStuff() { // ********* Start no threads before here ********* // // // NOW we can start the ping file thread. @@ -372,7 +382,14 @@ public class Router implements RouterClock.ClockShiftListener { public RouterContext getContext() { return _context; } + /** + * Initializes the RouterContext. + * Starts the threads. Does not install updates. + */ void runRouter() { + if (_isAlive) + throw new IllegalStateException(); + startupStuff(); _isAlive = true; _started = _context.clock().now(); try { @@ -1266,13 +1283,18 @@ public class Router implements RouterClock.ClockShiftListener { public static void main(String args[]) { System.out.println("Starting I2P " + RouterVersion.FULL_VERSION); - // installUpdates() moved to constructor so we can get file locations from the context - // installUpdates(); //verifyWrapperConfig(); Router r = new Router(); if ( (args != null) && (args.length == 1) && ("rebuild".equals(args[0])) ) { r.rebuildNewIdentity(); } else { + // This is here so that we can get the directory location from the context + // for the zip file and the base location to unzip to. + // If it does an update, it never returns. + // I guess it's better to have the other-router check above this, we don't want to + // overwrite an existing running router's jar files. Other than ours. + r.installUpdates(); + // ********* Start no threads before here ********* // r.runRouter(); } } @@ -1281,6 +1303,7 @@ public class Router implements RouterClock.ClockShiftListener { private static final String DELETE_FILE = "deletelist.txt"; /** + * Context must be available. * Unzip update file found in the router dir OR base dir, to the base dir * * If we can't write to the base dir, complain. diff --git a/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java b/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java index 18612c3cd113f16ac9cbf2dcdcfde44ed2356146..cfcdd8a34b49c44cb807ad8e520509cfa08bc235 100644 --- a/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java +++ b/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java @@ -41,7 +41,7 @@ public class PublishLocalRouterInfoJob extends JobImpl { RouterInfo ri = new RouterInfo(getContext().router().getRouterInfo()); if (_log.shouldLog(Log.DEBUG)) _log.debug("Old routerInfo contains " + ri.getAddresses().size() - + " addresses and " + ri.getOptions().size() + " options"); + + " addresses and " + ri.getOptionsMap().size() + " options"); Properties stats = getContext().statPublisher().publishStatistics(); stats.setProperty(RouterInfo.PROP_NETWORK_ID, ""+Router.NETWORK_ID); try { @@ -60,7 +60,7 @@ public class PublishLocalRouterInfoJob extends JobImpl { getContext().router().setRouterInfo(ri); if (_log.shouldLog(Log.INFO)) _log.info("Newly updated routerInfo is published with " + stats.size() - + "/" + ri.getOptions().size() + " options on " + + "/" + ri.getOptionsMap().size() + " options on " + new Date(ri.getPublished())); try { getContext().netDb().publish(ri); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillMonitorJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillMonitorJob.java index 5c719d76ca362e4df3b544c5f25d61cec1af25dc..2d23532960fdc9093d570e61cbffa7faafbe3962 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillMonitorJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/FloodfillMonitorJob.java @@ -133,8 +133,7 @@ class FloodfillMonitorJob extends JobImpl { if (ra == null) happy = false; else { - Properties props = ra.getOptions(); - if (props == null || props.getProperty("ihost0") != null) + if (ra.getOption("ihost0") != null) happy = false; } } diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java index f3a046a45bb29e9bf6866e9f6abe5c44b5ffa593..8a44d5a4e6d94dc9d00fd9d7de5e55cc70a83de9 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java @@ -786,8 +786,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { RouterAddress ra = routerInfo.getTargetAddress("SSU"); if (ra != null) { // Introducers change often, introducee will ping introducer for 2 hours - Properties props = ra.getOptions(); - if (props != null && props.getProperty("ihost0") != null) + if (ra.getOption("ihost0") != null) return "Peer " + key.toBase64() + " published > 75m ago with SSU Introducers"; if (routerInfo.getTargetAddress("NTCP") == null) return "Peer " + key.toBase64() + " published > 75m ago, SSU only without introducers"; @@ -822,10 +821,10 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { if (err != null) throw new IllegalArgumentException("Invalid store attempt - " + err); - //if (_log.shouldLog(Log.DEBUG)) - // _log.debug("RouterInfo " + key.toBase64() + " is stored with " - // + routerInfo.getOptions().size() + " options on " - // + new Date(routerInfo.getPublished())); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("RouterInfo " + key.toBase64() + " is stored with " + + routerInfo.getOptionsMap().size() + " options on " + + new Date(routerInfo.getPublished())); _context.peerManager().setCapabilities(key, routerInfo.getCapabilities()); _ds.put(key, routerInfo, persist); diff --git a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java index 093ca8ed2dd188b946466e7fb9c096fe8bddc274..8b14d22a0f5a8ce73d096b5a318d46508f56ebe9 100644 --- a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java +++ b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java @@ -701,8 +701,7 @@ public class ProfileOrganizer { continue; } // This is the quick way of doing UDPAddress.getIntroducerCount() > 0 - Properties props = ra.getOptions(); - if (props != null && props.getProperty("ihost0") != null) + if (ra.getOption("ihost0") != null) l.add(peer); } } @@ -1263,9 +1262,7 @@ public class ProfileOrganizer { if (paddr == null) return rv; for (RouterAddress pa : paddr) { - Properties pprops = pa.getOptions(); - if (pprops == null) continue; - String phost = pprops.getProperty("host"); + String phost = pa.getOption("host"); if (phost == null) continue; InetAddress pi; try { diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index ae4bfe224cbba5047637b3b243c4b469d60a87b9..8b6b29713d63307d6445fe041acc94b98a3a526e 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -343,15 +343,12 @@ public class TransportManager implements TransportEventListener { for (Transport t : _transports.values()) { int port = t.getRequestedPort(); if (t.getCurrentAddress() != null) { - Properties opts = t.getCurrentAddress().getOptions(); - if (opts != null) { - String s = opts.getProperty("port"); + String s = t.getCurrentAddress().getOption("port"); if (s != null) { try { port = Integer.parseInt(s); } catch (NumberFormatException nfe) {} } - } } // Use UDP port for NTCP too - see comment in NTCPTransport.getRequestedPort() for why this is here if (t.getStyle().equals(NTCPTransport.STYLE) && port <= 0 && diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPAddress.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPAddress.java index e13bcc31c7f3c881a675c67cb949323dd3673269..acced89f433a2cbd9fbf5687ef5bba52d1021f5d 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPAddress.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPAddress.java @@ -57,13 +57,13 @@ public class NTCPAddress { _port = -1; return; } - String host = addr.getOptions().getProperty(PROP_HOST); + String host = addr.getOption(PROP_HOST); if (host == null) { _host = null; _port = -1; } else { _host = host.trim(); - String port = addr.getOptions().getProperty(PROP_PORT); + String port = addr.getOption(PROP_PORT); if ( (port != null) && (port.trim().length() > 0) && !("null".equals(port)) ) { try { _port = Integer.parseInt(port.trim()); @@ -156,9 +156,7 @@ public class NTCPAddress { public boolean equals(RouterAddress addr) { if (addr == null) return false; - Properties opts = addr.getOptions(); - if (opts == null) return false; - return ( (_host.equals(opts.getProperty(PROP_HOST))) && - (Integer.toString(_port).equals(opts.getProperty(PROP_PORT))) ); + return ( (_host.equals(addr.getOption(PROP_HOST))) && + (Integer.toString(_port).equals(addr.getOption(PROP_PORT))) ); } } diff --git a/router/java/src/net/i2p/router/transport/udp/UDPAddress.java b/router/java/src/net/i2p/router/transport/udp/UDPAddress.java index bcee07605e44a222faed26f1fc4c4b3b7f957d29..7fa6060db9ca148091ecfb90c24c11418494b35e 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPAddress.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPAddress.java @@ -64,31 +64,30 @@ public class UDPAddress { private void parse(RouterAddress addr) { if (addr == null) return; - Properties opts = addr.getOptions(); - _host = opts.getProperty(PROP_HOST); + _host = addr.getOption(PROP_HOST); if (_host != null) _host = _host.trim(); try { - String port = opts.getProperty(PROP_PORT); + String port = addr.getOption(PROP_PORT); if (port != null) _port = Integer.parseInt(port); } catch (NumberFormatException nfe) { _port = -1; } - String key = opts.getProperty(PROP_INTRO_KEY); + String key = addr.getOption(PROP_INTRO_KEY); if (key != null) _introKey = Base64.decode(key.trim()); for (int i = MAX_INTRODUCERS; i >= 0; i--) { - String host = opts.getProperty(PROP_INTRO_HOST_PREFIX + i); + String host = addr.getOption(PROP_INTRO_HOST_PREFIX + i); if (host == null) continue; - String port = opts.getProperty(PROP_INTRO_PORT_PREFIX + i); + String port = addr.getOption(PROP_INTRO_PORT_PREFIX + i); if (port == null) continue; - String k = opts.getProperty(PROP_INTRO_KEY_PREFIX + i); + String k = addr.getOption(PROP_INTRO_KEY_PREFIX + i); if (k == null) continue; byte ikey[] = Base64.decode(k); if ( (ikey == null) || (ikey.length != SessionKey.KEYSIZE_BYTES) ) continue; - String t = opts.getProperty(PROP_INTRO_TAG_PREFIX + i); + String t = addr.getOption(PROP_INTRO_TAG_PREFIX + i); if (t == null) continue; int p = -1; try {