package net.tomp2p.message; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.socket.DatagramChannel; import io.netty.util.Attribute; import io.netty.util.AttributeKey; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.Signature; import java.security.spec.InvalidKeySpecException; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.Queue; import net.tomp2p.connection2.SignatureFactory; import net.tomp2p.message.Message2.Content; import net.tomp2p.peers.Number160; import net.tomp2p.peers.Number480; import net.tomp2p.peers.PeerAddress; import net.tomp2p.rpc.SimpleBloomFilter; import net.tomp2p.storage.Data; import net.tomp2p.utils.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TomP2PDecoder { public static final AttributeKey<InetSocketAddress> INET_ADDRESS_KEY = new AttributeKey<InetSocketAddress>( "inet-addr"); public static final AttributeKey<PeerAddress> PEER_ADDRESS_KEY = new AttributeKey<PeerAddress>( "peer-addr"); private static final Logger LOG = LoggerFactory.getLogger(TomP2PDecoder.class); private final Queue<Content> contentTypes = new LinkedList<Message2.Content>(); // private Message2 result = null; // current state - needs to be deleted if we want to reuse private Message2 message = null; private int neighborSize = -1; private NeighborSet neighborSet = null; private int keysSize = -1; private Keys keys = null; private int mapsSize = -1; private DataMap dataMap = null; private Data data = null; private int keysMapSize = -1; private KeysMap keysMap = null; private int bufferSize = -1; private Buffer buffer = null; private int trackerDataSize = -1; private TrackerData trackerData = null; private Data currentTrackerData = null; private Content lastContent = null; private final SignatureFactory signatureFactory; public TomP2PDecoder(SignatureFactory signatureFactory) { this.signatureFactory = signatureFactory; } public boolean decode(ChannelHandlerContext ctx, final ByteBuf buf, InetSocketAddress recipient, final InetSocketAddress sender) { LOG.debug("decode of TomP2P starts now"); // store positions for the verification int[] pos = new int[buf.nioBufferCount()]; for (int i = 0; i < buf.nioBufferCount(); i++) { pos[i] = buf.nioBuffers()[i].position(); } boolean retVal; try { retVal = decode0(ctx, buf, recipient, sender); // if retMsg == null, then we even could not read the message due to lack of data if (message != null && message.isSign()) { for (int i = 0; i < buf.nioBufferCount(); i++) { ByteBuffer byteBuffer = buf.nioBuffers()[i]; // since we read the bytebuffer, the nio buffer also has a byteBuffer.position(pos[i]); if (retVal && i + 1 == buf.nioBufferCount()) { byteBuffer.limit(byteBuffer.limit() - (Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE)); } message.signatureForVerification().update(byteBuffer); } byte[] signatureReceived = message.receivedSignature().encode(); if (message.signatureForVerification().verify(signatureReceived)) { // set public key only if signature is correct message.setPublicKey(message.receivedPublicKey()); LOG.debug("signature check ok"); } else { LOG.debug("wrong signature!"); } } buf.discardSomeReadBytes(); } catch (Exception e) { ctx.fireExceptionCaught(e); e.printStackTrace(); retVal = true; } //System.err.println("can read more: " + buf.readableBytes() + " / " + retVal); return retVal; } public boolean decode0(ChannelHandlerContext ctx, final ByteBuf buf, InetSocketAddress recipient, final InetSocketAddress sender) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { // set the sender of this message for handling timeout final Attribute<InetSocketAddress> attributeInet = ctx.attr(INET_ADDRESS_KEY); attributeInet.set(sender); // we don't have the header yet, we need the full header first if (message == null) { if (buf.readableBytes() < MessageHeaderCodec.HEADER_SIZE) { // wait for more data return false; } message = MessageHeaderCodec.decodeHeader(buf, recipient, sender); // store the sender as an attribute final Attribute<PeerAddress> attributePeerAddress = ctx.attr(PEER_ADDRESS_KEY); attributePeerAddress.set(message.getSender()); // we have set the content types already message.presetContentTypes(true); message.udp(ctx.channel() instanceof DatagramChannel); for (Content content : message.getContentTypes()) { if (content == Content.EMPTY) { break; } if (content == Content.PUBLIC_KEY_SIGNATURE) { message.setHintSign(); } contentTypes.offer(content); } // if we receive a keep alive message, remove the timeout handler. Maybe its better to add a longer timeout // in the future if (message.isKeepAlive()) { if (ctx.channel().pipeline().names().contains("timeout-server0")) { ctx.channel().pipeline().remove("timeout-server0"); } if (ctx.channel().pipeline().names().contains("timeout-server1")) { ctx.channel().pipeline().remove("timeout-server1"); } } LOG.debug("parsed message {}", message); } LOG.debug("about to pass message {} to {}", message, message.senderSocket()); if (!message.hasContent()) { return true; } // payload comes here int size; while (contentTypes.size() > 0) { Content content = contentTypes.peek(); switch (content) { case INTEGER: if (buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) { return false; } message.setInteger(buf.readInt()); lastContent = contentTypes.poll(); break; case LONG: if (buf.readableBytes() < Utils.LONG_BYTE_SIZE) { return false; } message.setLong(buf.readLong()); lastContent = contentTypes.poll(); break; case KEY: if (buf.readableBytes() < Number160.BYTE_ARRAY_SIZE) { return false; } byte[] me = new byte[Number160.BYTE_ARRAY_SIZE]; buf.readBytes(me); message.setKey(new Number160(me)); lastContent = contentTypes.poll(); break; case BLOOM_FILTER: if (buf.readableBytes() < Utils.SHORT_BYTE_SIZE) { return false; } size = buf.getUnsignedShort(buf.readerIndex()); if (buf.readableBytes() < size) { return false; } message.setBloomFilter(new SimpleBloomFilter<Number160>(buf)); lastContent = contentTypes.poll(); break; case SET_NEIGHBORS: if (neighborSize == -1 && buf.readableBytes() < Utils.BYTE_SIZE) { return false; } if (neighborSize == -1) { neighborSize = buf.readUnsignedByte(); } if (neighborSet == null) { neighborSet = new NeighborSet(-1, new ArrayList<PeerAddress>(neighborSize)); } for (int i = neighborSet.size(); i < neighborSize; i++) { int header = buf.getUnsignedShort(buf.readerIndex()); size = PeerAddress.size(header); if (buf.readableBytes() < size) { return false; } PeerAddress pa = new PeerAddress(buf); neighborSet.add(pa); } message.setNeighborsSet(neighborSet); lastContent = contentTypes.poll(); neighborSize = -1; neighborSet = null; break; case SET_KEY480: if (keysSize == -1 && buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) { return false; } if (keysSize == -1) { keysSize = buf.readInt(); } if (keys == null) { keys = new Keys(new ArrayList<Number480>(keysSize)); } for (int i = keys.size(); i < keysSize; i++) { if (buf.readableBytes() < Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE) { return false; } byte[] me2 = new byte[Number160.BYTE_ARRAY_SIZE]; buf.readBytes(me2); Number160 locationKey = new Number160(me2); buf.readBytes(me2); Number160 domainKey = new Number160(me2); buf.readBytes(me2); Number160 contentKey = new Number160(me2); keys.add(new Number480(locationKey, domainKey, contentKey)); } message.setKeys(keys); lastContent = contentTypes.poll(); keysSize = -1; keys = null; break; case MAP_KEY480_DATA: if (mapsSize == -1 && buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) { return false; } if (mapsSize == -1) { mapsSize = buf.readInt(); } if (dataMap == null) { dataMap = new DataMap(new HashMap<Number480, Data>(2 * mapsSize)); } if (data != null) { if (!data.decodeBuffer(buf)) { return false; } if (!data.decodeDone(buf)) { return false; } data = null; } for (int i = dataMap.size(); i < mapsSize; i++) { if (buf.readableBytes() < Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE) { return false; } byte[] me3 = new byte[Number160.BYTE_ARRAY_SIZE]; buf.readBytes(me3); Number160 locationKey = new Number160(me3); buf.readBytes(me3); Number160 domainKey = new Number160(me3); buf.readBytes(me3); Number160 contentKey = new Number160(me3); data = Data.decodeHeader(buf); if (data == null) { return false; } dataMap.dataMap().put(new Number480(locationKey, domainKey, contentKey), data); if (message.isSign()) { data.publicKey(message.publicKeyReference()); } if (!data.decodeBuffer(buf)) { return false; } if (!data.decodeDone(buf)) { return false; } data = null; } message.setDataMap(dataMap); lastContent = contentTypes.poll(); mapsSize = -1; dataMap = null; break; case MAP_KEY480_KEY: if (keysMapSize == -1 && buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) { return false; } if (keysMapSize == -1) { keysMapSize = buf.readInt(); } if (keysMap == null) { keysMap = new KeysMap(new HashMap<Number480, Number160>(2 * keysMapSize)); } for (int i = keysMap.size(); i < keysMapSize; i++) { if (buf.readableBytes() < Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE) { return false; } byte[] me3 = new byte[Number160.BYTE_ARRAY_SIZE]; buf.readBytes(me3); Number160 locationKey = new Number160(me3); buf.readBytes(me3); Number160 domainKey = new Number160(me3); buf.readBytes(me3); Number160 contentKey = new Number160(me3); buf.readBytes(me3); Number160 valueKey = new Number160(me3); keysMap.put(new Number480(locationKey, domainKey, contentKey), valueKey); } message.setKeysMap(keysMap); lastContent = contentTypes.poll(); keysMapSize = -1; keysMap = null; break; case BYTE_BUFFER: if (bufferSize == -1 && buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) { return false; } if (bufferSize == -1) { bufferSize = buf.readInt(); } if (buffer == null) { ByteBuf tmp = Unpooled.compositeBuffer(); buffer = new Buffer(tmp, bufferSize); } int already = buffer.alreadyRead(); int readable = buf.readableBytes(); int remaining = bufferSize - already; int toread = Math.min(remaining, readable); //Unpooled.copiedBuffer(buf.duplicate().writerIndex(writerIndex)) buffer.addComponent(buf.slice(buf.readerIndex(), toread)); // slice and addComponent do not modifie the reader or the writer, thus we need to do this on our own buf.skipBytes(toread); // buffer.buffer().writerIndex(buffer.buffer().writerIndex() + toread); // increase writer index if (buffer.incRead(toread) != bufferSize) { LOG.debug("we are still looking for data, indicate that we are not finished yet, " + "read = {}, size = {}", buffer.alreadyRead(), bufferSize); return false; } message.setBuffer(buffer); lastContent = contentTypes.poll(); bufferSize = -1; buffer = null; break; case SET_TRACKER_DATA: if (trackerDataSize == -1 && buf.readableBytes() < Utils.BYTE_SIZE) { return false; } if (trackerDataSize == -1) { trackerDataSize = buf.readUnsignedByte(); } if (trackerData == null) { trackerData = new TrackerData(new HashMap<PeerAddress, Data>(2 * trackerDataSize), message.getSender()); } if (currentTrackerData != null) { if (!currentTrackerData.decodeBuffer(buf)) { return false; } if (!currentTrackerData.decodeDone(buf)) { return false; } currentTrackerData = null; } for (int i = trackerData.size(); i < trackerDataSize; i++) { if (buf.readableBytes() < Utils.BYTE_SIZE) { return false; } int header = buf.getUnsignedShort(buf.readerIndex()); size = PeerAddress.size(header); if (buf.readableBytes() < size) { return false; } PeerAddress pa = new PeerAddress(buf); currentTrackerData = Data.decodeHeader(buf); if (currentTrackerData == null) { return false; } trackerData.map().put(pa, currentTrackerData); if (message.isSign()) { currentTrackerData.publicKey(message.publicKeyReference()); } if (!currentTrackerData.decodeBuffer(buf)) { return false; } if (!currentTrackerData.decodeDone(buf)) { return false; } currentTrackerData = null; } message.setTrackerData(trackerData); lastContent = contentTypes.poll(); trackerDataSize = -1; trackerData = null; break; case PUBLIC_KEY_SIGNATURE: if (buf.readableBytes() < 2) { return false; } int len = buf.getUnsignedShort(buf.readerIndex()); if (buf.readableBytes() + Utils.SHORT_BYTE_SIZE < len) { return false; } me = new byte[len]; buf.skipBytes(2); buf.readBytes(me); Signature signature = signatureFactory.signatureInstance(); PublicKey receivedPublicKey = signatureFactory.decodePublicKey(me); signature.initVerify(receivedPublicKey); message.signatureForVerification(signature, receivedPublicKey); lastContent = contentTypes.poll(); break; default: case EMPTY: break; } } if (message.isSign()) { if (buf.readableBytes() < Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE) { return false; } byte[] me = new byte[Number160.BYTE_ARRAY_SIZE]; buf.readBytes(me); Number160 number1 = new Number160(me); buf.readBytes(me); Number160 number2 = new Number160(me); SHA1Signature signatureEncode = new SHA1Signature(number1, number2); message.receivedSignature(signatureEncode); } return true; } public Message2 prepareFinish() { Message2 ret = message; message.setDone(); contentTypes.clear(); // message = null; neighborSize = -1; neighborSet = null; keysSize = -1; keys = null; mapsSize = -1; dataMap = null; data = null; keysMapSize = -1; keysMap = null; bufferSize = -1; buffer = null; return ret; } public Message2 message() { return message; } public Content lastContent() { return lastContent; } }