* Data Structures:

- Make Destination and RouterIdentity keys and cert immutable
   - Add Destination cache
This commit is contained in:
zzz
2013-10-07 13:04:01 +00:00
parent 890f40b2ac
commit 31f117e74c
17 changed files with 176 additions and 40 deletions

View File

@ -1,5 +1,6 @@
package net.i2p.client.streaming; package net.i2p.client.streaming;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
@ -580,12 +581,15 @@ class Packet {
cur += 2; cur += 2;
} }
if (isFlagSet(FLAG_FROM_INCLUDED)) { if (isFlagSet(FLAG_FROM_INCLUDED)) {
Destination optionFrom = new Destination(); ByteArrayInputStream bais = new ByteArrayInputStream(buffer, cur, length - cur);
try { try {
cur += optionFrom.readBytes(buffer, cur); Destination optionFrom = Destination.create(bais);
cur += optionFrom.size();
setOptionalFrom(optionFrom); setOptionalFrom(optionFrom);
} catch (IOException ioe) {
throw new IllegalArgumentException("Bad from field", ioe);
} catch (DataFormatException dfe) { } 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)) { if (isFlagSet(FLAG_MAX_PACKET_SIZE_INCLUDED)) {
@ -631,10 +635,10 @@ class Packet {
Log l = ctx.logManager().getLog(Packet.class); Log l = ctx.logManager().getLog(Packet.class);
if (l.shouldLog(Log.WARN)) if (l.shouldLog(Log.WARN))
l.warn("Signature failed on " + toString(), new Exception("moo")); l.warn("Signature failed on " + toString(), new Exception("moo"));
if (false) { //if (false) {
l.error(Base64.encode(buffer, 0, size)); // l.error(Base64.encode(buffer, 0, size));
l.error("Signature: " + Base64.encode(_optionSignature.getData())); // l.error("Signature: " + Base64.encode(_optionSignature.getData()));
} //}
} }
return ok; return ok;
} }

View File

@ -65,12 +65,9 @@ public final class I2PDatagramDissector {
this.valid = false; this.valid = false;
try { try {
rxDest = new Destination();
rxSign = new Signature();
// read destination // read destination
rxDest.readBytes(dgStream); rxDest = Destination.create(dgStream);
rxSign = new Signature();
// read signature // read signature
rxSign.readBytes(dgStream); rxSign.readBytes(dgStream);
@ -155,6 +152,7 @@ public final class I2PDatagramDissector {
* @return The Destination of the I2P repliable datagram sender * @return The Destination of the I2P repliable datagram sender
*/ */
public Destination extractSender() { public Destination extractSender() {
/****
if (this.rxDest == null) if (this.rxDest == null)
return null; return null;
Destination retDest = new Destination(); Destination retDest = new Destination();
@ -167,6 +165,9 @@ public final class I2PDatagramDissector {
} }
return retDest; return retDest;
****/
// dests are no longer modifiable
return rxDest;
} }
/** /**

View File

@ -1162,12 +1162,12 @@ public class BlockfileNamingService extends DummyNamingService {
/** returns null on error */ /** returns null on error */
public Object construct(byte[] b) { public Object construct(byte[] b) {
DestEntry rv = new DestEntry(); DestEntry rv = new DestEntry();
Destination dest = new Destination();
rv.dest = dest;
ByteArrayInputStream bais = new ByteArrayInputStream(b); ByteArrayInputStream bais = new ByteArrayInputStream(b);
try { try {
rv.props = DataHelper.readProperties(bais); 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) { } catch (IOException ioe) {
logError("DB Read Fail", ioe); logError("DB Read Fail", ioe);
return null; return null;

View File

@ -45,7 +45,7 @@ public class Certificate extends DataStructureImpl {
/** /**
* If null cert, return immutable static instance, else create new * 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 * @since 0.8.3
*/ */
public static Certificate create(byte[] data, int off) { public static Certificate create(byte[] data, int off) {

View File

@ -32,10 +32,13 @@ import java.io.OutputStream;
* @author jrandom * @author jrandom
*/ */
public interface DataStructure /* extends Serializable */ { public interface DataStructure /* extends Serializable */ {
/** /**
* Load up the current object with data from the given stream. Data loaded * Load up the current object with data from the given stream. Data loaded
* this way must match the I2P data structure specification. * 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 * @param in stream to read from
* @throws DataFormatException if the data is improperly formatted * @throws DataFormatException if the data is improperly formatted
* @throws IOException if there was a problem reading the stream * @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 * 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; public void fromBase64(String data) throws DataFormatException;
/**
* @return may be null if data is not set
*/
public byte[] toByteArray(); 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; public void fromByteArray(byte data[]) throws DataFormatException;
/** /**

View File

@ -32,16 +32,19 @@ public abstract class DataStructureImpl implements DataStructure {
return Base64.encode(data); return Base64.encode(data);
} }
public void fromBase64(String data) throws DataFormatException { public void fromBase64(String data) throws DataFormatException {
if (data == null) throw new DataFormatException("Null data passed in"); if (data == null) throw new DataFormatException("Null data passed in");
byte bytes[] = Base64.decode(data); byte bytes[] = Base64.decode(data);
fromByteArray(bytes); fromByteArray(bytes);
} }
public Hash calculateHash() { public Hash calculateHash() {
byte data[] = toByteArray(); byte data[] = toByteArray();
if (data != null) return SHA256Generator.getInstance().calculateHash(data); if (data != null) return SHA256Generator.getInstance().calculateHash(data);
return null; return null;
} }
public byte[] toByteArray() { public byte[] toByteArray() {
try { try {
ByteArrayOutputStream baos = new ByteArrayOutputStream(512); ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
@ -57,6 +60,7 @@ public abstract class DataStructureImpl implements DataStructure {
return null; return null;
} }
} }
public void fromByteArray(byte data[]) throws DataFormatException { public void fromByteArray(byte data[]) throws DataFormatException {
if (data == null) throw new DataFormatException("Null data passed in"); if (data == null) throw new DataFormatException("Null data passed in");
try { try {

View File

@ -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 * 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 * 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, * 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. * 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 * @author jrandom
*/ */
public class Destination extends KeysAndCert { 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 * alternative constructor which takes a base64 string representation
* @param s a Base64 representation of the destination, as (eg) is used in hosts.txt * @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); 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 * deprecated, used only by Packet.java in streaming
* @return the written length (NOT the new offset) * @return the written length (NOT the new offset)
@ -49,11 +109,17 @@ public class Destination extends KeysAndCert {
return cur - offset; 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 { public int readBytes(byte source[], int offset) throws DataFormatException {
if (source == null) throw new DataFormatException("Null source"); if (source == null) throw new DataFormatException("Null source");
if (source.length <= offset + PublicKey.KEYSIZE_BYTES + SigningPublicKey.KEYSIZE_BYTES) if (source.length <= offset + PublicKey.KEYSIZE_BYTES + SigningPublicKey.KEYSIZE_BYTES)
throw new DataFormatException("Not enough data (len=" + source.length + " off=" + offset + ")"); throw new DataFormatException("Not enough data (len=" + source.length + " off=" + offset + ")");
if (_publicKey != null || _signingKey != null || _certificate != null)
throw new IllegalStateException();
int cur = offset; int cur = offset;
_publicKey = PublicKey.create(source, cur); _publicKey = PublicKey.create(source, cur);
@ -71,4 +137,26 @@ public class Destination extends KeysAndCert {
public int size() { public int size() {
return PublicKey.KEYSIZE_BYTES + SigningPublicKey.KEYSIZE_BYTES + _certificate.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();
}
}
} }

View File

@ -24,6 +24,9 @@ import net.i2p.crypto.SHA256Generator;
* Implemented in 0.8.2 and retrofitted over Destination and RouterIdentity. * Implemented in 0.8.2 and retrofitted over Destination and RouterIdentity.
* There's actually no difference between the two of them. * 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 * @since 0.8.2
* @author zzz * @author zzz
*/ */
@ -37,7 +40,12 @@ public class KeysAndCert extends DataStructureImpl {
return _certificate; return _certificate;
} }
/**
* @throws IllegalStateException if was already set
*/
public void setCertificate(Certificate cert) { public void setCertificate(Certificate cert) {
if (_certificate != null)
throw new IllegalStateException();
_certificate = cert; _certificate = cert;
__calculatedHash = null; __calculatedHash = null;
} }
@ -46,7 +54,12 @@ public class KeysAndCert extends DataStructureImpl {
return _publicKey; return _publicKey;
} }
/**
* @throws IllegalStateException if was already set
*/
public void setPublicKey(PublicKey key) { public void setPublicKey(PublicKey key) {
if (_publicKey != null)
throw new IllegalStateException();
_publicKey = key; _publicKey = key;
__calculatedHash = null; __calculatedHash = null;
} }
@ -55,20 +68,24 @@ public class KeysAndCert extends DataStructureImpl {
return _signingKey; return _signingKey;
} }
/**
* @throws IllegalStateException if was already set
*/
public void setSigningPublicKey(SigningPublicKey key) { public void setSigningPublicKey(SigningPublicKey key) {
if (_signingKey != null)
throw new IllegalStateException();
_signingKey = key; _signingKey = key;
__calculatedHash = null; __calculatedHash = null;
} }
/**
* @throws IllegalStateException if data already set
*/
public void readBytes(InputStream in) throws DataFormatException, IOException { public void readBytes(InputStream in) throws DataFormatException, IOException {
//_publicKey = new PublicKey(); if (_publicKey != null || _signingKey != null || _certificate != null)
//_publicKey.readBytes(in); throw new IllegalStateException();
_publicKey = PublicKey.create(in); _publicKey = PublicKey.create(in);
//_signingKey = new SigningPublicKey();
//_signingKey.readBytes(in);
_signingKey = SigningPublicKey.create(in); _signingKey = SigningPublicKey.create(in);
//_certificate = new Certificate();
//_certificate.readBytes(in);
_certificate = Certificate.create(in); _certificate = Certificate.create(in);
__calculatedHash = null; __calculatedHash = null;
} }
@ -86,9 +103,10 @@ public class KeysAndCert extends DataStructureImpl {
if (object == this) return true; if (object == this) return true;
if ((object == null) || !(object instanceof KeysAndCert)) return false; if ((object == null) || !(object instanceof KeysAndCert)) return false;
KeysAndCert ident = (KeysAndCert) object; KeysAndCert ident = (KeysAndCert) object;
return DataHelper.eq(_certificate, ident._certificate) return
&& DataHelper.eq(_signingKey, ident._signingKey) DataHelper.eq(_signingKey, ident._signingKey)
&& DataHelper.eq(_publicKey, ident._publicKey); && 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 */ /** the public key has enough randomness in it to use it by itself for speed */

View File

@ -311,8 +311,7 @@ public class LeaseSet extends DatabaseEntry {
public void readBytes(InputStream in) throws DataFormatException, IOException { public void readBytes(InputStream in) throws DataFormatException, IOException {
if (_destination != null) if (_destination != null)
throw new IllegalStateException(); throw new IllegalStateException();
_destination = new Destination(); _destination = Destination.create(in);
_destination.readBytes(in);
_encryptionKey = PublicKey.create(in); _encryptionKey = PublicKey.create(in);
_signingKey = SigningPublicKey.create(in); _signingKey = SigningPublicKey.create(in);
int numLeases = (int) DataHelper.readLong(in, 1); int numLeases = (int) DataHelper.readLong(in, 1);

View File

@ -26,8 +26,10 @@ public class PublicKey extends SimpleDataStructure {
private static final SDSCache<PublicKey> _cache = new SDSCache(PublicKey.class, KEYSIZE_BYTES, CACHE_SIZE); private static final SDSCache<PublicKey> _cache = new SDSCache(PublicKey.class, KEYSIZE_BYTES, CACHE_SIZE);
/** /**
* Pull from cache or return new * Pull from cache or return new.
* @throws AIOOBE if not enough bytes * Deprecated - used only by deprecated Destination.readBytes(data, off)
*
* @throws AIOOBE if not enough bytes, FIXME should throw DataFormatException
* @since 0.8.3 * @since 0.8.3
*/ */
public static PublicKey create(byte[] data, int off) { public static PublicKey create(byte[] data, int off) {

View File

@ -13,6 +13,9 @@ package net.i2p.data;
* Defines the unique identifier of a router, including any certificate or * Defines the unique identifier of a router, including any certificate or
* public key. * 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 * @author jrandom
*/ */
public class RouterIdentity extends KeysAndCert { public class RouterIdentity extends KeysAndCert {

View File

@ -139,7 +139,7 @@ public class SDSCache<V extends SimpleDataStructure> {
found = 0; found = 0;
} }
} }
I2PAppContext.getGlobalContext().statManager().addRateData(_statName, found, 0); I2PAppContext.getGlobalContext().statManager().addRateData(_statName, found);
return rv; return rv;
} }

View File

@ -32,8 +32,10 @@ public class SigningPublicKey extends SimpleDataStructure {
private final SigType _type; private final SigType _type;
/** /**
* Pull from cache or return new * Pull from cache or return new.
* @throws AIOOBE if not enough bytes * Deprecated - used only by deprecated Destination.readBytes(data, off)
*
* @throws AIOOBE if not enough bytes, FIXME should throw DataFormatException
* @since 0.8.3 * @since 0.8.3
*/ */
public static SigningPublicKey create(byte[] data, int off) { public static SigningPublicKey create(byte[] data, int off) {

View File

@ -62,9 +62,7 @@ public class DestReplyMessage extends I2CPMessageImpl {
if (size == Hash.HASH_LENGTH) { if (size == Hash.HASH_LENGTH) {
_hash = Hash.create(in); _hash = Hash.create(in);
} else { } else {
Destination d = new Destination(); _dest = Destination.create(in);
d.readBytes(in);
_dest = d;
} }
} catch (DataFormatException dfe) { } catch (DataFormatException dfe) {
_dest = null; _dest = null;

View File

@ -92,8 +92,7 @@ public class SendMessageMessage extends I2CPMessageImpl {
try { try {
_sessionId = new SessionId(); _sessionId = new SessionId();
_sessionId.readBytes(in); _sessionId.readBytes(in);
_destination = new Destination(); _destination = Destination.create(in);
_destination.readBytes(in);
_payload = new Payload(); _payload = new Payload();
_payload.readBytes(in); _payload.readBytes(in);
_nonce = DataHelper.readLong(in, 4); _nonce = DataHelper.readLong(in, 4);

View File

@ -186,7 +186,7 @@ public class SessionConfig extends DataStructureImpl {
} }
public void readBytes(InputStream rawConfig) throws DataFormatException, IOException { public void readBytes(InputStream rawConfig) throws DataFormatException, IOException {
_destination = new Destination(); _destination = Destination.create(rawConfig);
_destination.readBytes(rawConfig); _destination.readBytes(rawConfig);
_options = DataHelper.readProperties(rawConfig); _options = DataHelper.readProperties(rawConfig);
_creationDate = DataHelper.readDate(rawConfig); _creationDate = DataHelper.readDate(rawConfig);

View File

@ -30,6 +30,7 @@ import java.util.concurrent.ConcurrentHashMap;
import net.i2p.data.Certificate; import net.i2p.data.Certificate;
import net.i2p.data.DataFormatException; import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.data.Destination;
import net.i2p.data.RouterInfo; import net.i2p.data.RouterInfo;
import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPrivateKey;
import net.i2p.data.i2np.GarlicMessage; import net.i2p.data.i2np.GarlicMessage;
@ -312,6 +313,7 @@ public class Router implements RouterClock.ClockShiftListener {
public static final void clearCaches() { public static final void clearCaches() {
ByteCache.clearAll(); ByteCache.clearAll();
SimpleByteCache.clearAll(); SimpleByteCache.clearAll();
Destination.clearCache();
} }
/** /**