package net.tomp2p.message;
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.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 net.tomp2p.connection.Dispatcher;
import net.tomp2p.connection.SignatureFactory;
import net.tomp2p.message.Message.Content;
import net.tomp2p.p2p.PeerBuilder;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.Number640;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.peers.PeerSocketAddress.PeerSocket4Address;
import net.tomp2p.peers.PeerSocketAddress.PeerSocket6Address;
import net.tomp2p.rpc.SimpleBloomFilter;
import net.tomp2p.storage.Data;
import net.tomp2p.utils.Utils;
public class Decoder {
public static final AttributeKey<InetSocketAddress> INET_ADDRESS_KEY = AttributeKey.valueOf("inet-addr");
public static final AttributeKey<PeerAddress> PEER_ADDRESS_KEY = AttributeKey.valueOf("peer-addr");
private static final Logger LOG = LoggerFactory.getLogger(Decoder.class);
private final Queue<Content> contentTypes = new LinkedList<Message.Content>();
// private Message2 result = null;
// current state - needs to be deleted if we want to reuse
private Message message = new Message();
private boolean headerDone = false;
private Signature signature = null;
private int neighborSize = -1;
private NeighborSet neighborSet = null;
private int keyCollectionSize = -1;
private KeyCollection keyCollection = null;
private int mapSize = -1;
private DataMap dataMap = null;
private Data data = null;
private Number640 key = null;
private int keyMap640KeysSize = -1;
private KeyMap640Keys keyMap640Keys = null;
private int keyMapByteSize = -1;
private KeyMapByte keyMapByte = null;
private int bufferSize = -1;
private int bufferTransferred = 0;
private ByteBuf buffer = null;
private int trackerDataSize = -1;
private TrackerData trackerData = null;
private Data currentTrackerData = null;
private Content lastContent = null;
private final SignatureFactory signatureFactory;
public Decoder(SignatureFactory signatureFactory) {
this.signatureFactory = signatureFactory;
}
public boolean decode(ChannelHandlerContext ctx, final ByteBuf buf, InetSocketAddress recipient,
final InetSocketAddress sender) {
LOG.debug("Decoding of TomP2P starts now. Readable: {}.", buf.readableBytes());
try {
final int readerBefore = buf.readerIndex();
// set the sender of this message for handling timeout
final Attribute<InetSocketAddress> attributeInet = ctx.attr(INET_ADDRESS_KEY);
attributeInet.set(sender);
if (!headerDone) {
headerDone = decodeHeader(buf, recipient, sender);
if (headerDone) {
// store the sender as an attribute
final Attribute<PeerAddress> attributePeerAddress = ctx.attr(PEER_ADDRESS_KEY);
attributePeerAddress.set(message.sender());
message.udp(ctx.channel() instanceof DatagramChannel);
if (message.isFireAndForget() && message.isUdp()) {
//TODO: find better place for this
Dispatcher.removeTimeout(ctx);
}
} else {
return false;
}
}
final boolean donePayload = decodePayload(buf);
decodeSignature(buf, readerBefore, donePayload);
// see https://github.com/netty/netty/issues/1976
buf.discardSomeReadBytes();
return donePayload;
} catch (Exception e) {
ctx.fireExceptionCaught(e);
e.printStackTrace();
return true;
}
}
public void decodeSignature(final ByteBuf buf, final int readerBefore, final boolean donePayload) throws InvalidKeyException, SignatureException {
final int readerAfter = buf.readerIndex();
final int len = readerAfter - readerBefore;
if(len > 0) {
verifySignature(buf, readerBefore, len, donePayload);
}
}
private void verifySignature(final ByteBuf buf, final int readerBefore, final int len, final boolean donePayload)
throws SignatureException, InvalidKeyException {
if (!message.isSign()) {
return;
}
// if we read the complete data, we also read the signature
// for the verification, we should not use this for the signature
final int length = donePayload ? len - signatureFactory.signatureSize() : len;
ByteBuffer[] byteBuffers = buf.nioBuffers(readerBefore, length);
if(signature == null) {
signature = signatureFactory.update(message.publicKey(0), byteBuffers);
} else {
for (int i = 0; i < byteBuffers.length; i++) {
signature.update(byteBuffers[i]);
}
}
if (donePayload) {
byte[] signatureReceived = message.receivedSignature().encode();
LOG.debug("Verifying received signature: {}", Arrays.toString(signatureReceived));
if (signature.verify(signatureReceived)) {
// set public key only if signature is correct
message.setVerified();
LOG.debug("Signature check OK.");
} else {
LOG.warn("Signature check NOT OK. Message: {}.", message);
}
}
}
public boolean decodeHeader(final ByteBuf buf, InetSocketAddress recipient, final InetSocketAddress sender) {
if (!headerDone) {
if (buf.readableBytes() < MessageHeaderCodec.HEADER_SIZE_MIN) {
// we don't have the header yet, we need the full header first
// wait for more data
return false;
}
final int readerIndex = buf.readerIndex();
headerDone = MessageHeaderCodec.decodeHeader(buf, recipient, sender, message);
if(!headerDone) {
buf.readerIndex(readerIndex);
return false;
}
}
// we have set the content types already
message.presetContentTypes(true);
for (Content content : message.contentTypes()) {
if (content == Content.EMPTY) {
break;
}
if (content == Content.PUBLIC_KEY_SIGNATURE) {
message.setHintSign();
}
contentTypes.offer(content);
}
LOG.debug("Parsed message {}.", message);
return true;
}
public boolean decodePayload(final ByteBuf buf) throws NoSuchAlgorithmException, InvalidKeySpecException,
InvalidKeyException {
LOG.debug("About to pass message {} to {}. Buffer to read: {}.", message, message.senderSocket(), buf.readableBytes());
if (!message.hasContent()) {
return true;
}
// payload comes here
int size;
PublicKey receivedPublicKey;
while (contentTypes.size() > 0) {
Content content = contentTypes.peek();
LOG.debug("Parse content: {} in message {}", content, message);
switch (content) {
case INTEGER:
if (buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) {
return false;
}
message.intValue(buf.readInt());
lastContent = contentTypes.poll();
break;
case LONG:
if (buf.readableBytes() < Utils.LONG_BYTE_SIZE) {
return false;
}
message.longValue(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.key(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.bloomFilter(new SimpleBloomFilter<Number160>(buf));
lastContent = contentTypes.poll();
break;
case SET_NEIGHBORS:
if (neighborSize == -1 && buf.readableBytes() < Utils.BYTE_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++) {
if (buf.readableBytes() < PeerAddress.HEADER_SIZE) {
return false;
}
size = PeerAddress.size(buf.getUnsignedMedium(buf.readerIndex()));
if (buf.readableBytes() < size) {
return false;
}
PeerAddress pa = PeerAddress.decode(buf);
neighborSet.add(pa);
}
message.neighborsSet(neighborSet);
lastContent = contentTypes.poll();
neighborSize = -1;
neighborSet = null;
break;
case PEER_SOCKET4:
if (buf.readableBytes() < PeerSocket4Address.SIZE) {
return false;
}
message.peerSocket4Address(PeerSocket4Address.decode(buf));
lastContent = contentTypes.poll();
break;
case PEER_SOCKET6:
if (buf.readableBytes() < PeerSocket6Address.SIZE) {
return false;
}
message.peerSocket6Address(PeerSocket6Address.decode(buf));
lastContent = contentTypes.poll();
break;
case SET_KEY640:
if (keyCollectionSize == -1 && buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) {
return false;
}
if (keyCollectionSize == -1) {
keyCollectionSize = buf.readInt();
}
if (keyCollection == null) {
keyCollection = new KeyCollection(new ArrayList<Number640>(keyCollectionSize));
}
for (int i = keyCollection.size(); i < keyCollectionSize; i++) {
if (buf.readableBytes() < Number160.BYTE_ARRAY_SIZE + 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);
buf.readBytes(me2);
Number160 versionKey = new Number160(me2);
keyCollection.add(new Number640(locationKey, domainKey, contentKey, versionKey));
}
message.keyCollection(keyCollection);
lastContent = contentTypes.poll();
keyCollectionSize = -1;
keyCollection = null;
break;
case MAP_KEY640_DATA:
if (mapSize == -1 && buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) {
return false;
}
if (mapSize == -1) {
mapSize = buf.readInt();
}
if (dataMap == null) {
dataMap = new DataMap(new TreeMap<Number640, Data>());
}
if (data != null) {
if (!data.decodeBuffer(buf)) {
return false;
}
if (!data.decodeDone(buf, message.publicKey(0), signatureFactory)) {
return false;
}
data = null;
key = null;
}
for (int i = dataMap.size(); i < mapSize; i++) {
if (key == null) {
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 versionKey = new Number160(me3);
key = new Number640(locationKey, domainKey, contentKey, versionKey);
}
LOG.debug("Key decoded in message {}, remaining {}", message, buf.readableBytes());
data = Data.decodeHeader(buf, signatureFactory);
if (data == null) {
return false;
}
LOG.debug("Header decoded in message {}, remaining {}", message, buf.readableBytes());
dataMap.dataMap().put(key, data);
if (!data.decodeBuffer(buf)) {
return false;
}
LOG.debug("Buffer decoded in message {}", message);
if (!data.decodeDone(buf, message.publicKey(0), signatureFactory)) {
return false;
}
LOG.debug("Done decoded in message {}", message);
// if we have signed the message, set the public key anyway, but only if we indicated so
inheritPublicKey(message, data);
data = null;
key = null;
}
message.setDataMap(dataMap);
lastContent = contentTypes.poll();
mapSize = -1;
dataMap = null;
break;
case MAP_KEY640_KEYS:
if (keyMap640KeysSize == -1 && buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) {
return false;
}
if (keyMap640KeysSize == -1) {
keyMap640KeysSize = buf.readInt();
}
if (keyMap640Keys == null) {
keyMap640Keys = new KeyMap640Keys(new TreeMap<Number640, Collection<Number160>>());
}
final int meta = Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE
+ Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE;
for (int i = keyMap640Keys.size(); i < keyMap640KeysSize; i++) {
if (buf.readableBytes() < meta + Utils.BYTE_BYTE_SIZE) {
return false;
}
size = buf.getUnsignedByte(buf.readerIndex() + meta);
if (buf.readableBytes() < meta + Utils.BYTE_BYTE_SIZE + (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 versionKey = new Number160(me3);
int numBasedOn = buf.readByte();
Set<Number160> value = new HashSet<Number160>(numBasedOn);
for (int j = 0; j < numBasedOn; j++) {
buf.readBytes(me3);
Number160 basedOnKey = new Number160(me3);
value.add(basedOnKey);
}
keyMap640Keys.put(new Number640(locationKey, domainKey, contentKey, versionKey), value);
}
message.keyMap640Keys(keyMap640Keys);
lastContent = contentTypes.poll();
keyMap640KeysSize = -1;
keyMap640Keys = null;
break;
case MAP_KEY640_BYTE:
if (keyMapByteSize == -1 && buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) {
return false;
}
if (keyMapByteSize == -1) {
keyMapByteSize = buf.readInt();
}
if (keyMapByte == null) {
keyMapByte = new KeyMapByte(new HashMap<Number640, Byte>(2 * keyMapByteSize));
}
for (int i = keyMapByte.size(); i < keyMapByteSize; i++) {
if (buf.readableBytes() < Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE
+ Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE + 1) {
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 versionKey = new Number160(me3);
byte value = buf.readByte();
keyMapByte.put(new Number640(locationKey, domainKey, contentKey, versionKey), value);
}
message.keyMapByte(keyMapByte);
lastContent = contentTypes.poll();
keyMapByteSize = -1;
keyMapByte = null;
break;
case BYTE_BUFFER:
if (bufferSize == -1 && buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) {
return false;
}
if (bufferSize == -1) {
bufferSize = buf.readInt();
}
if(buf.readableBytes() < bufferSize) {
LOG.debug("Still looking for data. Indicating that its not finished yet. Readable = {}, Size = {}.", buf.readableBytes(), bufferSize);
return false;
}
if (buffer == null) {
buffer = Unpooled.buffer();
}
buffer.writeBytes(buf, bufferSize);
message.buffer(new Buffer(buffer, bufferSize));
lastContent = contentTypes.poll();
bufferSize = -1;
bufferTransferred = 0;
buffer = null;
break;
case SET_TRACKER_DATA:
if (trackerDataSize == -1 && buf.readableBytes() < Utils.BYTE_BYTE_SIZE) {
return false;
}
if (trackerDataSize == -1) {
trackerDataSize = buf.readUnsignedByte();
}
if (trackerData == null) {
trackerData = new TrackerData(new HashMap<PeerAddress, Data>(2 * trackerDataSize));
}
if (currentTrackerData != null) {
if (!currentTrackerData.decodeBuffer(buf)) {
return false;
}
if (!currentTrackerData.decodeDone(buf, message.publicKey(0), signatureFactory)) {
return false;
}
currentTrackerData = null;
}
for (int i = trackerData.size(); i < trackerDataSize; i++) {
if (buf.readableBytes() < 4) {
return false;
}
size = PeerAddress.size(buf.getInt(buf.readerIndex()));
if (buf.readableBytes() < size) {
return false;
}
PeerAddress pa = PeerAddress.decode(buf);
currentTrackerData = Data.decodeHeader(buf, signatureFactory);
if (currentTrackerData == null) {
return false;
}
trackerData.peerAddresses().put(pa, currentTrackerData);
if (message.isSign()) {
currentTrackerData.publicKey(message.publicKey(0));
}
if (!currentTrackerData.decodeBuffer(buf)) {
return false;
}
if (!currentTrackerData.decodeDone(buf, message.publicKey(0), signatureFactory)) {
return false;
}
currentTrackerData = null;
}
message.trackerData(trackerData);
lastContent = contentTypes.poll();
trackerDataSize = -1;
trackerData = null;
break;
case PUBLIC_KEY: // fall-through
case PUBLIC_KEY_SIGNATURE:
receivedPublicKey = signatureFactory.decodePublicKey(buf);
if(content == Content.PUBLIC_KEY_SIGNATURE) {
if (receivedPublicKey == PeerBuilder.EMPTY_PUBLIC_KEY) {
throw new InvalidKeyException("The public key cannot be empty.");
}
}
if (receivedPublicKey == null) {
return false;
}
message.publicKey(receivedPublicKey);
lastContent = contentTypes.poll();
break;
default:
break;
}
}
LOG.debug("Parsed content in message {}", message);
if (message.isSign()) {
size = signatureFactory.signatureSize();
if(buf.readableBytes() < size) {
return false;
}
SignatureCodec signatureEncode = signatureFactory.signatureCodec(buf);
message.receivedSignature(signatureEncode);
}
return true;
}
public Message prepareFinish() {
final Message ret = message;
message = new Message();
ret.setDone();
contentTypes.clear();
headerDone = false;
neighborSize = -1;
neighborSet = null;
keyCollectionSize = -1;
keyCollection = null;
mapSize = -1;
dataMap = null;
data = null;
keyMap640KeysSize = -1;
keyMap640Keys = null;
bufferSize = -1;
bufferTransferred = 0;
buffer = null;
signature = null;
return ret;
}
public Message message() {
return message;
}
public Content lastContent() {
return lastContent;
}
public static void inheritPublicKey(Message message, Data data) {
if (message.isSign() && message.publicKey(0) != null && data.hasPublicKey()
&& (data.publicKey() == null || data.publicKey() == PeerBuilder.EMPTY_PUBLIC_KEY)) {
data.publicKey(message.publicKey(0));
}
}
public void release() {
if(message!=null) {
message.release();
}
//release partial data
if(data != null) {
data.release();
}
if(dataMap != null) {
for(Data data: dataMap.dataMap().values()) {
data.release();
}
}
if(buffer != null) {
buffer.release();
}
if(currentTrackerData != null) {
currentTrackerData.release();
}
if(trackerData != null) {
for(Data data: trackerData.peerAddresses().values()) {
data.release();
}
}
}
}