* 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

@ -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;
}
/**

View File

@ -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;

View File

@ -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) {

View File

@ -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;
/**

View File

@ -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 {

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
* 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();
}
}
}

View File

@ -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 */

View File

@ -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);

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);
/**
* 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) {

View File

@ -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 {

View File

@ -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;
}

View File

@ -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) {

View File

@ -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;

View File

@ -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);

View File

@ -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);