├── .gitignore ├── .hgignore ├── .travis.yml ├── README.md ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── bitlet │ │ └── wetorrent │ │ ├── Event.java │ │ ├── Metafile.java │ │ ├── Sample.java │ │ ├── Torrent.java │ │ ├── Tracker.java │ │ ├── bencode │ │ ├── Bencode.java │ │ └── DictionaryComparator.java │ │ ├── choker │ │ └── Choker.java │ │ ├── disk │ │ ├── FilePieceMapper.java │ │ ├── Piece.java │ │ ├── PlainFileSystemTorrentDisk.java │ │ ├── ResumeListener.java │ │ └── TorrentDisk.java │ │ ├── peer │ │ ├── IncomingPeerListener.java │ │ ├── Peer.java │ │ ├── PeersManager.java │ │ ├── TorrentPeer.java │ │ ├── WebSeed.java │ │ ├── message │ │ │ ├── Cancel.java │ │ │ ├── Have.java │ │ │ ├── Message.java │ │ │ ├── Piece.java │ │ │ └── Request.java │ │ └── task │ │ │ ├── Handshake.java │ │ │ ├── MessageReceiver.java │ │ │ ├── MessageSender.java │ │ │ ├── SendBitfield.java │ │ │ ├── StartConnection.java │ │ │ └── StartMessageReceiver.java │ │ ├── pieceChooser │ │ ├── PieceChooser.java │ │ ├── RequestExtended.java │ │ └── RouletteWheelPieceChooser.java │ │ └── util │ │ ├── Utils.java │ │ ├── stream │ │ ├── BandwidthLimiter.java │ │ └── OutputStreamLimiter.java │ │ └── thread │ │ ├── InterruptableTasksThread.java │ │ └── ThreadTask.java └── resources │ └── license.txt └── test ├── java └── org │ └── bitlet │ └── wetorrent │ ├── MetafileTest.java │ └── bencode │ └── BencodeTest.java └── resources └── ubuntu-15.04-desktop-amd64.iso.torrent /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | # use glob syntax. 2 | syntax: glob 3 | 4 | target/* 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BitLet 2 | ### A tiny bittorrent library 3 | 4 | BitLet is a simple Java implementation of the [BitTorrent](http://en.wikipedia.org/wiki/BitTorrent) protocol. 5 | 6 | It is the library that powers [BitLet.org](http://bitlet.org) (a BitTorrent client that runs entirely in the browser plugin, as a Java applet). 7 | 8 | ## Trying out the BitLet library 9 | You can build the project sources using Maven, and execute a sample client by calling: 10 | 11 | java -cp target/wetorrent-1.0-SNAPSHOT.jar org.bitlet.wetorrent.Sample $1 12 | 13 | Where $1 is a .torrent file you have on your filesystem. 14 | 15 | ## Developing with BitLet 16 | You can review this [annotated example](https://github.com/bitletorg/bitlet/wiki/Annotated-Example) for an overview on how to use the BitLet library. 17 | 18 | ## License 19 | BitLet is distributed under the Apache license. See `src/main/resources/license.txt` for the full license terms. 20 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | org.bitlet 5 | 4.0.0 6 | wetorrent 7 | jar 8 | wetorrent 9 | https://github.com/bitletorg/bitlet 10 | 1.0-SNAPSHOT 11 | 12 | 13 | 14 | org.apache.maven.plugins 15 | maven-compiler-plugin 16 | 17 | 1.5 18 | 1.5 19 | ${project.build.sourceEncoding} 20 | 21 | 22 | 23 | org.apache.maven.plugins 24 | maven-resources-plugin 25 | 26 | ${project.build.sourceEncoding} 27 | 28 | 29 | 30 | 31 | 32 | 33 | junit 34 | junit 35 | test 36 | 4.11 37 | 38 | 39 | 40 | UTF-8 41 | 42 | 43 | scm:git:https://github.com/bitletorg/bitlet.git 44 | scm:git:ssh://git@github.com:bitletorg/bitlet.git 45 | https://github.com/bitletorg/bitlet 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/Event.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent; 19 | 20 | import java.util.logging.Level; 21 | 22 | public class Event { 23 | 24 | private String description; 25 | private Object author; 26 | private Level level; 27 | 28 | /** Creates a new instance of Event */ 29 | public Event(Object author, String description, Level level) { 30 | this.author = author; 31 | this.description = description; 32 | this.level = level; 33 | } 34 | 35 | public String getDescription() { 36 | return description; 37 | } 38 | 39 | public void setDescription(String description) { 40 | this.description = description; 41 | } 42 | 43 | public Object getAuthor() { 44 | return author; 45 | } 46 | 47 | public void setAuthor(Object author) { 48 | this.author = author; 49 | } 50 | 51 | public Level getLevel() { 52 | return level; 53 | } 54 | 55 | public void setLevel(Level level) { 56 | this.level = level; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/Metafile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent; 19 | 20 | import static org.bitlet.wetorrent.util.Utils.toByteBuffer; 21 | 22 | import java.io.ByteArrayOutputStream; 23 | import java.io.IOException; 24 | import java.io.InputStream; 25 | import java.nio.ByteBuffer; 26 | import java.security.MessageDigest; 27 | import java.security.NoSuchAlgorithmException; 28 | import java.util.LinkedList; 29 | import java.util.List; 30 | import java.util.Map; 31 | import java.util.SortedMap; 32 | import java.util.TreeMap; 33 | import org.bitlet.wetorrent.bencode.Bencode; 34 | import org.bitlet.wetorrent.bencode.DictionaryComparator; 35 | import org.bitlet.wetorrent.util.Utils; 36 | 37 | public class Metafile extends Bencode { 38 | 39 | SortedMap rootDictionary; 40 | private SortedMap info; 41 | private String announce; 42 | private List announceList; 43 | private Long creationDate; 44 | private String comment; 45 | private String createdBy; 46 | private long pieceLength; 47 | private Long length; 48 | private List piecesSha = new LinkedList(); 49 | private List files = new LinkedList(); 50 | private String name; 51 | private byte[] infoSha1; 52 | private String infoSha1Encoded; 53 | 54 | /** 55 | * Creates a new instance of Metafile 56 | */ 57 | public Metafile(InputStream is) throws IOException, NoSuchAlgorithmException { 58 | super(is); 59 | 60 | rootDictionary = (SortedMap) getRootElement(); 61 | info = (SortedMap) rootDictionary.get(toByteBuffer("info")); 62 | 63 | pieceLength = (Long) info.get(toByteBuffer("piece length")); 64 | 65 | byte[] piecesByteString = ((ByteBuffer) info.get(toByteBuffer("pieces"))).array(); 66 | for (int i = 0; i < piecesByteString.length; i += 20) { 67 | byte[] sha1 = new byte[20]; 68 | System.arraycopy(piecesByteString, i, sha1, 0, 20); 69 | piecesSha.add(sha1); 70 | } 71 | 72 | name = new String(((ByteBuffer) info.get(toByteBuffer("name"))).array()); 73 | 74 | length = (Long) info.get(toByteBuffer("length")); 75 | 76 | List files = (List) info.get(toByteBuffer("files")); 77 | if (files != null) { 78 | length = new Long(0); 79 | for (Object fileObj : files) { 80 | Map file = (Map) fileObj; 81 | this.files.add(file); 82 | length += (Long) file.get(toByteBuffer("length")); 83 | } 84 | } 85 | 86 | byte[] announceByteString = ((ByteBuffer) rootDictionary.get(toByteBuffer("announce"))).array(); 87 | if (announceByteString != null) { 88 | announce = new String(announceByteString); 89 | } 90 | announceList = (List) rootDictionary.get(toByteBuffer("announce-list")); 91 | 92 | creationDate = (Long) rootDictionary.get(toByteBuffer("creation date")); 93 | 94 | ByteBuffer commentByteBuffer = (ByteBuffer) rootDictionary.get(toByteBuffer("comment")); 95 | if (commentByteBuffer != null) { 96 | comment = new String(commentByteBuffer.array()); 97 | } 98 | ByteBuffer createdByByteBuffer = (ByteBuffer) rootDictionary.get(toByteBuffer("created by")); 99 | if (createdByByteBuffer != null) { 100 | createdBy = new String(createdByByteBuffer.array()); 101 | } 102 | computeInfoSha1(); 103 | } 104 | 105 | public Metafile() { 106 | rootDictionary = new TreeMap(new DictionaryComparator()); 107 | setRootElement(rootDictionary); 108 | info = new TreeMap(new DictionaryComparator()); 109 | rootDictionary.put(toByteBuffer("info"), getInfo()); 110 | announceList = new LinkedList(); 111 | } 112 | 113 | public SortedMap getInfo() { 114 | return info; 115 | } 116 | 117 | public void setInfo(SortedMap info) { 118 | this.info = info; 119 | } 120 | 121 | public String getAnnounce() { 122 | return announce; 123 | } 124 | 125 | public void setAnnounce(String announce) { 126 | this.announce = announce; 127 | } 128 | 129 | public List getAnnounceList() { 130 | return announceList; 131 | } 132 | 133 | public void setAnnounceList(List announceList) { 134 | this.announceList = announceList; 135 | } 136 | 137 | public Long getCreationDate() { 138 | return creationDate; 139 | } 140 | 141 | public void setCreationDate(Long creationDate) { 142 | this.creationDate = creationDate; 143 | } 144 | 145 | public String getComment() { 146 | return comment; 147 | } 148 | 149 | public void setComment(String comment) { 150 | this.comment = comment; 151 | } 152 | 153 | public String getCreatedBy() { 154 | return createdBy; 155 | } 156 | 157 | public void setCreatedBy(String createdBy) { 158 | this.createdBy = createdBy; 159 | } 160 | 161 | public Long getPieceLength() { 162 | return pieceLength; 163 | } 164 | 165 | public void setPieceLength(Long pieceLength) { 166 | this.pieceLength = pieceLength; 167 | } 168 | 169 | public List getPieces() { 170 | return piecesSha; 171 | } 172 | 173 | public void setPieces(List pieces) { 174 | this.piecesSha = pieces; 175 | } 176 | 177 | public void computeInfoSha1() throws NoSuchAlgorithmException, IOException { 178 | 179 | Bencode bencode = new Bencode(); 180 | bencode.setRootElement(info); 181 | 182 | MessageDigest md = MessageDigest.getInstance("SHA1"); 183 | 184 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 185 | bencode.print(out); 186 | 187 | md.update(ByteBuffer.wrap(out.toByteArray())); 188 | 189 | infoSha1 = md.digest(); 190 | } 191 | 192 | public byte[] getInfoSha1() { 193 | return infoSha1; 194 | } 195 | 196 | public String getInfoSha1Encoded() { 197 | if (infoSha1Encoded == null) { 198 | infoSha1Encoded = Utils.byteArrayToURLString(getInfoSha1()); 199 | } 200 | return infoSha1Encoded; 201 | } 202 | 203 | public long getLength() { 204 | return length; 205 | } 206 | 207 | public String getName() { 208 | return name; 209 | } 210 | 211 | public void setName(String name) { 212 | this.name = name; 213 | } 214 | 215 | public List getFiles() { 216 | return files; 217 | } 218 | 219 | public boolean isSingleFile() { 220 | return (getFiles().size() == 0); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/Sample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent; 19 | 20 | import java.io.BufferedInputStream; 21 | import java.io.File; 22 | import java.io.FileInputStream; 23 | import org.bitlet.wetorrent.disk.PlainFileSystemTorrentDisk; 24 | import org.bitlet.wetorrent.disk.TorrentDisk; 25 | import org.bitlet.wetorrent.peer.IncomingPeerListener; 26 | 27 | public class Sample { 28 | private static final int PORT = 6881; 29 | 30 | public static void main(String[] args) throws Exception { 31 | // read torrent filename from command line arg 32 | String filename = args[0]; 33 | 34 | // Parse the metafile 35 | Metafile metafile = new Metafile(new BufferedInputStream(new FileInputStream(filename))); 36 | 37 | // Create the torrent disk, this is the destination where the torrent file/s will be saved 38 | TorrentDisk tdisk = new PlainFileSystemTorrentDisk(metafile, new File(".")); 39 | tdisk.init(); 40 | 41 | IncomingPeerListener peerListener = new IncomingPeerListener(PORT); 42 | peerListener.start(); 43 | 44 | Torrent torrent = new Torrent(metafile, tdisk, peerListener); 45 | torrent.startDownload(); 46 | 47 | while (!torrent.isCompleted()) { 48 | 49 | try { 50 | Thread.sleep(1000); 51 | } catch(InterruptedException ie) { 52 | break; 53 | } 54 | 55 | torrent.tick(); 56 | System.out.printf("Got %s peers, completed %d bytes\n", 57 | torrent.getPeersManager().getActivePeersNumber(), 58 | torrent.getTorrentDisk().getCompleted()); 59 | } 60 | 61 | torrent.interrupt(); 62 | peerListener.interrupt(); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/Torrent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent; 19 | 20 | import static org.bitlet.wetorrent.util.Utils.toByteBuffer; 21 | 22 | import java.net.InetAddress; 23 | import java.net.UnknownHostException; 24 | import java.nio.ByteBuffer; 25 | import java.util.Collections; 26 | import java.util.LinkedList; 27 | import java.util.List; 28 | import java.util.Map; 29 | import java.util.Random; 30 | import java.util.logging.Level; 31 | import org.bitlet.wetorrent.choker.Choker; 32 | import org.bitlet.wetorrent.peer.IncomingPeerListener; 33 | import org.bitlet.wetorrent.peer.Peer; 34 | import org.bitlet.wetorrent.peer.PeersManager; 35 | import org.bitlet.wetorrent.pieceChooser.RouletteWheelPieceChooser; 36 | import org.bitlet.wetorrent.util.stream.BandwidthLimiter; 37 | import org.bitlet.wetorrent.peer.message.Have; 38 | import org.bitlet.wetorrent.peer.message.Request; 39 | import org.bitlet.wetorrent.disk.TorrentDisk; 40 | import org.bitlet.wetorrent.pieceChooser.PieceChooser; 41 | import org.bitlet.wetorrent.util.thread.InterruptableTasksThread; 42 | import org.bitlet.wetorrent.util.Utils; 43 | 44 | public class Torrent extends InterruptableTasksThread { 45 | 46 | public static final short maxUnfulfilledRequestNumber = 6; 47 | private Metafile metafile; 48 | String name; 49 | private byte[] peerId; 50 | private String peerIdEncoded; 51 | private int port; 52 | private Tracker activeTracker = null; 53 | private List> trackerTiers = new LinkedList>(); 54 | private PeersManager peersManager = new PeersManager(this); 55 | private TorrentDisk torrentDisk; 56 | private IncomingPeerListener incomingPeerListener; 57 | private PieceChooser pieceChooser = null; 58 | private Choker choker = new Choker(this); 59 | public static final String agent = "BitLet.org/0.1"; 60 | public static final boolean verbose = false; 61 | private BandwidthLimiter uploadBandwidthLimiter; 62 | 63 | private boolean stopped = false; 64 | 65 | public BandwidthLimiter getUploadBandwidthLimiter() { 66 | return uploadBandwidthLimiter; 67 | } 68 | 69 | public Torrent(Metafile metafile, TorrentDisk torrentDisk, IncomingPeerListener incomingPeerListener) { 70 | this(metafile, torrentDisk, incomingPeerListener, null); 71 | } 72 | 73 | public Torrent(Metafile metafile, TorrentDisk torrentDisk, IncomingPeerListener incomingPeerListener, BandwidthLimiter uploadBandwidthLimiter) { 74 | this(metafile, torrentDisk, incomingPeerListener, uploadBandwidthLimiter, null); 75 | } 76 | 77 | public Torrent(Metafile metafile, TorrentDisk torrentDisk, IncomingPeerListener incomingPeerListener, BandwidthLimiter uploadBandwidthLimiter, PieceChooser pieceChooser) { 78 | 79 | this.uploadBandwidthLimiter = uploadBandwidthLimiter; 80 | this.incomingPeerListener = incomingPeerListener; 81 | this.metafile = metafile; 82 | this.torrentDisk = torrentDisk; 83 | 84 | if (pieceChooser != null) { 85 | this.pieceChooser = pieceChooser; 86 | } else { 87 | this.pieceChooser = new RouletteWheelPieceChooser(); 88 | } 89 | this.pieceChooser.setTorrent(this); 90 | 91 | 92 | peerId = new byte[20]; 93 | 94 | Random random = new Random(System.currentTimeMillis()); 95 | 96 | random.nextBytes(peerId); 97 | System.arraycopy("-WT-0001".getBytes(), 0, peerId, 0, 8); 98 | 99 | peerIdEncoded = Utils.byteArrayToURLString(peerId); 100 | if (Torrent.verbose) { 101 | addEvent(new Event(this, "peerId generated: " + peerIdEncoded, Level.INFO)); 102 | } 103 | this.incomingPeerListener = incomingPeerListener; 104 | this.port = incomingPeerListener.getPort(); 105 | incomingPeerListener.register(this); 106 | 107 | List announceList = metafile.getAnnounceList(); 108 | if (announceList != null) { 109 | for (Object elem : announceList) { 110 | List tier = (List) elem; 111 | List trackerTier = new LinkedList(); 112 | 113 | for (Object trackerElem : tier) { 114 | ByteBuffer trackerAnnounce = (ByteBuffer) trackerElem; 115 | Tracker tracker = new Tracker(new String(trackerAnnounce.array())); 116 | 117 | byte[] keyBytes = new byte[4]; 118 | random.nextBytes(keyBytes); 119 | tracker.setKey(Utils.byteArrayToURLString(keyBytes)); 120 | 121 | trackerTier.add(tracker); 122 | } 123 | Collections.shuffle(trackerTier); 124 | trackerTiers.add(trackerTier); 125 | } 126 | } else { 127 | List uninqueTracker = new LinkedList(); 128 | Tracker tracker = new Tracker(metafile.getAnnounce()); 129 | 130 | byte[] keyBytes = new byte[4]; 131 | random.nextBytes(keyBytes); 132 | tracker.setKey(Utils.byteArrayToURLString(keyBytes)); 133 | uninqueTracker.add(tracker); 134 | trackerTiers.add(uninqueTracker); 135 | } 136 | activeTracker = trackerTiers.get(0).get(0); 137 | 138 | } 139 | 140 | public Map trackerRequest(String event) { 141 | 142 | 143 | for (List trackers : trackerTiers) { 144 | for (Tracker t : trackers) { 145 | try { 146 | Map responseDictionary = t.trackerRequest(this, event); 147 | if (responseDictionary != null) { 148 | activeTracker = t; 149 | trackers.remove(t); 150 | trackers.add(0, t); 151 | return responseDictionary; 152 | } 153 | } catch (Exception e) { 154 | int ciccio = 2; 155 | if (Torrent.verbose) { 156 | addEvent(new Event(this, e.toString(), Level.INFO)); 157 | } 158 | } 159 | } 160 | 161 | } 162 | 163 | return null; 164 | } 165 | 166 | public synchronized void addEvent(Event event) { 167 | 168 | System.err.println(event.getDescription() + ": " + event.getAuthor()); 169 | /* events.add(event); */ 170 | } 171 | 172 | public Metafile getMetafile() { 173 | return metafile; 174 | } 175 | 176 | public TorrentDisk getTorrentDisk() { 177 | return torrentDisk; 178 | } 179 | 180 | public byte[] getPeerId() { 181 | return peerId; 182 | } 183 | 184 | public String getPeerIdEncoded() { 185 | return peerIdEncoded; 186 | } 187 | 188 | public PeersManager getPeersManager() { 189 | return peersManager; 190 | } 191 | 192 | // This function notifies that peer has just sent an amInterested message 193 | public void have(int index, Peer peer) { 194 | 195 | if (!torrentDisk.isCompleted(index)) { 196 | peer.setAmInterested(true); 197 | if (!peer.isAmChoked()) { 198 | addRequests(peer); 199 | } 200 | } 201 | 202 | } 203 | 204 | public void bitfield(byte[] bitfield, Peer peer) { 205 | 206 | for (int i = 0; i < metafile.getPieces().size(); i++) { 207 | if (peer.hasPiece(i) && !torrentDisk.isCompleted(i)) { 208 | peer.setAmInterested(true); 209 | return; 210 | } 211 | } 212 | } 213 | 214 | public void choke(Peer peer) { 215 | choker.choke(peer); 216 | pieceChooser.interrupted(peer); 217 | } 218 | 219 | public void unchoke(Peer peer) { 220 | /* remove all the pending request */ 221 | pieceChooser.interrupted(peer); 222 | choker.unchoke(peer); 223 | addRequests(peer); 224 | } 225 | 226 | public void interested(Peer peer) { 227 | choker.interested(peer); 228 | } 229 | 230 | public void notInterested(Peer peer) { 231 | choker.notInterested(peer); 232 | } 233 | 234 | public void piece(int index, int begin, byte[] block, Peer peer) { 235 | 236 | try { 237 | torrentDisk.write(index, begin, block); 238 | pieceChooser.piece(index, begin, block, peer); 239 | } catch (Exception e) { 240 | if (Torrent.verbose) { 241 | addEvent(new Event(e, "Exception writing piece", Level.SEVERE)); 242 | } 243 | e.printStackTrace(System.err); 244 | } 245 | 246 | 247 | if (Torrent.verbose) { 248 | addEvent(new Event(peer, "PIECE " + index + " " + ((float) torrentDisk.getDownloaded(index) / torrentDisk.getLength(index)), Level.FINEST)); 249 | } 250 | if (torrentDisk.isCompleted(index)) { 251 | peersManager.sendHave(new Have(index)); 252 | } 253 | 254 | addRequests(peer); 255 | 256 | } 257 | 258 | public void interrupted(Peer peer) { 259 | choker.interrupted(peer); 260 | pieceChooser.interrupted(peer); 261 | } 262 | 263 | private void addRequests(Peer peer) { 264 | Request request = null; 265 | 266 | int[] piecesFrequencies = peersManager.getPiecesFrequencies(); 267 | 268 | while (peer.getUnfulfilledRequestNumber() < maxUnfulfilledRequestNumber && (request = pieceChooser.getNextBlockRequest(peer, piecesFrequencies)) != null) { 269 | peer.sendMessage(request); 270 | } 271 | if (request == null && peer.getUnfulfilledRequestNumber() == 0) { 272 | peer.setAmInterested(false); 273 | } 274 | } 275 | 276 | public void addPeers(Object peers) throws UnknownHostException { 277 | if (peers instanceof List) { 278 | 279 | List peersList = (List) peers; 280 | for (Object elem : peersList) { 281 | Map peerMap = (Map) elem; 282 | ByteBuffer addressByteBuffer = (ByteBuffer) peerMap.get(toByteBuffer("ip")); 283 | InetAddress address = InetAddress.getByName(new String(addressByteBuffer.array())); 284 | int port = ((Long) peerMap.get(toByteBuffer("port"))).intValue(); 285 | ByteBuffer peerIdByteByteBuffer = (ByteBuffer) peerMap.get(toByteBuffer("peer id")); 286 | byte[] peerIdByteString = peerIdByteByteBuffer.array(); 287 | 288 | if (Torrent.verbose) { 289 | addEvent(new Event(this, "Offering new peer: " + address, Level.FINE)); 290 | } 291 | peersManager.offer(peerIdByteString, address, port); 292 | } 293 | } else if (peers instanceof ByteBuffer) { 294 | byte[] peersString = ((ByteBuffer) peers).array(); 295 | for (int i = 0; i < peersString.length / 6; i++) { 296 | byte[] peerByteAddress = new byte[4]; 297 | System.arraycopy(peersString, i * 6, peerByteAddress, 0, 4); 298 | InetAddress address = InetAddress.getByAddress(peerByteAddress); 299 | int port = ( (peersString[i * 6 + 4] & 0xFF) << 8) | (peersString[i * 6 + 5] & 0xFF); 300 | 301 | if (Torrent.verbose) { 302 | addEvent(new Event(this, "Offering new peer: " + address, Level.FINE)); 303 | } 304 | peersManager.offer(null, address, port); 305 | } 306 | 307 | } else { 308 | System.err.println("WTF!!!"); 309 | } 310 | } 311 | 312 | public boolean isCompleted() { 313 | return torrentDisk.getCompleted() == metafile.getLength(); 314 | } 315 | 316 | public void tick() { 317 | 318 | peersManager.tick(); 319 | choker.tick(); 320 | 321 | 322 | Long waitTime = activeTracker.getInterval(); 323 | if (incomingPeerListener.getReceivedConnection() == 0 || peersManager.getActivePeersNumber() < 4) { 324 | waitTime = activeTracker.getMinInterval() != null ? activeTracker.getMinInterval() : 60; 325 | } 326 | 327 | long now = System.currentTimeMillis(); 328 | if (now - activeTracker.getLastRequestTime() >= waitTime * 1000) { 329 | 330 | if (!stopped) { 331 | try { 332 | Object peers = trackerRequest(null).get(toByteBuffer("peers")); 333 | if (peers != null) { 334 | addPeers(peers); 335 | } 336 | } catch (Exception e) { 337 | e.printStackTrace(); 338 | } 339 | } 340 | } 341 | } 342 | 343 | public IncomingPeerListener getIncomingPeerListener() { 344 | return incomingPeerListener; 345 | } 346 | 347 | public void stopDownload() { 348 | stopped = true; 349 | new Thread() { 350 | 351 | public void run() { 352 | try { 353 | trackerRequest("stopped"); 354 | } catch (Exception ex) { 355 | } 356 | } 357 | }.start(); 358 | 359 | getPeersManager().interrupt(); 360 | } 361 | 362 | public void startDownload() throws Exception { 363 | stopped = false; 364 | Map firstResponseDictionary = trackerRequest("started"); 365 | 366 | if (firstResponseDictionary == null) { 367 | throw new Exception("Problem while sending tracker request"); 368 | } 369 | 370 | Object peers = firstResponseDictionary.get(toByteBuffer("peers")); 371 | 372 | addPeers(peers); 373 | } 374 | } 375 | 376 | 377 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/Tracker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent; 19 | 20 | import static org.bitlet.wetorrent.util.Utils.toByteBuffer; 21 | 22 | import java.io.BufferedInputStream; 23 | import java.io.IOException; 24 | import java.net.HttpURLConnection; 25 | import java.net.MalformedURLException; 26 | import java.net.URL; 27 | import java.nio.ByteBuffer; 28 | import java.util.Map; 29 | import java.util.logging.Level; 30 | import org.bitlet.wetorrent.bencode.Bencode; 31 | 32 | public class Tracker { 33 | 34 | String announce; 35 | private Long lastRequestTime; 36 | private Long interval; 37 | private Long minInterval; 38 | private String trackerId; 39 | private String key; 40 | private Long complete; 41 | private Long incomplete; 42 | 43 | public Tracker(String announce) { 44 | this.announce = announce; 45 | } 46 | 47 | public Map trackerRequest(Torrent torrent, String event) throws MalformedURLException, IOException { 48 | 49 | String trackerUrlString = announce; 50 | trackerUrlString += "?info_hash=" + torrent.getMetafile().getInfoSha1Encoded(); 51 | trackerUrlString += "&peer_id=" + torrent.getPeerIdEncoded(); 52 | trackerUrlString += "&port=" + torrent.getIncomingPeerListener().getPort(); 53 | trackerUrlString += "&uploaded=" + torrent.getPeersManager().getUploaded(); 54 | trackerUrlString += "&downloaded=" + torrent.getPeersManager().getDownloaded(); 55 | trackerUrlString += "&left=" + (torrent.getMetafile().getLength() - torrent.getTorrentDisk().getCompleted()); 56 | trackerUrlString += "&compact=1"; 57 | if (event != null) { 58 | trackerUrlString += "&event=" + event; 59 | } 60 | if (trackerId != null) { 61 | trackerUrlString += "&tracker_id=" + trackerId; 62 | } 63 | if (key != null){ 64 | trackerUrlString += "&key=" + key; 65 | } 66 | URL trackerUrl = new URL(trackerUrlString); 67 | 68 | if (Torrent.verbose) { 69 | torrent.addEvent(new Event(this, "Querying tracker: " + trackerUrl.toString(), Level.FINE)); 70 | } 71 | HttpURLConnection conn = (HttpURLConnection) trackerUrl.openConnection(); 72 | conn.setRequestProperty("User-Agent", torrent.agent); 73 | 74 | Bencode trackerResponse = new Bencode(new BufferedInputStream(conn.getInputStream())); 75 | lastRequestTime = System.currentTimeMillis(); 76 | 77 | Map responseDictionary = (Map) trackerResponse.getRootElement(); 78 | 79 | byte[] failureReasonByteString = null; 80 | ByteBuffer failureReasonByteBuffer = (ByteBuffer) responseDictionary.get(toByteBuffer("failure reason")); 81 | if (failureReasonByteBuffer != null) { 82 | failureReasonByteString = failureReasonByteBuffer.array(); 83 | } 84 | if (failureReasonByteString != null) { 85 | String failureReason = new String(failureReasonByteString); 86 | if (Torrent.verbose) { 87 | torrent.addEvent(new Event(this, "Tracker response failure: " + failureReason, Level.SEVERE)); 88 | } 89 | return null; 90 | } 91 | 92 | try { 93 | byte[] warningMessageByteString = null; 94 | ByteBuffer warningMessageByteBuffer = (ByteBuffer) responseDictionary.get(toByteBuffer("warning message")); 95 | if (warningMessageByteBuffer != null) { 96 | warningMessageByteString = warningMessageByteBuffer.array(); 97 | } 98 | if (warningMessageByteString != null) { 99 | String warningMessage = new String(warningMessageByteString); 100 | if (Torrent.verbose) { 101 | torrent.addEvent(new Event(this, "Tracker response warning " + warningMessage, Level.WARNING)); 102 | } 103 | } 104 | } catch (Exception e) { 105 | } 106 | 107 | byte[] trackerIdByteString = null; 108 | ByteBuffer trackerIdByteBuffer = (ByteBuffer) responseDictionary.get(toByteBuffer("tracker id")); 109 | if (trackerIdByteBuffer != null) { 110 | trackerIdByteString = trackerIdByteBuffer.array(); 111 | } 112 | if (trackerIdByteString != null) { 113 | trackerId = new String(trackerIdByteString); 114 | if (Torrent.verbose) { 115 | torrent.addEvent(new Event(this, "Tracker id: " + trackerId, Level.FINE)); 116 | } 117 | } 118 | 119 | interval = (Long) responseDictionary.get(toByteBuffer("interval")); 120 | minInterval = (Long) responseDictionary.get(toByteBuffer("min interval")); 121 | complete = (Long) responseDictionary.get(toByteBuffer("complete")); 122 | incomplete = (Long) responseDictionary.get(toByteBuffer("incomplete")); 123 | 124 | return responseDictionary; 125 | } 126 | 127 | public Long getInterval() { 128 | return interval; 129 | } 130 | 131 | public Long getMinInterval() { 132 | return minInterval; 133 | } 134 | 135 | public Long getLastRequestTime() { 136 | return lastRequestTime; 137 | } 138 | 139 | public void setKey(String key) { 140 | this.key = key; 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/bencode/Bencode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /** 19 | * This is just a little class that lets you read and write bencoded files. 20 | * It uses List, Map, Long, and ByteBuffer in memory to represent data 21 | */ 22 | package org.bitlet.wetorrent.bencode; 23 | 24 | import java.io.IOException; 25 | import java.io.InputStream; 26 | import java.io.OutputStream; 27 | import java.nio.ByteBuffer; 28 | import java.util.LinkedList; 29 | import java.util.List; 30 | import java.util.Map; 31 | import java.util.SortedMap; 32 | import java.util.TreeMap; 33 | 34 | public class Bencode { 35 | 36 | private Object rootElement = null; 37 | 38 | /** 39 | * This creates and parse a bencoded InputStream 40 | */ 41 | public Bencode(InputStream is) throws IOException { 42 | if (!is.markSupported()) { 43 | throw new IOException("is.markSupported should be true"); 44 | } 45 | rootElement = parse(is); 46 | } 47 | 48 | /** 49 | * This creates a new instance of Bencode class 50 | */ 51 | public Bencode() { 52 | } 53 | 54 | /** 55 | * This method prints the bencoded file on the OutputStream os 56 | */ 57 | public void print(OutputStream os) throws IOException { 58 | print(rootElement, os); 59 | } 60 | 61 | private void print(Object object, OutputStream os) throws IOException { 62 | if (object instanceof Long) { 63 | os.write('i'); 64 | os.write(((Long) object).toString().getBytes()); 65 | os.write('e'); 66 | } 67 | if (object instanceof ByteBuffer) { 68 | byte[] byteString = ((ByteBuffer) object).array(); 69 | os.write(Integer.toString(byteString.length).getBytes()); 70 | os.write(':'); 71 | for (int i = 0; i < byteString.length; i++) { 72 | os.write(byteString[i]); 73 | } 74 | } else if (object instanceof List) { 75 | List list = (List) object; 76 | os.write('l'); 77 | for (Object elem : list) { 78 | print(elem, os); 79 | } 80 | os.write('e'); 81 | } else if (object instanceof Map) { 82 | Map map = (Map) object; 83 | os.write('d'); 84 | 85 | SortedMap sortedMap = new TreeMap(new DictionaryComparator()); 86 | // sortedMap.putAll(map); 87 | 88 | for (Object elem : map.entrySet()) { 89 | Map.Entry entry = (Map.Entry) elem; 90 | sortedMap.put((ByteBuffer) entry.getKey(), entry.getValue()); 91 | } 92 | 93 | for (Object elem : sortedMap.entrySet()) { 94 | Map.Entry entry = (Map.Entry) elem; 95 | print(entry.getKey(), os); 96 | print(entry.getValue(), os); 97 | } 98 | os.write('e'); 99 | } 100 | } 101 | 102 | private Object parse(InputStream is) throws IOException { 103 | is.mark(0); 104 | int readChar = is.read(); 105 | switch (readChar) { 106 | case 'i': 107 | return parseInteger(is); 108 | case 'l': 109 | return parseList(is); 110 | case 'd': 111 | return parseDictionary(is); 112 | case '0': 113 | case '1': 114 | case '2': 115 | case '3': 116 | case '4': 117 | case '5': 118 | case '6': 119 | case '7': 120 | case '8': 121 | case '9': 122 | is.reset(); 123 | return parseByteString(is); 124 | default: 125 | throw new IOException("Problem parsing bencoded file"); 126 | } 127 | } 128 | 129 | public Object getRootElement() { 130 | return rootElement; 131 | } 132 | 133 | public void setRootElement(Object rootElement) { 134 | this.rootElement = rootElement; 135 | } 136 | 137 | private Long parseInteger(InputStream is) throws IOException { 138 | 139 | int readChar = is.read(); 140 | 141 | StringBuffer buff = new StringBuffer(); 142 | do { 143 | if (readChar < 0) { 144 | throw new IOException("Unexpected EOF found"); 145 | } 146 | buff.append((char) readChar); 147 | readChar = is.read(); 148 | } while (readChar != 'e'); 149 | 150 | // System.out.println("Loaded int: " + buff); 151 | return Long.parseLong(buff.toString()); 152 | } 153 | 154 | private List parseList(InputStream is) throws IOException { 155 | 156 | List list = new LinkedList(); 157 | is.mark(0); 158 | int readChar = is.read(); 159 | while (readChar != 'e') { 160 | if (readChar < 0) { 161 | throw new IOException("Unexpected EOF found"); 162 | } 163 | is.reset(); 164 | list.add(parse(is)); 165 | is.mark(0); 166 | readChar = is.read(); 167 | } 168 | 169 | return list; 170 | } 171 | 172 | private SortedMap parseDictionary(InputStream is) throws IOException { 173 | SortedMap map = new TreeMap(new DictionaryComparator()); 174 | is.mark(0); 175 | int readChar = is.read(); 176 | while (readChar != 'e') { 177 | if (readChar < 0) { 178 | throw new IOException("Unexpected EOF found"); 179 | } 180 | is.reset(); 181 | map.put(parseByteString(is), parse(is)); 182 | is.mark(0); 183 | readChar = is.read(); 184 | } 185 | 186 | return map; 187 | } 188 | 189 | private ByteBuffer parseByteString(InputStream is) throws IOException { 190 | 191 | int readChar = is.read(); 192 | 193 | StringBuffer buff = new StringBuffer(); 194 | do { 195 | if (readChar < 0) { 196 | throw new IOException("Unexpected EOF found"); 197 | } 198 | buff.append((char) readChar); 199 | readChar = is.read(); 200 | } while (readChar != ':'); 201 | Integer length = Integer.parseInt(buff.toString()); 202 | 203 | byte[] byteString = new byte[length]; 204 | for (int i = 0; i < byteString.length; i++) { 205 | byteString[i] = (byte) is.read(); 206 | // System.out.println("Loaded string: " + new String(byteString)); 207 | } 208 | return ByteBuffer.wrap(byteString); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/bencode/DictionaryComparator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* 19 | * This is a very simple byte string comparator. 20 | */ 21 | package org.bitlet.wetorrent.bencode; 22 | 23 | import java.nio.ByteBuffer; 24 | import java.util.Comparator; 25 | 26 | public class DictionaryComparator implements Comparator { 27 | 28 | public DictionaryComparator() { 29 | } 30 | 31 | public int bitCompare(byte b1, byte b2) { 32 | int int1 = b1 & 0xFF; 33 | int int2 = b2 & 0xFF; 34 | return int1 - int2; 35 | } 36 | 37 | public int compare(ByteBuffer o1, ByteBuffer o2) { 38 | byte[] byteString1 = o1.array(); 39 | byte[] byteString2 = o2.array(); 40 | int minLength = byteString1.length > byteString2.length ? byteString2.length : byteString1.length; 41 | for (int i = 0; i < minLength; i++) { 42 | int bitCompare = bitCompare(byteString1[i], byteString2[i]); 43 | if (bitCompare != 0) { 44 | return bitCompare; 45 | } 46 | } 47 | 48 | if (byteString1.length > byteString2.length) { 49 | return 1; 50 | } else if (byteString1.length < byteString2.length) { 51 | return -1; 52 | } 53 | return 0; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/choker/Choker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.choker; 19 | 20 | import java.util.HashMap; 21 | import java.util.HashSet; 22 | import java.util.LinkedList; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.Random; 26 | import java.util.Set; 27 | import org.bitlet.wetorrent.Torrent; 28 | import org.bitlet.wetorrent.peer.Peer; 29 | 30 | public class Choker { 31 | 32 | private Torrent torrent; 33 | private static final int maxUnchoked = 16; 34 | private Map unchoked = new HashMap(); 35 | private Set interested = new HashSet(); 36 | 37 | public Choker(Torrent torrent) { 38 | this.torrent = torrent; 39 | } 40 | 41 | public synchronized void interested(Peer peer) { 42 | interested.add(peer); 43 | if (unchoked.size() < maxUnchoked) { 44 | peer.setIsChoked(false); 45 | unchoked.put(peer, System.currentTimeMillis()); 46 | } 47 | } 48 | 49 | public synchronized void notInterested(Peer peer) { 50 | peer.setIsChoked(true); 51 | unchoked.remove(peer); 52 | interested.remove(peer); 53 | } 54 | 55 | public synchronized void choke(Peer peer) { 56 | } 57 | 58 | public synchronized void unchoke(Peer peer) { 59 | } 60 | 61 | public synchronized void interrupted(Peer peer) { 62 | unchoked.remove(peer); 63 | interested.remove(peer); 64 | } 65 | 66 | public synchronized void tick() { 67 | 68 | 69 | if (unchoked.size() == maxUnchoked && interested.size() > maxUnchoked) { 70 | long now = System.currentTimeMillis(); 71 | Peer slowest = null; 72 | for (Map.Entry e : unchoked.entrySet()) { 73 | 74 | if (now - e.getValue() > 10000) { 75 | if (slowest == null) { 76 | slowest = e.getKey(); 77 | } else { 78 | if ((float) slowest.getDownloaded() / (now - unchoked.get(slowest)) > 79 | (float) e.getKey().getDownloaded() / (now - e.getValue())) { 80 | slowest = e.getKey(); 81 | } 82 | } 83 | } 84 | } 85 | 86 | if (slowest != null) { 87 | slowest.setIsChoked(true); 88 | unchoked.remove(slowest); 89 | 90 | List interestedChoked = new LinkedList(interested); 91 | interestedChoked.removeAll(unchoked.keySet()); 92 | 93 | Peer newUnchoked = interestedChoked.get(new Random().nextInt(interestedChoked.size())); 94 | newUnchoked.setIsChoked(false); 95 | unchoked.put(newUnchoked, now); 96 | } 97 | 98 | } 99 | 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/disk/FilePieceMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.disk; 19 | 20 | import java.io.RandomAccessFile; 21 | 22 | public class FilePieceMapper { 23 | 24 | private Long fileOffset; 25 | private Long pieceOffset; 26 | private RandomAccessFile file; 27 | 28 | public FilePieceMapper(RandomAccessFile file, Long fileOffset, Long pieceOffset) { 29 | this.file = file; 30 | this.fileOffset = fileOffset; 31 | this.pieceOffset = pieceOffset; 32 | } 33 | 34 | public Long getFileOffset() { 35 | return fileOffset; 36 | } 37 | 38 | public Long getPieceOffset() { 39 | return pieceOffset; 40 | } 41 | 42 | public RandomAccessFile getFile() { 43 | return file; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/disk/Piece.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.disk; 19 | 20 | import java.io.EOFException; 21 | import java.io.IOException; 22 | import java.io.RandomAccessFile; 23 | import java.security.MessageDigest; 24 | import java.security.NoSuchAlgorithmException; 25 | import java.util.ArrayList; 26 | import java.util.Collection; 27 | import java.util.Comparator; 28 | import java.util.Iterator; 29 | import java.util.LinkedList; 30 | import java.util.List; 31 | import java.util.Set; 32 | import java.util.TreeSet; 33 | import org.bitlet.wetorrent.util.Utils; 34 | 35 | public class Piece { 36 | 37 | private byte[] sha1; 38 | private int length; 39 | private List files = new ArrayList(); 40 | 41 | private class PieceBlock { 42 | 43 | public PieceBlock(Integer begin, Integer lenght) { 44 | this.begin = begin; 45 | this.length = lenght; 46 | } 47 | public Integer begin; 48 | public Integer length; 49 | 50 | public String toString() { 51 | return "b:" + begin + " l:" + length; 52 | } 53 | } 54 | private Set blocks = new TreeSet(new Comparator() { 55 | 56 | public int compare(Piece.PieceBlock o1, Piece.PieceBlock o2) { 57 | int beginDiff = o1.begin - o2.begin; 58 | if (beginDiff != 0) { 59 | return beginDiff; 60 | } 61 | return o1.length - o2.length; 62 | } 63 | }); 64 | 65 | /** 66 | * Creates a new instance of Piece 67 | */ 68 | public Piece(byte[] sha1) { 69 | this.sha1 = sha1; 70 | } 71 | 72 | public void addFilePointer(FilePieceMapper filePointer) { 73 | files.add(filePointer); 74 | } 75 | 76 | public int getCompleted() { 77 | Integer completed = 0; 78 | 79 | for (PieceBlock block : blocks) { 80 | completed += block.length; 81 | } 82 | return completed; 83 | } 84 | 85 | public void setLength(int length) { 86 | this.length = length; 87 | } 88 | 89 | public int getLength() { 90 | return length; 91 | } 92 | 93 | public boolean isCompleted() { 94 | return getCompleted() == length; 95 | } 96 | 97 | public void write(int begin, byte[] block) throws IOException { 98 | 99 | int filePieceIndex = findFilePieceIndex(begin); 100 | FilePieceMapper filePiece = files.get(filePieceIndex); 101 | 102 | int writtenBytes = 0; 103 | while (writtenBytes < block.length) { 104 | RandomAccessFile raf = filePiece.getFile(); 105 | Long seek = filePiece.getFileOffset() + ((begin + writtenBytes) - filePiece.getPieceOffset()); 106 | raf.seek(seek); 107 | 108 | int byteToWrite = block.length - writtenBytes; 109 | Long byteAvaiableInThisFile = raf.length() - seek; 110 | 111 | Long byteAvaiableToWrite = byteToWrite < byteAvaiableInThisFile ? byteToWrite : byteAvaiableInThisFile; 112 | raf.write(block, writtenBytes, byteAvaiableToWrite.intValue()); 113 | writtenBytes += byteAvaiableToWrite.intValue(); 114 | 115 | if (byteAvaiableToWrite.equals(byteAvaiableInThisFile) && writtenBytes < block.length) { 116 | filePiece = files.get(++filePieceIndex); 117 | } 118 | } 119 | 120 | 121 | addPieceBlock(begin, block.length); 122 | 123 | if (isCompleted()) { 124 | if (!checkSha1()) { 125 | blocks.clear(); 126 | throw new IOException("sha check failed"); 127 | } 128 | } 129 | } 130 | 131 | public byte[] read(int begin, int length) throws IOException { 132 | 133 | if (!isAvaiable(begin, length)) { 134 | throw new EOFException("Data not available " + "begin: " + begin + " length: " + length); 135 | } 136 | int filePieceIndex = findFilePieceIndex(begin); 137 | FilePieceMapper filePiece = files.get(filePieceIndex); 138 | byte[] block = new byte[length]; 139 | 140 | int readBytes = 0; 141 | while (readBytes < length) { 142 | RandomAccessFile raf = filePiece.getFile(); 143 | Long seek = filePiece.getFileOffset() + ((begin + readBytes) - filePiece.getPieceOffset()); 144 | raf.seek(seek); 145 | 146 | int byteToRead = length - readBytes; 147 | Long byteAvaiableInThisFile = raf.length() - seek; 148 | 149 | Long byteAvaiableToRead = byteToRead < byteAvaiableInThisFile ? byteToRead : byteAvaiableInThisFile; 150 | raf.readFully(block, readBytes, byteAvaiableToRead.intValue()); 151 | readBytes += byteAvaiableToRead.intValue(); 152 | 153 | if (byteAvaiableToRead.equals(byteAvaiableInThisFile) && readBytes < length) { 154 | filePiece = files.get(++filePieceIndex); 155 | } 156 | } 157 | 158 | return block; 159 | } 160 | 161 | /* TODO: Optimize with bisection 162 | */ 163 | private int findFilePieceIndex(int begin) { 164 | int i = 0; 165 | for (i = 0; i < files.size() - 1; i++) { 166 | if (files.get(i).getPieceOffset() <= begin && files.get(i + 1).getPieceOffset() > begin) { 167 | return i; 168 | } 169 | } 170 | 171 | return i; 172 | } 173 | 174 | public void addPieceBlock(int begin, int length) { 175 | 176 | PieceBlock newPieceBlock = new PieceBlock(begin, length); 177 | 178 | /*The pieceBlocks are sorted using begin as key*/ 179 | blocks.add(newPieceBlock); 180 | 181 | Iterator iterator = blocks.iterator(); 182 | PieceBlock prev = iterator.next(); 183 | 184 | Collection blocksToBeRemoved = new LinkedList(); 185 | 186 | while (iterator.hasNext()) { 187 | PieceBlock p = iterator.next(); 188 | if (prev.begin + prev.length >= p.begin) { 189 | 190 | p.length = Math.max(p.length + (p.begin - prev.begin), prev.length); 191 | p.begin = prev.begin; 192 | blocksToBeRemoved.add(prev); 193 | } 194 | prev = p; 195 | } 196 | 197 | for (PieceBlock pb : blocksToBeRemoved) { 198 | blocks.remove(pb); 199 | } 200 | } 201 | 202 | public boolean isAvaiable(int begin, int length) { 203 | for (PieceBlock block : blocks) { 204 | if (begin >= block.begin && length <= block.length) { 205 | return true; 206 | } else if (begin + length < block.begin) { 207 | return false; 208 | } 209 | } 210 | 211 | return false; 212 | } 213 | 214 | public int getFirstMissingByte() { 215 | 216 | if (blocks.size() > 0) { 217 | PieceBlock firstBlock = blocks.iterator().next(); 218 | if (firstBlock.begin == 0) { 219 | return firstBlock.length; 220 | } else { 221 | return 0; 222 | } 223 | } else { 224 | return 0; 225 | } 226 | } 227 | 228 | public boolean checkSha1() throws IOException { 229 | 230 | MessageDigest md = null; 231 | try { 232 | md = MessageDigest.getInstance("SHA1"); 233 | } catch (NoSuchAlgorithmException ex) { 234 | ex.printStackTrace(); 235 | } 236 | 237 | byte[] pieceBuffer = read(0, length); 238 | byte[] sha1Digest = md.digest(pieceBuffer); 239 | return Utils.bytesCompare(sha1, sha1Digest); 240 | } 241 | 242 | public void clear() { 243 | blocks.clear(); 244 | } 245 | 246 | public int available(int begin) { 247 | for (PieceBlock pb : blocks) { 248 | if (pb.begin <= begin && (pb.begin + pb.length) > begin) { 249 | return (pb.begin + pb.length) - begin; 250 | } 251 | } 252 | 253 | return 0; 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/disk/PlainFileSystemTorrentDisk.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.disk; 19 | 20 | import static org.bitlet.wetorrent.util.Utils.toByteBuffer; 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.io.RandomAccessFile; 25 | import java.nio.ByteBuffer; 26 | import java.util.ArrayList; 27 | import java.util.Iterator; 28 | import java.util.LinkedList; 29 | import java.util.List; 30 | import java.util.Map; 31 | import org.bitlet.wetorrent.Metafile; 32 | 33 | public class PlainFileSystemTorrentDisk implements TorrentDisk { 34 | 35 | Metafile metafile; 36 | private List pieces = new ArrayList(); 37 | List files = new LinkedList(); 38 | File saveDirectory; 39 | 40 | public PlainFileSystemTorrentDisk(Metafile metafile, File saveDirectory) { 41 | this.metafile = metafile; 42 | this.saveDirectory = saveDirectory; 43 | } 44 | 45 | public synchronized void resume() throws IOException { 46 | resume(null); 47 | } 48 | 49 | public synchronized void resume(ResumeListener rl) throws IOException { 50 | 51 | long completed = 0; 52 | long resumed = 0; 53 | for (Piece p : pieces) { 54 | /*pretend that the piece is already downloaded and check the hash*/ 55 | if (rl != null) { 56 | rl.percent(completed, resumed); 57 | } 58 | p.addPieceBlock(0, p.getLength()); 59 | if (!p.checkSha1()) { 60 | p.clear(); 61 | } else { 62 | resumed += p.getLength(); 63 | } 64 | completed += p.getLength(); 65 | } 66 | if (rl != null) { 67 | rl.percent(completed, resumed); 68 | } 69 | } 70 | 71 | public synchronized boolean init() throws IOException { 72 | 73 | boolean resume = false; 74 | 75 | /* create pieces */ 76 | Long pieceNumber = 0l; 77 | for (Object elem : metafile.getPieces()) { 78 | byte[] sha1 = (byte[]) elem; 79 | Piece piece = new Piece(sha1); 80 | if (pieceNumber < metafile.getPieces().size() - 1 && (metafile.getLength() % metafile.getPieceLength()) > 0) { 81 | piece.setLength(metafile.getPieceLength().intValue()); 82 | } else { 83 | piece.setLength(new Long(metafile.getLength() % metafile.getPieceLength()).intValue()); 84 | } 85 | pieces.add(piece); 86 | 87 | pieceNumber++; 88 | } 89 | 90 | saveDirectory.mkdirs(); 91 | /*create files*/ 92 | Long fileLength = 0l; 93 | if (metafile.isSingleFile()) { 94 | File persistentFile = new File(saveDirectory, metafile.getName()); 95 | if (persistentFile.exists()) { 96 | resume = true; 97 | } 98 | RandomAccessFile raf = new RandomAccessFile(persistentFile, "rw"); 99 | raf.setLength(metafile.getLength()); 100 | files.add(raf); 101 | } else { 102 | if (!saveDirectory.getName().equals(metafile.getName())) { 103 | saveDirectory = new File(saveDirectory, metafile.getName()); 104 | saveDirectory.mkdir(); 105 | } 106 | 107 | for (Object elem : metafile.getFiles()) { 108 | Map file = (Map) elem; 109 | List path = (List) file.get(toByteBuffer("path")); 110 | String pathName = ""; 111 | 112 | Iterator pathIterator = path.iterator(); 113 | while (pathIterator.hasNext()) { 114 | byte[] pathElem = ((ByteBuffer) pathIterator.next()).array(); 115 | pathName += "/" + new String(pathElem); 116 | if (pathIterator.hasNext()) { 117 | new File(saveDirectory, pathName).mkdir(); 118 | } 119 | } 120 | /* 121 | for (Object pathElem : path) 122 | pathName += "/" + new String((byte[])pathElem );*/ 123 | 124 | 125 | Long length = (Long) file.get(toByteBuffer("length")); 126 | File persistentFile = new File(saveDirectory.getAbsolutePath() + pathName); 127 | if (persistentFile.exists()) { 128 | resume = true; 129 | } 130 | RandomAccessFile raf = new RandomAccessFile(persistentFile, "rw"); 131 | raf.setLength(length); 132 | fileLength += length; 133 | files.add(raf); 134 | } 135 | } 136 | 137 | 138 | 139 | /*Associate pieces to files*/ 140 | Iterator fileIterator = files.iterator(); 141 | Iterator pieceIterator = pieces.iterator(); 142 | 143 | Long fileOffset = 0l; 144 | Long pieceOffset = 0l; 145 | 146 | Piece piece = pieceIterator.next(); 147 | RandomAccessFile file = fileIterator.next(); 148 | 149 | while (piece != null && file != null) { 150 | 151 | piece.addFilePointer(new FilePieceMapper(file, fileOffset, pieceOffset)); 152 | 153 | Long pieceFreeBytes = piece.getLength() - pieceOffset; 154 | Long fileMissingBytes = file.length() - fileOffset; 155 | 156 | if (pieceFreeBytes < fileMissingBytes) { 157 | fileOffset += pieceFreeBytes; 158 | if (pieceIterator.hasNext()) { 159 | piece = pieceIterator.next(); 160 | } else { 161 | piece = null; 162 | } 163 | pieceOffset = 0l; 164 | } else if (pieceFreeBytes > fileMissingBytes) { 165 | pieceOffset += fileMissingBytes; 166 | if (fileIterator.hasNext()) { 167 | file = fileIterator.next(); 168 | } else { 169 | file = null; 170 | } 171 | fileOffset = 0l; 172 | } else /* == */ { 173 | fileOffset = 0l; 174 | pieceOffset = 0l; 175 | if (fileIterator.hasNext()) { 176 | file = fileIterator.next(); 177 | } else { 178 | file = null; 179 | } 180 | if (pieceIterator.hasNext()) { 181 | piece = pieceIterator.next(); 182 | } else { 183 | piece = null; 184 | } 185 | } 186 | } 187 | 188 | return resume; 189 | } 190 | 191 | public synchronized byte[] getBitfieldCopy() { 192 | byte[] bitField = new byte[(pieces.size() >> 3) + ((pieces.size() & 0x7) != 0 ? 1 : 0)]; 193 | for (int i = 0; i < pieces.size(); i++) { 194 | bitField[i >> 3] |= (pieces.get(i).isCompleted() ? 0x80 : 0) >> (i & 0x7); 195 | } 196 | return bitField; 197 | } 198 | 199 | public synchronized void write(int index, int begin, byte[] block) throws IOException { 200 | Piece piece = pieces.get(index); 201 | if (!piece.isCompleted()) { 202 | piece.write(begin, block); 203 | } 204 | } 205 | 206 | public synchronized byte[] read(int index, int begin, int length) throws IOException { 207 | Piece piece = pieces.get(index); 208 | return piece.read(begin, length); 209 | } 210 | 211 | public synchronized Long getCompleted() { 212 | Long completed = 0l; 213 | for (Piece p : pieces) { 214 | completed += p.getCompleted(); 215 | } 216 | return completed; 217 | } 218 | 219 | public synchronized boolean isCompleted(int index) { 220 | return pieces.get(index).isCompleted(); 221 | } 222 | 223 | public synchronized int getDownloaded(int index) { 224 | return pieces.get(index).getCompleted(); 225 | } 226 | 227 | public synchronized boolean isAvailable(int index, int begin, int length) { 228 | return pieces.get(index).isAvaiable(begin, length); 229 | } 230 | 231 | public synchronized int getLength(int index) { 232 | return pieces.get(index).getLength(); 233 | } 234 | 235 | public synchronized int getFirstMissingByte(int index) { 236 | return pieces.get(index).getFirstMissingByte(); 237 | } 238 | 239 | public synchronized void close() { 240 | for (RandomAccessFile file : files) { 241 | try { 242 | file.close(); 243 | } catch (IOException ex) { 244 | } 245 | } 246 | } 247 | 248 | public synchronized long available(int index, int begin) { 249 | return available(index, begin, Long.MAX_VALUE); 250 | } 251 | 252 | public synchronized long available(int index, int begin, long length) { 253 | 254 | int pieceLength = pieces.get(0).getLength(); 255 | 256 | boolean goOn = true; 257 | long avaiable = 0; 258 | while (goOn) { 259 | Piece p = pieces.get(index); 260 | int pieceAvaiable = p.available(begin); 261 | 262 | avaiable += pieceAvaiable; 263 | index++; 264 | begin = 0; 265 | 266 | if (pieceAvaiable != p.getLength() - begin || 267 | pieceAvaiable >= length) { 268 | goOn = false; 269 | } 270 | } 271 | 272 | return avaiable; 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/disk/ResumeListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.disk; 19 | 20 | public interface ResumeListener { 21 | 22 | public void percent(long completed, long resumed); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/disk/TorrentDisk.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.disk; 19 | 20 | import java.io.IOException; 21 | 22 | public interface TorrentDisk { 23 | 24 | public void resume() throws IOException; 25 | 26 | public void resume(ResumeListener rl) throws IOException; 27 | 28 | /* It returns true if a resume could be performed */ 29 | public boolean init() throws IOException; 30 | 31 | public byte[] getBitfieldCopy(); 32 | 33 | public void write(int index, int begin, byte[] block) throws IOException; 34 | 35 | public byte[] read(int index, int begin, int length) throws IOException; 36 | 37 | public Long getCompleted(); 38 | 39 | public boolean isCompleted(int index); 40 | 41 | public int getDownloaded(int index); 42 | 43 | public boolean isAvailable(int index, int begin, int length); 44 | 45 | public long available(int index, int begin); 46 | 47 | public long available(int index, int begin, long maxLength); 48 | 49 | public int getLength(int index); 50 | 51 | public int getFirstMissingByte(int index); 52 | 53 | public void close(); 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/peer/IncomingPeerListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.peer; 19 | 20 | import java.net.ServerSocket; 21 | import java.net.Socket; 22 | import java.net.SocketException; 23 | import java.nio.ByteBuffer; 24 | import java.util.HashMap; 25 | import java.util.HashSet; 26 | import java.util.Map; 27 | import java.util.Set; 28 | import org.bitlet.wetorrent.Torrent; 29 | import org.bitlet.wetorrent.util.thread.InterruptableTasksThread; 30 | import org.bitlet.wetorrent.util.thread.ThreadTask; 31 | 32 | public class IncomingPeerListener extends InterruptableTasksThread { 33 | 34 | ServerSocket serverSocket; 35 | private Map torrents = new HashMap(); 36 | private Set dispatchingPeers = new HashSet(); 37 | private int port; 38 | private int receivedConnection = 0; 39 | 40 | public IncomingPeerListener(int port) { 41 | 42 | if (Torrent.verbose) { 43 | System.err.println("Binding incoming server socket"); 44 | } 45 | while (port < 65535 && serverSocket == null) { 46 | try { 47 | serverSocket = new ServerSocket(port); 48 | } catch (Exception e) { 49 | 50 | if (Torrent.verbose) { 51 | System.err.println("Cannot bind port " + port); 52 | } 53 | port++; 54 | } 55 | } 56 | 57 | if (serverSocket == null) { 58 | 59 | System.err.println("Cannot bind the incoming socket"); 60 | return; 61 | } 62 | 63 | this.port = port; 64 | 65 | final IncomingPeerListener incomingPeerListener = this; 66 | addTask(new ThreadTask() { 67 | 68 | public boolean execute() throws Exception { 69 | 70 | try { 71 | Socket socket = serverSocket.accept(); 72 | 73 | receivedConnection++; 74 | TorrentPeer peer = new TorrentPeer(socket, incomingPeerListener); 75 | dispatchingPeers.add(peer); 76 | peer.start(); 77 | } catch (SocketException e) { 78 | return false; 79 | } 80 | return true; 81 | } 82 | 83 | public void interrupt() { 84 | try { 85 | serverSocket.close(); 86 | } catch (Exception e) { 87 | } 88 | } 89 | 90 | public void exceptionCought(Exception e) { 91 | e.printStackTrace(); 92 | throw new RuntimeException(e); 93 | } 94 | }); 95 | 96 | 97 | } 98 | 99 | public int getPort() { 100 | return port; 101 | } 102 | 103 | public int getReceivedConnection() { 104 | return receivedConnection; 105 | } 106 | 107 | public synchronized void register(Torrent torrent) { 108 | torrents.put(ByteBuffer.wrap(torrent.getMetafile().getInfoSha1()), torrent); 109 | } 110 | 111 | public synchronized void unregister(Torrent torrent) { 112 | torrents.remove(ByteBuffer.wrap(torrent.getMetafile().getInfoSha1())); 113 | } 114 | 115 | public synchronized void peer(TorrentPeer dispatchingPeer) { 116 | dispatchingPeers.add(dispatchingPeer); 117 | } 118 | 119 | public synchronized boolean dispatchPeer(TorrentPeer dispatchingPeer, byte[] infoSha1) { 120 | dispatchingPeers.remove(dispatchingPeer); 121 | Torrent torrent = torrents.get(ByteBuffer.wrap(infoSha1)); 122 | if (torrent != null) { 123 | dispatchingPeer.setPeersManager(torrent.getPeersManager()); 124 | torrent.getPeersManager().offer(dispatchingPeer); 125 | return true; 126 | } else { 127 | return false; 128 | } 129 | } 130 | 131 | public void interrupt() { 132 | super.interrupt(); 133 | 134 | for (TorrentPeer p : dispatchingPeers) { 135 | p.interrupt(); 136 | } 137 | } 138 | 139 | public synchronized void removePeer(TorrentPeer peer) { 140 | dispatchingPeers.remove(peer); 141 | } 142 | } 143 | 144 | 145 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/peer/Peer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.peer; 19 | 20 | import java.net.InetAddress; 21 | import org.bitlet.wetorrent.peer.message.Message; 22 | 23 | public interface Peer { 24 | 25 | public void sendMessage(Message message); 26 | 27 | public void setAmInterested(boolean b); 28 | 29 | public void setIsChoked(boolean b); 30 | 31 | byte[] getPeerId(); 32 | 33 | long getDownloaded(); 34 | 35 | InetAddress getIp(); 36 | 37 | public int getPort(); 38 | 39 | long getLastReceivedMessageMillis(); 40 | 41 | int getUnfulfilledRequestNumber(); 42 | 43 | long getUploaded(); 44 | 45 | boolean hasPiece(int index); 46 | 47 | void interrupt(); 48 | 49 | boolean isAmChoked(); 50 | 51 | boolean isSeeder(); 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/peer/PeersManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.peer; 19 | 20 | import java.net.InetAddress; 21 | import java.util.LinkedList; 22 | import java.util.List; 23 | import java.util.logging.Level; 24 | 25 | import org.bitlet.wetorrent.Event; 26 | import org.bitlet.wetorrent.Torrent; 27 | import org.bitlet.wetorrent.peer.message.Have; 28 | import org.bitlet.wetorrent.peer.message.Message; 29 | import org.bitlet.wetorrent.util.Utils; 30 | 31 | public class PeersManager { 32 | 33 | private List connectingPeers = new LinkedList(); 34 | private List activePeers = new LinkedList(); 35 | /** 36 | * Creates a new instance of PeersManager 37 | */ 38 | private int connectionCreationTreshold = 35; 39 | private int maxConnection = 45; 40 | private Torrent torrent; 41 | private long disconnectedClientDownloaded = 0l; 42 | private long disconnectedClientUploaded = 0l; 43 | 44 | public PeersManager(Torrent torrent) { 45 | this.torrent = torrent; 46 | } 47 | 48 | /* 49 | * This is called when we would like to start a connection to 50 | */ 51 | public synchronized TorrentPeer offer(byte[] peerId, InetAddress ip, int port) { 52 | 53 | // TODO: this could be optimized with a proper indexing 54 | for (Peer peer : connectingPeers) { 55 | if (peer.getPort() == port && peer.getIp().equals(ip)) { 56 | return null; 57 | } 58 | } 59 | for (Peer peer : activePeers) { 60 | if (peer.getPort() == port && peer.getIp().equals(ip)) { 61 | return null; 62 | } 63 | } 64 | 65 | if (activePeers.size() < connectionCreationTreshold) { 66 | TorrentPeer peer = new TorrentPeer(peerId, ip, port, this); 67 | 68 | if (Torrent.verbose) { 69 | torrent.addEvent(new Event(this, "Starting connection to new peer: " + ip, Level.FINE)); 70 | } 71 | peer.start(); 72 | connectingPeers.add(peer); 73 | return peer; 74 | } 75 | 76 | if (Torrent.verbose) { 77 | torrent.addEvent(new Event(this, "Too many connection, peer refused: " + ip, Level.FINER)); 78 | } 79 | return null; 80 | } 81 | 82 | public synchronized TorrentPeer offer(TorrentPeer peer) { 83 | if (activePeers.size() > maxConnection) { 84 | if (Torrent.verbose) { 85 | torrent.addEvent(new Event(this, "Refusing incoming connection: too many connection", Level.FINER)); 86 | } 87 | peer.interrupt(); 88 | return null; 89 | } 90 | 91 | if (Torrent.verbose) { 92 | torrent.addEvent(new Event(this, "Accpeting incoming peer connection ", Level.FINER)); 93 | } 94 | peer.setPeersManager(this); 95 | connectingPeers.add(peer); 96 | return peer; 97 | } 98 | 99 | public Torrent getTorrent() { 100 | return torrent; 101 | } 102 | 103 | public synchronized long getUploaded() { 104 | long uploaded = disconnectedClientUploaded; 105 | for (Peer p : activePeers) { 106 | uploaded += p.getUploaded(); 107 | } 108 | return uploaded; 109 | } 110 | 111 | public synchronized void interrupted(Peer peer) { 112 | connectingPeers.remove(peer); 113 | if (activePeers.remove(peer)) { 114 | torrent.interrupted(peer); 115 | disconnectedClientDownloaded += peer.getDownloaded(); 116 | disconnectedClientUploaded += peer.getUploaded(); 117 | } 118 | 119 | if (Torrent.verbose) { 120 | torrent.addEvent(new Event(this, activePeers.size() + " active peers, " + connectingPeers.size() + "connecting peer", Level.INFO)); 121 | } 122 | } 123 | 124 | public synchronized int[] getPiecesFrequencies() { 125 | int[] frequencies = new int[torrent.getMetafile().getPieces().size()]; 126 | 127 | for (int i = 0; i < frequencies.length; i++) { 128 | for (Peer p : activePeers) { 129 | if (p.hasPiece(i)) { 130 | frequencies[i]++; 131 | } 132 | } 133 | } 134 | 135 | return frequencies; 136 | } 137 | 138 | public synchronized long getDownloaded() { 139 | long downloaded = disconnectedClientDownloaded; 140 | for (Peer p : activePeers) { 141 | downloaded += p.getDownloaded(); 142 | } 143 | return downloaded; 144 | 145 | } 146 | 147 | public synchronized void tick() { 148 | long now = System.currentTimeMillis(); 149 | 150 | List peersTimedOut = new LinkedList(); 151 | // TODO: Remove hardcoded millis 152 | for (Peer p : activePeers) { 153 | if (now - p.getLastReceivedMessageMillis() > 120000) { 154 | peersTimedOut.add(p); 155 | } else if (now - p.getLastReceivedMessageMillis() > 110000) { 156 | p.sendMessage(new Message(Message.KEEP_ALIVE, null)); 157 | } 158 | } 159 | 160 | for (Peer p : peersTimedOut) { 161 | p.interrupt(); 162 | interrupted(p); 163 | } 164 | } 165 | 166 | public synchronized void interrupt() { 167 | while (connectingPeers.size() > 0) { 168 | connectingPeers.get(0).interrupt(); 169 | } 170 | while (activePeers.size() > 0) { 171 | activePeers.get(0).interrupt(); 172 | } 173 | } 174 | 175 | public synchronized void sendHave(Have have) { 176 | for (Peer p : activePeers) { 177 | if (!p.hasPiece(have.getIndex())) { 178 | p.sendMessage(have); 179 | } 180 | } 181 | } 182 | 183 | public synchronized int getActivePeersNumber() { 184 | return activePeers.size(); 185 | } 186 | 187 | public synchronized int getSeedersNumber() { 188 | int acc = 0; 189 | for (Peer peer : activePeers) 190 | acc += peer.isSeeder() ? 1 : 0; 191 | return acc; 192 | } 193 | public synchronized void connected(Peer peer) { 194 | 195 | if (!connectingPeers.remove(peer)) { 196 | if (Torrent.verbose) { 197 | torrent.addEvent(new Event(peer, "Peer connected", Level.WARNING)); 198 | } 199 | } 200 | for (Peer p : activePeers) { 201 | if (Utils.bytesCompare(peer.getPeerId(), p.getPeerId())) { 202 | peer.interrupt(); 203 | torrent.addEvent(new Event(this, "Peer already connected", Level.FINE)); 204 | return; 205 | } 206 | } 207 | 208 | activePeers.add(peer); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/peer/TorrentPeer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.peer; 19 | 20 | import java.net.InetAddress; 21 | import java.net.Socket; 22 | import java.util.LinkedList; 23 | import java.util.List; 24 | import java.util.logging.Level; 25 | import org.bitlet.wetorrent.Event; 26 | import org.bitlet.wetorrent.Torrent; 27 | import org.bitlet.wetorrent.peer.message.Cancel; 28 | import org.bitlet.wetorrent.peer.message.Piece; 29 | import org.bitlet.wetorrent.peer.message.Message; 30 | import org.bitlet.wetorrent.peer.message.Request; 31 | import org.bitlet.wetorrent.peer.task.StartMessageReceiver; 32 | import org.bitlet.wetorrent.peer.task.MessageReceiver; 33 | import org.bitlet.wetorrent.peer.task.MessageSender; 34 | import org.bitlet.wetorrent.peer.task.Handshake; 35 | import org.bitlet.wetorrent.peer.task.SendBitfield; 36 | import org.bitlet.wetorrent.peer.task.StartConnection; 37 | import org.bitlet.wetorrent.util.thread.InterruptableTasksThread; 38 | import org.bitlet.wetorrent.util.Utils; 39 | 40 | public class TorrentPeer implements Peer { 41 | 42 | private byte[] peerId; 43 | private String peerIdEncoded; 44 | private int port; 45 | private InetAddress ip; 46 | private byte[] bitfield; 47 | private Socket socket; 48 | boolean isChoked = true; 49 | boolean isInterested = false; 50 | boolean amChoked = true; 51 | boolean amInterested = false; 52 | private PeersManager peersManager; 53 | private InterruptableTasksThread receiverThread; 54 | private InterruptableTasksThread mainThread; 55 | private long downloaded; 56 | private MessageSender messageSender; 57 | private MessageReceiver messageReceiver; 58 | private List unfulfilledRequests = new LinkedList(); 59 | 60 | public TorrentPeer(byte[] peerId, InetAddress ip, int port, PeersManager peersManager) { 61 | this.peersManager = peersManager; 62 | this.peerId = peerId; 63 | peerIdEncoded = Utils.byteArrayToURLString(peerId); 64 | this.port = port; 65 | this.ip = ip; 66 | 67 | bitfield = new byte[peersManager.getTorrent().getTorrentDisk().getBitfieldCopy().length]; 68 | 69 | mainThread = new InterruptableTasksThread(); 70 | mainThread.addTask(new StartConnection(this)); 71 | mainThread.addTask(new Handshake(this)); 72 | mainThread.addTask(new SendBitfield(this)); 73 | 74 | receiverThread = new InterruptableTasksThread(); 75 | messageReceiver = new MessageReceiver(this); 76 | receiverThread.addTask(messageReceiver); 77 | mainThread.addTask(new StartMessageReceiver(this)); 78 | messageSender = new MessageSender(this); 79 | mainThread.addTask(messageSender); 80 | 81 | } 82 | 83 | public TorrentPeer(Socket socket, IncomingPeerListener incomingPeerListener) { 84 | this.socket = socket; 85 | port = socket.getPort(); 86 | ip = socket.getInetAddress(); 87 | 88 | 89 | mainThread = new InterruptableTasksThread(); 90 | mainThread.addTask(new Handshake(this, incomingPeerListener)); 91 | mainThread.addTask(new SendBitfield(this)); 92 | 93 | receiverThread = new InterruptableTasksThread(); 94 | messageReceiver = new MessageReceiver(this); 95 | receiverThread.addTask(messageReceiver); 96 | mainThread.addTask(new StartMessageReceiver(this)); 97 | messageSender = new MessageSender(this); 98 | mainThread.addTask(messageSender); 99 | 100 | } 101 | 102 | public void start() { 103 | mainThread.start(); 104 | } 105 | 106 | public void exceptionCought(Exception e) { 107 | 108 | if (Torrent.verbose) { 109 | peersManager.getTorrent().addEvent(new Event(e, "UUoops exception cought.", Level.WARNING)); 110 | } 111 | receiverThread.interrupt(); 112 | mainThread.interrupt(); 113 | } 114 | 115 | public String getPeerIdEncoded() { 116 | return peerIdEncoded; 117 | } 118 | 119 | public byte[] getPeerId() { 120 | return peerId; 121 | } 122 | 123 | public void setPeerId(byte[] peerId) { 124 | this.peerId = peerId; 125 | } 126 | 127 | public void setSocket(Socket socket) { 128 | this.socket = socket; 129 | } 130 | 131 | public Socket getSocket() { 132 | return socket; 133 | } 134 | 135 | public PeersManager getPeersManager() { 136 | return peersManager; 137 | } 138 | 139 | public InetAddress getIp() { 140 | return ip; 141 | } 142 | 143 | public int getPort() { 144 | return port; 145 | } 146 | 147 | public synchronized void setBitfield(byte[] bitfield) { 148 | this.bitfield = bitfield; 149 | } 150 | 151 | /*messages sent by remote peer*/ 152 | public void bitfield(byte[] bitfield) { 153 | 154 | if (Torrent.verbose) { 155 | peersManager.getTorrent().addEvent(new Event(this, "Bitfield received ", Level.FINEST)); 156 | } 157 | setBitfield(bitfield); 158 | peersManager.getTorrent().bitfield(bitfield, this); 159 | } 160 | 161 | public void choke() { 162 | 163 | if (Torrent.verbose) { 164 | peersManager.getTorrent().addEvent(new Event(this, "Choke received ", Level.FINEST)); 165 | } 166 | amChoked = true; 167 | peersManager.getTorrent().choke(this); 168 | } 169 | 170 | public void unchoke() { 171 | if (Torrent.verbose) { 172 | peersManager.getTorrent().addEvent(new Event(this, "Unchoke received ", Level.FINEST)); 173 | } 174 | amChoked = false; 175 | /* if there are pending request not satisfied */ 176 | messageSender.cancelAll(); 177 | peersManager.getTorrent().unchoke(this); 178 | } 179 | 180 | public void interested() { 181 | if (Torrent.verbose) { 182 | peersManager.getTorrent().addEvent(new Event(this, "Interested received ", Level.FINEST)); 183 | } 184 | isInterested = true; 185 | peersManager.getTorrent().interested(this); 186 | } 187 | 188 | public void notInterested() { 189 | if (Torrent.verbose) { 190 | peersManager.getTorrent().addEvent(new Event(this, "Not interested received ", Level.FINEST)); 191 | } 192 | isInterested = false; 193 | peersManager.getTorrent().notInterested(this); 194 | } 195 | 196 | public void have(int i) { 197 | 198 | if (Torrent.verbose) { 199 | peersManager.getTorrent().addEvent(new Event(this, "Have received ", Level.FINEST)); 200 | } 201 | setPiece(i); 202 | peersManager.getTorrent().have(i, this); 203 | } 204 | 205 | public void request(int index, int begin, int length) { 206 | if (Torrent.verbose) { 207 | peersManager.getTorrent().addEvent(new Event(this, "Request received ", Level.FINEST)); 208 | /* TODO: Check block avaiabilty */ 209 | } 210 | if (!isChoked) { 211 | messageSender.addMessage(new Piece(index, begin, length, peersManager.getTorrent().getTorrentDisk())); 212 | } 213 | } 214 | 215 | public void piece(int index, int begin, byte[] block) { 216 | if (Torrent.verbose) { 217 | peersManager.getTorrent().addEvent(new Event(this, "Piece received " + index + " " + begin + " " + block.length, Level.FINEST)); 218 | } 219 | downloaded += block.length; 220 | if (requestFulfilled(index, begin, block)) 221 | peersManager.getTorrent().piece(index, begin, block, this); 222 | } 223 | 224 | public void cancel(int index, int begin, int length) { 225 | if (Torrent.verbose) { 226 | peersManager.getTorrent().addEvent(new Event(this, "Cancel received ", Level.FINEST)); 227 | } 228 | messageSender.cancel(index, begin, length); 229 | } 230 | 231 | public InterruptableTasksThread getReceiverThread() { 232 | return receiverThread; 233 | } 234 | 235 | public synchronized void sendMessage(Message message) { 236 | 237 | messageSender.addMessage(message); 238 | switch (message.getType()) { 239 | case Message.REQUEST: 240 | addRequest((Request) message); 241 | break; 242 | case Message.CANCEL: 243 | Cancel cancel = (Cancel) message; 244 | requestCanceled(cancel.getIndex(), cancel.getBegin()); 245 | break; 246 | } 247 | 248 | 249 | } 250 | 251 | public void interrupt() { 252 | mainThread.interrupt(); 253 | receiverThread.interrupt(); 254 | if (peersManager != null) { 255 | peersManager.interrupted(this); 256 | } 257 | } 258 | 259 | public synchronized byte[] getBitfieldCopy() { 260 | return bitfield.clone(); 261 | } 262 | 263 | public synchronized boolean hasPiece(int index) { 264 | return (bitfield[index >> 3] & (0x80 >> (index & 0x7))) > 0; 265 | } 266 | 267 | public synchronized void setPiece(int index) { 268 | bitfield[index >> 3] |= (0x80 >> (index & 0x7)); 269 | } 270 | 271 | public void keepAlive() { 272 | long now = System.currentTimeMillis(); 273 | 274 | if (now - messageSender.getLastSentMessageMillis() < 2000) { 275 | sendMessage(new Message(Message.KEEP_ALIVE, null)); 276 | } 277 | } 278 | 279 | public long getUploaded() { 280 | return messageSender.getUploaded(); 281 | } 282 | 283 | public long getDownloaded() { 284 | return downloaded; 285 | } 286 | 287 | public void setAmInterested(boolean amInterested) { 288 | if (!this.amInterested && amInterested) { 289 | sendMessage(new Message(Message.INTERESTED, null)); 290 | } else if (this.amInterested && !amInterested) { 291 | sendMessage(new Message(Message.NOT_INTERESTED, null)); 292 | } 293 | this.amInterested = amInterested; 294 | 295 | } 296 | 297 | public void setIsChoked(boolean isChoked) { 298 | if (!this.isChoked && isChoked) { 299 | sendMessage(new Message(Message.CHOKE, null)); 300 | } else if (this.isChoked && !isChoked) { 301 | sendMessage(new Message(Message.UNCHOKE, null)); 302 | } 303 | this.isChoked = isChoked; 304 | } 305 | 306 | public boolean isIsChoked() { 307 | return isChoked; 308 | } 309 | 310 | public boolean isAmChoked() { 311 | return amChoked; 312 | } 313 | 314 | public synchronized boolean isSeeder() { 315 | for (int i = 0; i < getPeersManager().getTorrent().getMetafile().getPieces().size(); i++) { 316 | if(!hasPiece(i)) 317 | return false; 318 | } 319 | return true; 320 | } 321 | 322 | private synchronized void addRequest(Request request) { 323 | unfulfilledRequests.add(request); 324 | } 325 | 326 | public synchronized int getUnfulfilledRequestNumber() { 327 | return unfulfilledRequests.size(); 328 | } 329 | 330 | private synchronized boolean requestFulfilled(int index, int begin, byte[] block) { 331 | for (Request r : unfulfilledRequests) { 332 | if (r.getIndex() == index && r.getBegin() == begin && r.getLength() == block.length) { 333 | unfulfilledRequests.remove(r); 334 | return true; 335 | } 336 | } 337 | 338 | return false; 339 | } 340 | 341 | private synchronized boolean requestCanceled(int index, int begin) { 342 | for (Request r : unfulfilledRequests) { 343 | if (r.getIndex() == index && r.getBegin() == begin) { 344 | unfulfilledRequests.remove(r); 345 | return true; 346 | } 347 | } 348 | 349 | return false; 350 | } 351 | 352 | public synchronized Request getLastUnfulfilledRequest() { 353 | if (unfulfilledRequests.size() == 0) { 354 | return null; 355 | } 356 | Request last = unfulfilledRequests.get(unfulfilledRequests.size() - 1); 357 | return last; 358 | } 359 | 360 | public long getLastReceivedMessageMillis() { 361 | return messageReceiver.getLastReceivedMessageMillis(); 362 | } 363 | 364 | void setPeersManager(PeersManager peersManager) { 365 | this.peersManager = peersManager; 366 | bitfield = new byte[peersManager.getTorrent().getTorrentDisk().getBitfieldCopy().length]; 367 | } 368 | } 369 | 370 | 371 | 372 | 373 | 374 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/peer/WebSeed.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.peer; 19 | 20 | import java.io.DataInputStream; 21 | import java.io.FileOutputStream; 22 | import java.io.IOException; 23 | import java.net.HttpURLConnection; 24 | import java.net.InetAddress; 25 | import java.net.URL; 26 | 27 | import java.net.UnknownHostException; 28 | import java.util.Random; 29 | import java.util.concurrent.BlockingQueue; 30 | import java.util.concurrent.LinkedBlockingQueue; 31 | import java.util.logging.Level; 32 | import java.util.logging.Logger; 33 | import org.bitlet.wetorrent.peer.message.Message; 34 | import org.bitlet.wetorrent.peer.message.Request; 35 | import org.bitlet.wetorrent.util.thread.InterruptableTasksThread; 36 | import org.bitlet.wetorrent.util.thread.ThreadTask; 37 | 38 | /** 39 | * 40 | * @author Alex 41 | */ 42 | public class WebSeed implements Peer{ 43 | 44 | private URL url; 45 | private HttpURLConnection connection; 46 | private InterruptableTasksThread downloaderThread; 47 | private final BlockingQueue pendingRequests = new LinkedBlockingQueue();; 48 | private final static Request requestEnd = new Request(Integer.MAX_VALUE, 0, 0); 49 | 50 | private PeersManager peersManager; 51 | private byte[] peerId = new byte[20]; 52 | private byte[] bitfield; 53 | 54 | private long downloaded = 0; 55 | 56 | public WebSeed(URL url, PeersManager peersManager) { 57 | 58 | this.url = url; 59 | this.peersManager = peersManager; 60 | 61 | bitfield = peersManager.getTorrent().getTorrentDisk().getBitfieldCopy(); 62 | 63 | for (int i = 0; i < bitfield.length; i++) 64 | bitfield[i] |= 0xFF; 65 | 66 | Random random = new Random(System.currentTimeMillis()); 67 | random.nextBytes(peerId); 68 | System.arraycopy("-WT-HTTP".getBytes(), 0, peerId, 0, 8); 69 | 70 | downloaderThread = new InterruptableTasksThread(); 71 | downloaderThread.addTask(new HttpRangeDownloader()); 72 | } 73 | 74 | public void requestEnd(){ 75 | pendingRequests.add(requestEnd); 76 | } 77 | 78 | public void start() { 79 | downloaderThread.start(); 80 | } 81 | 82 | public void interrupt(){ 83 | downloaderThread.interrupt(); 84 | } 85 | 86 | public void sendMessage(Message message) { 87 | if (message.getType() == Message.REQUEST){ 88 | pendingRequests.add((Request)message); 89 | } 90 | } 91 | 92 | public void setAmInterested(boolean b) { 93 | peersManager.getTorrent().unchoke(this); 94 | } 95 | 96 | public void setIsChoked(boolean b) {} 97 | 98 | public long getDownloaded() { 99 | return downloaded; 100 | } 101 | 102 | public InetAddress getIp() { 103 | InetAddress ia = null; 104 | try { 105 | ia = InetAddress.getByName(url.getHost()); 106 | } catch (UnknownHostException ex) { 107 | Logger.getLogger(WebSeed.class.getName()).log(Level.SEVERE, null, ex); 108 | } 109 | return ia; 110 | } 111 | 112 | public int getPort() { 113 | return url.getPort(); 114 | } 115 | 116 | public byte[] getPeerId() { 117 | return peerId; 118 | } 119 | 120 | 121 | 122 | public long getLastReceivedMessageMillis() { 123 | throw new UnsupportedOperationException("Not supported yet."); 124 | } 125 | 126 | public int getUnfulfilledRequestNumber() { 127 | return pendingRequests.size(); 128 | } 129 | 130 | public long getUploaded() { 131 | return 0; 132 | } 133 | 134 | public boolean hasPiece(int index) { 135 | return true; 136 | } 137 | 138 | 139 | public boolean isAmChoked() { 140 | return false; 141 | } 142 | 143 | 144 | public boolean isSeeder() { 145 | return true; 146 | } 147 | 148 | class HttpRangeDownloader implements ThreadTask { 149 | 150 | public boolean execute() throws Exception { 151 | Request nextRequest = pendingRequests.take(); 152 | 153 | if (nextRequest != requestEnd){ 154 | byte[] range = downloadRange(nextRequest); 155 | 156 | // XXX placeholder 157 | byteSink(range); 158 | return true; 159 | }else 160 | return false; 161 | } 162 | 163 | public void interrupt() { 164 | try { 165 | pendingRequests.put(requestEnd); 166 | } catch (InterruptedException ex) { 167 | Logger.getLogger(WebSeed.class.getName()).log(Level.SEVERE, null, ex); 168 | } 169 | 170 | } 171 | 172 | public void exceptionCaught(Exception e) { 173 | e.printStackTrace(); 174 | } 175 | 176 | @Deprecated 177 | public void exceptionCought(Exception e) { 178 | exceptionCaught(e); 179 | } 180 | 181 | private byte[] downloadRange(Request request) throws IOException { 182 | byte[] result = new byte[request.getLength()]; 183 | // TODO connection should be openend elsewhere 184 | connection = (HttpURLConnection) url.openConnection(); 185 | connection.setRequestProperty("Connection", "Keep-Alive"); 186 | connection.setRequestProperty("Range", "bytes=" + request.getBegin() + "-" + (request.getBegin() + request.getLength() - 1)); 187 | System.out.println("Range: bytes=" + request.getBegin() + "-" + (request.getBegin() + request.getLength() - 1)); 188 | 189 | connection.connect(); 190 | 191 | int response = connection.getResponseCode(); 192 | DataInputStream is = new DataInputStream(connection.getInputStream()); 193 | 194 | if (response == HttpURLConnection.HTTP_ACCEPTED || 195 | response == HttpURLConnection.HTTP_OK || 196 | response == HttpURLConnection.HTTP_PARTIAL) { 197 | 198 | is.readFully(result, 0, request.getLength()); 199 | } 200 | 201 | is.close(); 202 | 203 | return result; 204 | } 205 | } 206 | 207 | // public static void main(String[] args) throws Exception { 208 | // final int TOT_SIZE = 22636132; 209 | // final int BLOCK_SIZE = 1024; 210 | // 211 | // URL theUrl = new URL("http://nofatclips.com/02009/01/21/magic/Preproduction.ogv"); 212 | // 213 | // WebSeed seed = new WebSeed(theUrl); 214 | // 215 | // HttpURLConnection conn = (HttpURLConnection) theUrl.openConnection(); 216 | // 217 | // conn.connect(); 218 | // InputStream is = conn.getInputStream(); 219 | // 220 | // FileOutputStream fos = new FileOutputStream("/Users/Alex/Desktop/target.ogv"); 221 | // 222 | // byte[] buf = new byte[BLOCK_SIZE]; 223 | // int len = 0; 224 | // int offset = 0; 225 | // 226 | // while ((len = is.read(buf)) > 0) { 227 | // fos.write(buf, 0, len); 228 | // offset += len; 229 | // 230 | // System.out.println("offset: " + offset); 231 | // } 232 | // 233 | // is.close(); 234 | // fos.close(); 235 | // conn.disconnect(); 236 | // } 237 | 238 | static FileOutputStream fos; 239 | static int wrote = 0; 240 | 241 | private void byteSink(byte[] stuff) throws Exception { 242 | fos.write(stuff); 243 | wrote += stuff.length; 244 | System.out.println("wrote: " + wrote); 245 | 246 | if (wrote == TOT_SIZE) { 247 | fos.close(); 248 | } 249 | 250 | } 251 | 252 | // static final int TOT_SIZE = 422279; 253 | static final int TOT_SIZE = 3576986; 254 | /* 255 | public static void main(String[] args) throws Exception { 256 | final int BLOCK_SIZE = 1 << 19; 257 | 258 | URL theUrl = new URL("http://nofatclips.com/02009/01/21/magic/Preproduction.ogv"); 259 | fos = new FileOutputStream("/Users/casta/Desktop/target.ogv"); 260 | 261 | // URL theUrl = new URL("http://www.ietf.org/rfc/rfc2616.txt"); 262 | // fos = new FileOutputStream("/Users/Alex/Desktop/target.txt"); 263 | 264 | WebSeed seed = new WebSeed(theUrl); 265 | 266 | int i = 0; 267 | int offset = 0; 268 | int length = BLOCK_SIZE; 269 | // splittone 270 | while (offset < TOT_SIZE) { 271 | if (offset + length > TOT_SIZE) 272 | length = TOT_SIZE - offset; 273 | seed.requestPiece(i, offset, length); 274 | offset += length; 275 | } 276 | 277 | seed.requestEnd(); 278 | 279 | seed.start(); 280 | 281 | 282 | } 283 | */ 284 | 285 | } 286 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/peer/message/Cancel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.peer.message; 19 | 20 | import org.bitlet.wetorrent.util.Utils; 21 | 22 | public class Cancel extends Message { 23 | 24 | private int index; 25 | private int length; 26 | private int begin; 27 | 28 | /** Creates a new instance of Piece */ 29 | public Cancel(int index, int begin, int length) { 30 | super(Message.CANCEL, null); 31 | 32 | this.index = index; 33 | this.length = length; 34 | this.begin = begin; 35 | } 36 | 37 | public int getIndex() { 38 | return index; 39 | } 40 | 41 | public int getLength() { 42 | return length; 43 | } 44 | 45 | public int getBegin() { 46 | return begin; 47 | } 48 | 49 | public byte[] getPayload() { 50 | if (super.getPayload() == null) { 51 | 52 | byte[] payload = new byte[12]; 53 | 54 | System.arraycopy(Utils.intToByteArray(index), 0, payload, 0, 4); 55 | System.arraycopy(Utils.intToByteArray(begin), 0, payload, 4, 4); 56 | System.arraycopy(Utils.intToByteArray(length), 0, payload, 8, 4); 57 | 58 | setPayload(payload); 59 | } 60 | return super.getPayload(); 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/peer/message/Have.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.peer.message; 19 | 20 | import org.bitlet.wetorrent.util.Utils; 21 | 22 | public class Have extends Message { 23 | 24 | private int index; 25 | 26 | public Have(int index) { 27 | 28 | super(Message.HAVE, null); 29 | 30 | setPayload(Utils.intToByteArray(index)); 31 | 32 | } 33 | 34 | public int getIndex() { 35 | return index; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/peer/message/Message.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.peer.message; 19 | 20 | public class Message { 21 | 22 | public static final byte KEEP_ALIVE = -1; 23 | public static final byte CHOKE = 0; 24 | public static final byte UNCHOKE = 1; 25 | public static final byte INTERESTED = 2; 26 | public static final byte NOT_INTERESTED = 3; 27 | public static final byte HAVE = 4; 28 | public static final byte BITFIELD = 5; 29 | public static final byte REQUEST = 6; 30 | public static final byte PIECE = 7; 31 | public static final byte CANCEL = 8; 32 | private byte type; 33 | private byte[] payload; 34 | 35 | public Message(byte type, byte[] payload) { 36 | this.type = type; 37 | } 38 | 39 | public byte[] getPayload() { 40 | return payload; 41 | } 42 | 43 | public byte getType() { 44 | return type; 45 | } 46 | 47 | public void setPayload(byte[] payload) { 48 | this.payload = payload; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/peer/message/Piece.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.peer.message; 19 | 20 | import java.io.IOException; 21 | import org.bitlet.wetorrent.disk.TorrentDisk; 22 | import org.bitlet.wetorrent.util.Utils; 23 | 24 | public class Piece extends Message { 25 | 26 | private int index; 27 | private int length; 28 | private int begin; 29 | private TorrentDisk torrentDisk; 30 | 31 | /** Creates a new instance of Piece */ 32 | public Piece(int index, int begin, int length, TorrentDisk torrentDisk) { 33 | super(Message.PIECE, null); 34 | 35 | this.torrentDisk = torrentDisk; 36 | this.index = index; 37 | this.length = length; 38 | this.begin = begin; 39 | } 40 | 41 | public int getIndex() { 42 | return index; 43 | } 44 | 45 | public int getLength() { 46 | return length; 47 | } 48 | 49 | public int getBegin() { 50 | return begin; 51 | } 52 | 53 | public byte[] getPayload() { 54 | if (super.getPayload() == null) { 55 | try { 56 | 57 | byte[] block = torrentDisk.read(index, begin, length); 58 | byte[] payload = new byte[block.length + 8]; 59 | 60 | System.arraycopy(block, 0, payload, 8, block.length); 61 | System.arraycopy(Utils.intToByteArray(index), 0, payload, 0, 4); 62 | System.arraycopy(Utils.intToByteArray(begin), 0, payload, 4, 4); 63 | 64 | setPayload(payload); 65 | } catch (IOException ex) { 66 | } 67 | } 68 | return super.getPayload(); 69 | 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/peer/message/Request.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.peer.message; 19 | 20 | import org.bitlet.wetorrent.util.Utils; 21 | 22 | public class Request extends Message { 23 | 24 | private int index; 25 | private int length; 26 | private int begin; 27 | 28 | /** Creates a new instance of Piece */ 29 | public Request(int index, int begin, int length) { 30 | super(Message.REQUEST, null); 31 | 32 | this.index = index; 33 | this.begin = begin; 34 | this.length = length; 35 | 36 | byte[] payload = new byte[12]; 37 | System.arraycopy(Utils.intToByteArray(index), 0, payload, 0, 4); 38 | System.arraycopy(Utils.intToByteArray(begin), 0, payload, 4, 4); 39 | System.arraycopy(Utils.intToByteArray(length), 0, payload, 8, 4); 40 | setPayload(payload); 41 | } 42 | 43 | public int getIndex() { 44 | return index; 45 | } 46 | 47 | public int getLength() { 48 | return length; 49 | } 50 | 51 | public int getBegin() { 52 | return begin; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/peer/task/Handshake.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.peer.task; 19 | 20 | import java.io.DataInputStream; 21 | import java.io.DataOutputStream; 22 | import java.io.EOFException; 23 | import java.io.IOException; 24 | import java.util.logging.Level; 25 | import org.bitlet.wetorrent.Event; 26 | import org.bitlet.wetorrent.Torrent; 27 | import org.bitlet.wetorrent.peer.IncomingPeerListener; 28 | import org.bitlet.wetorrent.peer.TorrentPeer; 29 | import org.bitlet.wetorrent.util.stream.OutputStreamLimiter; 30 | import org.bitlet.wetorrent.util.thread.ThreadTask; 31 | import org.bitlet.wetorrent.util.Utils; 32 | 33 | public class Handshake implements ThreadTask { 34 | 35 | TorrentPeer peer; 36 | 37 | public Handshake(TorrentPeer peer) { 38 | this.peer = peer; 39 | } 40 | IncomingPeerListener incomingPeerListener = null; 41 | 42 | public Handshake(TorrentPeer peer, IncomingPeerListener incomingPeerListener) { 43 | this.peer = peer; 44 | this.incomingPeerListener = incomingPeerListener; 45 | } 46 | 47 | private void sendProtocolHeader(TorrentPeer peer) throws IOException { 48 | DataOutputStream os = new DataOutputStream(new OutputStreamLimiter(peer.getSocket().getOutputStream(), peer.getPeersManager().getTorrent().getUploadBandwidthLimiter())); 49 | os.writeByte(19); 50 | os.write("BitTorrent protocol".getBytes()); 51 | os.write(new byte[8]); 52 | os.write(peer.getPeersManager().getTorrent().getMetafile().getInfoSha1()); 53 | os.write(peer.getPeersManager().getTorrent().getPeerId()); 54 | } 55 | 56 | public boolean execute() throws Exception { 57 | 58 | try { 59 | DataInputStream is = new DataInputStream(peer.getSocket().getInputStream()); 60 | 61 | 62 | /* if *we* are starting the connection */ 63 | if (peer.getPeersManager() != null && incomingPeerListener == null) { 64 | sendProtocolHeader(peer); 65 | } 66 | int protocolIdentifierLength = is.readByte(); 67 | 68 | if (protocolIdentifierLength != 19) { 69 | throw new Exception("Error, wrong protocol identifier length " + protocolIdentifierLength); 70 | } 71 | byte[] protocolByteString = new byte[protocolIdentifierLength]; 72 | is.readFully(protocolByteString); 73 | 74 | if (!Utils.bytesCompare("BitTorrent protocol".getBytes(), protocolByteString)) { 75 | throw new Exception("Error, wrong protocol identifier"); 76 | } 77 | byte[] reserved = new byte[8]; 78 | is.readFully(reserved); 79 | 80 | byte[] infoHash = new byte[20]; 81 | is.readFully(infoHash); 82 | /* if it's an incoming connection */ 83 | if (peer.getPeersManager() == null && incomingPeerListener != null) { 84 | if (!incomingPeerListener.dispatchPeer(peer, infoHash)) { 85 | peer.getSocket().close(); 86 | throw new Exception("Wrong info hash"); 87 | } 88 | sendProtocolHeader(peer); 89 | 90 | } else if (!Utils.bytesCompare(infoHash, peer.getPeersManager().getTorrent().getMetafile().getInfoSha1())) { 91 | peer.getSocket().close(); 92 | throw new Exception("Wrong info hash"); 93 | } 94 | 95 | byte[] peerId = new byte[20]; 96 | is.readFully(peerId); 97 | 98 | if (Torrent.verbose) { 99 | peer.getPeersManager().getTorrent().addEvent(new Event(this, "new peerId: " + Utils.byteArrayToURLString(peerId), Level.INFO)); 100 | } 101 | if (Utils.bytesCompare(peerId, peer.getPeersManager().getTorrent().getPeerId())) { 102 | peer.getSocket().close(); 103 | throw new Exception("Avoid self connections"); 104 | } 105 | 106 | if (peer.getPeerId() != null) { 107 | if (!Utils.bytesCompare(peer.getPeerId(), peerId)) { 108 | peer.getSocket().close(); 109 | throw new Exception("Wrong peer id"); 110 | } 111 | } else { 112 | peer.setPeerId(peerId); 113 | } 114 | if (Torrent.verbose) { 115 | peer.getPeersManager().getTorrent().addEvent(new Event(this, "Connection header correctly parsed ", Level.FINER)); 116 | } 117 | peer.getPeersManager().connected(peer); 118 | 119 | } catch (IOException e) { 120 | if (Torrent.verbose) { 121 | System.err.println("Problem parsing header"); 122 | } 123 | throw e; 124 | } 125 | return false; 126 | } 127 | 128 | public void interrupt() { 129 | try { 130 | peer.getSocket().close(); 131 | } catch (IOException ex) { 132 | } 133 | } 134 | 135 | public void exceptionCought(Exception e) { 136 | if (e instanceof EOFException) { 137 | if (Torrent.verbose) { 138 | System.err.println("Connection dropped"); 139 | } 140 | } 141 | if (incomingPeerListener != null) { 142 | incomingPeerListener.removePeer(peer); 143 | } 144 | peer.interrupt(); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/peer/task/MessageReceiver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.peer.task; 19 | 20 | import java.io.DataInputStream; 21 | import java.io.IOException; 22 | import java.net.ProtocolException; 23 | import java.util.logging.Level; 24 | import org.bitlet.wetorrent.Event; 25 | import org.bitlet.wetorrent.Torrent; 26 | import org.bitlet.wetorrent.peer.TorrentPeer; 27 | import org.bitlet.wetorrent.peer.message.Message; 28 | import org.bitlet.wetorrent.util.thread.ThreadTask; 29 | 30 | public class MessageReceiver implements ThreadTask { 31 | 32 | private TorrentPeer peer; 33 | private long lastReceivedMessageMillis; 34 | private long receivedBytes; 35 | 36 | public MessageReceiver(TorrentPeer peer) { 37 | this.peer = peer; 38 | } 39 | 40 | private synchronized void addReceivedBytes(Integer byteNumber) { 41 | receivedBytes += byteNumber; 42 | } 43 | 44 | public synchronized Long getReceivedBytes() { 45 | return receivedBytes; 46 | } 47 | 48 | public boolean execute() throws Exception { 49 | 50 | try { 51 | 52 | DataInputStream is = new DataInputStream(peer.getSocket().getInputStream()); 53 | 54 | // peer.getPeersManager().getTorrent().addEvent(new Event(this, "Waiting new message",Level.FINEST)); 55 | int prefixLength = is.readInt(); 56 | 57 | addReceivedBytes(4 + prefixLength); 58 | 59 | if (prefixLength == 0) { // keep alive message 60 | 61 | peer.keepAlive(); 62 | } else if (prefixLength > 0) { 63 | byte messageId = is.readByte(); 64 | switch (messageId) { 65 | case Message.CHOKE: // choke: 66 | 67 | if (prefixLength != 1) { 68 | throw new ProtocolException("pl " + prefixLength); 69 | } 70 | peer.choke(); 71 | break; 72 | case Message.UNCHOKE: // unchoke: 73 | 74 | if (prefixLength != 1) { 75 | throw new ProtocolException(); 76 | } 77 | peer.unchoke(); 78 | break; 79 | case Message.INTERESTED: // interested 80 | 81 | if (prefixLength != 1) { 82 | throw new ProtocolException(); 83 | } 84 | peer.interested(); 85 | break; 86 | case Message.NOT_INTERESTED: // not interested 87 | 88 | if (prefixLength != 1) { 89 | throw new ProtocolException(); 90 | } 91 | peer.notInterested(); 92 | break; 93 | case Message.HAVE: // have: 94 | 95 | if (prefixLength != 5) { 96 | throw new ProtocolException(); 97 | } 98 | peer.have(is.readInt()); 99 | break; 100 | case Message.BITFIELD: // bitfield: 101 | 102 | if (prefixLength != 1 + peer.getBitfieldCopy().length) { 103 | throw new ProtocolException(); 104 | } 105 | byte[] bitField = new byte[prefixLength - 1]; 106 | is.readFully(bitField); 107 | peer.bitfield(bitField); 108 | break; 109 | case Message.REQUEST: // request: 110 | 111 | if (prefixLength != 13) { 112 | throw new ProtocolException(); 113 | } 114 | peer.request(is.readInt(), is.readInt(), is.readInt()); 115 | break; 116 | case Message.PIECE: // piece: 117 | 118 | if (prefixLength < 10) { 119 | throw new ProtocolException(); 120 | } 121 | int index = is.readInt(); 122 | int begin = is.readInt(); 123 | byte[] block = new byte[prefixLength - 9]; 124 | is.readFully(block); 125 | peer.piece(index, begin, block); 126 | break; 127 | case Message.CANCEL: // cancel: 128 | 129 | if (prefixLength != 13) { 130 | throw new ProtocolException(); 131 | } 132 | peer.cancel(is.readInt(), is.readInt(), is.readInt()); 133 | break; 134 | default: 135 | // discard it 136 | is.skipBytes(prefixLength - 1); 137 | if (Torrent.verbose) { 138 | peer.getPeersManager().getTorrent().addEvent(new Event(this, "message discarded " + messageId, Level.FINE)); 139 | } 140 | break; 141 | 142 | } 143 | 144 | } else { 145 | throw new Exception("Negative prefix length"); 146 | } 147 | setLastReceivedMessageMillis(System.currentTimeMillis()); 148 | 149 | } catch (Exception e) { 150 | if (Torrent.verbose) { 151 | peer.getPeersManager().getTorrent().addEvent(new Event(peer, "Problem waiting for new message", Level.WARNING)); 152 | } 153 | e.printStackTrace(System.err); 154 | throw e; 155 | } 156 | return true; 157 | } 158 | 159 | public void interrupt() { 160 | try { 161 | peer.getSocket().close(); 162 | } catch (IOException ex) { 163 | } 164 | } 165 | 166 | public void exceptionCought(Exception e) { 167 | peer.interrupt(); 168 | } 169 | 170 | public synchronized long getLastReceivedMessageMillis() { 171 | return lastReceivedMessageMillis; 172 | } 173 | 174 | public synchronized void setLastReceivedMessageMillis(long lastReceivedMessageMillis) { 175 | this.lastReceivedMessageMillis = lastReceivedMessageMillis; 176 | } 177 | } -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/peer/task/MessageSender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.peer.task; 19 | 20 | import java.io.DataOutputStream; 21 | import java.io.IOException; 22 | import java.util.Collection; 23 | import java.util.LinkedList; 24 | import java.util.Queue; 25 | import java.util.logging.Level; 26 | import org.bitlet.wetorrent.Event; 27 | import org.bitlet.wetorrent.Torrent; 28 | import org.bitlet.wetorrent.peer.message.Message; 29 | import org.bitlet.wetorrent.peer.TorrentPeer; 30 | import org.bitlet.wetorrent.peer.message.Piece; 31 | import org.bitlet.wetorrent.util.stream.OutputStreamLimiter; 32 | import org.bitlet.wetorrent.util.thread.ThreadTask; 33 | 34 | public class MessageSender implements ThreadTask { 35 | 36 | private Queue messagesToBeSent = new LinkedList(); 37 | private boolean interrupted = false; 38 | private TorrentPeer peer; 39 | private long sentBytes; 40 | private long lastSentMessageMillis; 41 | private long uploaded = 0; 42 | 43 | public MessageSender(TorrentPeer peer) { 44 | this.peer = peer; 45 | } 46 | 47 | public synchronized void addSentBytes(Integer byteNumber) { 48 | sentBytes += byteNumber; 49 | } 50 | 51 | public synchronized long getSentBytes() { 52 | return sentBytes; 53 | } 54 | 55 | public boolean execute() throws Exception { 56 | 57 | try { 58 | 59 | Message message = getMessage(); 60 | if (message != null) { 61 | DataOutputStream os = new DataOutputStream(new OutputStreamLimiter(peer.getSocket().getOutputStream(), peer.getPeersManager().getTorrent().getUploadBandwidthLimiter())); 62 | 63 | if (message.getType() != Message.KEEP_ALIVE) { 64 | 65 | byte[] payload = message.getPayload(); 66 | 67 | if (payload != null) { 68 | os.writeInt(payload.length + 1); 69 | os.writeByte(message.getType()); 70 | addSentBytes(4 + 1); 71 | 72 | int payloadOffset = 0; 73 | while (payloadOffset < payload.length) { 74 | int payloadMissing = payload.length - payloadOffset; 75 | int payloadChunk = payloadMissing > 1 << 10 ? 1 << 10 : payloadMissing; 76 | os.write(payload, payloadOffset, payloadChunk); 77 | addSentBytes(payloadChunk); 78 | 79 | if (message.getType() == message.PIECE) { 80 | uploaded += payloadChunk - (payloadOffset == 0 ? 13 : 0); 81 | } 82 | payloadOffset += payloadChunk; 83 | } 84 | 85 | } else { 86 | os.writeInt(1); 87 | os.writeByte(message.getType()); 88 | addSentBytes(4 + 1); 89 | } 90 | 91 | 92 | } else { /* keep alive */ 93 | os.writeInt(0); 94 | addSentBytes(4); 95 | } 96 | 97 | setLastSentMessageMillis(System.currentTimeMillis()); 98 | if (Torrent.verbose) { 99 | peer.getPeersManager().getTorrent().addEvent(new Event(this, "Message sent " + message.getType(), Level.FINEST)); 100 | } 101 | } 102 | 103 | } catch (Exception e) { 104 | if (Torrent.verbose) { 105 | peer.getPeersManager().getTorrent().addEvent(new Event(this, "Problem sending message", Level.WARNING)); 106 | } 107 | throw e; 108 | } 109 | return true; 110 | } 111 | 112 | public synchronized void addMessage(Message message) { 113 | messagesToBeSent.add(message); 114 | notify(); 115 | } 116 | 117 | private synchronized Message getMessage() { 118 | while (messagesToBeSent.size() == 0 && !interrupted) { 119 | try { 120 | wait(2000); 121 | } catch (InterruptedException ex) { 122 | } 123 | } 124 | return messagesToBeSent.poll(); 125 | } 126 | 127 | public synchronized void interrupt() { 128 | try { 129 | peer.getSocket().close(); 130 | } catch (IOException ex) { 131 | } 132 | messagesToBeSent.clear(); 133 | interrupted = true; 134 | notify(); 135 | } 136 | 137 | public void exceptionCought(Exception e) { 138 | peer.interrupt(); 139 | e.printStackTrace(); 140 | } 141 | 142 | public synchronized void cancel(int index, int begin, int length) { 143 | Collection messagesToCancel = new LinkedList(); 144 | for (Message elem : messagesToBeSent) { 145 | if (elem.getType() == Message.PIECE) { 146 | Piece block = (Piece) elem; 147 | if (block.getIndex() == index && block.getBegin() == begin && block.getLength() == length) { 148 | messagesToCancel.add(elem); 149 | } 150 | } 151 | } 152 | 153 | messagesToBeSent.removeAll(messagesToCancel); 154 | } 155 | 156 | public synchronized void cancelAll() { 157 | Collection messagesToCancel = new LinkedList(); 158 | for (Message elem : messagesToBeSent) { 159 | if (elem.getType() == Message.PIECE) { 160 | messagesToCancel.add(elem); 161 | } 162 | } 163 | 164 | messagesToBeSent.removeAll(messagesToCancel); 165 | } 166 | 167 | public long getUploaded() { 168 | return uploaded; 169 | } 170 | 171 | public synchronized long getLastSentMessageMillis() { 172 | return lastSentMessageMillis; 173 | } 174 | 175 | public synchronized void setLastSentMessageMillis(long lastSentMessageMillis) { 176 | this.lastSentMessageMillis = lastSentMessageMillis; 177 | } 178 | } -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/peer/task/SendBitfield.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.peer.task; 19 | 20 | import java.io.DataOutputStream; 21 | import java.io.IOException; 22 | import java.util.logging.Level; 23 | import org.bitlet.wetorrent.Event; 24 | import org.bitlet.wetorrent.Torrent; 25 | import org.bitlet.wetorrent.peer.TorrentPeer; 26 | import org.bitlet.wetorrent.util.stream.OutputStreamLimiter; 27 | import org.bitlet.wetorrent.util.thread.ThreadTask; 28 | 29 | public class SendBitfield implements ThreadTask { 30 | 31 | TorrentPeer peer; 32 | 33 | public SendBitfield(TorrentPeer peer) { 34 | this.peer = peer; 35 | } 36 | 37 | public boolean execute() throws Exception { 38 | if (Torrent.verbose) { 39 | peer.getPeersManager().getTorrent().addEvent(new Event(this, "Sending bitField ", Level.FINER)); 40 | } 41 | try { 42 | DataOutputStream os = new DataOutputStream(new OutputStreamLimiter(peer.getSocket().getOutputStream(), peer.getPeersManager().getTorrent().getUploadBandwidthLimiter())); 43 | byte[] bitField = peer.getPeersManager().getTorrent().getTorrentDisk().getBitfieldCopy(); 44 | 45 | os.writeInt(1 + bitField.length); 46 | os.writeByte(5); 47 | os.write(bitField); 48 | 49 | } catch (Exception e) { 50 | if (Torrent.verbose) { 51 | peer.getPeersManager().getTorrent().addEvent(new Event(this, "Problem sending bitfield", Level.WARNING)); 52 | } 53 | throw e; 54 | } 55 | return false; 56 | } 57 | 58 | public void interrupt() { 59 | try { 60 | peer.getSocket().close(); 61 | } catch (IOException ex) { 62 | ex.printStackTrace(); 63 | } 64 | } 65 | 66 | public void exceptionCought(Exception e) { 67 | e.printStackTrace(); 68 | peer.interrupt(); 69 | } 70 | } -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/peer/task/StartConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.peer.task; 19 | 20 | import java.net.ConnectException; 21 | import java.net.InetAddress; 22 | import java.net.Socket; 23 | import java.util.logging.Level; 24 | import org.bitlet.wetorrent.Event; 25 | import org.bitlet.wetorrent.Torrent; 26 | import org.bitlet.wetorrent.peer.TorrentPeer; 27 | import org.bitlet.wetorrent.util.thread.ThreadTask; 28 | 29 | public class StartConnection implements ThreadTask { 30 | 31 | boolean interrupted = false; 32 | private TorrentPeer peer; 33 | 34 | public StartConnection(TorrentPeer peer) { 35 | this.peer = peer; 36 | } 37 | 38 | public boolean execute() throws Exception { 39 | Socket socket = connect(peer.getIp(), peer.getPort()); 40 | peer.setSocket(socket); 41 | if (socket != null) { 42 | if (Torrent.verbose) { 43 | peer.getPeersManager().getTorrent().addEvent(new Event(this, "Connected to " + peer.getIp(), Level.FINE)); 44 | } 45 | } else { 46 | throw new Exception("Problem connecting to " + peer.getIp()); 47 | } 48 | return false; 49 | 50 | } 51 | 52 | public void interrupt() { 53 | interrupted = true; 54 | } 55 | 56 | public synchronized boolean isInterrupted() { 57 | return interrupted; 58 | } 59 | 60 | public synchronized Socket connect(InetAddress address, int port) throws Exception { 61 | if (!interrupted) { 62 | return new Socket(address, port); 63 | } else { 64 | throw new Exception("Interrupted before connecting"); 65 | } 66 | } 67 | 68 | public void exceptionCought(Exception e) { 69 | if (e instanceof ConnectException) { 70 | if (Torrent.verbose) { 71 | peer.getPeersManager().getTorrent().addEvent(new Event(this, "Connection refused: " + peer.getIp(), Level.FINE)); 72 | } 73 | } 74 | peer.interrupt(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/peer/task/StartMessageReceiver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.peer.task; 19 | 20 | import java.util.logging.Level; 21 | import org.bitlet.wetorrent.Event; 22 | import org.bitlet.wetorrent.Torrent; 23 | import org.bitlet.wetorrent.peer.TorrentPeer; 24 | import org.bitlet.wetorrent.util.thread.ThreadTask; 25 | 26 | public class StartMessageReceiver implements ThreadTask { 27 | 28 | private TorrentPeer peer; 29 | 30 | public StartMessageReceiver(TorrentPeer peer) { 31 | this.peer = peer; 32 | } 33 | 34 | public boolean execute() throws Exception { 35 | 36 | try { 37 | peer.getReceiverThread().start(); 38 | } catch (Exception e) { 39 | 40 | if (Torrent.verbose) { 41 | peer.getPeersManager().getTorrent().addEvent(new Event(this, "Problem starting MessageReceiver", Level.WARNING)); 42 | } 43 | throw e; 44 | } 45 | return false; 46 | } 47 | 48 | public void interrupt() { 49 | } 50 | 51 | public void exceptionCought(Exception e) { 52 | peer.interrupt(); 53 | } 54 | } -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/pieceChooser/PieceChooser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.pieceChooser; 19 | 20 | import java.util.Collection; 21 | import java.util.HashMap; 22 | import java.util.HashSet; 23 | import java.util.LinkedList; 24 | import java.util.Map; 25 | import java.util.Set; 26 | import java.util.logging.Level; 27 | import org.bitlet.wetorrent.Event; 28 | import org.bitlet.wetorrent.Torrent; 29 | import org.bitlet.wetorrent.peer.Peer; 30 | import org.bitlet.wetorrent.peer.message.Cancel; 31 | import org.bitlet.wetorrent.peer.message.Request; 32 | 33 | 34 | public abstract class PieceChooser { 35 | 36 | protected abstract Integer choosePiece(Peer peer, int[] piecesFrequencies); 37 | 38 | protected Torrent torrent; 39 | 40 | public final static int blockLength = 1<<14; 41 | 42 | /** 43 | * Pieces in this set are almost complete or complete. We have already requested all the blocks 44 | */ 45 | private Set completingPieces = new HashSet(); 46 | 47 | /** 48 | * Associates a peer to the next piece that should be suggested to download 49 | */ 50 | private Map peerPiece = new HashMap(); 51 | 52 | 53 | /** 54 | * Associates a peer to its pending requests 55 | */ 56 | private Map> peerPendingRequests = new HashMap>(); 57 | 58 | /** 59 | * Associates a piece to its pending requests 60 | */ 61 | private Map> piecePendingRequests = new HashMap>(); 62 | protected Set getPiecePendingRequests(int index){ 63 | return piecePendingRequests.get(index); 64 | } 65 | 66 | 67 | /* 68 | * All the pending requests, ordered by the creation date 69 | */ 70 | private Set allPendingRequests = new HashSet(); 71 | /* 72 | * All the pending requests, ordered by the creation date 73 | */ 74 | private Set orphanPendingRequests = new HashSet(); 75 | 76 | protected RequestExtended recoverRequest(Peer peer){ 77 | 78 | /* Search request in limbo */ 79 | for (RequestExtended re : orphanPendingRequests) { 80 | if (peer.hasPiece(re.getIndex())){ 81 | 82 | if (Torrent.verbose) 83 | torrent.addEvent(new Event(this,"Recovering limbo piece ",Level.FINER)); 84 | return re; 85 | } 86 | } 87 | return null; 88 | } 89 | 90 | public synchronized Request getNextBlockRequest(Peer peer, int[] piecesFrequencies){ 91 | 92 | RequestExtended recoveredRequest = recoverRequest(peer); 93 | if (recoveredRequest != null){ 94 | associateRequest(peer,recoveredRequest); 95 | return recoveredRequest; 96 | } 97 | 98 | Integer pieceIndex = peerPiece.get(peer); 99 | // null if not downloading, completing if piece is completing 100 | if (pieceIndex==null || torrent.getTorrentDisk().isCompleted(pieceIndex) || completingPieces.contains(pieceIndex)){ 101 | 102 | peerPiece.remove(peer); 103 | // get next piece 104 | pieceIndex = choosePiece(peer,piecesFrequencies); 105 | 106 | if (Torrent.verbose) 107 | torrent.addEvent(new Event(this,"chosen piece " + pieceIndex,Level.FINER)); 108 | 109 | // no more pieces to download 110 | if (pieceIndex == null) 111 | return endGameBlockRequest(peer); 112 | 113 | // requested piece pieceIndex to peer 114 | peerPiece.put(peer,pieceIndex); 115 | } 116 | 117 | int firstMissingByte = getFirstMissingByte(pieceIndex); 118 | if (firstMissingByte >= torrent.getTorrentDisk().getLength(pieceIndex)) 119 | throw new AssertionError("this piece is already completely requested, we should not be here."); 120 | int pieceMissingBytes = torrent.getTorrentDisk().getLength(pieceIndex) - firstMissingByte; 121 | int requestBlockLength = blockLength; 122 | 123 | // we have all but the last block 124 | if (pieceMissingBytes <= blockLength){ 125 | completingPieces.add(pieceIndex); 126 | peerPiece.remove(peer); 127 | 128 | requestBlockLength = pieceMissingBytes; 129 | 130 | if (Torrent.verbose) 131 | torrent.addEvent(new Event(this,"request piece completed " + pieceIndex + " " + firstMissingByte + " " + requestBlockLength,Level.FINER)); 132 | } 133 | 134 | RequestExtended re = new RequestExtended(pieceIndex,firstMissingByte,requestBlockLength); 135 | 136 | associateRequest(peer,re); 137 | return re; 138 | } 139 | 140 | private int getFirstMissingByte(Integer pieceIndex) { 141 | int firstMissingByte = torrent.getTorrentDisk().getFirstMissingByte(pieceIndex); 142 | Collection pendingRequests = piecePendingRequests.get(pieceIndex); 143 | 144 | /* 145 | * This works just because we made the requests in a sequential order, 146 | * So, we know that the max( firstMissingByte ,max( pendingRequests.getEnd() )) 147 | * is the next missing byte. 148 | */ 149 | if (pendingRequests != null){ 150 | for (RequestExtended r : pendingRequests) { 151 | // if the first missing byte is being downloaded by the other peer 152 | if (firstMissingByte < r.getBegin() + r.getLength()){ 153 | firstMissingByte = r.getBegin() + r.getLength(); 154 | } 155 | } 156 | } 157 | 158 | return firstMissingByte; 159 | } 160 | 161 | public synchronized void interrupted(Peer peer){ 162 | peerPiece.remove(peer); 163 | Set pendingRequests = peerPendingRequests.get(peer); 164 | if (pendingRequests != null){ 165 | for (RequestExtended re : pendingRequests) { 166 | re.getPeers().remove(peer); 167 | if (re.getPeers().size() == 0) 168 | orphanPendingRequests.add(re); 169 | } 170 | peerPendingRequests.remove(peer); 171 | } 172 | } 173 | 174 | /** 175 | * A piece has been received 176 | */ 177 | public synchronized void piece(int index, int begin, byte[] block, Peer peer) { 178 | RequestExtended request = null; 179 | Set pendingRequests = null; 180 | 181 | pendingRequests = peerPendingRequests.get(peer); 182 | if (pendingRequests != null){ 183 | for (RequestExtended re : pendingRequests){ 184 | if (re.getIndex() == index && re.getBegin() == begin && re.getLength() == block.length){ 185 | request = re; 186 | break; 187 | } 188 | } 189 | if (request != null){ 190 | pendingRequests.remove(request); 191 | if (pendingRequests.size() == 0) 192 | peerPendingRequests.remove(peer); 193 | request.getPeers().remove(peer); 194 | 195 | /* 196 | * Cancel the same request to all the other peers 197 | */ 198 | for (Peer p : request.getPeers()) { 199 | p.sendMessage(new Cancel(index,begin,block.length)); 200 | pendingRequests = peerPendingRequests.get(p); 201 | pendingRequests.remove(request); 202 | if (pendingRequests.size() == 0) 203 | peerPendingRequests.remove(p); 204 | } 205 | 206 | piecePendingRequests.get(index).remove(request); 207 | allPendingRequests.remove(request); 208 | } 209 | } 210 | /* 211 | * else, the peer is sending us a block we didn't request 212 | * or a block we already canceled 213 | */ 214 | 215 | } 216 | 217 | private Request endGameBlockRequest(Peer peer) { 218 | RequestExtended reToReturn = null; 219 | for (RequestExtended re : allPendingRequests) { 220 | if (peer.hasPiece(re.getIndex()) && !re.getPeers().contains(peer) && 221 | (reToReturn == null || reToReturn.getPeers().size()>re.getPeers().size())) 222 | reToReturn = re; 223 | } 224 | 225 | if (reToReturn != null){ 226 | associateRequest(peer,reToReturn); 227 | if (Torrent.verbose) 228 | torrent.addEvent(new Event(this,"Duplicated request ",Level.FINER)); 229 | } 230 | return reToReturn; 231 | } 232 | 233 | private void associateRequest(Peer peer, RequestExtended re){ 234 | Set pendingRequests = null; 235 | /* 236 | * piecePengingRequests 237 | */ 238 | pendingRequests = piecePendingRequests.get(re.getIndex()); 239 | if (pendingRequests == null){ 240 | pendingRequests = new HashSet(); 241 | piecePendingRequests.put(re.getIndex(),pendingRequests); 242 | } 243 | pendingRequests.add(re); 244 | 245 | /* 246 | * peerPengingRequests 247 | */ 248 | pendingRequests = peerPendingRequests.get(peer); 249 | if (pendingRequests == null){ 250 | pendingRequests = new HashSet(); 251 | peerPendingRequests.put(peer,pendingRequests); 252 | } 253 | pendingRequests.add(re); 254 | 255 | allPendingRequests.add(re); 256 | orphanPendingRequests.remove(re); 257 | re.getPeers().add(peer); 258 | } 259 | 260 | public void setTorrent(Torrent torrent) { 261 | this.torrent = torrent; 262 | } 263 | 264 | public Torrent getTorrent() { 265 | return torrent; 266 | } 267 | 268 | public boolean isCompletingPiece(int pieceIndex){ 269 | return completingPieces.contains(pieceIndex); 270 | } 271 | } -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/pieceChooser/RequestExtended.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.pieceChooser; 19 | 20 | import java.util.Collection; 21 | import java.util.LinkedList; 22 | import org.bitlet.wetorrent.peer.Peer; 23 | import org.bitlet.wetorrent.peer.message.Request; 24 | 25 | // TODO: This really sucks and looks like a M$ piece of code. 26 | // An HashMap would be probabily better even if a little bit slower. 27 | 28 | public class RequestExtended extends Request implements Comparable{ 29 | private long millisTimeStamp = 0; 30 | private Collection peers = new LinkedList(); 31 | 32 | public RequestExtended(int index, int begin, int length){ 33 | super(index, begin, length); 34 | this.millisTimeStamp = System.currentTimeMillis(); 35 | } 36 | 37 | public long getMillisTimeStamp() { 38 | return millisTimeStamp; 39 | } 40 | 41 | public Collection getPeers() { 42 | return peers; 43 | } 44 | 45 | public String toString() { 46 | return millisTimeStamp + " " + getIndex() + " " + getBegin() + " " + getLength(); 47 | } 48 | 49 | public int compareTo(RequestExtended o) { 50 | return (int) (this.millisTimeStamp - o.millisTimeStamp); 51 | } 52 | 53 | 54 | } -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/pieceChooser/RouletteWheelPieceChooser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.pieceChooser; 19 | 20 | import java.util.Random; 21 | import org.bitlet.wetorrent.Torrent; 22 | import org.bitlet.wetorrent.peer.Peer; 23 | 24 | public class RouletteWheelPieceChooser extends PieceChooser { 25 | 26 | protected Integer choosePiece(Peer peer, int[] piecesFrequencies) { 27 | 28 | int[] probabilities = piecesFrequencies.clone(); 29 | int maxFrequency = 0; 30 | Torrent torrent = getTorrent(); 31 | for (int i = 0; i < torrent.getMetafile().getPieces().size(); i++) { 32 | if (peer.hasPiece(i) && !torrent.getTorrentDisk().isCompleted(i) && !isCompletingPiece(i)) { 33 | if (maxFrequency < probabilities[i]) { 34 | maxFrequency = probabilities[i]; 35 | } 36 | } else { 37 | probabilities[i] = Integer.MAX_VALUE; 38 | } 39 | } 40 | 41 | int total = 0; 42 | for (int i = 0; i < torrent.getMetafile().getPieces().size(); i++) { 43 | if (probabilities[i] == Integer.MAX_VALUE) { 44 | probabilities[i] = 0; 45 | } else { 46 | probabilities[i] = 1 + maxFrequency - probabilities[i]; 47 | } 48 | total += probabilities[i]; 49 | probabilities[i] = total; 50 | } 51 | 52 | if (total == 0) { 53 | return null; 54 | } 55 | long random = new Random(System.currentTimeMillis()).nextInt(total); 56 | int i; 57 | if (random < probabilities[0]) { 58 | return 0; 59 | } 60 | for (i = 1; i < probabilities.length; i++) { 61 | if (probabilities[i - 1] <= random && probabilities[i] > random) { 62 | break; 63 | } 64 | } 65 | 66 | return i; 67 | } 68 | } -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/util/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.util; 19 | 20 | import java.io.StringWriter; 21 | import java.nio.ByteBuffer; 22 | import java.nio.charset.StandardCharsets; 23 | import java.text.DecimalFormat; 24 | import java.text.FieldPosition; 25 | import java.text.NumberFormat; 26 | 27 | public final class Utils { 28 | 29 | public static String byteArrayToURLString(byte in[]) { 30 | byte ch = 0x00; 31 | int i = 0; 32 | if (in == null || in.length <= 0) { 33 | return null; 34 | } 35 | String pseudo[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 36 | "A", "B", "C", "D", "E", "F" 37 | }; 38 | StringBuffer out = new StringBuffer(in.length * 2); 39 | 40 | while (i < in.length) { 41 | // First check to see if we need ASCII or HEX 42 | if ((in[i] >= '0' && in[i] <= '9') || (in[i] >= 'a' && in[i] <= 'z') || (in[i] >= 'A' && in[i] <= 'Z') || in[i] == '-' || in[i] == '_' || in[i] == '.' || in[i] == '~') { 43 | out.append((char) in[i]); 44 | i++; 45 | } else { 46 | out.append('%'); 47 | ch = (byte) (in[i] & 0xF0); // Strip off high nibble 48 | 49 | ch = (byte) (ch >>> 4); // shift the bits down 50 | 51 | ch = (byte) (ch & 0x0F); // must do this is high order bit is 52 | // on! 53 | 54 | out.append(pseudo[(int) ch]); // convert the nibble to a 55 | // String Character 56 | 57 | ch = (byte) (in[i] & 0x0F); // Strip off low nibble 58 | 59 | out.append(pseudo[(int) ch]); // convert the nibble to a 60 | // String Character 61 | 62 | i++; 63 | } 64 | } 65 | 66 | String rslt = new String(out); 67 | 68 | return rslt; 69 | 70 | } 71 | 72 | /** 73 | * 74 | * Convert a byte[] array to readable string format. This makes the "hex" 75 | * readable! 76 | */ 77 | // Taken from http://www.devx.com/tips/Tip/13540 78 | public static String byteArrayToByteString(byte in[]) { 79 | byte ch = 0x00; 80 | int i = 0; 81 | if (in == null || in.length <= 0) { 82 | return null; 83 | } 84 | String pseudo[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 85 | "A", "B", "C", "D", "E", "F" 86 | }; 87 | StringBuffer out = new StringBuffer(in.length * 2); 88 | 89 | while (i < in.length) { 90 | ch = (byte) (in[i] & 0xF0); // Strip off high nibble 91 | 92 | ch = (byte) (ch >>> 4); // shift the bits down 93 | 94 | ch = (byte) (ch & 0x0F); // must do this is high order bit is on! 95 | 96 | out.append(pseudo[(int) ch]); // convert the nibble to a String 97 | // Character 98 | 99 | ch = (byte) (in[i] & 0x0F); // Strip off low nibble 100 | 101 | out.append(pseudo[(int) ch]); // convert the nibble to a String 102 | // Character 103 | 104 | i++; 105 | } 106 | 107 | String rslt = new String(out); 108 | 109 | return rslt; 110 | } 111 | 112 | public static byte[] intToByteArray(int value) { 113 | byte[] b = new byte[4]; 114 | for (int i = 0; i < 4; i++) { 115 | int offset = (b.length - 1 - i) * 8; 116 | b[i] = (byte) ((value >>> offset) & 0xFF); 117 | } 118 | return b; 119 | } 120 | 121 | public static boolean bytesCompare(byte[] a, byte[] b) { 122 | if (a.length != b.length) { 123 | return false; 124 | } 125 | for (int i = 0; i < a.length; i++) { 126 | if (a[i] != b[i]) { 127 | return false; 128 | } 129 | } 130 | return true; 131 | } 132 | 133 | private static String hex(char ch) { 134 | return Integer.toHexString(ch).toUpperCase(); 135 | } 136 | 137 | public static String escapeJavascriptString(String string) { 138 | if (string == null) { 139 | return null; 140 | } 141 | StringWriter writer = new StringWriter(string.length() * 2); 142 | { 143 | 144 | int sz; 145 | sz = string.length(); 146 | for (int i = 0; i < sz; i++) { 147 | char ch = string.charAt(i); 148 | 149 | // handle unicode 150 | if (ch > 0xfff) { 151 | writer.write("\\u" + hex(ch)); 152 | } else if (ch > 0xff) { 153 | writer.write("\\u0" + hex(ch)); 154 | } else if (ch > 0x7f) { 155 | writer.write("\\u00" + hex(ch)); 156 | } else if (ch < 32) { 157 | switch (ch) { 158 | case '\b': 159 | writer.write('\\'); 160 | writer.write('b'); 161 | break; 162 | case '\n': 163 | writer.write('\\'); 164 | writer.write('n'); 165 | break; 166 | case '\t': 167 | writer.write('\\'); 168 | writer.write('t'); 169 | break; 170 | case '\f': 171 | writer.write('\\'); 172 | writer.write('f'); 173 | break; 174 | case '\r': 175 | writer.write('\\'); 176 | writer.write('r'); 177 | break; 178 | default: 179 | if (ch > 0xf) { 180 | writer.write("\\u00" + hex(ch)); 181 | } else { 182 | writer.write("\\u000" + hex(ch)); 183 | } 184 | break; 185 | } 186 | } else { 187 | switch (ch) { 188 | case '\'': 189 | writer.write('\\'); 190 | writer.write('\''); 191 | break; 192 | case '"': 193 | writer.write('\\'); 194 | writer.write('"'); 195 | break; 196 | case '\\': 197 | writer.write('\\'); 198 | writer.write('\\'); 199 | break; 200 | default: 201 | writer.write(ch); 202 | break; 203 | } 204 | } 205 | } 206 | } 207 | return writer.toString(); 208 | } 209 | 210 | /** 211 | * 212 | * @param byteNumber 213 | * @return 214 | * @deprecated The javascript version should be used, instead 215 | */ 216 | @Deprecated 217 | static public String byteToHumanReadableString(long byteNumber) { 218 | if (byteNumber < 1 << 10) { 219 | return byteNumber + " B"; 220 | } else if (byteNumber < 1 << 20) { 221 | Double kiloBytePerSecond = (double) byteNumber / (1 << 10); 222 | DecimalFormat df = new DecimalFormat(); 223 | df.setMaximumFractionDigits(2); 224 | StringBuffer sb = df.format(kiloBytePerSecond, new StringBuffer(), new FieldPosition(NumberFormat.FRACTION_FIELD)); 225 | return sb + " KB"; 226 | } else if (byteNumber < 1 << 30) { 227 | Double megaBytePerSecond = (double) byteNumber / (1 << 20); 228 | DecimalFormat df = new DecimalFormat(); 229 | df.setMaximumFractionDigits(2); 230 | StringBuffer sb = df.format(megaBytePerSecond, new StringBuffer(), new FieldPosition(NumberFormat.FRACTION_FIELD)); 231 | return sb + " MB"; 232 | } else { 233 | Double gigaBytePerSecond = (double) byteNumber / (1 << 30); 234 | DecimalFormat df = new DecimalFormat(); 235 | df.setMaximumFractionDigits(2); 236 | StringBuffer sb = df.format(gigaBytePerSecond, new StringBuffer(), new FieldPosition(NumberFormat.FRACTION_FIELD)); 237 | return sb + " GB"; 238 | } 239 | } 240 | 241 | public static ByteBuffer toByteBuffer(String input) { 242 | return ByteBuffer.wrap(input.getBytes(StandardCharsets.UTF_8)); 243 | } 244 | 245 | private Utils() { } 246 | } 247 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/util/stream/BandwidthLimiter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.util.stream; 19 | 20 | public class BandwidthLimiter { 21 | 22 | private final static int CHUNK_SIZE = 256; 23 | int bytesToChunk = 0; 24 | long lastChunkSent = System.nanoTime(); 25 | int maximumRate = 24; 26 | long nanosToWait = (1000000000l * CHUNK_SIZE) / (maximumRate * 1024l); 27 | 28 | public synchronized void setMaximumRate(int maximumRate) throws IllegalArgumentException { 29 | if (maximumRate < 1) { 30 | throw new IllegalArgumentException("maximumRate must be grater than 0"); 31 | } 32 | this.maximumRate = maximumRate; 33 | nanosToWait = (1000000000l * CHUNK_SIZE) / (maximumRate * 1024l); 34 | } 35 | 36 | public synchronized void limitNextByte() { 37 | limitNextBytes(1); 38 | } 39 | 40 | public synchronized void limitNextBytes(int len) { 41 | 42 | bytesToChunk += len; 43 | 44 | while (bytesToChunk > CHUNK_SIZE) { /* passed a chunk */ 45 | long now = System.nanoTime(); 46 | long missingNanos = nanosToWait - (now - lastChunkSent); 47 | if (missingNanos > 0) { 48 | try { 49 | Thread.sleep(missingNanos / 1000000, (int) missingNanos % 1000000); 50 | } catch (InterruptedException ex) { 51 | } 52 | } 53 | bytesToChunk -= CHUNK_SIZE; 54 | lastChunkSent = now + (missingNanos > 0 ? missingNanos : 0); 55 | } 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/util/stream/OutputStreamLimiter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.util.stream; 19 | 20 | import java.io.IOException; 21 | import java.io.OutputStream; 22 | 23 | public class OutputStreamLimiter extends OutputStream{ 24 | 25 | private OutputStream os; 26 | private BandwidthLimiter bandwidthLimiter; 27 | 28 | /** Creates a new instance of OutputStreamLimiter */ 29 | public OutputStreamLimiter( OutputStream os, BandwidthLimiter bandwidthLimiter) { 30 | this.os = os; 31 | this.bandwidthLimiter = bandwidthLimiter; 32 | } 33 | 34 | public void write(int b) throws IOException { 35 | if (bandwidthLimiter != null) 36 | bandwidthLimiter.limitNextBytes(1); 37 | os.write(b); 38 | } 39 | 40 | public void write(byte[] b, int off, int len) throws IOException { 41 | if (bandwidthLimiter != null) 42 | bandwidthLimiter.limitNextBytes(len); 43 | os.write(b,off,len); 44 | } 45 | 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/util/thread/InterruptableTasksThread.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.util.thread; 19 | 20 | import java.util.LinkedList; 21 | import java.util.Queue; 22 | 23 | public class InterruptableTasksThread extends Thread { 24 | 25 | Queue tasks = new LinkedList(); 26 | ThreadTask currentTask = null; 27 | private boolean closing = false; 28 | 29 | /** 30 | * Creates a new instance of InterruptableTasksThread 31 | */ 32 | public InterruptableTasksThread() { 33 | } 34 | 35 | public InterruptableTasksThread(String name) { 36 | super(name); 37 | } 38 | 39 | public synchronized void addTask(ThreadTask task) { 40 | tasks.add(task); 41 | if (tasks.size() == 1) { 42 | notify(); 43 | } 44 | } 45 | 46 | public void run() { 47 | ThreadTask currentTask = nextTask(); 48 | while (!isClosing() && currentTask != null) { 49 | try { 50 | if (!currentTask.execute()) { 51 | currentTask = nextTask(); 52 | } 53 | } catch (Exception e) { 54 | closing = true; 55 | currentTask.exceptionCought(e); 56 | } 57 | } 58 | } 59 | 60 | public synchronized ThreadTask nextTask() { 61 | currentTask = null; 62 | while (!isClosing() && (currentTask = tasks.poll()) == null) { 63 | try { 64 | wait(); 65 | } catch (InterruptedException ex) { 66 | closing = true; 67 | } 68 | } 69 | return currentTask; 70 | } 71 | 72 | public synchronized ThreadTask getCurrentTask() { 73 | return currentTask; 74 | } 75 | 76 | public synchronized void interrupt() { 77 | if (currentTask != null) { 78 | currentTask.interrupt(); 79 | } 80 | closing = true; 81 | notify(); 82 | } 83 | 84 | public synchronized boolean isClosing() { 85 | return closing; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/org/bitlet/wetorrent/util/thread/ThreadTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package org.bitlet.wetorrent.util.thread; 19 | 20 | public interface ThreadTask { 21 | 22 | public boolean execute() throws Exception; 23 | 24 | public void interrupt(); 25 | 26 | public void exceptionCought(Exception e); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/license.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2008 Alessandro Bahgat Shehata, Daniele Castagna 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/test/java/org/bitlet/wetorrent/MetafileTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.bitlet.wetorrent; 18 | 19 | import static org.bitlet.wetorrent.util.Utils.toByteBuffer; 20 | 21 | import org.junit.Test; 22 | 23 | import java.io.ByteArrayInputStream; 24 | import java.io.ByteArrayOutputStream; 25 | import java.io.InputStream; 26 | import java.util.Collections; 27 | import java.util.LinkedList; 28 | import java.util.List; 29 | 30 | import static org.junit.Assert.assertEquals; 31 | import static org.junit.Assert.assertNull; 32 | 33 | /** Unit tests for {@link Metafile}. */ 34 | public class MetafileTest { 35 | 36 | private static String TEST_TORRENT_PATH = "/ubuntu-15.04-desktop-amd64.iso.torrent"; 37 | 38 | @Test 39 | public void readMetafile() throws Exception { 40 | InputStream stream = MetafileTest.class.getResourceAsStream(TEST_TORRENT_PATH); 41 | Metafile metafile = new Metafile(stream); 42 | assertEquals("http://torrent.ubuntu.com:6969/announce", metafile.getAnnounce()); 43 | List announceList = new LinkedList(); 44 | List firstList = new LinkedList(); 45 | firstList.add(toByteBuffer("http://torrent.ubuntu.com:6969/announce")); 46 | announceList.add(firstList); 47 | List secondList = new LinkedList(); 48 | secondList.add(toByteBuffer("http://ipv6.torrent.ubuntu.com:6969/announce")); 49 | announceList.add(secondList); 50 | assertEquals(announceList, metafile.getAnnounceList()); 51 | assertEquals("Ubuntu CD releases.ubuntu.com", metafile.getComment()); 52 | assertNull(metafile.getCreatedBy()); 53 | assertEquals(1429786237L, metafile.getCreationDate().longValue()); 54 | assertEquals(Collections.emptyList(), metafile.getFiles()); 55 | // TODO assert on metafile.getInfo()); 56 | assertEquals(1150844928L, metafile.getLength()); 57 | assertEquals("ubuntu-15.04-desktop-amd64.iso", metafile.getName()); 58 | assertEquals(524288L, metafile.getPieceLength().longValue()); 59 | assertEquals(2196, metafile.getPieces().size()); 60 | } 61 | 62 | @Test 63 | public void updateManifestComment() throws Exception { 64 | InputStream stream = MetafileTest.class.getResourceAsStream(TEST_TORRENT_PATH); 65 | Metafile metafile = new Metafile(stream); 66 | metafile.setComment("foo"); 67 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 68 | metafile.print(outputStream); 69 | ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); 70 | Metafile metafileWithUpdatedFields = new Metafile(inputStream); 71 | assertEquals("foo", metafile.getComment()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/org/bitlet/wetorrent/bencode/BencodeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * bitlet - Simple bittorrent library 3 | * Copyright (C) 2008 Alessandro Bahgat Shehata, Daniele Castagna 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package org.bitlet.wetorrent.bencode; 18 | 19 | import org.junit.Test; 20 | 21 | import java.io.ByteArrayInputStream; 22 | import java.io.ByteArrayOutputStream; 23 | import java.nio.ByteBuffer; 24 | import java.nio.charset.StandardCharsets; 25 | import java.util.Arrays; 26 | import java.util.List; 27 | import java.util.Map; 28 | import java.util.TreeMap; 29 | 30 | import static org.bitlet.wetorrent.util.Utils.toByteBuffer; 31 | import static org.junit.Assert.assertEquals; 32 | import static org.junit.Assert.assertNotNull; 33 | 34 | /** Unit tests for {@link Bencode}. */ 35 | public class BencodeTest { 36 | 37 | @Test 38 | public void testParsing() throws Exception { 39 | String bencodedDictionary = "d3:str4:spam3:numi42e4:listli1ei2eee"; 40 | ByteArrayInputStream testStream = 41 | new ByteArrayInputStream(bencodedDictionary.getBytes(StandardCharsets.UTF_8)); 42 | Bencode bencode = new Bencode(testStream); 43 | Map result = (Map) bencode.getRootElement(); 44 | assertNotNull(result); 45 | assertEquals(3, result.size()); 46 | assertEquals(toByteBuffer("spam"), result.get(toByteBuffer("str"))); 47 | assertEquals(42L, result.get(toByteBuffer("num"))); 48 | List list = (List) result.get(toByteBuffer("list")); 49 | assertEquals(1L, list.get(0).longValue()); 50 | assertEquals(2L, list.get(1).longValue()); 51 | } 52 | 53 | @Test 54 | public void testPrint() throws Exception { 55 | Map source = new TreeMap(); 56 | source.put(toByteBuffer("str"), toByteBuffer("spam")); 57 | source.put(toByteBuffer("num"), 42L); 58 | source.put(toByteBuffer("list"), Arrays.asList(1L, 2L)); 59 | Bencode bencode = new Bencode(); 60 | bencode.setRootElement(source); 61 | ByteArrayOutputStream testStream = new ByteArrayOutputStream(); 62 | bencode.print(testStream); 63 | String result = testStream.toString(StandardCharsets.UTF_8.name()); 64 | String expected = "d4:listli1ei2ee3:numi42e3:str4:spame"; 65 | assertEquals(expected, result); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/resources/ubuntu-15.04-desktop-amd64.iso.torrent: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitletorg/bitlet/e101b51f86614b43dc86b1b8076615334f741c16/src/test/resources/ubuntu-15.04-desktop-amd64.iso.torrent --------------------------------------------------------------------------------