com.turn.ttorrent.client.io.PeerMessage.java Source code

Java tutorial

Introduction

Here is the source code for com.turn.ttorrent.client.io.PeerMessage.java

Source

/**
 * Copyright (C) 2011-2012 Turn, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.turn.ttorrent.client.io;

import com.google.common.base.Preconditions;
import com.turn.ttorrent.client.PeerPieceProvider;
import com.turn.ttorrent.client.io.PeerExtendedMessage.ExtendedType;
import com.turn.ttorrent.protocol.TorrentUtils;
import io.netty.buffer.ByteBuf;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.text.ParseException;
import java.util.BitSet;
import java.util.Map;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * BitTorrent peer protocol messages representations.
 *
 * <p>
 * This class and its <em>*Messages</em> subclasses provide POJO
 * representations of the peer protocol messages, along with easy parsing from
 * an input ByteBuffer to quickly get a usable representation of an incoming
 * message.
 * </p>
 *
 * @author mpetazzoni
 * @see <a href="http://wiki.theory.org/BitTorrentSpecification#Peer_wire_protocol_.28TCP.29">BitTorrent peer wire protocol</a>
 */
public abstract class PeerMessage {

    private static final Logger LOG = LoggerFactory.getLogger(PeerMessage.class);
    /** The size, in bytes, of the length field in a message (one 32-bit
     * integer). */
    public static final int MESSAGE_LENGTH_FIELD_SIZE = 4;

    /**
     * Message type.
     *
     * <p>
     * Note that the keep-alive messages don't actually have an type ID defined
     * in the protocol as they are of length 0.
     * </p>
     */
    public enum Type {

        HANDSHAKE(-2), KEEP_ALIVE(-1), CHOKE(0), UNCHOKE(1), INTERESTED(2), NOT_INTERESTED(3), HAVE(4), BITFIELD(
                5), REQUEST(6), PIECE(7), CANCEL(8), EXTENDED(20);
        private byte id;

        Type(int id) {
            this.id = (byte) id;
        }

        public byte getTypeByte() {
            return id;
        }
    };

    @Nonnull
    public abstract Type getType();

    /** Reads everything except the length and type code. */
    public abstract void fromWire(@Nonnull ByteBuf in) throws IOException;

    /** Writes everything except the length. */
    public void toWire(@Nonnull ByteBuf out, @Nonnull Map<? extends ExtendedType, ? extends Byte> extendedTypes)
            throws IOException {
        out.writeByte(getType().getTypeByte());
    }

    /**
     * Validate that this message makes sense for the torrent it's related to.
     *
     * <p>
     * This method is meant to be overloaded by distinct message types, where
     * it makes sense. Otherwise, it defaults to true.
     * </p>
     *
     * @param torrent The torrent this message is about.
     */
    public PeerMessage validate(PeerPieceProvider torrent) throws MessageValidationException {
        return this;
    }

    @Override
    public String toString() {
        return this.getType().name();
    }

    public static class MessageValidationException extends ParseException {

        static final long serialVersionUID = -1;

        public MessageValidationException(PeerMessage m) {
            super("Message " + m + " is not valid!", 0);
        }
    }

    /**
     * Keep alive message.
     *
     * <len=0000>
     */
    public static class KeepAliveMessage extends PeerMessage {

        @Override
        public Type getType() {
            return Type.KEEP_ALIVE;
        }

        @Override
        public void fromWire(ByteBuf in) {
        }

        @Override
        public void toWire(ByteBuf out, Map<? extends ExtendedType, ? extends Byte> extendedTypes) {
        }
    }

    /**
     * Choke message.
     *
     * <len=0001><id=0>
     */
    public static class ChokeMessage extends PeerMessage {

        @Override
        public Type getType() {
            return Type.CHOKE;
        }

        @Override
        public void fromWire(ByteBuf in) {
        }
    }

    /**
     * Unchoke message.
     *
     * <len=0001><id=1>
     */
    public static class UnchokeMessage extends PeerMessage {

        @Override
        public Type getType() {
            return Type.UNCHOKE;
        }

        @Override
        public void fromWire(ByteBuf in) {
        }
    }

    /**
     * Interested message.
     *
     * <len=0001><id=2>
     */
    public static class InterestedMessage extends PeerMessage {

        @Override
        public Type getType() {
            return Type.INTERESTED;
        }

        @Override
        public void fromWire(ByteBuf in) {
        }
    }

    /**
     * Not interested message.
     *
     * <len=0001><id=3>
     */
    public static class NotInterestedMessage extends PeerMessage {

        @Override
        public Type getType() {
            return Type.NOT_INTERESTED;
        }

        @Override
        public void fromWire(ByteBuf in) {
        }
    }

    /**
     * Have message.
     *
     * <len=0005><id=4><piece index=xxxx>
     */
    public static class HaveMessage extends PeerMessage {

        private int piece;

        public HaveMessage() {
        }

        public HaveMessage(@Nonnegative int piece) {
            this.piece = piece;
        }

        @Override
        public Type getType() {
            return Type.HAVE;
        }

        @Nonnegative
        public int getPiece() {
            return this.piece;
        }

        @Override
        public void fromWire(ByteBuf in) {
            piece = in.readInt();
        }

        @Override
        public void toWire(ByteBuf out, Map<? extends ExtendedType, ? extends Byte> extendedTypes)
                throws IOException {
            super.toWire(out, extendedTypes);
            out.writeInt(piece);
        }

        @Override
        public HaveMessage validate(PeerPieceProvider torrent) throws MessageValidationException {
            if (this.piece >= 0 && this.piece < torrent.getPieceCount())
                return this;
            throw new MessageValidationException(this);
        }

        @Override
        public String toString() {
            return super.toString() + " #" + this.getPiece();
        }
    }

    /**
     * Bitfield message.
     *
     * <len=0001+X><id=5><bitfield>
     */
    public static class BitfieldMessage extends PeerMessage {

        private BitSet bitfield;

        public BitfieldMessage() {
        }

        public BitfieldMessage(@Nonnull BitSet bitfield) {
            this.bitfield = bitfield;
        }

        @Override
        public Type getType() {
            return Type.BITFIELD;
        }

        public static byte reverse(byte b) {
            int i = b & 0xFF;
            i = (i & 0x55) << 1 | (i >>> 1) & 0x55;
            i = (i & 0x33) << 2 | (i >>> 2) & 0x33;
            i = (i & 0x0f) << 4 | (i >>> 4) & 0x0f;
            return (byte) i;
        }

        @Nonnull
        public BitSet getBitfield() {
            return this.bitfield;
        }

        @Override
        public void fromWire(ByteBuf in) {
            byte[] bytes = new byte[in.readableBytes()];
            in.readBytes(bytes);
            for (int i = 0; i < bytes.length; i++)
                bytes[i] = reverse(bytes[i]);
            bitfield = BitSet.valueOf(bytes);
        }

        @Override
        public void toWire(ByteBuf out, Map<? extends ExtendedType, ? extends Byte> extendedTypes)
                throws IOException {
            super.toWire(out, extendedTypes);
            byte[] bytes = bitfield.toByteArray();
            for (int i = 0; i < bytes.length; i++)
                bytes[i] = reverse(bytes[i]);
            out.writeBytes(bytes);
            // TODO: Extend this byte array to the required length.
            // out.writeBytes(new byte[torrent.getPieceCount() / 8 - bytes.length]);
        }

        @Override
        public BitfieldMessage validate(PeerPieceProvider torrent) throws MessageValidationException {
            if (this.bitfield.length() > torrent.getPieceCount())
                throw new MessageValidationException(this);
            return this;
        }

        @Override
        public String toString() {
            return super.toString() + " " + this.getBitfield().cardinality();
        }
    }

    public static abstract class AbstractPieceMessage extends PeerMessage {

        private int piece;
        private int offset;

        public AbstractPieceMessage() {
        }

        public AbstractPieceMessage(int piece, int offset) {
            this.piece = piece;
            this.offset = offset;
        }

        @Nonnegative
        public int getPiece() {
            return this.piece;
        }

        @Nonnegative
        public int getOffset() {
            return this.offset;
        }

        @Nonnegative
        public abstract int getLength();

        @Override
        public void fromWire(ByteBuf in) {
            piece = in.readInt();
            offset = in.readInt();
        }

        @Override
        public void toWire(ByteBuf out, Map<? extends ExtendedType, ? extends Byte> extendedTypes)
                throws IOException {
            super.toWire(out, extendedTypes);
            out.writeInt(piece);
            out.writeInt(offset);
        }

        @Override
        public AbstractPieceMessage validate(PeerPieceProvider torrent) throws MessageValidationException {
            if (getPiece() < 0)
                throw new MessageValidationException(this);
            if (getPiece() > torrent.getPieceCount())
                throw new MessageValidationException(this);
            if (getOffset() + getLength() > torrent.getPieceLength(piece))
                throw new MessageValidationException(this);
            return this;
        }

        public boolean answers(@Nonnull AbstractPieceMessage message) {
            Preconditions.checkNotNull(message, "Message was null.");
            return getPiece() == message.getPiece() && getOffset() == message.getOffset()
                    && getLength() == message.getLength();
        }

        @Override
        public String toString() {
            return super.toString() + " #" + this.getPiece() + " (" + this.getLength() + "@" + this.getOffset()
                    + ")";
        }
    }

    /**
     * Request message.
     *
     * <len=00013><id=6><piece index><block offset><block length>
     */
    public static class RequestMessage extends AbstractPieceMessage {

        private int length;

        public RequestMessage() {
        }

        public RequestMessage(@Nonnegative int piece, @Nonnegative int offset, @Nonnegative int length) {
            super(piece, offset);
            this.length = length;
        }

        @Override
        public Type getType() {
            return Type.REQUEST;
        }

        @Override
        public int getLength() {
            return length;
        }

        @Override
        public void fromWire(ByteBuf in) {
            super.fromWire(in);
            length = in.readInt();
        }

        @Override
        public void toWire(ByteBuf out, Map<? extends ExtendedType, ? extends Byte> extendedTypes)
                throws IOException {
            super.toWire(out, extendedTypes);
            out.writeInt(length);
        }
    }

    /**
     * Piece message.
     *
     * <len=0009+X><id=7><piece index><block offset><block data>
     */
    public static class PieceMessage extends AbstractPieceMessage {

        private static final int BASE_SIZE = 9;
        // TODO: Use a FileRegion.
        private ByteBuffer block;

        public PieceMessage() {
        }

        public PieceMessage(int piece, int offset, ByteBuffer block) {
            super(piece, offset);
            this.block = block;
        }

        @Override
        public Type getType() {
            return Type.PIECE;
        }

        @Override
        public int getLength() {
            return getBlock().remaining();
        }

        public ByteBuffer getBlock() {
            return this.block;
        }

        @Override
        public void fromWire(ByteBuf in) {
            super.fromWire(in);
            block = ByteBuffer.allocate(in.readableBytes());
            in.readBytes(block);
            block.flip();
            // We can't do this because netty recycles the buffer.
            // block = in.nioBuffer();
            // in.readerIndex(in.writerIndex());
        }

        @Override
        public void toWire(ByteBuf out, Map<? extends ExtendedType, ? extends Byte> extendedTypes)
                throws IOException {
            super.toWire(out, extendedTypes);
            out.writeBytes(block);
        }

        @Override
        public String toString() {
            return super.toString() + " " + TorrentUtils.toString(block, 16);
        }
    }

    /**
     * Cancel message.
     *
     * <len=00013><id=8><piece index><block offset><block length>
     */
    public static class CancelMessage extends AbstractPieceMessage {

        private int length;

        public CancelMessage() {
        }

        public CancelMessage(@Nonnegative int piece, @Nonnegative int offset, @Nonnegative int length) {
            super(piece, offset);
            this.length = length;
        }

        public CancelMessage(@Nonnull RequestMessage request) {
            this(request.getPiece(), request.getOffset(), request.getLength());
        }

        @Override
        public Type getType() {
            return Type.CANCEL;
        }

        @Override
        public int getLength() {
            return length;
        }

        @Override
        public void fromWire(ByteBuf in) {
            super.fromWire(in);
            length = in.readInt();
        }

        @Override
        public void toWire(ByteBuf out, Map<? extends ExtendedType, ? extends Byte> extendedTypes)
                throws IOException {
            super.toWire(out, extendedTypes);
            out.writeInt(length);
        }
    }
}