From 8fa720539a9e470f81c411db2a8fa0b469f22768 Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Fri, 23 Dec 2011 21:41:58 +0000
Subject: [PATCH] RouterInfo, RouterAddress: Optimizations and integrity checks
     - Remove synchronization     - Do not allow contents to change after
 being set, throw IllegalStateException     - Do not copy contents out in
 getters     - Make options final     - Add getOption() and getOptionsMap()
 methods

---
 .../src/net/i2p/router/web/NetDbRenderer.java |  14 +-
 core/java/src/net/i2p/data/DataHelper.java    |  13 ++
 core/java/src/net/i2p/data/DatabaseEntry.java |   6 +
 core/java/src/net/i2p/data/RouterAddress.java |  61 ++++-
 core/java/src/net/i2p/data/RouterInfo.java    | 217 ++++++++++--------
 router/java/src/net/i2p/router/Blocklist.java |   4 +-
 .../networkdb/PublishLocalRouterInfoJob.java  |   4 +-
 .../kademlia/FloodfillMonitorJob.java         |   3 +-
 .../KademliaNetworkDatabaseFacade.java        |   5 +-
 .../router/peermanager/ProfileOrganizer.java  |   7 +-
 .../router/transport/TransportManager.java    |   5 +-
 .../router/transport/ntcp/NTCPAddress.java    |  10 +-
 .../i2p/router/transport/udp/UDPAddress.java  |  15 +-
 13 files changed, 213 insertions(+), 151 deletions(-)

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 7e44cd6e83..1a207763a1 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/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java
index 95dd1875d2..420775065a 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 3c7b665ca2..943fdbd733 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 91a05e6d4e..012ef0194e 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 ca44204030..6c34ad2023 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 f76e6eb499..153b14e7c9 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/networkdb/PublishLocalRouterInfoJob.java b/router/java/src/net/i2p/router/networkdb/PublishLocalRouterInfoJob.java
index 18612c3cd1..cfcdd8a34b 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 5c719d76ca..2d23532960 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 9c86b7f01b..8a44d5a4e6 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";
@@ -824,7 +823,7 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade {
         
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("RouterInfo " + key.toBase64() + " is stored with "
-                       + routerInfo.getOptions().size() + " options on "
+                       + routerInfo.getOptionsMap().size() + " options on "
                        + new Date(routerInfo.getPublished()));
     
         _context.peerManager().setCapabilities(key, routerInfo.getCapabilities());
diff --git a/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java b/router/java/src/net/i2p/router/peermanager/ProfileOrganizer.java
index 093ca8ed2d..8b14d22a0f 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 ae4bfe224c..8b6b29713d 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 e13bcc31c7..acced89f43 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 bcee07605e..7fa6060db9 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 { 
-- 
GitLab