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 05c3f2b2eb844622ccf44219f7f6f7b47e27e1a6..6e10805f39824bf6a8ac7ec80ed739f69388a5b2 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 945043ea6c145ade9fb7f8c0331fac40ddd1bbb8..00c897a6f5d4a637afe0c8eb1f6795d593294835 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 ff92edf0834a28ff59d8f6b13b9204a5f4cf182e..d1fd7530a3054bae6e35f03c70fd00f199da4371 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 5c61df0472ea473634877ed8a1142933ab817ba8..ef6484f31ed4d147e47fb06562ad6e1aa7b75c12 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 0b9e65ba2e744b5ad41f7e8942f12b2c7e57592d..ab7086492a45c040cc09f44e7d53f82549c1dd9d 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 1ca043672ef2ae14f818bdc1921d113e0ae40b52..264f5918893f109d6c2f157c6c56c34943de619d 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 6a1764805fa58cd0e3b3484edb16750de4b294db..716140edde30204911130dc27a81442684aecc44 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 933eaa55e9dd031f3f1c91c283c02a9fa1c88a10..429d89be7c56bbeef7d195e9cbf1d12351ead5b8 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 a339ed694457a94aa4950223d8c6c5f5bfea8996..e497a1e08ec09373d0fd60bc86dc939396de8359 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 556ca95e66ac468ee582ba49272518e98aefc5ec..2a719ffc55c8833c8daff6be9fd515ee45be5603 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 4666242ea88dd841f569f5e7aa2769b6a840d6eb..9ee7a15fa7a3f2d27fcfb875e6e160aec3d45fba 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 b31f5b14c73098fbb3df5ebb011a3f6999e3d2ea..464ca7f971032c4a9e6909a262e92872798dfb46 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 a5119e7952010882e716a0fd96ceae660911d007..d1c1a4687f627e4c617e28e239ed0d023f71c983 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 ee0f1b0d2f01996582e6bb8cf5feea0fe56dbdb0..ac5d43af61fdaf3976038426acbc4a3d04b44cac 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 c067bdc61033f73776138828a410c1f11bfcbba7..ef0e9419468c1bddbfcb7066e259f81062a7c3a5 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 663f7fcd499c05dee796cae0134fdb64e4679c47..5f0cc4811915f965d7c8ebc687edc1d4ee2111d2 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 c70b84f8f1f3c16722fa6dc27eb13a2d1d75e721..9a27b61b4ef306e7540d9259a9af195fd0197899 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(); } /**