├── .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