/* * bitlet - Simple bittorrent library * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna * * 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 org.bitlet.wetorrent; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; import java.util.logging.Level; import org.bitlet.wetorrent.choker.Choker; import org.bitlet.wetorrent.peer.IncomingPeerListener; import org.bitlet.wetorrent.peer.Peer; import org.bitlet.wetorrent.peer.PeersManager; import org.bitlet.wetorrent.pieceChooser.RouletteWheelPieceChooser; import org.bitlet.wetorrent.util.stream.BandwidthLimiter; import org.bitlet.wetorrent.peer.message.Have; import org.bitlet.wetorrent.peer.message.Request; import org.bitlet.wetorrent.disk.TorrentDisk; import org.bitlet.wetorrent.pieceChooser.PieceChooser; import org.bitlet.wetorrent.util.thread.InterruptableTasksThread; import org.bitlet.wetorrent.util.Utils; public class Torrent extends InterruptableTasksThread { public static final short maxUnfulfilledRequestNumber = 6; private Metafile metafile; String name; private byte[] peerId; private String peerIdEncoded; private int port; private Tracker activeTracker = null; private List<List<Tracker>> trackerTiers = new LinkedList<List<Tracker>>(); private PeersManager peersManager = new PeersManager(this); private TorrentDisk torrentDisk; private IncomingPeerListener incomingPeerListener; private PieceChooser pieceChooser = null; private Choker choker = new Choker(this); public static final String agent = "AcademicTorrentsDownloader/0.1; WeTorrentEngine"; public static final boolean verbose = false; private BandwidthLimiter uploadBandwidthLimiter; private boolean stopped = false; public BandwidthLimiter getUploadBandwidthLimiter() { return uploadBandwidthLimiter; } public Torrent(Metafile metafile, TorrentDisk torrentDisk, IncomingPeerListener incomingPeerListener) { this(metafile, torrentDisk, incomingPeerListener, null); } public Torrent(Metafile metafile, TorrentDisk torrentDisk, IncomingPeerListener incomingPeerListener, BandwidthLimiter uploadBandwidthLimiter) { this(metafile, torrentDisk, incomingPeerListener, uploadBandwidthLimiter, null); } public Torrent(Metafile metafile, TorrentDisk torrentDisk, IncomingPeerListener incomingPeerListener, BandwidthLimiter uploadBandwidthLimiter, PieceChooser pieceChooser) { this.uploadBandwidthLimiter = uploadBandwidthLimiter; this.incomingPeerListener = incomingPeerListener; this.metafile = metafile; this.torrentDisk = torrentDisk; if (pieceChooser != null) { this.pieceChooser = pieceChooser; } else { this.pieceChooser = new RouletteWheelPieceChooser(); } this.pieceChooser.setTorrent(this); peerId = new byte[20]; Random random = new Random(System.currentTimeMillis()); random.nextBytes(peerId); System.arraycopy("-WT-0001".getBytes(), 0, peerId, 0, 8); peerIdEncoded = Utils.byteArrayToURLString(peerId); if (Torrent.verbose) { addEvent(new Event(this, "peerId generated: " + peerIdEncoded, Level.INFO)); } this.incomingPeerListener = incomingPeerListener; this.port = incomingPeerListener.getPort(); incomingPeerListener.register(this); List announceList = metafile.getAnnounceList(); if (announceList != null) { for (Object elem : announceList) { List tier = (List) elem; List trackerTier = new LinkedList<Tracker>(); for (Object trackerElem : tier) { ByteBuffer trackerAnnounce = (ByteBuffer) trackerElem; Tracker tracker = new Tracker(new String(trackerAnnounce.array())); byte[] keyBytes = new byte[4]; random.nextBytes(keyBytes); tracker.setKey(Utils.byteArrayToURLString(keyBytes)); trackerTier.add(tracker); } Collections.shuffle(trackerTier); trackerTiers.add(trackerTier); } } else { List<Tracker> uninqueTracker = new LinkedList<Tracker>(); Tracker tracker = new Tracker(metafile.getAnnounce()); byte[] keyBytes = new byte[4]; random.nextBytes(keyBytes); tracker.setKey(Utils.byteArrayToURLString(keyBytes)); uninqueTracker.add(tracker); trackerTiers.add(uninqueTracker); } activeTracker = trackerTiers.get(0).get(0); } public Map trackerRequest(String event) { for (List<Tracker> trackers : trackerTiers) { for (Tracker t : trackers) { try { Map responseDictionary = t.trackerRequest(this, event); if (responseDictionary != null) { activeTracker = t; trackers.remove(t); trackers.add(0, t); return responseDictionary; } } catch (Exception e) { int ciccio = 2; if (Torrent.verbose) { addEvent(new Event(this, e.toString(), Level.INFO)); } } } } return null; } public synchronized void addEvent(Event event) { System.err.println(event.getDescription() + ": " + event.getAuthor()); /* events.add(event); */ } public Metafile getMetafile() { return metafile; } public TorrentDisk getTorrentDisk() { return torrentDisk; } public byte[] getPeerId() { return peerId; } public String getPeerIdEncoded() { return peerIdEncoded; } public PeersManager getPeersManager() { return peersManager; } // This function notifies that peer has just sent an amInterested message public void have(int index, Peer peer) { if (!torrentDisk.isCompleted(index)) { peer.setAmInterested(true); if (!peer.isAmChoked()) { addRequests(peer); } } } public void bitfield(byte[] bitfield, Peer peer) { for (int i = 0; i < metafile.getPieces().size(); i++) { if (peer.hasPiece(i) && !torrentDisk.isCompleted(i)) { peer.setAmInterested(true); return; } } } public void choke(Peer peer) { choker.choke(peer); pieceChooser.interrupted(peer); } public void unchoke(Peer peer) { /* remove all the pending request */ pieceChooser.interrupted(peer); choker.unchoke(peer); addRequests(peer); } public void interested(Peer peer) { choker.interested(peer); } public void notInterested(Peer peer) { choker.notInterested(peer); } public void piece(int index, int begin, byte[] block, Peer peer) { try { torrentDisk.write(index, begin, block); pieceChooser.piece(index, begin, block, peer); } catch (Exception e) { if (Torrent.verbose) { addEvent(new Event(e, "Exception writing piece", Level.SEVERE)); } e.printStackTrace(System.err); } if (Torrent.verbose) { addEvent(new Event(peer, "PIECE " + index + " " + ((float) torrentDisk.getDownloaded(index) / torrentDisk.getLength(index)), Level.FINEST)); } if (torrentDisk.isCompleted(index)) { peersManager.sendHave(new Have(index)); } addRequests(peer); } public void interrupted(Peer peer) { choker.interrupted(peer); pieceChooser.interrupted(peer); } private void addRequests(Peer peer) { Request request = null; int[] piecesFrequencies = peersManager.getPiecesFrequencies(); while (peer.getUnfulfilledRequestNumber() < maxUnfulfilledRequestNumber && (request = pieceChooser.getNextBlockRequest(peer, piecesFrequencies)) != null) { peer.sendMessage(request); } if (request == null && peer.getUnfulfilledRequestNumber() == 0) { peer.setAmInterested(false); } } public void addPeers(Object peers) throws UnknownHostException { if (peers instanceof List) { List peersList = (List) peers; for (Object elem : peersList) { Map peerMap = (Map) elem; ByteBuffer addressByteBuffer = (ByteBuffer) peerMap.get(ByteBuffer.wrap("ip".getBytes())); InetAddress address = InetAddress.getByName(new String(addressByteBuffer.array())); int port = ((Long) peerMap.get(ByteBuffer.wrap("port".getBytes()))).intValue(); ByteBuffer peerIdByteByteBuffer = (ByteBuffer) peerMap.get(ByteBuffer.wrap("peer id".getBytes())); byte[] peerIdByteString = peerIdByteByteBuffer.array(); if (Torrent.verbose) { addEvent(new Event(this, "Offering new peer: " + address, Level.FINE)); } peersManager.offer(peerIdByteString, address, port); } } else if (peers instanceof ByteBuffer) { byte[] peersString = ((ByteBuffer) peers).array(); for (int i = 0; i < peersString.length / 6; i++) { byte[] peerByteAddress = new byte[4]; System.arraycopy(peersString, i * 6, peerByteAddress, 0, 4); InetAddress address = InetAddress.getByAddress(peerByteAddress); int port = ( (peersString[i * 6 + 4] & 0xFF) << 8) | (peersString[i * 6 + 5] & 0xFF); if (Torrent.verbose) { addEvent(new Event(this, "Offering new peer: " + address, Level.FINE)); } peersManager.offer(null, address, port); } } else { System.err.println("WTF!!!"); } } public boolean isCompleted() { return torrentDisk.getCompleted() == metafile.getLength(); } public void tick() { peersManager.tick(); choker.tick(); Long waitTime = activeTracker.getInterval(); if (incomingPeerListener.getReceivedConnection() == 0 || peersManager.getActivePeersNumber() < 4) { waitTime = activeTracker.getMinInterval() != null ? activeTracker.getMinInterval() : 60; } long now = System.currentTimeMillis(); if (now - activeTracker.getLastRequestTime() >= waitTime * 1000) { if (!stopped) { try { Object peers = trackerRequest(null).get(ByteBuffer.wrap("peers".getBytes())); if (peers != null) { addPeers(peers); } } catch (Exception e) { e.printStackTrace(); } } } } public IncomingPeerListener getIncomingPeerListener() { return incomingPeerListener; } public void stopDownload() { stopped = true; new Thread() { public void run() { try { trackerRequest("stopped"); } catch (Exception ex) { } } }.start(); getPeersManager().interrupt(); } public void startDownload() throws Exception { stopped = false; Map firstResponseDictionary = trackerRequest("started"); if (firstResponseDictionary == null) { throw new Exception("Problem while sending tracker request"); } Object peers = firstResponseDictionary.get(ByteBuffer.wrap("peers".getBytes())); addPeers(peers); } }