package net.tomp2p.message;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.CompositeByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.net.InetSocketAddress;
import net.tomp2p.connection.SignatureFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TomP2PCumulationTCP extends ChannelInboundHandlerAdapter {
private static final Logger LOG = LoggerFactory
.getLogger(TomP2PCumulationTCP.class);
private final Decoder decoder;
private final ByteBufAllocator byteBufAllocator;
private CompositeByteBuf cumulation = null;
private int lastId = 0;
public TomP2PCumulationTCP(final SignatureFactory signatureFactory, ByteBufAllocator byteBufAllocator) {
decoder = new Decoder(signatureFactory);
this.byteBufAllocator = byteBufAllocator;
}
@Override
public void channelRead(final ChannelHandlerContext ctx, final Object msg)
throws Exception {
if (!(msg instanceof ByteBuf)) {
ctx.fireChannelRead(msg);
return;
}
final ByteBuf buf = (ByteBuf) msg;
final InetSocketAddress sender = (InetSocketAddress) ctx.channel().remoteAddress();
try {
if (cumulation == null) {
cumulation = byteBufAllocator.compositeBuffer();
} else {
//cumulation.addComponent(buf);
}
cumulation.writeBytes(buf);
decoding(ctx, sender);
} catch (Throwable t) {
LOG.error("Error in TCP decoding", t);
throw new Exception(t);
} finally {
//the cumulation buffer now maintains the buffer buf, so we can release it here
buf.release();
if (!cumulation.isReadable()) {
cumulation.release();
cumulation = null;
} // no need to discard bytes as this was done in the decoder already
}
}
private void decoding(final ChannelHandlerContext ctx,
final InetSocketAddress sender) {
boolean finished = true;
boolean moreData = true;
while (finished && moreData) {
finished = decoder.decode(ctx, cumulation, (InetSocketAddress) ctx
.channel().localAddress(), sender);
if (finished) {
lastId = decoder.message().messageId();
moreData = cumulation.readableBytes() > 0;
ctx.fireChannelRead(decoder.prepareFinish());
} else {
if(decoder.message() == null) {
//wait for more data. This may happen if we don't get the first 58 bytes,
//which is the size of the header.
return;
}
// this id was the same as the last and the last message already
// finished the parsing. So this message
// is finished as well although it may send only partial data.
if (lastId == decoder.message().messageId()) {
finished = true;
moreData = cumulation.readableBytes() > 0;
ctx.fireChannelRead(decoder.prepareFinish());
} else if (decoder.message().isStreaming()) {
ctx.fireChannelRead(decoder.message());
}
}
}
}
@Override
public void channelInactive(final ChannelHandlerContext ctx)
throws Exception {
decoder.release();
final InetSocketAddress sender = (InetSocketAddress) ctx.channel()
.remoteAddress();
try {
if (cumulation != null) {
decoding(ctx, sender);
}
} catch (Throwable t) {
LOG.error("Error in TCP decoding. (Inactive)", t);
throw new Exception(t);
} finally {
if (cumulation != null) {
cumulation.release();
cumulation = null;
}
ctx.fireChannelInactive();
}
}
@Override
public void exceptionCaught(final ChannelHandlerContext ctx,
final Throwable cause) throws Exception {
if (cumulation != null) {
cumulation.release();
cumulation = null;
}
Message msg = decoder.message();
decoder.release();
// don't use getLocalizedMessage() -
// http://stackoverflow.com/questions/8699521/any-way-to-ignore-only-connection-reset-by-peer-ioexceptions
if (cause.getMessage().equals("Connection reset by peer")) {
return; // ignore
} else if (cause
.getMessage()
.equals("An existing connection was forcibly closed by the remote host")) {
// with windows we see the following message
return; // ignore
} else if (cause
.getMessage()
.equals("Eine vorhandene Verbindung wurde vom Remotehost geschlossen")) {
return;
} else if (cause.getMessage().equals("Connexion ré-initialisée par le correspondant")) {
return;
}
if (msg == null && decoder.lastContent() == null) {
LOG.error("Exception in decoding TCP. Occurred before starting to decode.", cause);
} else if (msg != null && !msg.isDone()) {
LOG.error("Exception in decoding TCP. Occurred after starting to decode.", cause);
}
}
}