package net.tomp2p.message;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.DatagramPacket;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.security.PublicKey;
import java.security.Signature;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.tomp2p.connection2.Sender;
import net.tomp2p.connection2.SignatureFactory;
import net.tomp2p.futures.BaseFutureAdapter;
import net.tomp2p.futures.FutureProgres;
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;
public class TomP2POutbound extends ChannelOutboundHandlerAdapter {
private static final Logger LOG = LoggerFactory.getLogger(TomP2POutbound.class);
private final boolean preferDirect;
private boolean header = false;
private boolean resume = false;
private Message2 message;
private SignatureFactory signatureFactory;
public TomP2POutbound(boolean preferDirect, SignatureFactory signatureFactory) {
this.preferDirect = preferDirect;
this.signatureFactory = signatureFactory;
}
@Override
public void write(final ChannelHandlerContext ctx, final Object msg, final ChannelPromise promise)
throws Exception {
ByteBuf buf = null;
try {
if (msg instanceof Message2) {
message = (Message2) msg;
LOG.debug("message for outbound {}", message);
}
if (preferDirect) {
buf = ctx.alloc().ioBuffer();
} else {
buf = ctx.alloc().heapBuffer();
}
if (!header) {
MessageHeaderCodec.encodeHeader(buf, (Message2) msg);
header = true;
} else {
LOG.debug("send a follow up message {}", message);
resume = true;
}
LOG.debug("entering loop");
boolean done = loop(buf);
LOG.debug("exiting loop");
// write out what we have
if (buf.isReadable()) {
// check if we need to sign the message
if (message.isSign()) {
Signature signature = signatureFactory.signatureInstance();
signature.initSign(message.getPrivateKey());
// debug2 = message.getPublicKey();
for (int i = 0; i < buf.nioBufferCount(); i++) {
ByteBuffer buffer = buf.nioBuffers()[i];
signature.update(buffer);
}
byte[] signatureData = signature.sign();
SHA1Signature decodedSignature = new SHA1Signature();
decodedSignature.decode(signatureData);
buf.writeBytes(decodedSignature.getNumber1().toByteArray());
buf.writeBytes(decodedSignature.getNumber2().toByteArray());
}
// this will release the buffer
if (ctx.channel() instanceof DatagramChannel) {
if (message.senderSocket() == null) {
message.senderSocket(message.getRecipient().createSocketUDP());
}
DatagramPacket d = new DatagramPacket(buf, message.senderSocket(), message.recipientSocket());
LOG.debug("Send UPD message {}, datagram: {}", message, d);
ctx.writeAndFlush(d, promise);
if (done) {
message.done(true);
// we wrote the complete message, reset state
header = false;
}
} else {
LOG.debug("Send TCP message {} to {}", message, message.senderSocket());
ctx.writeAndFlush(buf, promise);
if (done) {
message.done(true);
header = false;
}
}
} else {
buf.release();
ctx.write(Unpooled.EMPTY_BUFFER, promise);
}
buf = null;
} finally {
if (buf != null) {
buf.release();
}
}
}
private boolean loop(ByteBuf buf) {
NumberType next;
while ((next = message.contentRefencencs().peek()) != null) {
switch (next.content()) {
case KEY:
buf.writeBytes(message.getKey(next.number()).toByteArray());
message.contentRefencencs().poll();
break;
case INTEGER:
buf.writeInt(message.getInteger(next.number()));
message.contentRefencencs().poll();
break;
case LONG:
buf.writeLong(message.getLong(next.number()));
message.contentRefencencs().poll();
break;
case SET_NEIGHBORS:
NeighborSet neighborSet = message.getNeighborsSet(next.number());
// length
buf.writeByte(neighborSet.size());
for (PeerAddress neighbor : neighborSet.neighbors()) {
buf.writeBytes(neighbor.toByteArray());
}
message.contentRefencencs().poll();
break;
case BLOOM_FILTER:
SimpleBloomFilter<Number160> simpleBloomFilter = message.getBloomFilter(next.number());
simpleBloomFilter.toByteBuf(buf);
message.contentRefencencs().poll();
break;
case SET_KEY480:
// length
Keys keys = message.getKeys(next.number());
buf.writeInt(keys.size());
if (keys.isConvert()) {
for (Number160 key : keys.keysConvert()) {
buf.writeBytes(keys.locationKey().toByteArray());
buf.writeBytes(keys.domainKey().toByteArray());
buf.writeBytes(key.toByteArray());
}
} else {
for (Number480 key : keys.keys()) {
buf.writeBytes(key.getLocationKey().toByteArray());
buf.writeBytes(key.getDomainKey().toByteArray());
buf.writeBytes(key.getContentKey().toByteArray());
}
}
message.contentRefencencs().poll();
break;
case MAP_KEY480_DATA:
DataMap dataMap = message.getDataMap(next.number());
buf.writeInt(dataMap.size());
if (dataMap.isConvert()) {
for (Entry<Number160, Data> entry : dataMap.dataMapConvert().entrySet()) {
buf.writeBytes(dataMap.locationKey().toByteArray());
buf.writeBytes(dataMap.domainKey().toByteArray());
buf.writeBytes(entry.getKey().toByteArray());
Data data = entry.getValue().duplicate();
data.encode(buf);
data.encodeDone(buf);
}
} else {
for (Entry<Number480, Data> entry : dataMap.dataMap().entrySet()) {
buf.writeBytes(entry.getKey().getLocationKey().toByteArray());
buf.writeBytes(entry.getKey().getDomainKey().toByteArray());
buf.writeBytes(entry.getKey().getContentKey().toByteArray());
Data data = entry.getValue().duplicate();
data.encode(buf);
data.encodeDone(buf);
}
}
message.contentRefencencs().poll();
break;
case MAP_KEY480_KEY:
KeysMap keysMap = message.getKeysMap(next.number());
buf.writeInt(keysMap.size());
for (Entry<Number480, Number160> entry : keysMap.keysMap().entrySet()) {
buf.writeBytes(entry.getKey().getLocationKey().toByteArray());
buf.writeBytes(entry.getKey().getDomainKey().toByteArray());
buf.writeBytes(entry.getKey().getContentKey().toByteArray());
buf.writeBytes(entry.getValue().toByteArray());
}
message.contentRefencencs().poll();
break;
case BYTE_BUFFER:
Buffer buffer = message.getBuffer(next.number());
if (!resume) {
buf.writeInt(buffer.length());
}
int readable = buffer.readable();
buf.writeBytes(buffer.buffer(), readable);
if (buffer.incRead(readable) == buffer.length()) {
message.contentRefencencs().poll();
} else if (message.isStreaming()) {
LOG.debug("we sent a partial message of length {}", readable);
return false;
} else {
LOG.debug("Announced a larger buffer, but not in streaming mode. This is wrong.");
throw new RuntimeException(
"Announced a larger buffer, but not in streaming mode. This is wrong.");
}
break;
case SET_TRACKER_DATA:
TrackerData trackerData = message.getTrackerData(next.number());
buf.writeByte(trackerData.getPeerAddresses().size()); // 1 bytes - length, max. 255
for (Map.Entry<PeerAddress, Data> entry : trackerData.getPeerAddresses().entrySet()) {
buf.writeBytes(entry.getKey().toByteArray());
Data data = entry.getValue().duplicate();
data.encode(buf);
data.encodeDone(buf);
}
message.contentRefencencs().poll();
break;
case PUBLIC_KEY_SIGNATURE:
// flag to encode public key
message.setHintSign();
byte[] data = message.getPublicKey().getEncoded();
buf.writeShort(data.length);
buf.writeBytes(data);
message.contentRefencencs().poll();
break;
default:
throw new RuntimeException("Unknown type: " + next.content());
}
}
return true;
}
@Override
public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) throws Exception {
if (message == null) {
LOG.error("exception in encoding, starting", cause);
cause.printStackTrace();
} else if (message != null && !message.isDone()) {
LOG.error("exception in encoding, started", cause);
cause.printStackTrace();
}
}
/*
* private void writeFutureEventually(final ChannelHandlerContext ctx, FutureProgres<Collection<PeerAddress>>
* futureProgres, final ChannelPromise promise, final int limit) { final AtomicInteger counter = new
* AtomicInteger(0); futureProgres.addListener(new BaseFutureAdapter<FutureProgres<Collection<PeerAddress>>>() {
*
* @Override public void operationComplete(final FutureProgres<Collection<PeerAddress>> future) throws Exception {
* if (future.isSuccess()) { boolean done = false; for (PeerAddress neighbor : future.getObject()) {
* ctx.write(neighbor, promise); if (counter.addAndGet(neighbor.size()) >= limit) { done = true; break; } }
*
* if (future.getNext() != null) { if (done) { future.getNext().setDone(null); ctx.write(CONTINUE, promise); } else
* { future.getNext().addListener(this); } } else { ctx.write(CONTINUE, promise); } } else {
* promise.setFailure(null); } } }); }
*/
}