import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Timer; import java.util.logging.Logger; /** * The Class Peer handles the creation of connections with peers * that are in the swarm. It also handles generating and checking handshakes * as well as request messages from peers and sending messages to peers * * @author Deepak, Mike, Josh */ public class Peer extends Thread { /** The Constant log. */ private static final Logger log = Log2.getLogger(Peer.class); /** The peer id. */ protected byte[] peerId; /** The port. */ protected int port; /** The ip. */ protected String ip; /** The we are choking peer. */ boolean weAreChokingPeer = true; /** The we are interested in peer. */ boolean weAreInterestedInPeer = false; /** The peer choking us. */ boolean peerChokingUs = true; /** The peer interested in us. */ boolean peerInterestedInUs = false; /** The bitfield. */ public boolean[] bitfield = null; /** The previous index. */ public int previousIndex = -1; /** The manager. */ Manager manager; /** The peer uploader. */ PeerUploader peerUploader; /** The peer keep alive. */ PeerKeepAlive peerKeepAlive; /** The socket. */ private Socket socket = null; /** The in. */ protected InputStream in; /** The out. */ protected OutputStream out; /** The upload rate. */ public double uploadRate = 0; /** The download rate. */ public double downloadRate = 0; /** The current piece index. */ private int currentPieceIndex = -1; /** The current byte offset. */ private int currentByteOffset = 0; /** The piece. */ private ByteArrayOutputStream piece = null; /** The total bytes written. */ private int totalBytesWritten = 0; private boolean isRunning = true; private long currentTime = 0L; long totalDownload =0L; int downloadCount =0; public long totalUpload=0L; long rateCalulatorTotalDownload =0L; long rateCalculatorTotalUpload = 0L; /** * Instantiates a new peer. * * @param peerId the peer id * @param port the port * @param ip the ip * @param manager the manager */ public Peer(byte[] peerId, int port, String ip, Manager manager) { super("Peer@" + ip + ":" + port); this.peerId = peerId; this.port = port; this.ip = ip; this.manager = manager; this.bitfield = new boolean[this.manager.torrentInfo.piece_hashes.length]; this.peerUploader = new PeerUploader(this); this.peerUploader.isRunning = true; this.peerUploader.start(); this.peerKeepAlive = new PeerKeepAlive(this); Arrays.fill(this.bitfield, false); } /** * Inits the peer * * @return true, if successful */ public boolean init() { byte[] id = new byte[6]; System.arraycopy(this.peerId, 0, id, 0, 6); log.info("Peer IP: " + this.ip); if (!Arrays.equals(id, Manager.PEER_ID_WE_NEED) && !Arrays.equals(id, new byte[] { '-', 'G', 'P', '0', '3', '-'})){ return false; } try { isRunning = true; currentTime= System.currentTimeMillis(); this.connect(); DataOutputStream os = new DataOutputStream(this.out); DataInputStream is = new DataInputStream(this.in); if (is == null || os == null) { log.severe("Unable to create stream to peer"); this.disconnect(); return false; } os.write(Peer.generateHandshake(Manager.peerId, manager.torrentInfo.info_hash.array())); os.flush(); byte[] response = new byte[68]; this.socket.setSoTimeout(10000); is.readFully(response); this.socket.setSoTimeout(130000); if(!checkHandshake(manager.torrentInfo.info_hash.array(), response)){ return false; } log.info("Handshake Response: " + Arrays.toString(response)); if(this.manager.curUnchoked < this.manager.maxUnchoked){ this.weAreChokingPeer = false; this.manager.curUnchoked++; } else { this.weAreChokingPeer = true; } this.start(); RUBTClient.log("Connected to " + this); return true; } catch (Exception e) { log.severe("Error connecting with peer"); return false; } } /** * Gets the next request message * * @return the next request */ public Message.Request getNextRequest() { int piece_length = this.manager.torrentInfo.piece_length; int file_length = this.manager.torrentInfo.file_length; int requestSize = Tracker.requestSize; int numPieces = this.manager.torrentInfo.piece_hashes.length; if(this.currentPieceIndex == -1){ if((this.currentPieceIndex = this.manager.getRarestPiece(this)) == -1){ RUBTClient.log("Failed to get next piece"); return null; } } if(this.currentPieceIndex == (numPieces - 1)){ piece_length = file_length % this.manager.torrentInfo.piece_length; } if((this.currentByteOffset + requestSize) > piece_length){ requestSize = piece_length % requestSize; } Message.Request request = new Message.Request(this.currentPieceIndex, this.currentByteOffset, requestSize); if((this.currentByteOffset + requestSize) >= piece_length){ this.currentPieceIndex = -1; this.currentByteOffset = 0; } else { this.currentByteOffset += requestSize; } return request; } /** * Append to piece and verify if complete. * * Keeps building a piece and checks if we have the whole piece * * @param pieceMsg the piece msg * @param hashes the hashes * @param manager the manager * @return true, if successful */ public boolean appendToPieceAndVerifyIfComplete(Message.Piece pieceMsg, ByteBuffer[] hashes, Manager manager) { int currentPieceLength = (pieceMsg.index == (this.manager.torrentInfo.piece_hashes.length - 1)) ? this.manager.torrentInfo.file_length % this.manager.torrentInfo.piece_length : this.manager.torrentInfo.piece_length; if (this.piece == null) { this.piece = new ByteArrayOutputStream(); } try { piece.write(pieceMsg.block, 0, pieceMsg.block.length); //RUBTClient.log(Integer.toString(this.piece.toByteArray().length)); } catch (Exception e) { log.severe("Unable to write to file at " + pieceMsg.index + " with offset " + pieceMsg.start); } if(this.currentPieceIndex == -1){ this.totalBytesWritten = 0; this.previousIndex = pieceMsg.index; this.manager.curRequestedBitfield[this.previousIndex] = false; try { if (manager.UpdateFile(pieceMsg, hashes[pieceMsg.index], piece.toByteArray())) { totalDownload+= currentPieceLength; this.manager.ourBitfield[pieceMsg.index] = true; rateCalulatorTotalDownload+= totalDownload; piece = null; return true; } else { piece = null; } } catch (Exception e) { log.severe("Error writing to file"); piece = null; } } return false; } /** * Connect. * * Connect to a peer * * @throws IOException Signals that an I/O exception has occurred. */ public synchronized void connect() throws IOException { this.socket = new Socket(this.ip, this.port); this.in = this.socket.getInputStream(); this.out = this.socket.getOutputStream(); } /** * Disconnect. * * Disconnect from a peer */ public synchronized void disconnect() { if(this.weAreChokingPeer == false){ this.manager.curUnchoked -= 1; } if (this.socket != null) { this.peerKeepAlive.isRunning = false; this.peerUploader.isRunning = false; this.peerKeepAlive.interrupt(); this.peerUploader.interrupt(); } try { if(this.socket!= null) this.socket.close(); } catch (IOException e) { log.severe("Error closing socket peer"); } finally { this.socket = null; this.in = null; this.out = null; //this.manager.peers.remove(this); isRunning = false; } } /** * Send message. * * Send a message to a peer * * @param m the m * @throws IOException Signals that an I/O exception has occurred. */ public synchronized void sendMessage(Message m) throws IOException { if (this.out == null) { throw new IOException(this + " cannot send a message on an empty socket."); } log.info("Sending " + m + " to " + this); Message.encode(m, this.out); this.peerKeepAlive.interrupt(); } /* (non-Javadoc) * @see java.lang.Thread#toString() */ public String toString() { return new String(peerId) + " " + port + " " + ip; } /** * Generate handshake. * * Generate a handshake on our end to send to the peer * * @param peer the peer * @param infohash the infohash * @return the byte[] */ public static byte[] generateHandshake(byte[] peer, byte[] infohash) { int index = 0; byte[] handshake = new byte[68]; handshake[index] = 0x13; index++; byte[] BTChars = { 'B', 'i', 't', 'T', 'o', 'r', 'r', 'e', 'n', 't', ' ', 'p', 'r', 'o', 't', 'o', 'c', 'o', 'l' }; System.arraycopy(BTChars, 0, handshake, index, BTChars.length); index += BTChars.length; byte[] zero = new byte[8]; System.arraycopy(zero, 0, handshake, index, zero.length); index += zero.length; log.fine("Info hash length: " + infohash.length); System.arraycopy(infohash, 0, handshake, index, infohash.length); index += infohash.length; System.arraycopy(peer, 0, handshake, index, peer.length); log.fine("Peer ID Length: " + peer.length); return handshake; } /* (non-Javadoc) * @see java.lang.Thread#run() */ public void run() { log.info("Starting " + this); while (this.socket != null && !this.socket.isClosed() && isRunning) { Message myMessage = null; try { myMessage = Message.decode(this.in); } catch (IOException e) { log.severe("Invalid stream for peer " + this); break; } if (myMessage != null) { if(myMessage.id == Message.requestID){ this.peerUploader.recieveRequest((Message.Request)myMessage); } else { this.manager.recieveMessage(new PeerMessage(this, myMessage)); } } } //this.manager.peers.remove(this); log.info("Finished " + this); } public boolean checkHandshake(byte[] infoHash, byte[] response) { byte[] peerHash = new byte[20]; System.arraycopy(response, 28, peerHash, 0, 20); if(!Arrays.equals(peerHash, infoHash)) { log.info("Handshake verification failed with Peer: " + peerId); return false; } log.info("Verified Handshake."); return true; } public void choke(){ try { this.sendMessage(Message.CHOKE); } catch (IOException e) { log.severe("Unable to send choke to peer"); } this.weAreChokingPeer = true; RUBTClient.updatePeerChokeStatus(this, true); } public void unchoke(){ try { this.sendMessage(Message.UNCHOKE); } catch (IOException e) { log.severe("Unable to send unchoke to peer"); } this.weAreChokingPeer = false; RUBTClient.updatePeerChokeStatus(this, false); } }