diff --git a/core/java/src/net/i2p/data/Address.java b/core/java/src/net/i2p/data/Address.java index 777be8661..18ad7f946 100644 --- a/core/java/src/net/i2p/data/Address.java +++ b/core/java/src/net/i2p/data/Address.java @@ -4,6 +4,10 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +/** + * ???????? + * deprecated unused except by naming/FilesystemAddressDB, which is unused. + */ public class Address extends DataStructureImpl { private String _hostname; private Destination _destination; diff --git a/core/java/src/net/i2p/data/DatabaseEntry.java b/core/java/src/net/i2p/data/DatabaseEntry.java new file mode 100644 index 000000000..3c7b665ca --- /dev/null +++ b/core/java/src/net/i2p/data/DatabaseEntry.java @@ -0,0 +1,176 @@ +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 net.i2p.crypto.DSAEngine; + +/** + * Base implementation of common methods for the two data structures + * that are stored in the netDb, i.e. LeaseSet and RouterInfo. + * Implemented in 0.8.2 and retrofitted over LeaseSet and RouterInfo. + * + * This consolidates some common code and makes it easier to + * implement the NetDB and I2NP without doing instanceof all over the place. + * + * DatabaseEntries have a SHA256 hash, a routing key, a timestamp, and + * signatures. + * + * @author zzz + * @since 0.8.2 + */ +public abstract class DatabaseEntry extends DataStructureImpl { + /** these are the same as in i2np's DatabaseStoreMessage */ + public final static int KEY_TYPE_ROUTERINFO = 0; + public final static int KEY_TYPE_LEASESET = 1; + + protected volatile Signature _signature; + protected volatile Hash _currentRoutingKey; + protected volatile byte[] _routingKeyGenMod; + + /** + * A common interface to the timestamp of the two subclasses. + * Identical to getEarliestLeaseData() in LeaseSet, + * and getPublished() in RouterInfo. + * Note that for a LeaseSet this will be in the future, + * and for a RouterInfo it will be in the past. + * Either way, it's a timestamp. + * + * @since 0.8.2 + */ + public abstract long getDate(); + + /** + * Get the keys and the cert + * Identical to getDestination() in LeaseSet, + * and getIdentity() in RouterInfo. + * + * @return KAC or null + * @since 0.8.2 + */ + protected abstract KeysAndCert getKeysAndCert(); + + /** + * A common interface to the Hash of the two subclasses. + * Identical to getDestination().calculateHash() in LeaseSet, + * and getIdentity().getHash() in RouterInfo. + * + * @return Hash or null + * @since 0.8.2 + */ + public Hash getHash() { + KeysAndCert kac = getKeysAndCert(); + if (kac == null) + return null; + return kac.getHash(); + } + + /** + * Get the type of the data structure. + * This should be faster than instanceof. + * + * @return KEY_TYPE_ROUTERINFO or KEY_TYPE_LEASESET + * @since 0.8.2 + */ + public abstract int getType(); + + /** + * Returns the raw payload data, excluding the signature, to be signed by sign(). + * FIXME RouterInfo throws DFE and LeaseSet returns null + * @return null on error ??????????????????????? + */ + protected abstract byte[] getBytes() throws DataFormatException; + + /** + * Get the routing key for the structure using the current modifier in the RoutingKeyGenerator. + * This only calculates a new one when necessary though (if the generator's key modifier changes) + * + */ + public Hash getRoutingKey() { + RoutingKeyGenerator gen = RoutingKeyGenerator.getInstance(); + if ((gen.getModData() == null) || (_routingKeyGenMod == null) + || (!DataHelper.eq(gen.getModData(), _routingKeyGenMod))) { + _currentRoutingKey = gen.getRoutingKey(getHash()); + _routingKeyGenMod = gen.getModData(); + } + return _currentRoutingKey; + } + + + public void setRoutingKey(Hash key) { + _currentRoutingKey = key; + } + + public boolean validateRoutingKey() { + Hash destKey = getHash(); + Hash rk = RoutingKeyGenerator.getInstance().getRoutingKey(destKey); + return rk.equals(getRoutingKey()); + } + + /** + * Retrieve the proof that the identity stands behind the info here + * + */ + public Signature getSignature() { + return _signature; + } + + /** + * Configure the proof that the entity stands behind the info here + * + */ + public void setSignature(Signature signature) { + _signature = signature; + } + + /** + * Sign the structure using the supplied signing key + * + */ + public void sign(SigningPrivateKey key) throws DataFormatException { + byte[] bytes = getBytes(); + if (bytes == null) throw new DataFormatException("Not enough data to sign"); + // now sign with the key + _signature = DSAEngine.getInstance().sign(bytes, key); + } + + /** + * Identical to getDestination().getSigningPublicKey() in LeaseSet, + * and getIdentity().getSigningPublicKey() in RouterInfo. + * + * @return SPK or null + * @since 0.8.2 + */ + protected SigningPublicKey getSigningPublicKey() { + KeysAndCert kac = getKeysAndCert(); + if (kac == null) + return null; + return kac.getSigningPublicKey(); + } + + /** + * This is the same as isValid() in RouterInfo + * or verifySignature() in LeaseSet. + * @return valid + */ + protected boolean verifySignature() { + if (_signature == null) + return false; + byte data[]; + try { + data = getBytes(); + } catch (DataFormatException dfe) { + return false; + } + if (data == null) + return false; + // if the data is non-null the SPK will be non-null + return DSAEngine.getInstance().verifySignature(_signature, data, getSigningPublicKey()); + } +} diff --git a/core/java/src/net/i2p/data/Destination.java b/core/java/src/net/i2p/data/Destination.java index 4a00d78c9..41478275b 100644 --- a/core/java/src/net/i2p/data/Destination.java +++ b/core/java/src/net/i2p/data/Destination.java @@ -9,22 +9,13 @@ package net.i2p.data; * */ -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - /** * 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 * * @author jrandom */ -public class Destination extends DataStructureImpl { - protected Certificate _certificate; - protected SigningPublicKey _signingKey; - protected PublicKey _publicKey; - protected Hash __calculatedHash; +public class Destination extends KeysAndCert { public Destination() { } @@ -37,51 +28,7 @@ public class Destination extends DataStructureImpl { fromBase64(s); } - public Certificate getCertificate() { - return _certificate; - } - - public void setCertificate(Certificate cert) { - _certificate = cert; - __calculatedHash = null; - } - - public PublicKey getPublicKey() { - return _publicKey; - } - - public void setPublicKey(PublicKey key) { - _publicKey = key; - __calculatedHash = null; - } - - public SigningPublicKey getSigningPublicKey() { - return _signingKey; - } - - public void setSigningPublicKey(SigningPublicKey key) { - _signingKey = key; - __calculatedHash = null; - } - - public void readBytes(InputStream in) throws DataFormatException, IOException { - _publicKey = new PublicKey(); - _publicKey.readBytes(in); - _signingKey = new SigningPublicKey(); - _signingKey.readBytes(in); - _certificate = new Certificate(); - _certificate.readBytes(in); - __calculatedHash = null; - } - - public void writeBytes(OutputStream out) throws DataFormatException, IOException { - if ((_certificate == null) || (_publicKey == null) || (_signingKey == null)) - throw new DataFormatException("Not enough data to format the destination"); - _publicKey.writeBytes(out); - _signingKey.writeBytes(out); - _certificate.writeBytes(out); - } - + /** deprecated, used only by Packet.java in streaming */ public int writeBytes(byte target[], int offset) { int cur = offset; System.arraycopy(_publicKey.getData(), 0, target, cur, PublicKey.KEYSIZE_BYTES); @@ -92,6 +39,7 @@ public class Destination extends DataStructureImpl { return cur - offset; } + /** deprecated, used only by Packet.java in streaming */ 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) @@ -119,57 +67,4 @@ public class Destination extends DataStructureImpl { public int size() { return PublicKey.KEYSIZE_BYTES + SigningPublicKey.KEYSIZE_BYTES + _certificate.size(); } - - @Override - public boolean equals(Object object) { - if ((object == null) || !(object instanceof Destination)) return false; - Destination dst = (Destination) object; - return DataHelper.eq(_certificate, dst.getCertificate()) - && DataHelper.eq(_signingKey, dst.getSigningPublicKey()) - && DataHelper.eq(_publicKey, dst.getPublicKey()); - } - - /** the public key has enough randomness in it to use it by itself for speed */ - @Override - public int hashCode() { - if (_publicKey == null) - return 0; - return _publicKey.hashCode(); - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(128); - buf.append("[Destination: "); - buf.append("\n\tHash: ").append(calculateHash().toBase64()); - buf.append("\n\tPublic Key: ").append(getPublicKey()); - buf.append("\n\tSigning Public Key: ").append(getSigningPublicKey()); - buf.append("\n\tCertificate: ").append(getCertificate()); - buf.append("]"); - return buf.toString(); - } - - @Override - public Hash calculateHash() { - if (__calculatedHash == null) __calculatedHash = super.calculateHash(); - return __calculatedHash; - } - - public static void main(String args[]) { - if (args.length == 0) { - System.err.println("Usage: Destination filename"); - } else { - FileInputStream in = null; - try { - in = new FileInputStream(args[0]); - Destination d = new Destination(); - d.readBytes(in); - System.out.println(d.toBase64()); - } catch (Exception e) { - e.printStackTrace(); - } finally { - if (in != null) try { in.close(); } catch (IOException ioe) {} - } - } - } } diff --git a/core/java/src/net/i2p/data/Hash.java b/core/java/src/net/i2p/data/Hash.java index afd173c24..96c07c997 100644 --- a/core/java/src/net/i2p/data/Hash.java +++ b/core/java/src/net/i2p/data/Hash.java @@ -11,7 +11,6 @@ package net.i2p.data; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; /** * Defines the hash as defined by the I2P data structure spec. @@ -19,8 +18,7 @@ import java.io.OutputStream; * * @author jrandom */ -public class Hash extends DataStructureImpl { - private byte[] _data; +public class Hash extends SimpleDataStructure { private volatile String _stringified; private volatile String _base64ed; private int _cachedHashCode; @@ -29,46 +27,34 @@ public class Hash extends DataStructureImpl { public final static Hash FAKE_HASH = new Hash(new byte[HASH_LENGTH]); public Hash() { + super(); } /** @throws IllegalArgumentException if data is not 32 bytes (null is ok) */ public Hash(byte data[]) { + super(); setData(data); } - public byte[] getData() { - return _data; + public int length() { + return HASH_LENGTH; } /** @throws IllegalArgumentException if data is not 32 bytes (null is ok) */ + @Override public void setData(byte[] data) { - if (data != null && data.length != HASH_LENGTH) - throw new IllegalArgumentException("Hash must be 32 bytes"); - _data = data; + super.setData(data); _stringified = null; _base64ed = null; - _cachedHashCode = calcHashCode(); + _cachedHashCode = super.hashCode(); } + @Override public void readBytes(InputStream in) throws DataFormatException, IOException { - _data = new byte[HASH_LENGTH]; + super.readBytes(in); _stringified = null; _base64ed = null; - int read = read(in, _data); - if (read != HASH_LENGTH) throw new DataFormatException("Not enough bytes to read the hash"); - _cachedHashCode = calcHashCode(); - } - - public void writeBytes(OutputStream out) throws DataFormatException, IOException { - if (_data == null) throw new DataFormatException("No data in the hash to write out"); - if (_data.length != HASH_LENGTH) throw new DataFormatException("Invalid size of data in the hash"); - out.write(_data); - } - - @Override - public boolean equals(Object obj) { - if ((obj == null) || !(obj instanceof Hash)) return false; - return DataHelper.eq(_data, ((Hash) obj)._data); + _cachedHashCode = super.hashCode(); } /** a Hash is a hash, so just use the first 4 bytes for speed */ @@ -77,32 +63,6 @@ public class Hash extends DataStructureImpl { return _cachedHashCode; } - /** a Hash is a hash, so just use the first 4 bytes for speed */ - private int calcHashCode() { - int rv = 0; - if (_data != null) { - for (int i = 0; i < 4; i++) - rv ^= (_data[i] << (i*8)); - } - return rv; - } - - @Override - public String toString() { - if (_stringified == null) { - StringBuilder buf = new StringBuilder(64); - buf.append("[Hash: "); - if (_data == null) { - buf.append("null hash"); - } else { - buf.append(toBase64()); - } - buf.append("]"); - _stringified = buf.toString(); - } - return _stringified; - } - @Override public String toBase64() { if (_base64ed == null) { diff --git a/core/java/src/net/i2p/data/KeysAndCert.java b/core/java/src/net/i2p/data/KeysAndCert.java new file mode 100644 index 000000000..046d428a3 --- /dev/null +++ b/core/java/src/net/i2p/data/KeysAndCert.java @@ -0,0 +1,129 @@ +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.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import net.i2p.crypto.SHA256Generator; + +/** + * KeysAndCert has a public key, a signing key, and a certificate. + * In that order. + * We also store a cached Hash. + * + * Implemented in 0.8.2 and retrofitted over Destination and RouterIdentity. + * There's actually no difference between the two of them. + * + * @since 0.8.2 + * @author zzz + */ +public class KeysAndCert extends DataStructureImpl { + protected PublicKey _publicKey; + protected SigningPublicKey _signingKey; + protected Certificate _certificate; + protected Hash __calculatedHash; + + public Certificate getCertificate() { + return _certificate; + } + + public void setCertificate(Certificate cert) { + _certificate = cert; + __calculatedHash = null; + } + + public PublicKey getPublicKey() { + return _publicKey; + } + + public void setPublicKey(PublicKey key) { + _publicKey = key; + __calculatedHash = null; + } + + public SigningPublicKey getSigningPublicKey() { + return _signingKey; + } + + public void setSigningPublicKey(SigningPublicKey key) { + _signingKey = key; + __calculatedHash = null; + } + + public void readBytes(InputStream in) throws DataFormatException, IOException { + _publicKey = new PublicKey(); + _publicKey.readBytes(in); + _signingKey = new SigningPublicKey(); + _signingKey.readBytes(in); + _certificate = new Certificate(); + _certificate.readBytes(in); + __calculatedHash = null; + } + + public void writeBytes(OutputStream out) throws DataFormatException, IOException { + if ((_certificate == null) || (_publicKey == null) || (_signingKey == null)) + throw new DataFormatException("Not enough data to format the router identity"); + _publicKey.writeBytes(out); + _signingKey.writeBytes(out); + _certificate.writeBytes(out); + } + + @Override + public boolean equals(Object object) { + 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); + } + + /** the public key has enough randomness in it to use it by itself for speed */ + @Override + public int hashCode() { + if (_publicKey == null) + return 0; + return _publicKey.hashCode(); + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(64); + buf.append('[').append(getClass().getSimpleName()).append(": "); + buf.append("\n\tHash: ").append(getHash().toBase64()); + buf.append("\n\tCertificate: ").append(_certificate); + buf.append("\n\tPublicKey: ").append(_publicKey); + buf.append("\n\tSigningPublicKey: ").append(_signingKey); + buf.append(']'); + return buf.toString(); + } + + @Override + public Hash calculateHash() { + return getHash(); + } + + public Hash getHash() { + if (__calculatedHash != null) + return __calculatedHash; + byte identBytes[]; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + writeBytes(baos); + identBytes = baos.toByteArray(); + } catch (Throwable t) { + return null; + } + __calculatedHash = SHA256Generator.getInstance().calculateHash(identBytes); + return __calculatedHash; + } +} diff --git a/core/java/src/net/i2p/data/LeaseSet.java b/core/java/src/net/i2p/data/LeaseSet.java index 05d71949d..e481765d0 100644 --- a/core/java/src/net/i2p/data/LeaseSet.java +++ b/core/java/src/net/i2p/data/LeaseSet.java @@ -55,16 +55,13 @@ import net.i2p.util.RandomSource; * * @author jrandom */ -public class LeaseSet extends DataStructureImpl { +public class LeaseSet extends DatabaseEntry { private final static Log _log = new Log(LeaseSet.class); private Destination _destination; private PublicKey _encryptionKey; private SigningPublicKey _signingKey; // Keep leases in the order received, or else signature verification will fail! private List _leases; - private Signature _signature; - private volatile Hash _currentRoutingKey; - private volatile byte[] _routingKeyGenMod; private boolean _receivedAsPublished; private boolean _receivedAsReply; // Store these since isCurrent() and getEarliestLeaseDate() are called frequently @@ -82,6 +79,18 @@ public class LeaseSet extends DataStructureImpl { _firstExpiration = Long.MAX_VALUE; } + public long getDate() { + return getEarliestLeaseDate(); + } + + protected KeysAndCert getKeysAndCert() { + return _destination; + } + + public int getType() { + return KEY_TYPE_LEASESET; + } + public Destination getDestination() { return _destination; } @@ -157,42 +166,6 @@ public class LeaseSet extends DataStructureImpl { return (Lease) _leases.get(index); } - public Signature getSignature() { - return _signature; - } - - public void setSignature(Signature sig) { - _signature = sig; - } - - /** - * Get the routing key for the structure using the current modifier in the RoutingKeyGenerator. - * This only calculates a new one when necessary though (if the generator's key modifier changes) - * - */ - public Hash getRoutingKey() { - RoutingKeyGenerator gen = RoutingKeyGenerator.getInstance(); - if ((gen.getModData() == null) || (_routingKeyGenMod == null) - || (!DataHelper.eq(gen.getModData(), _routingKeyGenMod))) { - setRoutingKey(gen.getRoutingKey(getDestination().calculateHash())); - _routingKeyGenMod = gen.getModData(); - } - return _currentRoutingKey; - } - - public void setRoutingKey(Hash key) { - _currentRoutingKey = key; - } - - public boolean validateRoutingKey() { - Hash destKey = getDestination().calculateHash(); - Hash rk = RoutingKeyGenerator.getInstance().getRoutingKey(destKey); - if (rk.equals(getRoutingKey())) - return true; - - return false; - } - /** * Retrieve the end date of the earliest lease include in this leaseSet. * This is the date that should be used in comparisons for leaseSet age - to @@ -207,27 +180,16 @@ public class LeaseSet extends DataStructureImpl { return _firstExpiration; } - /** - * Sign the structure using the supplied signing key - * - */ - public void sign(SigningPrivateKey key) throws DataFormatException { - byte[] bytes = getBytes(); - if (bytes == null) throw new DataFormatException("Not enough data to sign"); - // now sign with the key - Signature sig = DSAEngine.getInstance().sign(bytes, key); - setSignature(sig); - } - /** * Verify that the signature matches the lease set's destination's signing public key. * OR the included revocation key. * * @return true only if the signature matches */ + @Override public boolean verifySignature() { - if (getSignature() == null) return false; - if (getDestination() == null) return false; + if (_signature == null) return false; + if (_destination == null) return false; byte data[] = getBytes(); if (data == null) return false; boolean signedByDest = DSAEngine.getInstance().verifySignature(_signature, data, @@ -271,7 +233,7 @@ public class LeaseSet extends DataStructureImpl { return _lastExpiration > now - fudge; } - private byte[] getBytes() { + protected byte[] getBytes() { if ((_destination == null) || (_encryptionKey == null) || (_signingKey == null) || (_leases == null)) return null; int len = PublicKey.KEYSIZE_BYTES // dest diff --git a/core/java/src/net/i2p/data/PrivateKey.java b/core/java/src/net/i2p/data/PrivateKey.java index 48f8ad816..bf415bdc9 100644 --- a/core/java/src/net/i2p/data/PrivateKey.java +++ b/core/java/src/net/i2p/data/PrivateKey.java @@ -9,10 +9,6 @@ package net.i2p.data; * */ -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - import net.i2p.crypto.KeyGenerator; /** @@ -22,77 +18,28 @@ import net.i2p.crypto.KeyGenerator; * * @author jrandom */ -public class PrivateKey extends DataStructureImpl { - private byte[] _data; - +public class PrivateKey extends SimpleDataStructure { public final static int KEYSIZE_BYTES = 256; public PrivateKey() { + super(); } - public PrivateKey(byte data[]) { setData(data); } + public PrivateKey(byte data[]) { + super(data); + } /** constructs from base64 * @param base64Data a string of base64 data (the output of .toBase64() called * on a prior instance of PrivateKey */ public PrivateKey(String base64Data) throws DataFormatException { + super(); fromBase64(base64Data); } - public byte[] getData() { - return _data; - } - - public void setData(byte[] data) { - _data = data; - } - - public void readBytes(InputStream in) throws DataFormatException, IOException { - _data = new byte[KEYSIZE_BYTES]; - int read = read(in, _data); - if (read != KEYSIZE_BYTES) throw new DataFormatException("Not enough bytes to read the private key"); - } - - public void writeBytes(OutputStream out) throws DataFormatException, IOException { - if (_data == null) throw new DataFormatException("No data in the private key to write out"); - if (_data.length != KEYSIZE_BYTES) - throw new DataFormatException("Invalid size of data in the private key [" + _data.length + "]"); - out.write(_data); - } - - @Override - public boolean equals(Object obj) { - if ((obj == null) || !(obj instanceof PrivateKey)) return false; - return DataHelper.eq(_data, ((PrivateKey) obj)._data); - } - - /** the key has enough randomness in it, use the first 4 bytes for speed */ - @Override - public int hashCode() { - int rv = 0; - if (_data != null) { - for (int i = 0; i < 4; i++) - rv ^= (_data[i] << (i*8)); - } - return rv; - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(64); - buf.append("[PrivateKey: "); - if (_data == null) { - buf.append("null key"); - } else { - buf.append("size: ").append(_data.length); - //int len = 32; - //if (len > _data.length) len = _data.length; - //buf.append(" first ").append(len).append(" bytes: "); - //buf.append(DataHelper.toString(_data, len)); - } - buf.append("]"); - return buf.toString(); + public int length() { + return KEYSIZE_BYTES; } /** derives a new PublicKey object derived from the secret contents diff --git a/core/java/src/net/i2p/data/PublicKey.java b/core/java/src/net/i2p/data/PublicKey.java index 3d822949e..cf6b01434 100644 --- a/core/java/src/net/i2p/data/PublicKey.java +++ b/core/java/src/net/i2p/data/PublicKey.java @@ -9,10 +9,6 @@ package net.i2p.data; * */ -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - /** * Defines the PublicKey as defined by the I2P data structure spec. * A public key is 256byte Integer. The public key represents only the @@ -20,18 +16,19 @@ import java.io.OutputStream; * * @author jrandom */ -public class PublicKey extends DataStructureImpl { - private byte[] _data; - +public class PublicKey extends SimpleDataStructure { public final static int KEYSIZE_BYTES = 256; public PublicKey() { + super(); } + /** @param data must be non-null */ public PublicKey(byte data[]) { - if ( (data == null) || (data.length != KEYSIZE_BYTES) ) - throw new IllegalArgumentException("Data must be specified, and the correct size"); - setData(data); + super(); + if (data == null) + throw new IllegalArgumentException("Data must be specified"); + _data = data; } /** constructs from base64 @@ -39,61 +36,11 @@ public class PublicKey extends DataStructureImpl { * on a prior instance of PublicKey */ public PublicKey(String base64Data) throws DataFormatException { + super(); fromBase64(base64Data); } - - public byte[] getData() { - return _data; - } - - public void setData(byte[] data) { - _data = data; - } - public void readBytes(InputStream in) throws DataFormatException, IOException { - _data = new byte[KEYSIZE_BYTES]; - int read = read(in, _data); - if (read != KEYSIZE_BYTES) throw new DataFormatException("Not enough bytes to read the public key"); + public int length() { + return KEYSIZE_BYTES; } - - public void writeBytes(OutputStream out) throws DataFormatException, IOException { - if (_data == null) throw new DataFormatException("No data in the public key to write out"); - if (_data.length != KEYSIZE_BYTES) throw new DataFormatException("Invalid size of data in the public key"); - out.write(_data); - } - - @Override - public boolean equals(Object obj) { - if ((obj == null) || !(obj instanceof PublicKey)) return false; - return DataHelper.eq(_data, ((PublicKey) obj)._data); - } - - /** the key has enough randomness in it, use the first 4 bytes for speed */ - @Override - public int hashCode() { - int rv = 0; - if (_data != null) { - for (int i = 0; i < 4; i++) - rv ^= (_data[i] << (i*8)); - } - return rv; - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(64); - buf.append("[PublicKey: "); - if (_data == null) { - buf.append("null key"); - } else { - buf.append("size: ").append(_data.length); - //int len = 32; - //if (len > _data.length) len = _data.length; - //buf.append(" first ").append(len).append(" bytes: "); - //buf.append(DataHelper.toString(_data, len)); - } - buf.append("]"); - return buf.toString(); - } - } diff --git a/core/java/src/net/i2p/data/RouterIdentity.java b/core/java/src/net/i2p/data/RouterIdentity.java index 310bd11aa..4666242ea 100644 --- a/core/java/src/net/i2p/data/RouterIdentity.java +++ b/core/java/src/net/i2p/data/RouterIdentity.java @@ -9,57 +9,14 @@ package net.i2p.data; * */ -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import net.i2p.crypto.SHA256Generator; -import net.i2p.util.Log; - /** * Defines the unique identifier of a router, including any certificate or * public key. * * @author jrandom */ -public class RouterIdentity extends DataStructureImpl { - private final static Log _log = new Log(RouterIdentity.class); - private Certificate _certificate; - private SigningPublicKey _signingKey; - private PublicKey _publicKey; - private Hash __calculatedHash; +public class RouterIdentity extends KeysAndCert { - public RouterIdentity() { - } - - public Certificate getCertificate() { - return _certificate; - } - - public void setCertificate(Certificate cert) { - _certificate = cert; - __calculatedHash = null; - } - - public PublicKey getPublicKey() { - return _publicKey; - } - - public void setPublicKey(PublicKey key) { - _publicKey = key; - __calculatedHash = null; - } - - public SigningPublicKey getSigningPublicKey() { - return _signingKey; - } - - public void setSigningPublicKey(SigningPublicKey key) { - _signingKey = key; - __calculatedHash = null; - } - /** * This router specified that they should not be used as a part of a tunnel, * nor queried for the netDb, and that disclosure of their contact information @@ -69,73 +26,4 @@ public class RouterIdentity extends DataStructureImpl { public boolean isHidden() { return (_certificate != null) && (_certificate.getCertificateType() == Certificate.CERTIFICATE_TYPE_HIDDEN); } - - public void readBytes(InputStream in) throws DataFormatException, IOException { - _publicKey = new PublicKey(); - _publicKey.readBytes(in); - _signingKey = new SigningPublicKey(); - _signingKey.readBytes(in); - _certificate = new Certificate(); - _certificate.readBytes(in); - __calculatedHash = null; - } - - public void writeBytes(OutputStream out) throws DataFormatException, IOException { - if ((_certificate == null) || (_publicKey == null) || (_signingKey == null)) - throw new DataFormatException("Not enough data to format the router identity"); - _publicKey.writeBytes(out); - _signingKey.writeBytes(out); - _certificate.writeBytes(out); - } - - @Override - public boolean equals(Object object) { - if ((object == null) || !(object instanceof RouterIdentity)) return false; - RouterIdentity ident = (RouterIdentity) object; - return DataHelper.eq(_certificate, ident.getCertificate()) - && DataHelper.eq(_signingKey, ident.getSigningPublicKey()) - && DataHelper.eq(_publicKey, ident.getPublicKey()); - } - - /** the public key has enough randomness in it to use it by itself for speed */ - @Override - public int hashCode() { - if (_publicKey == null) - return 0; - return _publicKey.hashCode(); - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(64); - buf.append("[RouterIdentity: "); - buf.append("\n\tHash: ").append(getHash().toBase64()); - buf.append("\n\tCertificate: ").append(getCertificate()); - buf.append("\n\tPublicKey: ").append(getPublicKey()); - buf.append("\n\tSigningPublicKey: ").append(getSigningPublicKey()); - buf.append("]"); - return buf.toString(); - } - - @Override - public Hash calculateHash() { - return getHash(); - } - - public Hash getHash() { - if (__calculatedHash != null) { - //_log.info("Returning cached ident hash"); - return __calculatedHash; } - byte identBytes[] = null; - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - writeBytes(baos); - identBytes = baos.toByteArray(); - } catch (Throwable t) { - _log.error("Error writing out hash"); - return null; - } - __calculatedHash = SHA256Generator.getInstance().calculateHash(identBytes); - return __calculatedHash; - } } diff --git a/core/java/src/net/i2p/data/RouterInfo.java b/core/java/src/net/i2p/data/RouterInfo.java index bf2b49ba4..9c6db01f9 100644 --- a/core/java/src/net/i2p/data/RouterInfo.java +++ b/core/java/src/net/i2p/data/RouterInfo.java @@ -34,16 +34,13 @@ import net.i2p.util.OrderedProperties; * * @author jrandom */ -public class RouterInfo extends DataStructureImpl { +public class RouterInfo extends DatabaseEntry { private final static Log _log = new Log(RouterInfo.class); private RouterIdentity _identity; private volatile long _published; private final Set _addresses; private final Set _peers; private /* FIXME final FIXME */ Properties _options; - private volatile Signature _signature; - private volatile Hash _currentRoutingKey; - private volatile byte _routingKeyGenMod[]; private volatile boolean _validated; private volatile boolean _isValid; private volatile String _stringified; @@ -75,6 +72,18 @@ public class RouterInfo extends DataStructureImpl { setSignature(old.getSignature()); } + public long getDate() { + return _published; + } + + protected KeysAndCert getKeysAndCert() { + return _identity; + } + + public int getType() { + return KEY_TYPE_ROUTERINFO; + } + private void resetCache() { _stringified = null; _byteified = null; @@ -204,47 +213,13 @@ public class RouterInfo extends DataStructureImpl { resetCache(); } - /** - * Retrieve the proof that the identity stands behind the info here - * - */ - public Signature getSignature() { - return _signature; - } - - /** - * Configure the proof that the entity stands behind the info here - * - */ - public void setSignature(Signature signature) { - _signature = signature; - resetCache(); - } - - /** - * Sign the structure using the supplied signing key - * - */ - public synchronized void sign(SigningPrivateKey key) throws DataFormatException { - byte[] bytes = getBytes(); - if (bytes == null) throw new DataFormatException("Not enough data to sign"); - // now sign with the key - Signature sig = DSAEngine.getInstance().sign(bytes, key); - if (sig == null) throw new DataFormatException("Not enough data to sign, or other signature failure"); - setSignature(sig); - //_log.debug("Signed " + SHA256Generator.getInstance().calculateHash(bytes).toBase64() + " with " + key); - //_log.debug("verify ok? " + DSAEngine.getInstance().verifySignature(sig, bytes, getIdentity().getSigningPublicKey())); - //_log.debug("Signed data: \n" + Base64.encode(bytes)); - //_log.debug("Signature: " + getSignature()); - } - /** * Write out the raw payload of the routerInfo, excluding the signature. This * caches the data in memory if possible. * * @throws DataFormatException if the data is somehow b0rked (missing props, etc) */ - private byte[] getBytes() throws DataFormatException { + protected byte[] getBytes() throws DataFormatException { if (_byteified != null) return _byteified; if (_identity == null) throw new DataFormatException("Router identity isn't set? wtf!"); if (_addresses == null) throw new DataFormatException("Router addressess isn't set? wtf!"); @@ -392,35 +367,6 @@ public class RouterInfo extends DataStructureImpl { } } - - /** - * Get the routing key for the structure using the current modifier in the RoutingKeyGenerator. - * This only calculates a new one when necessary though (if the generator's key modifier changes) - * - */ - public synchronized Hash getRoutingKey() { - RoutingKeyGenerator gen = RoutingKeyGenerator.getInstance(); - if ((gen.getModData() == null) || (_routingKeyGenMod == null) - || (!DataHelper.eq(gen.getModData(), _routingKeyGenMod))) { - setRoutingKey(gen.getRoutingKey(_identity.getHash())); - _routingKeyGenMod = gen.getModData(); - } - return _currentRoutingKey; - } - - public void setRoutingKey(Hash key) { - _currentRoutingKey = key; - } - - public boolean validateRoutingKey() { - Hash identKey = _identity.getHash(); - Hash rk = RoutingKeyGenerator.getInstance().getRoutingKey(identKey); - if (rk.equals(getRoutingKey())) - return true; - - return false; - } - /** * Determine whether the router was published recently (within the given age milliseconds). * The age should be large enough to take into consideration any clock fudge factor, so @@ -453,6 +399,10 @@ public class RouterInfo extends DataStructureImpl { return null; } + /** + * For future multiple addresses per-transport (IPV6), currently unused + * @since 0.7.11 + */ public List getTargetAddresses(String transportStyle) { List ret = new Vector(); synchronized(this._addresses) { diff --git a/core/java/src/net/i2p/data/SessionKey.java b/core/java/src/net/i2p/data/SessionKey.java index a506c8387..7621c2b8a 100644 --- a/core/java/src/net/i2p/data/SessionKey.java +++ b/core/java/src/net/i2p/data/SessionKey.java @@ -9,32 +9,28 @@ package net.i2p.data; * */ -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - /** * Defines the SessionKey as defined by the I2P data structure spec. * A session key is 32byte Integer. * * @author jrandom */ -public class SessionKey extends DataStructureImpl { - private byte[] _data; +public class SessionKey extends SimpleDataStructure { private Object _preparedKey; public final static int KEYSIZE_BYTES = 32; public static final SessionKey INVALID_KEY = new SessionKey(new byte[KEYSIZE_BYTES]); public SessionKey() { + super(); } public SessionKey(byte data[]) { - setData(data); + super(data); } - public byte[] getData() { - return _data; + public int length() { + return KEYSIZE_BYTES; } /** @@ -43,6 +39,7 @@ public class SessionKey extends DataStructureImpl { * encryption/decryption (or if you do change it, be sure this object isn't * mid decrypt) */ + @Override public void setData(byte[] data) { _data = data; _preparedKey = null; @@ -54,54 +51,4 @@ public class SessionKey extends DataStructureImpl { */ public Object getPreparedKey() { return _preparedKey; } public void setPreparedKey(Object obj) { _preparedKey = obj; } - - public void readBytes(InputStream in) throws DataFormatException, IOException { - _data = new byte[KEYSIZE_BYTES]; - int read = read(in, _data); - if (read != KEYSIZE_BYTES) throw new DataFormatException("Not enough bytes to read the session key"); - } - - public void writeBytes(OutputStream out) throws DataFormatException, IOException { - if (_data == null) throw new DataFormatException("No data in the session key to write out"); - if (_data.length != KEYSIZE_BYTES) throw new DataFormatException("Invalid size of data in the private key"); - out.write(_data); - } - - @Override - public boolean equals(Object obj) { - if ((obj == null) || !(obj instanceof SessionKey)) return false; - return DataHelper.eq(_data, ((SessionKey) obj)._data); - } - - /** the key has enough randomness in it, use the first 4 bytes for speed */ - @Override - public int hashCode() { - int rv = 0; - if (_data != null) { - for (int i = 0; i < 4; i++) - rv ^= (_data[i] << (i*8)); - } - return rv; - } - - @Override - public String toString() { - return "SessionKey " + toBase64(); - /**** - if (true) return super.toString(); - StringBuilder buf = new StringBuilder(64); - buf.append("[SessionKey: "); - if (_data == null) { - buf.append("null key"); - } else { - buf.append("size: ").append(_data.length); - //int len = 32; - //if (len > _data.length) len = _data.length; - //buf.append(" first ").append(len).append(" bytes: "); - //buf.append(DataHelper.toString(_data, len)); - } - buf.append("]"); - return buf.toString(); - ****/ - } } diff --git a/core/java/src/net/i2p/data/SessionTag.java b/core/java/src/net/i2p/data/SessionTag.java index d50b392b4..761305d18 100644 --- a/core/java/src/net/i2p/data/SessionTag.java +++ b/core/java/src/net/i2p/data/SessionTag.java @@ -9,57 +9,35 @@ package net.i2p.data; * */ -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - import net.i2p.util.RandomSource; -public class SessionTag extends ByteArray { +/** + * 32 bytes, usually of random data. + * Changed from ByteArray to SimpleDataStructure in 0.8.2. + */ +public class SessionTag extends SimpleDataStructure { public final static int BYTE_LENGTH = 32; public SessionTag() { super(); } + /** + * @param create if true, instantiate the data array and fill it with random data. + */ public SessionTag(boolean create) { super(); if (create) { - byte buf[] = new byte[BYTE_LENGTH]; - RandomSource.getInstance().nextBytes(buf); - setData(buf); + _data = new byte[BYTE_LENGTH]; + RandomSource.getInstance().nextBytes(_data); } } public SessionTag(byte val[]) { - super(); - setData(val); + super(val); } - @Override - public void setData(byte val[]) throws IllegalArgumentException { - if (val == null) - throw new NullPointerException("SessionTags cannot be null"); - if (val.length != BYTE_LENGTH) - throw new IllegalArgumentException("SessionTags must be " + BYTE_LENGTH + " bytes"); - super.setData(val); - setValid(BYTE_LENGTH); - } - - public void readBytes(InputStream in) throws DataFormatException, IOException { - byte data[] = new byte[BYTE_LENGTH]; - int read = DataHelper.read(in, data); - if (read != BYTE_LENGTH) - throw new DataFormatException("Not enough data (read " + read + " wanted " + BYTE_LENGTH + ")"); - setData(data); - } - - public void writeBytes(OutputStream out) throws DataFormatException, IOException { - out.write(getData()); - } - - @Override - public String toString() { - return "SessionTag " + toBase64(); + public int length() { + return BYTE_LENGTH; } } diff --git a/core/java/src/net/i2p/data/Signature.java b/core/java/src/net/i2p/data/Signature.java index 8ed31a5b7..282517007 100644 --- a/core/java/src/net/i2p/data/Signature.java +++ b/core/java/src/net/i2p/data/Signature.java @@ -9,10 +9,6 @@ package net.i2p.data; * */ -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - /** * Defines the signature as defined by the I2P data structure spec. * A signature is a 40byte Integer verifying the authenticity of some data @@ -20,9 +16,7 @@ import java.io.OutputStream; * * @author jrandom */ -public class Signature extends DataStructureImpl { - private byte[] _data; - +public class Signature extends SimpleDataStructure { public final static int SIGNATURE_BYTES = 40; public final static byte[] FAKE_SIGNATURE = new byte[SIGNATURE_BYTES]; static { @@ -30,61 +24,15 @@ public class Signature extends DataStructureImpl { FAKE_SIGNATURE[i] = 0x00; } - public Signature() {} - - public Signature(byte data[]) { setData(data); } - - public byte[] getData() { - return _data; + public Signature() { + super(); } - public void setData(byte[] data) { - _data = data; - } - - public void readBytes(InputStream in) throws DataFormatException, IOException { - _data = new byte[SIGNATURE_BYTES]; - int read = read(in, _data); - if (read != SIGNATURE_BYTES) throw new DataFormatException("Not enough bytes to read the signature"); - } - - public void writeBytes(OutputStream out) throws DataFormatException, IOException { - if (_data == null) throw new DataFormatException("No data in the signature to write out"); - if (_data.length != SIGNATURE_BYTES) throw new DataFormatException("Invalid size of data in the private key"); - out.write(_data); - } - - @Override - public boolean equals(Object obj) { - if ((obj == null) || !(obj instanceof Signature)) return false; - return DataHelper.eq(_data, ((Signature) obj)._data); - } - - /** the sig has enough randomness in it, use the first 4 bytes for speed */ - @Override - public int hashCode() { - int rv = 0; - if (_data != null) { - for (int i = 0; i < 4; i++) - rv ^= (_data[i] << (i*8)); - } - return rv; + public Signature(byte data[]) { + super(data); } - @Override - public String toString() { - StringBuilder buf = new StringBuilder(64); - buf.append("[Signature: "); - if (_data == null) { - buf.append("null signature"); - } else { - buf.append("size: ").append(_data.length); - //int len = 32; - //if (len > _data.length) len = _data.length; - //buf.append(" first ").append(len).append(" bytes: "); - //buf.append(DataHelper.toString(_data, len)); - } - buf.append("]"); - return buf.toString(); + public int length() { + return SIGNATURE_BYTES; } } diff --git a/core/java/src/net/i2p/data/SigningPrivateKey.java b/core/java/src/net/i2p/data/SigningPrivateKey.java index 40fda013a..446e911dd 100644 --- a/core/java/src/net/i2p/data/SigningPrivateKey.java +++ b/core/java/src/net/i2p/data/SigningPrivateKey.java @@ -9,10 +9,6 @@ package net.i2p.data; * */ -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - import net.i2p.crypto.KeyGenerator; /** @@ -23,75 +19,28 @@ import net.i2p.crypto.KeyGenerator; * * @author jrandom */ -public class SigningPrivateKey extends DataStructureImpl { - private byte[] _data; - +public class SigningPrivateKey extends SimpleDataStructure { public final static int KEYSIZE_BYTES = 20; - public SigningPrivateKey() {} + public SigningPrivateKey() { + super(); + } - public SigningPrivateKey(byte data[]) { setData(data); } + public SigningPrivateKey(byte data[]) { + super(data); + } /** constructs from base64 * @param base64Data a string of base64 data (the output of .toBase64() called * on a prior instance of SigningPrivateKey */ public SigningPrivateKey(String base64Data) throws DataFormatException { + super(); fromBase64(base64Data); } - public byte[] getData() { - return _data; - } - - public void setData(byte[] data) { - _data = data; - } - - public void readBytes(InputStream in) throws DataFormatException, IOException { - _data = new byte[KEYSIZE_BYTES]; - int read = read(in, _data); - if (read != KEYSIZE_BYTES) throw new DataFormatException("Not enough bytes to read the private key"); - } - - public void writeBytes(OutputStream out) throws DataFormatException, IOException { - if (_data == null) throw new DataFormatException("No data in the private key to write out"); - if (_data.length != KEYSIZE_BYTES) throw new DataFormatException("Invalid size of data in the private key"); - out.write(_data); - } - - @Override - public boolean equals(Object obj) { - if ((obj == null) || !(obj instanceof SigningPrivateKey)) return false; - return DataHelper.eq(_data, ((SigningPrivateKey) obj)._data); - } - - /** the key has enough randomness in it, use the first 4 bytes for speed */ - @Override - public int hashCode() { - int rv = 0; - if (_data != null) { - for (int i = 0; i < 4; i++) - rv ^= (_data[i] << (i*8)); - } - return rv; - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(64); - buf.append("[SigningPrivateKey: "); - if (_data == null) { - buf.append("null key"); - } else { - buf.append("size: ").append(_data.length); - //int len = 32; - //if (len > _data.length) len = _data.length; - //buf.append(" first ").append(len).append(" bytes: "); - //buf.append(DataHelper.toString(_data, len)); - } - buf.append("]"); - return buf.toString(); + public int length() { + return KEYSIZE_BYTES; } /** converts this signing private key to its public equivalent diff --git a/core/java/src/net/i2p/data/SigningPublicKey.java b/core/java/src/net/i2p/data/SigningPublicKey.java index 4f2b68df8..0e11ad081 100644 --- a/core/java/src/net/i2p/data/SigningPublicKey.java +++ b/core/java/src/net/i2p/data/SigningPublicKey.java @@ -9,10 +9,6 @@ package net.i2p.data; * */ -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - /** * Defines the SigningPublicKey as defined by the I2P data structure spec. * A public key is 256byte Integer. The public key represents only the @@ -21,74 +17,27 @@ import java.io.OutputStream; * * @author jrandom */ -public class SigningPublicKey extends DataStructureImpl { - private byte[] _data; - +public class SigningPublicKey extends SimpleDataStructure { public final static int KEYSIZE_BYTES = 128; - public SigningPublicKey() {} + public SigningPublicKey() { + super(); + } - public SigningPublicKey(byte data[]) { setData(data); } + public SigningPublicKey(byte data[]) { + super(data); + } /** constructs from base64 * @param base64Data a string of base64 data (the output of .toBase64() called * on a prior instance of SigningPublicKey */ public SigningPublicKey(String base64Data) throws DataFormatException { + super(); fromBase64(base64Data); } - public byte[] getData() { - return _data; - } - - public void setData(byte[] data) { - _data = data; - } - - public void readBytes(InputStream in) throws DataFormatException, IOException { - _data = new byte[KEYSIZE_BYTES]; - int read = read(in, _data); - if (read != KEYSIZE_BYTES) throw new DataFormatException("Not enough bytes to read the public key"); - } - - public void writeBytes(OutputStream out) throws DataFormatException, IOException { - if (_data == null) throw new DataFormatException("No data in the public key to write out"); - if (_data.length != KEYSIZE_BYTES) throw new DataFormatException("Invalid size of data in the public key"); - out.write(_data); - } - - @Override - public boolean equals(Object obj) { - if ((obj == null) || !(obj instanceof SigningPublicKey)) return false; - return DataHelper.eq(_data, ((SigningPublicKey) obj)._data); - } - - /** the key has enough randomness in it, use the first 4 bytes for speed */ - @Override - public int hashCode() { - int rv = 0; - if (_data != null) { - for (int i = 0; i < 4; i++) - rv ^= (_data[i] << (i*8)); - } - return rv; - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(64); - buf.append("[SigningPublicKey: "); - if (_data == null) { - buf.append("null key"); - } else { - buf.append("size: ").append(_data.length); - //int len = 32; - //if (len > _data.length) len = _data.length; - //buf.append(" first ").append(len).append(" bytes: "); - //buf.append(DataHelper.toString(_data, len)); - } - buf.append("]"); - return buf.toString(); + public int length() { + return KEYSIZE_BYTES; } } diff --git a/core/java/src/net/i2p/data/SimpleDataStructure.java b/core/java/src/net/i2p/data/SimpleDataStructure.java new file mode 100644 index 000000000..16a2ec88e --- /dev/null +++ b/core/java/src/net/i2p/data/SimpleDataStructure.java @@ -0,0 +1,155 @@ +package net.i2p.data; + +/* + * Public domain + */ + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import net.i2p.crypto.SHA256Generator; + +/** + * A SimpleDataStructure contains only a single fixed-length byte array. + * The main reason to do this is to override + * toByteArray() and fromByteArray(), which are used by toBase64(), fromBase64(), + * and calculateHash() in DataStructureImpl - otherwise these would go through + * a wasteful array-to-stream-to-array pass. + * It also centralizes a lot of common code. + * + * Implemented in 0.8.2 and retrofitted over several of the classes in this package. + * + * @since 0.8.2 + * @author zzz + */ +public abstract class SimpleDataStructure extends DataStructureImpl { + protected byte[] _data; + /** this is just to avoid lots of calls to length() */ + protected final int _length; + + /** A new instance with the data set to null. Call readBytes(), setData(), or from ByteArray() after this to set the data */ + public SimpleDataStructure() { + _length = length(); + } + + /** @throws IllegalArgumentException if data is not the legal number of bytes (but null is ok) */ + public SimpleDataStructure(byte data[]) { + _length = length(); + setData(data); + } + + /** + * The legal length of the byte array in this data structure + * @since 0.8.2 + */ + abstract public int length(); + + /** + * Get the data reference (not a copy) + * @return the byte array, or null if unset + */ + public byte[] getData() { + return _data; + } + + /** + * Sets the data. + * @param data of correct length, or null + * @throws IllegalArgumentException if data is not the legal number of bytes (but null is ok) + */ + public void setData(byte[] data) { + if (data != null && data.length != _length) + throw new IllegalArgumentException("Bad data length"); + _data = data; + } + + public void readBytes(InputStream in) throws DataFormatException, IOException { + _data = new byte[_length]; + int read = read(in, _data); + if (read != _length) throw new DataFormatException("Not enough bytes to read the data"); + } + + public void writeBytes(OutputStream out) throws DataFormatException, IOException { + if (_data == null) throw new DataFormatException("No data to write out"); + out.write(_data); + } + + @Override + public String toBase64() { + if (_data == null) + return null; + return Base64.encode(_data); + } + + @Override + public void fromBase64(String data) throws DataFormatException { + if (data == null) throw new DataFormatException("Null data passed in"); + _data = Base64.decode(data); + } + + /** @return the SHA256 hash of the byte array, or null if the data is null */ + @Override + public Hash calculateHash() { + if (_data != null) return SHA256Generator.getInstance().calculateHash(_data); + return null; + } + + /** + * Overridden for efficiency. + * @return same thing as getData() + */ + @Override + public byte[] toByteArray() { + return _data; + } + + /** + * Overridden for efficiency. + * Does the same thing as getData() but null not allowed. + * @param data non-null + * @throws DataFormatException if null or wrong length + */ + @Override + public void fromByteArray(byte data[]) throws DataFormatException { + if (data == null) throw new DataFormatException("Null data passed in"); + if (data.length != _length) throw new DataFormatException("Bad data length"); + _data = data; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(64); + buf.append('[').append(getClass().getSimpleName()).append(": "); + if (_data == null) { + buf.append("null"); + } else if (_length <= 32) { + buf.append(toBase64()); + } else { + buf.append("size: ").append(Integer.toString(_length)); + } + buf.append(']'); + return buf.toString(); + } + + /** + * We assume the data has enough randomness in it, so use the first 4 bytes for speed. + * If this is not the case, override in the extending class. + */ + @Override + public int hashCode() { + if (_data == null) + return 0; + int rv = _data[0]; + for (int i = 1; i < 4; i++) + rv ^= (_data[i] << (i*8)); + return rv; + } + + @Override + public boolean equals(Object obj) { + if ((obj == null) || !(obj instanceof SimpleDataStructure)) return false; + return DataHelper.eq(_data, ((SimpleDataStructure) obj)._data); + } + +}