Java tutorial
/* * Copyright 2009 Thomas Bocek * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package net.tomp2p.storage; import io.netty.buffer.ByteBuf; import java.io.IOException; import java.nio.ByteBuffer; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SignatureException; import java.util.BitSet; import java.util.HashSet; import java.util.Set; import net.tomp2p.connection.DSASignatureFactory; import net.tomp2p.connection.SignatureFactory; import net.tomp2p.message.SignatureCodec; import net.tomp2p.p2p.PeerBuilder; import net.tomp2p.peers.Number160; import net.tomp2p.utils.Utils; /** * This class holds the data for the transport. The data is already serialized * and a hash may be created. It is reasonable to create the hash on the remote * peer, but not on the local peer. The remote peer uses the hash to tell the * other peers, which version is stored and its used quite often. * * @author Thomas Bocek */ public class Data { private static final int MAX_BYTE_SIZE = 256; /** * small means 8 bit, medium is 32bit. * * @author Thomas Bocek * */ public enum Type { SMALL, LARGE } private final Type type; private final int length; // the buffer contains data without the header private final DataBuffer buffer; // these flags can be modified private boolean basedOnFlag; private boolean signed; private boolean flag1; private boolean ttl; private boolean flag2; private boolean protectedEntry; private boolean publicKeyFlag; private boolean prepareFlag; // can be added later private SignatureCodec signature; private int ttlSeconds = -1; private Set<Number160> basedOnSet = new HashSet<Number160>(0); private PublicKey publicKey; //this goes never over the network! If this is set, we have to sign lazy private transient PrivateKey privateKey; // never serialized over the network in this object private long validFromMillis; private SignatureFactory signatureFactory; private Number160 hash; private boolean meta; public Data(final DataBuffer buffer) { this(buffer, buffer.length()); } /** * Create a data object that does have the complete data, but not the complete header * * @param length * The expected length of the buffer. This does not include the * header + size (2, 5, or 9). * @param version * The version of a data object, optional * @param ttlSeconds * The TTL of a data object, optional * @param hasHash * Indication if a hash should also be transmitted * @param isProtectedEntry * True if this entry is protected */ public Data(final DataBuffer buffer, final int length) { this.length = length; if (length < MAX_BYTE_SIZE) { this.type = Type.SMALL; } else { this.type = Type.LARGE; } this.buffer = buffer; this.validFromMillis = System.currentTimeMillis(); } /** * Creates an empty data object. The data can be filled at a later stage * using {@link #append(ByteBuf)}. * * @param header * The 8 bit header * @param length * The length, which depends on the header values */ public Data(final int header, final int length) { this.publicKeyFlag = hasPublicKey(header); this.flag1 = isFlag1(header); this.flag2 = isFlag2(header); this.basedOnFlag = hasBasedOn(header); this.signed = isSigned(header); this.ttl = hasTTL(header); this.protectedEntry = isProtectedEntry(header); this.type = type(header); this.prepareFlag = hasPrepareFlag(header); if (type == Type.SMALL && length > 255) { throw new IllegalArgumentException("Type is not small"); } else if (type == Type.LARGE && (length <= 255)) { throw new IllegalArgumentException("Type is not large"); } this.length = length; this.buffer = new DataBuffer(); this.validFromMillis = System.currentTimeMillis(); } public Data(final Object object) throws IOException { this(Utils.encodeJavaObject(object)); } public Data(final byte[] buffer) { this(buffer, 0, buffer.length); } public Data() { this(Utils.EMPTY_BYTE_ARRAY); } /** * Creates a data object from an already existing byte buffer. * * @param buffer * The data buffer * @param version * The version of a data object, optional * @param ttlSeconds * The ttl of a data object, optional * @param hasHash * Indication if a hash should also be transmitted * @param isProtectedEntry * True if this entry is protected */ public Data(final byte[] buffer, final int offest, final int length) { if (buffer.length == 0) { this.buffer = new DataBuffer(0); } else { this.buffer = new DataBuffer(buffer, offest, length); } this.length = length; if (length < MAX_BYTE_SIZE) { this.type = Type.SMALL; } else { this.type = Type.LARGE; } this.validFromMillis = System.currentTimeMillis(); } public boolean isEmpty() { return length == 0; } /** * Reads the header. Does not modify the buffer positions if header could * not be fully read. * * Header format: * <pre> * 1 byte - header * 1 or 4 bytes - length * 4 or 0 bytes - ttl (hasTTL) * 1 or 0 bytes - number of basedon keys (hasBasedOn) * n x 20 bytes - basedon keys (hasBasedOn, number of basedon keys) * 2 or 0 bytes - length of public key (hasPublicKey) * n bytes - public key (hasPublicKey, length of public key) * </pre> * * * @param buf * The buffer to read from * @return The data object, may be partially filled */ public static Data decodeHeader(final ByteBuf buf, final SignatureFactory signatureFactory) { // 2 is the smallest packet size, we could start if we know 1 byte to // decode the header, but we always need // a second byte. Thus, we are waiting for at least 2 bytes. if (buf.readableBytes() < Utils.BYTE_BYTE_SIZE + Utils.BYTE_BYTE_SIZE) { return null; } final int header = buf.getUnsignedByte(buf.readerIndex()); final Data.Type type = Data.type(header); //Data length final int length; final int indexLength = Utils.BYTE_BYTE_SIZE; final int indexTTL; switch (type) { case SMALL: length = buf.getUnsignedByte(buf.readerIndex() + indexLength); indexTTL = indexLength + Utils.BYTE_BYTE_SIZE; break; case LARGE: indexTTL = indexLength + Utils.INTEGER_BYTE_SIZE; if (buf.readableBytes() < indexTTL) { return null; } length = buf.getInt(buf.readerIndex() + indexLength); break; default: throw new IllegalArgumentException("unknown type"); } //TTL final int ttl; final int indexBasedOnNr; if (hasTTL(header)) { indexBasedOnNr = indexTTL + Utils.INTEGER_BYTE_SIZE; if (buf.readableBytes() < indexBasedOnNr) { return null; } ttl = buf.getInt(buf.readerIndex() + indexTTL); } else { ttl = -1; indexBasedOnNr = indexTTL; } //Nr BasedOn + basedon final int numBasedOn; final int indexPublicKeySize; final int indexBasedOn; final Set<Number160> basedOn = new HashSet<Number160>(); if (hasBasedOn(header)) { // get # of based on keys indexBasedOn = indexBasedOnNr + Utils.BYTE_BYTE_SIZE; if (buf.readableBytes() < indexBasedOn) { return null; } numBasedOn = buf.getUnsignedByte(buf.readerIndex() + indexBasedOnNr) + 1; indexPublicKeySize = indexBasedOn + (numBasedOn * Number160.BYTE_ARRAY_SIZE); if (buf.readableBytes() < indexPublicKeySize) { return null; } //get basedon int index = buf.readerIndex() + indexBasedOnNr + Utils.BYTE_BYTE_SIZE; final byte[] me = new byte[Number160.BYTE_ARRAY_SIZE]; for (int i = 0; i < numBasedOn; i++) { buf.getBytes(index, me); index += Number160.BYTE_ARRAY_SIZE; basedOn.add(new Number160(me)); } } else { // no based on keys indexPublicKeySize = indexBasedOnNr; numBasedOn = 0; } //public key and size final int publicKeySize; final int indexPublicKey; final int indexEnd; final PublicKey publicKey; if (hasPublicKey(header)) { indexPublicKey = indexPublicKeySize + Utils.SHORT_BYTE_SIZE; if (buf.readableBytes() < indexPublicKey) { return null; } publicKeySize = buf.getUnsignedShort(buf.readerIndex() + indexPublicKeySize); indexEnd = indexPublicKey + publicKeySize; if (buf.readableBytes() < indexEnd) { return null; } //get public key buf.skipBytes(indexPublicKeySize); publicKey = signatureFactory.decodePublicKey(buf); } else { publicKeySize = 0; indexPublicKey = indexPublicKeySize; buf.skipBytes(indexPublicKey); publicKey = null; } //now we have read the header and the length final Data data = new Data(header, length); data.ttlSeconds = ttl; data.basedOnSet = basedOn; data.publicKey = publicKey; return data; } /** * Add data to the byte buffer. * * @param buf * The byte buffer to append * @return True if we are done reading */ public boolean decodeBuffer(final ByteBuf buf) { final int already = buffer.alreadyTransferred(); final int remaining = length() - already; // already finished if (remaining == 0) { return true; } // make sure it gets not garbage collected. But we need to keep track of // it and when this object gets collected, // we need to release the buffer final int transfered = buffer.transferFrom(buf, remaining); return transfered == remaining; } public boolean decodeDone(final ByteBuf buf, SignatureFactory signatureFactory) { if (signed) { if (buf.readableBytes() < signatureFactory.signatureSize()) { // don't even try to create a signature return false; } signature = signatureFactory.signatureCodec(buf); } return true; } public boolean decodeDone(final ByteBuf buf, PublicKey publicKey, SignatureFactory signatureFactory) { if (signed) { if (publicKey != PeerBuilder.EMPTY_PUBLIC_KEY && publicKey != null && (this.publicKey == null || this.publicKey == PeerBuilder.EMPTY_PUBLIC_KEY)) { this.publicKey = publicKey; } if (buf.readableBytes() < signatureFactory.signatureSize()) { // don't even try to create a signature return false; } signature = signatureFactory.signatureCodec(buf); } return true; } public boolean verify(SignatureFactory signatureFactory) throws InvalidKeyException, SignatureException { return verify(publicKey, signatureFactory); } public boolean verify(PublicKey publicKey, SignatureFactory signatureFactory) throws InvalidKeyException, SignatureException { return signatureFactory.verify(publicKey, toByteBuffers(), signature); } /** * * Header format: * <pre> * 1 byte - header * 1 or 4 bytes - length * 4 or 0 bytes - ttl (hasTTL) * 1 or 0 bytes - number of basedon keys (hasBasedOn) * n x 20 bytes - basedon keys (hasBasedOn, number of basedon keys) * 2 or 0 bytes - length of public key (hasPublicKey) * n bytes - public key (hasPublicKey, length of public key) * </pre> * * @param buf * @param signatureFactory */ public void encodeHeader(final ByteBuf buf, SignatureFactory signatureFactory) { int header = type.ordinal(); if (prepareFlag) { header |= 0x02; } if (flag1) { header |= 0x04; } if (flag2) { header |= 0x08; } if (ttl) { header |= 0x10; } if (signed && publicKeyFlag && protectedEntry) { header |= (0x20 | 0x40); } else if (signed && publicKeyFlag) { header |= 0x40; } else if (publicKeyFlag) { header |= 0x20; } if (basedOnFlag) { header |= 0x80; } switch (type) { case SMALL: buf.writeByte(header); buf.writeByte(length); break; case LARGE: buf.writeByte(header); buf.writeInt(length); break; default: throw new IllegalArgumentException("unknown size"); } if (ttl) { buf.writeInt(ttlSeconds); } if (basedOnFlag) { buf.writeByte(basedOnSet.size() - 1); for (Number160 basedOn : basedOnSet) { buf.writeBytes(basedOn.toByteArray()); } } if (publicKeyFlag) { if (publicKey == null) { buf.writeShort(0); } else { signatureFactory.encodePublicKey(publicKey, buf); } } } public boolean encodeBuffer(final AlternativeCompositeByteBuf buf) { int already = buffer.alreadyTransferred(); int remaining = length() - already; // already finished if (remaining == 0) { return true; } buffer.transferTo(buf); return buffer.alreadyTransferred() == length(); } public void encodeDone(final ByteBuf buf, SignatureFactory signatureFactory) throws InvalidKeyException, SignatureException, IOException { encodeDone(buf, signatureFactory, null); } public void encodeDone(final ByteBuf buf, SignatureFactory signatureFactory, PrivateKey messagePrivateKey) throws InvalidKeyException, SignatureException, IOException { if (signed) { if (signature == null && privateKey != null) { signature = signatureFactory.sign(privateKey, toByteBuffers()); } else if (signature == null && messagePrivateKey != null) { signature = signatureFactory.sign(messagePrivateKey, toByteBuffers()); } else if (signature == null) { throw new IllegalArgumentException("you need a private key from somewhere"); } signature.write(buf); } } /** * If you use this, make sure you are aware of Nettys reference counting. * * http://netty.io/wiki/reference-counted-objects.html * * Once this object is destroyed, the ByteBuf cannot be accessed anymore (unless you use retain/release). * If you don't release it, you may run into java.lang.OufOfMemoryError: Direct Buffer Memory exceptions * * @return */ public ByteBuf buffer() { return buffer.toByteBuf(); } public Object object() throws ClassNotFoundException, IOException { return Utils.decodeJavaObject(buffer); } public long validFromMillis() { return validFromMillis; } public Data validFromMillis(long validFromMillis) { this.validFromMillis = validFromMillis; return this; } public Data signNow(KeyPair keyPair, SignatureFactory signatureFactory) throws InvalidKeyException, SignatureException, IOException { return signNow(keyPair, signatureFactory, false); } public Data protectEntryNow(KeyPair keyPair, SignatureFactory signatureFactory) throws InvalidKeyException, SignatureException, IOException { return signNow(keyPair, signatureFactory, true); } private Data signNow(KeyPair keyPair, SignatureFactory signatureFactory, boolean protectedEntry) throws InvalidKeyException, SignatureException, IOException { if (this.signature == null) { this.signed = true; this.signature = signatureFactory.sign(keyPair.getPrivate(), toByteBuffers()); this.publicKey = keyPair.getPublic(); this.publicKeyFlag = true; this.protectedEntry = protectedEntry; } return this; } public Data signNow(PrivateKey privateKey, SignatureFactory signatureFactory) throws InvalidKeyException, SignatureException, IOException { return signNow(privateKey, signatureFactory, false); } public Data protectEntryNow(PrivateKey privateKey, SignatureFactory signatureFactory) throws InvalidKeyException, SignatureException, IOException { return signNow(privateKey, signatureFactory, true); } private Data signNow(PrivateKey privateKey, SignatureFactory signatureFactory, boolean protectedEntry) throws InvalidKeyException, SignatureException, IOException { if (this.signature == null) { this.signed = true; this.signature = signatureFactory.sign(privateKey, toByteBuffers()); this.publicKeyFlag = true; this.protectedEntry = protectedEntry; } return this; } public Data protectEntry() { this.signed = true; this.publicKeyFlag = true; this.protectedEntry = true; return this; } public Data protectEntry(PrivateKey privateKey) { this.signed = true; this.publicKeyFlag = true; this.protectedEntry = true; this.privateKey = privateKey; return this; } public Data protectEntry(KeyPair keyPair) { this.signed = true; this.publicKeyFlag = true; this.protectedEntry = true; this.privateKey = keyPair.getPrivate(); this.publicKey = keyPair.getPublic(); return this; } public Data sign() { this.signed = true; this.publicKeyFlag = true; return this; } public Data sign(PrivateKey privateKey) { this.signed = true; this.publicKeyFlag = true; this.privateKey = privateKey; return this; } public Data sign(KeyPair keyPair) { this.signed = true; this.publicKeyFlag = true; this.privateKey = keyPair.getPrivate(); this.publicKey = keyPair.getPublic(); return this; } public int length() { return length; } public long expirationMillis() { return ttlSeconds <= 0 ? Long.MAX_VALUE : validFromMillis + (ttlSeconds * 1000L); } public int ttlSeconds() { return ttlSeconds; } public Data ttlSeconds(int ttlSeconds) { this.ttlSeconds = ttlSeconds; this.ttl = true; return this; } public Data addBasedOn(Number160 basedOn) { this.basedOnSet.add(basedOn); this.basedOnFlag = true; return this; } public Set<Number160> basedOnSet() { return basedOnSet; } public SignatureFactory signatureFactory() { if (signatureFactory == null) { return new DSASignatureFactory(); } else { return signatureFactory; } } public Data signatureFactory(SignatureFactory signatureFactory) { this.signatureFactory = signatureFactory; return this; } public boolean isProtectedEntry() { return protectedEntry; } public boolean isSigned() { return signed; } public Data signed(boolean signed) { this.signed = signed; this.publicKeyFlag = signed; return this; } public Data signed() { signed(true); return this; } public boolean isFlag1() { return flag1; } public Data flag1(boolean flag1) { if (flag1 && this.flag2) { throw new IllegalArgumentException("cannot set both flags, this means data is deleted"); } this.flag1 = flag1; return this; } public Data flag1() { return flag1(true); } public boolean isFlag2() { return flag2; } public Data flag2(boolean flag2) { if (flag2 && this.flag1) { throw new IllegalArgumentException("cannot set both flags, this means data is deleted"); } this.flag2 = flag2; return this; } public Data flag2() { return flag2(true); } public boolean hasPrepareFlag() { return prepareFlag; } public Data prepareFlag(boolean prepareFlag) { this.prepareFlag = prepareFlag; return this; } public Data prepareFlag() { this.prepareFlag = true; return this; } public Data deleted() { return deleted(true); } public Data deleted(boolean deleted) { if (this.flag1 || this.flag2) { throw new IllegalArgumentException("cannot set deleted, as one flag is already set"); } this.flag1 = deleted; this.flag2 = deleted; return this; } public boolean isDeleted() { return this.flag1 && this.flag2; } public boolean hasPublicKey() { return publicKeyFlag; } public Data publicKeyFlag(boolean publicKeyFlag) { this.publicKeyFlag = publicKeyFlag; return this; } public Data publicKeyFlag() { this.publicKeyFlag = true; return this; } public boolean isMeta() { return meta; } public Data meta(boolean meta) { this.meta = meta; return this; } public Data meta() { this.meta = true; return this; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Data[l:"); sb.append(length).append(",t:"); sb.append(ttlSeconds()).append(",hasPK:"); sb.append(publicKey != null).append(",h:"); sb.append(signature).append("]"); return sb.toString(); } public void resetAlreadyTransferred() { buffer.resetAlreadyTransferred(); } /** * @return A shallow copy where the data is shared but the reader and writer * index is not shared */ public Data duplicate() { Data data = new Data(buffer.shallowCopy(), length).publicKey(publicKey).signature(signature) .ttlSeconds(ttlSeconds); // duplicate based on keys data.basedOnSet.addAll(basedOnSet); // set all the flags. Although signature, basedOn, and ttlSeconds set a // flag, they will be overwritten with the data from this class data.publicKeyFlag = publicKeyFlag; data.flag1 = flag1; data.flag2 = flag2; data.basedOnFlag = basedOnFlag; data.signed = signed; data.ttl = ttl; data.protectedEntry = protectedEntry; data.privateKey = privateKey; data.validFromMillis = validFromMillis; data.prepareFlag = prepareFlag; return data; } public Data duplicateMeta() { Data data = new Data().publicKey(publicKey).signature(signature).ttlSeconds(ttlSeconds); // duplicate based on keys data.basedOnSet.addAll(basedOnSet); // set all the flags. Although signature, basedOn, and ttlSeconds set a // flag, they will be overwritten with the data from this class data.publicKeyFlag = publicKeyFlag; data.flag1 = flag1; data.flag2 = flag2; data.basedOnFlag = basedOnFlag; data.signed = signed; data.ttl = ttl; data.protectedEntry = protectedEntry; data.privateKey = privateKey; data.validFromMillis = validFromMillis; data.prepareFlag = prepareFlag; return data; } public static Type type(final int header) { return Type.values()[header & 0x1]; } private static boolean hasPrepareFlag(final int header) { return (header & 0x02) > 0; } private static boolean isFlag1(final int header) { return (header & 0x04) > 0; } private static boolean isFlag2(final int header) { return (header & 0x08) > 0; } private static boolean hasTTL(final int header) { return (header & 0x10) > 0; } private static boolean hasPublicKey(final int header) { return ((header >> 5) & (0x01 | 0x02)) > 0; } private static boolean isProtectedEntry(final int header) { return ((header >> 5) & (0x01 | 0x02)) > 2; } private static boolean isSigned(final int header) { return ((header >> 5) & (0x01 | 0x02)) > 1; } private static boolean hasBasedOn(final int header) { return (header & 0x80) > 0; } /** * @return The byte array that is the payload. Here we copy the buffer */ public byte[] toBytes() { return buffer.bytes(); } /** * @return The ByteBuffers that is the payload. We do not make a copy here */ public ByteBuffer[] toByteBuffers() { return buffer.bufferList().toArray(new ByteBuffer[0]); } public PublicKey publicKey() { return publicKey; } /** * @return A private key if we want to sign it lazy (during encoding). */ public PrivateKey privateKey() { return privateKey; } public Data publicKey(PublicKey publicKey) { this.publicKeyFlag = true; this.publicKey = publicKey; return this; } public SignatureCodec signature() { return signature; } public Data signature(SignatureCodec signature) { this.signature = signature; return this; } @Override public int hashCode() { BitSet bs = new BitSet(5); bs.set(0, signed); bs.set(1, ttl); bs.set(2, basedOnFlag); bs.set(3, protectedEntry); bs.set(4, publicKeyFlag); bs.set(5, flag1); bs.set(6, flag2); bs.set(7, prepareFlag); int hashCode = bs.hashCode() ^ ttlSeconds ^ type.ordinal() ^ length; for (Number160 basedOn : basedOnSet) { hashCode = hashCode ^ basedOn.hashCode(); } // This is a slow operation, use with care! return hashCode ^ buffer.hashCode(); } @Override public boolean equals(final Object obj) { if (!(obj instanceof Data)) { return false; } if (obj == this) { return true; } Data d = (Data) obj; //ignore ttl -> it's still the same data even if ttl is different if (d.signed != signed || d.basedOnFlag != basedOnFlag || d.protectedEntry != protectedEntry || d.publicKeyFlag != publicKeyFlag || flag1 != d.flag1 || flag2 != d.flag2 || prepareFlag != d.prepareFlag) { return false; } if (d.type != type || d.length != length) { return false; } //ignore ttl -> it's still the same data even if ttl is different return Utils.equals(basedOnSet, d.basedOnSet) && Utils.equals(signature, d.signature) && d.buffer.equals(buffer); // This is a slow operation, use // with care! } public Number160 hash() { if (hash == null) { hash = Utils.makeSHAHash(buffer); } return hash; } public Data release() { buffer.release(); return this; } }