package net.i2p.data; /* * free (adj.): unencumbered; not under the control of others * Written by jrandom in 2003 and released into the public domain * with no warranty of any kind, either expressed or implied. * It probably won't make your computer catch on fire, or eat * your children, but it might. Use at your own risk. * */ import java.io.InputStream; import java.io.IOException; import java.util.Map; 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 * * Note that the public (encryption) key is essentially unused, since * "end-to-end" encryption was removed in 0.6. The public key in the * LeaseSet is used instead. * * 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 { 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<SigningPublicKey, Destination>(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); byte[] padding; if (c.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) { // convert SPK to new SPK and padding KeyCertificate kcert = c.toKeyCertificate(); padding = sk.getPadding(kcert); sk = sk.toTypedKey(kcert); c = kcert; } else { padding = null; } 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, padding); _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 */ public Destination(String s) throws DataFormatException { fromBase64(s); } /** * @since 0.9.9 */ private Destination(PublicKey pk, SigningPublicKey sk, Certificate c, byte[] padding) { _publicKey = pk; _signingKey = sk; _certificate = c; _padding = padding; } /** * Deprecated, used only by Packet.java in streaming. * Broken for sig types P521 and RSA before 0.9.15 * @return the written length (NOT the new offset) */ public int writeBytes(byte target[], int offset) { int cur = offset; System.arraycopy(_publicKey.getData(), 0, target, cur, PublicKey.KEYSIZE_BYTES); cur += PublicKey.KEYSIZE_BYTES; if (_padding != null) { System.arraycopy(_padding, 0, target, cur, _padding.length); cur += _padding.length; } int spkTrunc = Math.min(SigningPublicKey.KEYSIZE_BYTES, _signingKey.length()); System.arraycopy(_signingKey.getData(), 0, target, cur, spkTrunc); cur += spkTrunc; cur += _certificate.writeBytes(target, cur); return cur - offset; } /** * deprecated was used only by Packet.java in streaming, now unused * Warning - used by i2p-bote. Does NOT support alternate key types. DSA-SHA1 only. * * @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); cur += PublicKey.KEYSIZE_BYTES; _signingKey = SigningPublicKey.create(source, cur); cur += SigningPublicKey.KEYSIZE_BYTES; _certificate = Certificate.create(source, cur); cur += _certificate.size(); return cur - offset; } public int size() { int rv = PublicKey.KEYSIZE_BYTES + _signingKey.length(); if (_certificate.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) { // cert data included in keys rv += 7; if (_padding != null) rv += _padding.length; } else { rv += _certificate.size(); } return rv; } /** * 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; } /** * For convenience. * @return "{52 chars}.b32.i2p" or null if fields not set. * @since 0.9.14 */ public String toBase32() { try { return Base32.encode(getHash().getData()) + ".b32.i2p"; } catch (IllegalStateException ise) { return null; } } /** * Clear the cache. * @since 0.9.9 */ public static void clearCache() { synchronized(_cache) { _cache.clear(); } } @Override public boolean equals(Object o) { return super.equals(o) && (o instanceof Destination); } @Override public int hashCode() { // findbugs return super.hashCode(); } }