/*
* BitTorrentDecoder.java
*
* Created on Feb 11, 2010, 1:11:10 PM
*
* Description: Provides a bit torrent message decoder.
*
* Copyright (C) Feb 11, 2010 reed.
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.texai.network.netty.handler;
import net.jcip.annotations.NotThreadSafe;
import org.apache.log4j.Logger;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.handler.codec.replay.ReplayingDecoder;
import org.jboss.netty.handler.codec.replay.VoidEnum;
import org.texai.torrent.message.BitTorrentBitFieldMessage;
import org.texai.torrent.message.BitTorrentCancelMessage;
import org.texai.torrent.message.BitTorrentChokeMessage;
import org.texai.torrent.message.BitTorrentHandshakeMessage;
import org.texai.torrent.message.BitTorrentHaveMessage;
import org.texai.torrent.message.BitTorrentInterestedMessage;
import org.texai.torrent.message.BitTorrentKeepAliveMessage;
import org.texai.torrent.message.BitTorrentNotInterestedMessage;
import org.texai.torrent.message.BitTorrentPieceMessage;
import org.texai.torrent.message.BitTorrentRequestMessage;
import org.texai.torrent.message.BitTorrentUnchokeMessage;
import org.texai.torrent.support.BitTorrentUtils;
import org.texai.util.ByteUtils;
/** Provides a bit torrent message decoder.
*
* @author reed
*/
@NotThreadSafe
public final class BitTorrentDecoder extends ReplayingDecoder<VoidEnum> {
/** the logger */
private static final Logger LOGGER = Logger.getLogger(BitTorrentDecoder.class);
/** Constructs a new BitTorrentDecoder instance. */
public BitTorrentDecoder() {
}
/** Decodes the received packets so far into a bit torrent message.
*
* @param channelHandlerContext the context of this handler
* @param channel the current channel
* @param channelBuffer the cumulative buffer of received packets so far.
* Note that the buffer might be empty, which means you
* should not make an assumption that the buffer contains
* at least one byte in your decoder implementation.
* @param state the current decoder state ({@code null} if unused)
*
* @return the bit torrent message
*/
@Override
protected Object decode(
final ChannelHandlerContext channelHandlerContext,
final Channel channel,
final ChannelBuffer channelBuffer,
final VoidEnum state) {
//Preconditions
assert channelHandlerContext != null : "channelHandlerContext must not be null";
assert channel != null : "channel must not be null";
assert channelBuffer != null : "channelBuffer must not be null";
final int magic1 = channelBuffer.getUnsignedByte(channelBuffer.readerIndex());
final int magic2 = channelBuffer.getUnsignedByte(channelBuffer.readerIndex() + 1);
final int magic3 = channelBuffer.getUnsignedByte(channelBuffer.readerIndex() + 2);
final int magic4 = channelBuffer.getUnsignedByte(channelBuffer.readerIndex() + 3);
final int magic5 = channelBuffer.getUnsignedByte(channelBuffer.readerIndex() + 4);
if (BitTorrentUtils.isBitTorrentCancel(magic1, magic2, magic3, magic4, magic5)) {
// cancel
final byte[] messageBytes = new byte[37];
channelBuffer.readBytes(messageBytes);
return BitTorrentCancelMessage.decode(messageBytes);
} else if (BitTorrentUtils.isBitTorrentChoke(magic1, magic2, magic3, magic4, magic5)) {
// choke
final byte[] messageBytes = new byte[25];
channelBuffer.readBytes(messageBytes);
return BitTorrentChokeMessage.decode(messageBytes);
} else if (BitTorrentUtils.isBitTorrentHave(magic1, magic2, magic3, magic4, magic5)) {
// have
final byte[] messageBytes = new byte[29];
channelBuffer.readBytes(messageBytes);
return BitTorrentHaveMessage.decode(messageBytes);
} else if (BitTorrentUtils.isBitTorrentInterested(magic1, magic2, magic3, magic4, magic5)) {
// interested
final byte[] messageBytes = new byte[25];
channelBuffer.readBytes(messageBytes);
return BitTorrentInterestedMessage.decode(messageBytes);
} else if (BitTorrentUtils.isBitTorrentNotInterested(magic1, magic2, magic3, magic4, magic5)) {
// not interested
final byte[] messageBytes = new byte[25];
channelBuffer.readBytes(messageBytes);
return BitTorrentNotInterestedMessage.decode(messageBytes);
} else if (BitTorrentUtils.isBitTorrentRequest(magic1, magic2, magic3, magic4, magic5)) {
// request
final byte[] messageBytes = new byte[37];
channelBuffer.readBytes(messageBytes);
return BitTorrentRequestMessage.decode(messageBytes);
} else if (BitTorrentUtils.isBitTorrentUnchoke(magic1, magic2, magic3, magic4, magic5)) {
// unchoke
final byte[] messageBytes = new byte[25];
channelBuffer.readBytes(messageBytes);
return BitTorrentUnchokeMessage.decode(messageBytes);
} else if (BitTorrentUtils.isBitTorrentKeepAlive(magic1, magic2, magic3, magic4)) {
// keep-alive
final byte[] messageBytes = new byte[24];
channelBuffer.readBytes(messageBytes);
return BitTorrentKeepAliveMessage.decode(messageBytes);
} else if (BitTorrentUtils.isBitTorrentHandshake(magic1)) {
// handshake
final byte[] messageBytes = new byte[68];
channelBuffer.readBytes(messageBytes);
return BitTorrentHandshakeMessage.decode(messageBytes);
} else if (BitTorrentUtils.isBitTorrentPiece(magic5)) {
// piece
final byte[] messageLengthBytes = new byte[4];
channelBuffer.readBytes(messageLengthBytes);
final int messageLength = ByteUtils.byteArrayToInt(messageLengthBytes);
// message bytes constist of the message length field followed by the message-length number of bytes
final byte[] messageBytes = new byte[4 + messageLength];
messageBytes[0] = messageLengthBytes[0];
messageBytes[1] = messageLengthBytes[1];
messageBytes[2] = messageLengthBytes[2];
messageBytes[3] = messageLengthBytes[3];
channelBuffer.readBytes(messageBytes, 4, messageLength);
return BitTorrentPieceMessage.decode(messageBytes);
} else if (BitTorrentUtils.isBitTorrentBitfield(magic5)) {
// bitfield
final byte[] messageLengthBytes = new byte[4];
channelBuffer.readBytes(messageLengthBytes);
final int messageLength = ByteUtils.byteArrayToInt(messageLengthBytes);
// message bytes constist of the message length field followed by the message-length number of bytes
final byte[] messageBytes = new byte[4 + messageLength];
messageBytes[0] = messageLengthBytes[0];
messageBytes[1] = messageLengthBytes[1];
messageBytes[2] = messageLengthBytes[2];
messageBytes[3] = messageLengthBytes[3];
channelBuffer.readBytes(messageBytes, 4, messageLength);
return BitTorrentBitFieldMessage.decode(messageBytes);
} else {
assert false : "first 5 bytes: " + magic1 + ", " + magic2 + ", " + magic3 + ", " + magic4 + ", " + magic5;
return null;
}
}
/** Handles disconnection from remote peer event.
*
* @param ctx the channel handler context
* @param e the channel state event
* @throws Exception if an exception occurs
*/
@Override
public void channelDisconnected(
final ChannelHandlerContext ctx,
final ChannelStateEvent e) throws Exception {
}
/** Handles closed channel event.
*
* @param ctx the channel handler context
* @param e the channel state event
* @throws Exception if an exception occurs
*/
@Override
public void channelClosed(
final ChannelHandlerContext ctx,
final ChannelStateEvent e) throws Exception {
}
}