From 31f117e74ce97392aa6724a99b1188f0115e63f0 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Mon, 7 Oct 2013 13:04:01 +0000 Subject: [PATCH] * Data Structures: - Make Destination and RouterIdentity keys and cert immutable - Add Destination cache --- .../src/net/i2p/client/streaming/Packet.java | 18 ++-- .../client/datagram/I2PDatagramDissector.java | 11 ++- .../client/naming/BlockfileNamingService.java | 6 +- core/java/src/net/i2p/data/Certificate.java | 2 +- core/java/src/net/i2p/data/DataStructure.java | 16 ++++ .../src/net/i2p/data/DataStructureImpl.java | 4 + core/java/src/net/i2p/data/Destination.java | 92 ++++++++++++++++++- core/java/src/net/i2p/data/KeysAndCert.java | 36 ++++++-- core/java/src/net/i2p/data/LeaseSet.java | 3 +- core/java/src/net/i2p/data/PublicKey.java | 6 +- .../java/src/net/i2p/data/RouterIdentity.java | 3 + core/java/src/net/i2p/data/SDSCache.java | 2 +- .../src/net/i2p/data/SigningPublicKey.java | 6 +- .../net/i2p/data/i2cp/DestReplyMessage.java | 4 +- .../net/i2p/data/i2cp/SendMessageMessage.java | 3 +- .../src/net/i2p/data/i2cp/SessionConfig.java | 2 +- router/java/src/net/i2p/router/Router.java | 2 + 17 files changed, 176 insertions(+), 40 deletions(-) diff --git a/apps/streaming/java/src/net/i2p/client/streaming/Packet.java b/apps/streaming/java/src/net/i2p/client/streaming/Packet.java index 05c3f2b2eb..6e10805f39 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/Packet.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/Packet.java @@ -1,5 +1,6 @@ package net.i2p.client.streaming; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Arrays; @@ -580,12 +581,15 @@ class Packet { cur += 2; } if (isFlagSet(FLAG_FROM_INCLUDED)) { - Destination optionFrom = new Destination(); + ByteArrayInputStream bais = new ByteArrayInputStream(buffer, cur, length - cur); try { - cur += optionFrom.readBytes(buffer, cur); + Destination optionFrom = Destination.create(bais); + cur += optionFrom.size(); setOptionalFrom(optionFrom); + } catch (IOException ioe) { + throw new IllegalArgumentException("Bad from field", ioe); } catch (DataFormatException dfe) { - throw new IllegalArgumentException("Bad from field: " + dfe.getMessage()); + throw new IllegalArgumentException("Bad from field", dfe); } } if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) { @@ -631,10 +635,10 @@ class Packet { Log l = ctx.logManager().getLog(Packet.class); if (l.shouldLog(Log.WARN)) l.warn("Signature failed on " + toString(), new Exception("moo")); - if (false) { - l.error(Base64.encode(buffer, 0, size)); - l.error("Signature: " + Base64.encode(_optionSignature.getData())); - } + //if (false) { + // l.error(Base64.encode(buffer, 0, size)); + // l.error("Signature: " + Base64.encode(_optionSignature.getData())); + //} } return ok; } diff --git a/core/java/src/net/i2p/client/datagram/I2PDatagramDissector.java b/core/java/src/net/i2p/client/datagram/I2PDatagramDissector.java index 945043ea6c..00c897a6f5 100644 --- a/core/java/src/net/i2p/client/datagram/I2PDatagramDissector.java +++ b/core/java/src/net/i2p/client/datagram/I2PDatagramDissector.java @@ -65,12 +65,9 @@ public final class I2PDatagramDissector { this.valid = false; try { - rxDest = new Destination(); - rxSign = new Signature(); - // read destination - rxDest.readBytes(dgStream); - + rxDest = Destination.create(dgStream); + rxSign = new Signature(); // read signature rxSign.readBytes(dgStream); @@ -155,6 +152,7 @@ public final class I2PDatagramDissector { * @return The Destination of the I2P repliable datagram sender */ public Destination extractSender() { + /**** if (this.rxDest == null) return null; Destination retDest = new Destination(); @@ -167,6 +165,9 @@ public final class I2PDatagramDissector { } return retDest; + ****/ + // dests are no longer modifiable + return rxDest; } /** diff --git a/core/java/src/net/i2p/client/naming/BlockfileNamingService.java b/core/java/src/net/i2p/client/naming/BlockfileNamingService.java index ff92edf083..d1fd7530a3 100644 --- a/core/java/src/net/i2p/client/naming/BlockfileNamingService.java +++ b/core/java/src/net/i2p/client/naming/BlockfileNamingService.java @@ -1162,12 +1162,12 @@ public class BlockfileNamingService extends DummyNamingService { /** returns null on error */ public Object construct(byte[] b) { DestEntry rv = new DestEntry(); - Destination dest = new Destination(); - rv.dest = dest; ByteArrayInputStream bais = new ByteArrayInputStream(b); try { rv.props = DataHelper.readProperties(bais); - dest.readBytes(bais); + //dest.readBytes(bais); + // Will this flush the dest cache too much? + rv.dest = Destination.create(bais); } catch (IOException ioe) { logError("DB Read Fail", ioe); return null; diff --git a/core/java/src/net/i2p/data/Certificate.java b/core/java/src/net/i2p/data/Certificate.java index 5c61df0472..ef6484f31e 100644 --- a/core/java/src/net/i2p/data/Certificate.java +++ b/core/java/src/net/i2p/data/Certificate.java @@ -45,7 +45,7 @@ public class Certificate extends DataStructureImpl { /** * If null cert, return immutable static instance, else create new - * @throws AIOOBE if not enough bytes + * @throws AIOOBE if not enough bytes, FIXME should throw DataFormatException * @since 0.8.3 */ public static Certificate create(byte[] data, int off) { diff --git a/core/java/src/net/i2p/data/DataStructure.java b/core/java/src/net/i2p/data/DataStructure.java index 0b9e65ba2e..ab7086492a 100644 --- a/core/java/src/net/i2p/data/DataStructure.java +++ b/core/java/src/net/i2p/data/DataStructure.java @@ -32,10 +32,13 @@ import java.io.OutputStream; * @author jrandom */ public interface DataStructure /* extends Serializable */ { + /** * Load up the current object with data from the given stream. Data loaded * this way must match the I2P data structure specification. * + * Warning - many classes will throw IllegalStateException if data is already set. + * * @param in stream to read from * @throws DataFormatException if the data is improperly formatted * @throws IOException if there was a problem reading the stream @@ -61,11 +64,24 @@ public interface DataStructure /* extends Serializable */ { /** * Load the structure from the base 64 encoded data provided * + * Warning - many classes will throw IllegalStateException if data is already set. + * Warning - many classes will throw IllegalArgumentException if data is the wrong size. + * */ public void fromBase64(String data) throws DataFormatException; + /** + * @return may be null if data is not set + */ public byte[] toByteArray(); + /** + * Load the structure from the data provided + * + * Warning - many classes will throw IllegalStateException if data is already set. + * Warning - many classes will throw IllegalArgumentException if data is the wrong size. + * + */ public void fromByteArray(byte data[]) throws DataFormatException; /** diff --git a/core/java/src/net/i2p/data/DataStructureImpl.java b/core/java/src/net/i2p/data/DataStructureImpl.java index 1ca043672e..264f591889 100644 --- a/core/java/src/net/i2p/data/DataStructureImpl.java +++ b/core/java/src/net/i2p/data/DataStructureImpl.java @@ -32,16 +32,19 @@ public abstract class DataStructureImpl implements DataStructure { return Base64.encode(data); } + public void fromBase64(String data) throws DataFormatException { if (data == null) throw new DataFormatException("Null data passed in"); byte bytes[] = Base64.decode(data); fromByteArray(bytes); } + public Hash calculateHash() { byte data[] = toByteArray(); if (data != null) return SHA256Generator.getInstance().calculateHash(data); return null; } + public byte[] toByteArray() { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(512); @@ -57,6 +60,7 @@ public abstract class DataStructureImpl implements DataStructure { return null; } } + public void fromByteArray(byte data[]) throws DataFormatException { if (data == null) throw new DataFormatException("Null data passed in"); try { diff --git a/core/java/src/net/i2p/data/Destination.java b/core/java/src/net/i2p/data/Destination.java index 6a1764805f..716140edde 100644 --- a/core/java/src/net/i2p/data/Destination.java +++ b/core/java/src/net/i2p/data/Destination.java @@ -9,6 +9,14 @@ package net.i2p.data; * */ +import java.io.InputStream; +import java.io.IOException; +import java.util.Map; + +import net.i2p.I2PAppContext; +import net.i2p.util.LHMCache; +import net.i2p.util.SystemVersion; + /** * Defines an end point in the I2P network. The Destination may move around * in the network, but messages sent to the Destination will find it @@ -20,13 +28,56 @@ package net.i2p.data; * The first bytes of the public key are used for the IV for leaseset encryption, * but that encryption is poorly designed and should be deprecated. * + * As of 0.9.9 this data structure is immutable after the two keys and the certificate + * are set; attempts to change them will throw an IllegalStateException. + * * @author jrandom */ public class Destination extends KeysAndCert { - public Destination() { + private String _cachedB64; + + //private static final boolean STATS = true; + private static final int CACHE_SIZE; + private static final int MIN_CACHE_SIZE = 32; + private static final int MAX_CACHE_SIZE = 512; + static { + long maxMemory = SystemVersion.getMaxMemory(); + CACHE_SIZE = (int) Math.min(MAX_CACHE_SIZE, Math.max(MIN_CACHE_SIZE, maxMemory / 512*1024)); + //if (STATS) + // I2PAppContext.getGlobalContext().statManager().createRateStat("DestCache", "Hit rate", "Router", new long[] { 10*60*1000 }); + } + + private static final Map<SigningPublicKey, Destination> _cache = new LHMCache(CACHE_SIZE); + + /** + * Pull from cache or return new + * @since 0.9.9 + */ + public static Destination create(InputStream in) throws DataFormatException, IOException { + PublicKey pk = PublicKey.create(in); + SigningPublicKey sk = SigningPublicKey.create(in); + Certificate c = Certificate.create(in); + Destination rv; + synchronized(_cache) { + rv = _cache.get(sk); + } + if (rv != null && rv.getPublicKey().equals(pk) && rv.getCertificate().equals(c)) { + //if (STATS) + // I2PAppContext.getGlobalContext().statManager().addRateData("DestCache", 1); + return rv; + } + //if (STATS) + // I2PAppContext.getGlobalContext().statManager().addRateData("DestCache", 0); + rv = new Destination(pk, sk, c); + synchronized(_cache) { + _cache.put(sk, rv); + } + return rv; } + public Destination() {} + /** * alternative constructor which takes a base64 string representation * @param s a Base64 representation of the destination, as (eg) is used in hosts.txt @@ -35,6 +86,15 @@ public class Destination extends KeysAndCert { fromBase64(s); } + /** + * @since 0.9.9 + */ + private Destination(PublicKey pk, SigningPublicKey sk, Certificate c) { + _publicKey = pk; + _signingKey = sk; + _certificate = c; + } + /** * deprecated, used only by Packet.java in streaming * @return the written length (NOT the new offset) @@ -49,11 +109,17 @@ public class Destination extends KeysAndCert { return cur - offset; } - /** deprecated, used only by Packet.java in streaming */ + /** + * @deprecated, was used only by Packet.java in streaming, now unused + * + * @throws IllegalStateException if data already set + */ public int readBytes(byte source[], int offset) throws DataFormatException { if (source == null) throw new DataFormatException("Null source"); if (source.length <= offset + PublicKey.KEYSIZE_BYTES + SigningPublicKey.KEYSIZE_BYTES) throw new DataFormatException("Not enough data (len=" + source.length + " off=" + offset + ")"); + if (_publicKey != null || _signingKey != null || _certificate != null) + throw new IllegalStateException(); int cur = offset; _publicKey = PublicKey.create(source, cur); @@ -71,4 +137,26 @@ public class Destination extends KeysAndCert { public int size() { return PublicKey.KEYSIZE_BYTES + SigningPublicKey.KEYSIZE_BYTES + _certificate.size(); } + + /** + * Cache it. + * Useful in I2PTunnelHTTPServer where it is added to the headers + * @since 0.9.9 + */ + @Override + public String toBase64() { + if (_cachedB64 == null) + _cachedB64 = super.toBase64(); + return _cachedB64; + } + + /** + * Clear the cache. + * @since 0.9.9 + */ + public static void clearCache() { + synchronized(_cache) { + _cache.clear(); + } + } } diff --git a/core/java/src/net/i2p/data/KeysAndCert.java b/core/java/src/net/i2p/data/KeysAndCert.java index 933eaa55e9..429d89be7c 100644 --- a/core/java/src/net/i2p/data/KeysAndCert.java +++ b/core/java/src/net/i2p/data/KeysAndCert.java @@ -24,6 +24,9 @@ import net.i2p.crypto.SHA256Generator; * Implemented in 0.8.2 and retrofitted over Destination and RouterIdentity. * There's actually no difference between the two of them. * + * As of 0.9.9 this data structure is immutable after the two keys and the certificate + * are set; attempts to change them will throw an IllegalStateException. + * * @since 0.8.2 * @author zzz */ @@ -37,7 +40,12 @@ public class KeysAndCert extends DataStructureImpl { return _certificate; } + /** + * @throws IllegalStateException if was already set + */ public void setCertificate(Certificate cert) { + if (_certificate != null) + throw new IllegalStateException(); _certificate = cert; __calculatedHash = null; } @@ -46,7 +54,12 @@ public class KeysAndCert extends DataStructureImpl { return _publicKey; } + /** + * @throws IllegalStateException if was already set + */ public void setPublicKey(PublicKey key) { + if (_publicKey != null) + throw new IllegalStateException(); _publicKey = key; __calculatedHash = null; } @@ -55,20 +68,24 @@ public class KeysAndCert extends DataStructureImpl { return _signingKey; } + /** + * @throws IllegalStateException if was already set + */ public void setSigningPublicKey(SigningPublicKey key) { + if (_signingKey != null) + throw new IllegalStateException(); _signingKey = key; __calculatedHash = null; } + /** + * @throws IllegalStateException if data already set + */ public void readBytes(InputStream in) throws DataFormatException, IOException { - //_publicKey = new PublicKey(); - //_publicKey.readBytes(in); + if (_publicKey != null || _signingKey != null || _certificate != null) + throw new IllegalStateException(); _publicKey = PublicKey.create(in); - //_signingKey = new SigningPublicKey(); - //_signingKey.readBytes(in); _signingKey = SigningPublicKey.create(in); - //_certificate = new Certificate(); - //_certificate.readBytes(in); _certificate = Certificate.create(in); __calculatedHash = null; } @@ -86,9 +103,10 @@ public class KeysAndCert extends DataStructureImpl { if (object == this) return true; if ((object == null) || !(object instanceof KeysAndCert)) return false; KeysAndCert ident = (KeysAndCert) object; - return DataHelper.eq(_certificate, ident._certificate) - && DataHelper.eq(_signingKey, ident._signingKey) - && DataHelper.eq(_publicKey, ident._publicKey); + return + DataHelper.eq(_signingKey, ident._signingKey) + && DataHelper.eq(_publicKey, ident._publicKey) + && DataHelper.eq(_certificate, ident._certificate); } /** the public key has enough randomness in it to use it by itself for speed */ diff --git a/core/java/src/net/i2p/data/LeaseSet.java b/core/java/src/net/i2p/data/LeaseSet.java index a339ed6944..e497a1e08e 100644 --- a/core/java/src/net/i2p/data/LeaseSet.java +++ b/core/java/src/net/i2p/data/LeaseSet.java @@ -311,8 +311,7 @@ public class LeaseSet extends DatabaseEntry { public void readBytes(InputStream in) throws DataFormatException, IOException { if (_destination != null) throw new IllegalStateException(); - _destination = new Destination(); - _destination.readBytes(in); + _destination = Destination.create(in); _encryptionKey = PublicKey.create(in); _signingKey = SigningPublicKey.create(in); int numLeases = (int) DataHelper.readLong(in, 1); diff --git a/core/java/src/net/i2p/data/PublicKey.java b/core/java/src/net/i2p/data/PublicKey.java index 556ca95e66..2a719ffc55 100644 --- a/core/java/src/net/i2p/data/PublicKey.java +++ b/core/java/src/net/i2p/data/PublicKey.java @@ -26,8 +26,10 @@ public class PublicKey extends SimpleDataStructure { private static final SDSCache<PublicKey> _cache = new SDSCache(PublicKey.class, KEYSIZE_BYTES, CACHE_SIZE); /** - * Pull from cache or return new - * @throws AIOOBE if not enough bytes + * Pull from cache or return new. + * Deprecated - used only by deprecated Destination.readBytes(data, off) + * + * @throws AIOOBE if not enough bytes, FIXME should throw DataFormatException * @since 0.8.3 */ public static PublicKey create(byte[] data, int off) { diff --git a/core/java/src/net/i2p/data/RouterIdentity.java b/core/java/src/net/i2p/data/RouterIdentity.java index 4666242ea8..9ee7a15fa7 100644 --- a/core/java/src/net/i2p/data/RouterIdentity.java +++ b/core/java/src/net/i2p/data/RouterIdentity.java @@ -13,6 +13,9 @@ package net.i2p.data; * Defines the unique identifier of a router, including any certificate or * public key. * + * As of 0.9.9 this data structure is immutable after the two keys and the certificate + * are set; attempts to change them will throw an IllegalStateException. + * * @author jrandom */ public class RouterIdentity extends KeysAndCert { diff --git a/core/java/src/net/i2p/data/SDSCache.java b/core/java/src/net/i2p/data/SDSCache.java index b31f5b14c7..464ca7f971 100644 --- a/core/java/src/net/i2p/data/SDSCache.java +++ b/core/java/src/net/i2p/data/SDSCache.java @@ -139,7 +139,7 @@ public class SDSCache<V extends SimpleDataStructure> { found = 0; } } - I2PAppContext.getGlobalContext().statManager().addRateData(_statName, found, 0); + I2PAppContext.getGlobalContext().statManager().addRateData(_statName, found); return rv; } diff --git a/core/java/src/net/i2p/data/SigningPublicKey.java b/core/java/src/net/i2p/data/SigningPublicKey.java index a5119e7952..d1c1a4687f 100644 --- a/core/java/src/net/i2p/data/SigningPublicKey.java +++ b/core/java/src/net/i2p/data/SigningPublicKey.java @@ -32,8 +32,10 @@ public class SigningPublicKey extends SimpleDataStructure { private final SigType _type; /** - * Pull from cache or return new - * @throws AIOOBE if not enough bytes + * Pull from cache or return new. + * Deprecated - used only by deprecated Destination.readBytes(data, off) + * + * @throws AIOOBE if not enough bytes, FIXME should throw DataFormatException * @since 0.8.3 */ public static SigningPublicKey create(byte[] data, int off) { diff --git a/core/java/src/net/i2p/data/i2cp/DestReplyMessage.java b/core/java/src/net/i2p/data/i2cp/DestReplyMessage.java index ee0f1b0d2f..ac5d43af61 100644 --- a/core/java/src/net/i2p/data/i2cp/DestReplyMessage.java +++ b/core/java/src/net/i2p/data/i2cp/DestReplyMessage.java @@ -62,9 +62,7 @@ public class DestReplyMessage extends I2CPMessageImpl { if (size == Hash.HASH_LENGTH) { _hash = Hash.create(in); } else { - Destination d = new Destination(); - d.readBytes(in); - _dest = d; + _dest = Destination.create(in); } } catch (DataFormatException dfe) { _dest = null; diff --git a/core/java/src/net/i2p/data/i2cp/SendMessageMessage.java b/core/java/src/net/i2p/data/i2cp/SendMessageMessage.java index c067bdc610..ef0e941946 100644 --- a/core/java/src/net/i2p/data/i2cp/SendMessageMessage.java +++ b/core/java/src/net/i2p/data/i2cp/SendMessageMessage.java @@ -92,8 +92,7 @@ public class SendMessageMessage extends I2CPMessageImpl { try { _sessionId = new SessionId(); _sessionId.readBytes(in); - _destination = new Destination(); - _destination.readBytes(in); + _destination = Destination.create(in); _payload = new Payload(); _payload.readBytes(in); _nonce = DataHelper.readLong(in, 4); diff --git a/core/java/src/net/i2p/data/i2cp/SessionConfig.java b/core/java/src/net/i2p/data/i2cp/SessionConfig.java index 663f7fcd49..5f0cc48119 100644 --- a/core/java/src/net/i2p/data/i2cp/SessionConfig.java +++ b/core/java/src/net/i2p/data/i2cp/SessionConfig.java @@ -186,7 +186,7 @@ public class SessionConfig extends DataStructureImpl { } public void readBytes(InputStream rawConfig) throws DataFormatException, IOException { - _destination = new Destination(); + _destination = Destination.create(rawConfig); _destination.readBytes(rawConfig); _options = DataHelper.readProperties(rawConfig); _creationDate = DataHelper.readDate(rawConfig); diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index c70b84f8f1..9a27b61b4e 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -30,6 +30,7 @@ import java.util.concurrent.ConcurrentHashMap; import net.i2p.data.Certificate; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; +import net.i2p.data.Destination; import net.i2p.data.RouterInfo; import net.i2p.data.SigningPrivateKey; import net.i2p.data.i2np.GarlicMessage; @@ -312,6 +313,7 @@ public class Router implements RouterClock.ClockShiftListener { public static final void clearCaches() { ByteCache.clearAll(); SimpleByteCache.clearAll(); + Destination.clearCache(); } /** -- GitLab