commit e9684fcdfd8ef67625440103f118da71e53c3881 Author: rogiel Date: Wed Apr 27 21:55:23 2011 -0300 Change-Id: I0f4f7ffe65dcfbaaaa792ebb43566c7d07c7569d diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..ad81989 --- /dev/null +++ b/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.project b/.project new file mode 100644 index 0000000..f9ad6f7 --- /dev/null +++ b/.project @@ -0,0 +1,23 @@ + + + libtorrent-java + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.maven.ide.eclipse.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.maven.ide.eclipse.maven2Nature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..e8b0e28 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,3 @@ +#Wed Apr 27 21:19:52 BRT 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning diff --git a/.settings/org.maven.ide.eclipse.prefs b/.settings/org.maven.ide.eclipse.prefs new file mode 100644 index 0000000..6f3a43e --- /dev/null +++ b/.settings/org.maven.ide.eclipse.prefs @@ -0,0 +1,8 @@ +#Wed Apr 27 21:17:36 BRT 2011 +activeProfiles= +eclipse.preferences.version=1 +fullBuildGoals=process-test-resources +resolveWorkspaceProjects=true +resourceFilterGoals=process-resources resources\:testResources +skipCompilerPlugin=true +version=1 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7a4a3ea --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..aa970ad --- /dev/null +++ b/pom.xml @@ -0,0 +1,28 @@ + + 4.0.0 + net.bittorrent + bittorrent-java + 1.0.0-alpha + Java Bittorrent Library + Download and upload torrents with this embeddable library! + + + + repository.jboss.org + https://repository.jboss.org/nexus/content/repositories/releases/ + + false + + + + + + + org.jboss.netty + netty + 3.2.4.Final + compile + + + \ No newline at end of file diff --git a/src/main/java/net/torrent/BitTorrentClient.java b/src/main/java/net/torrent/BitTorrentClient.java new file mode 100644 index 0000000..7c77786 --- /dev/null +++ b/src/main/java/net/torrent/BitTorrentClient.java @@ -0,0 +1,173 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent; + +import java.net.InetSocketAddress; +import java.util.Timer; +import java.util.TimerTask; + +import net.torrent.protocol.algorithm.TorrentAlgorithm; +import net.torrent.protocol.peerwire.PeerWireManager; +import net.torrent.protocol.peerwire.manager.TorrentManager; +import net.torrent.torrent.context.TorrentContext; +import net.torrent.torrent.context.TorrentPeer; + +/** + * This is the main class used to controll your torrent transfer. It is not + * recommended to directly instantiate this class, instead use + * {@link BitTorrentClientFactory} to create new instances. + * + * @author Rogiel Josias Sulzbach + */ +public class BitTorrentClient implements Runnable { + /** + * Configuration of an BitTorrentClient. + */ + private final BitTorrentConfiguration config; + + /** + * The torrent context + */ + private final TorrentContext context; + /** + * The torrent manager + */ + private final TorrentManager manager; + /** + * The peer wire protocol manager + */ + private final PeerWireManager peerWire; + /** + * The torrent algorithm + */ + private final TorrentAlgorithm algorithm; + + /** + * Timer used to create new connections + */ + private final Timer connectorTimer = new Timer(); + + /** + * Creates a new instance + * + * @param config + * the configuration + * @param manager + * the torrent manager + * @param peerWire + * the peer wire protocol manager + * @param algorithm + * the torrent algorithm + */ + public BitTorrentClient(final BitTorrentConfiguration config, + TorrentManager manager, PeerWireManager peerWire, + TorrentAlgorithm algorithm) { + this.config = config; + this.context = manager.getContext(); + this.manager = manager; + this.peerWire = peerWire; + this.algorithm = algorithm; + } + + /** + * Start this torrent + */ + public void start() { + start((InetSocketAddress[]) null); + } + + /** + * Start this torrent. Once network is up, tries to connect to all the peers + * in addrs. + * + * @param addrs + * addresses + */ + public void start(InetSocketAddress... addrs) { + if (config.getListenPort() > 0) + peerWire.listen(config.getListenPort()); + + // run every 10 seconds - only 1 connection per turn + connectorTimer.schedule(new ConnectorTimerTask(), 0, 10 * 1000); + + if (addrs != null) + for (final InetSocketAddress addr : addrs) { + peerWire.connect(addr); + } + } + + /** + * Task that creates new connections at a certain repeat rate. Only one + * connection per turn. + * + * @author Rogiel Josias Sulzbach (http://www.rogiel.com/) + */ + public class ConnectorTimerTask extends TimerTask { + @Override + public void run() { + TorrentPeer peer = null; + while ((peer = algorithm.getPeerAlgorithm().connect()) != null) { + peerWire.connect(peer.getSocketAddress()); + } + } + } + + @Override + public void run() { + this.start(); + } + + /** + * Get the torrent context + * + * @return the torrent context + */ + public TorrentContext getContext() { + return context; + } + + /** + * Get the torrent manager + * + * @return the torrent manager + */ + public TorrentManager getManager() { + return manager; + } + + /** + * The peerwire manager + * + * @return the peerwire manager + */ + public PeerWireManager getPeerWire() { + return peerWire; + } + + /** + * The torret + * + * @return + */ + public BitTorrentConfiguration getConfig() { + return config; + } + + public TorrentAlgorithm getAlgorithm() { + return algorithm; + } +} diff --git a/src/main/java/net/torrent/BitTorrentClientFactory.java b/src/main/java/net/torrent/BitTorrentClientFactory.java new file mode 100644 index 0000000..ee0f979 --- /dev/null +++ b/src/main/java/net/torrent/BitTorrentClientFactory.java @@ -0,0 +1,231 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; + +import net.torrent.protocol.algorithm.TorrentAlgorithm; +import net.torrent.protocol.algorithm.impl.TorrentStdAlgorithm; +import net.torrent.protocol.datastore.TorrentDatastore; +import net.torrent.protocol.datastore.impl.PlainTorrentDatastore; +import net.torrent.protocol.peerwire.PeerWireManager; +import net.torrent.protocol.peerwire.manager.TorrentManager; +import net.torrent.torrent.Torrent; +import net.torrent.torrent.context.TorrentContext; + +/** + * Factory class for {@link BitTorrentClient}. + * + * @author Rogiel Josias Sulzbach + */ +public class BitTorrentClientFactory { + /** + * The client's configuration + */ + private BitTorrentConfiguration config = new BitTorrentConfiguration(); + + /** + * The torrent context + */ + private final TorrentContext context; + /** + * The torrent datastore + */ + private TorrentDatastore datastore; + /** + * The torrent manager + */ + private TorrentManager manager; + /** + * The torrent algorithm + */ + private TorrentAlgorithm algorithm; + + /** + * Creates a new standard {@link BitTorrentClient BitTorrent client} + * + * @param file + * the torrent file + * @return a new client + * @throws IOException + * @throws URISyntaxException + */ + public static BitTorrentClient newStandardBitTorrentClient(File file) + throws IOException, URISyntaxException { + return new BitTorrentClientFactory(Torrent.load(file)) + .newBitTorrentClient(); + } + + /** + * Creates a new factory instance + * + * @param torrent + * the torrent + */ + public BitTorrentClientFactory(final Torrent torrent) { + context = new TorrentContext(torrent); + datastore = new PlainTorrentDatastore(new File("store.bin")); + manager = new TorrentManager(context, datastore); + algorithm = new TorrentStdAlgorithm(manager); + } + + /** + * Creates a new factory instance + * + * @param torrent + * the torrent + * @param algorithm + * the torrent algorithm + */ + public BitTorrentClientFactory(final Torrent torrent, + final TorrentAlgorithm algorithm) { + context = new TorrentContext(torrent); + datastore = new PlainTorrentDatastore(null); + manager = new TorrentManager(context, datastore); + this.algorithm = algorithm; + } + + /** + * Creates a new factory instance + * + * @param torrent + * the torrent + * @param datastore + * the torrent datastore + */ + public BitTorrentClientFactory(final Torrent torrent, + TorrentDatastore datastore) { + context = new TorrentContext(torrent); + this.datastore = datastore; + manager = new TorrentManager(context, datastore); + algorithm = new TorrentStdAlgorithm(manager); + } + + /** + * Creates a new factory instance + * + * @param torrent + * the torrent + * @param datastore + * the torrent datastore + * @param algorithm + * the torrent algorithm + */ + public BitTorrentClientFactory(final Torrent torrent, + TorrentDatastore datastore, final TorrentAlgorithm algorithm) { + context = new TorrentContext(torrent); + this.datastore = datastore; + manager = new TorrentManager(context, datastore); + this.algorithm = algorithm; + } + + /** + * Create the {@link BitTorrentClient} object + * + * @return the created {@link BitTorrentClient} + */ + public BitTorrentClient newBitTorrentClient() { + final PeerWireManager peerWire = new PeerWireManager(manager, algorithm); + return new BitTorrentClient(config, manager, peerWire, algorithm); + } + + /** + * Get the client configuration + * + * @return the client configuration + */ + public BitTorrentConfiguration getConfig() { + return config; + } + + /** + * Set the client configuration + * + * @param config + * the client configuration + */ + public void setConfig(BitTorrentConfiguration config) { + this.config = config; + } + + /** + * Get the datastore + * + * @return the datastore + */ + public TorrentDatastore getDatastore() { + return datastore; + } + + /** + * Set the datastore + * + * @param datastore + * the datastore + */ + public void setDatastore(TorrentDatastore datastore) { + this.datastore = datastore; + } + + /** + * Get the torrent manager + * + * @return the torrent manager + */ + public TorrentManager getManager() { + return manager; + } + + /** + * Set the torrent manager + * + * @param manager + * the torrent manager + */ + public void setManager(TorrentManager manager) { + this.manager = manager; + } + + /** + * Get the torrent algorithm + * + * @return the torrent algorithm + */ + public TorrentAlgorithm getAlgorithm() { + return algorithm; + } + + /** + * Set the torrent algorithm + * + * @param algorithm + * the torrent algorithm + */ + public void setAlgorithm(TorrentAlgorithm algorithm) { + this.algorithm = algorithm; + } + + /** + * Get the torrent context + * + * @return the torrent context + */ + public TorrentContext getContext() { + return context; + } +} diff --git a/src/main/java/net/torrent/BitTorrentConfiguration.java b/src/main/java/net/torrent/BitTorrentConfiguration.java new file mode 100644 index 0000000..a6be5c7 --- /dev/null +++ b/src/main/java/net/torrent/BitTorrentConfiguration.java @@ -0,0 +1,71 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent; + +/** + * Configurations for an {@link BitTorrentClient BitTorrent client} instance. + * + * @author Rogiel Josias Sulzbach + */ +public class BitTorrentConfiguration { + /** + * Default peerwire listen port + */ + private int listenPort = 58462; + /** + * Default peer id + */ + private byte[] peerID = null; + + /** + * Get the port + * + * @return the port + */ + public int getListenPort() { + return listenPort; + } + + /** + * Set the listening port for the server.
+ * 0 will disable server. + * + * @param listenPort + * the port + */ + public void setListenPort(int listenPort) { + this.listenPort = listenPort; + } + + /** + * Get the peer id. + * + * @return the peer id + */ + public byte[] getPeerID() { + return peerID; + } + + /** + * Set the peer id. If null a random peerid will be generated. + * + * @param peerID + * the peerid. Can be null. + */ + public void setPeerID(byte[] peerID) { + this.peerID = peerID; + } +} diff --git a/src/main/java/net/torrent/protocol/algorithm/TorrentAlgorithm.java b/src/main/java/net/torrent/protocol/algorithm/TorrentAlgorithm.java new file mode 100644 index 0000000..15cd97b --- /dev/null +++ b/src/main/java/net/torrent/protocol/algorithm/TorrentAlgorithm.java @@ -0,0 +1,60 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.algorithm; + +import net.torrent.protocol.peerwire.handler.PeerWireAlgorithmHandler; + +/** + * An {@link TorrentAlgorithm} defines the rules for download, upload and + * connection management. These algorithms provide limited control, in the + * boundaries of standard torrent behavior. If you wish more control, at the + * protocol layer, try implementing a new {@link PeerWireAlgorithmHandler}. + * + * @author Rogiel Josias Sulzbach + * @see TorrentPeerAlgorithm + * @see TorrentInterestAlgorithm + * @see TorrentPieceDownloadAlgorithm + * @see TorrentPieceUploadAlgorithm + */ +public interface TorrentAlgorithm { + /** + * Creates a new instance of {@link TorrentPeerAlgorithm}. + * + * @return the new {@link TorrentPeerAlgorithm} instance + */ + TorrentPeerAlgorithm getPeerAlgorithm(); + + /** + * Creates a new instance of {@link TorrentInterestAlgorithm}. + * + * @return the new {@link TorrentInterestAlgorithm} instance + */ + TorrentInterestAlgorithm getInterestAlgorithm(); + + /** + * Creates a new instance of {@link TorrentPieceDownloadAlgorithm}. + * + * @return the new {@link TorrentPieceDownloadAlgorithm} instance + */ + TorrentPieceDownloadAlgorithm getDownloadAlgorithm(); + + /** + * Creates a new instance of {@link TorrentPieceUploadAlgorithm}. + * + * @return the new {@link TorrentPieceUploadAlgorithm} instance + */ + TorrentPieceUploadAlgorithm getUploadAlgorithm(); +} diff --git a/src/main/java/net/torrent/protocol/algorithm/TorrentInterestAlgorithm.java b/src/main/java/net/torrent/protocol/algorithm/TorrentInterestAlgorithm.java new file mode 100644 index 0000000..102eb8f --- /dev/null +++ b/src/main/java/net/torrent/protocol/algorithm/TorrentInterestAlgorithm.java @@ -0,0 +1,49 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.algorithm; + +import net.torrent.torrent.context.TorrentPeer; +import net.torrent.torrent.context.TorrentPeer.ChokingState; +import net.torrent.torrent.context.TorrentPeer.InterestState; + +/** + * Algorithm used to determine the interest and choking in peers. + * + * @author Rogiel Josias Sulzbach + */ +public interface TorrentInterestAlgorithm { + /** + * Test if we are interested in this peer pieces. Interest is for download + * only. + * + * @param peer + * the peer + * @return our interest + * @see InterestState + */ + InterestState interested(TorrentPeer peer); + + /** + * Test if we want to choke this peer. This is normally invoked when we have + * no more interest in the peer pieces. + * + * @param peer + * the peer + * @return the choking state + * @see ChokingState + */ + ChokingState choke(TorrentPeer peer); +} diff --git a/src/main/java/net/torrent/protocol/algorithm/TorrentPeerAlgorithm.java b/src/main/java/net/torrent/protocol/algorithm/TorrentPeerAlgorithm.java new file mode 100644 index 0000000..bc2cd23 --- /dev/null +++ b/src/main/java/net/torrent/protocol/algorithm/TorrentPeerAlgorithm.java @@ -0,0 +1,162 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.algorithm; + +import net.torrent.torrent.context.TorrentPeer; +import net.torrent.torrent.context.TorrentPeer.ChokingState; +import net.torrent.torrent.context.TorrentPeer.InterestState; + +/** + * Algorithm that processes peer interest, choking state and connections. + * + * @author Rogiel Josias Sulzbach + */ +public interface TorrentPeerAlgorithm { + /** + * Get an peer to be connected. This method is invoked in many situations. + * + * @return the new peer to be connected + * @see TorrentPeer + */ + TorrentPeer connect(); + + /** + * Once a new peer is discovered, this method is called to test if we want + * to connect to it or not. + * + * @param peer + * the new discovered peer + * @return the desired action + */ + PeerDiscoveredAction discovered(TorrentPeer peer); + + /** + * Action to be performed when an peer is idle. + * + * @author Rogiel Josias Sulzbach (http://www.rogiel.com/) + */ + public enum PeerDiscoveredAction { + /** + * Try to establish an connection to this new peer + */ + CONNECT, + + /** + * Remove this peer from list + */ + REMOVE, + + /** + * Nothing is done. + */ + NONE; + } + + /** + * Test to keep this connection alive. The value sent represents the action + * which the handler will do with the idle peer. + * + * @param peer + * the peer + * @return the action to be done + * @see KeepAliveAction KeepAliveAction for a list of actions + */ + KeepAliveAction keepAlive(TorrentPeer peer); + + /** + * Action to be performed when an peer is idle. + * + * @author Rogiel Josias Sulzbach (http://www.rogiel.com/) + */ + public enum KeepAliveAction { + /** + * Keep this connection alive + */ + KEEP_ALIVE, + + /** + * Disconnect the peer. No new connection is made. + */ + DISCONNECT, + + /** + * Disconnect the peer AND connects another peer. + */ + CONNECT_NEW_PEER, + + /** + * Nothing is done. + */ + NONE; + } + + /** + * Called when the peer interested in us changes. Return the desired choke + * or unchoked action. + * + * @param peer + * the peer + * @param interest + * the new interest (it is also already set in the peer object) + * @return the desired choke/unchoke action + * @see ChokingState + */ + ChokingState interested(TorrentPeer peer, InterestState interest); + + /** + * Called when the peer choke state change with us (that is, when the + * peer choke or unchoke!). + * + * @param peer + * the peer + * @param state + * the new choking state + * @return the desired action to be taken + * @see PeerChokedAction + */ + PeerChokedAction choked(TorrentPeer peer, ChokingState state); + + /** + * Action to be performed when the peer changes it's choke state + * + * @author Rogiel Josias Sulzbach (http://www.rogiel.com/) + */ + public enum PeerChokedAction { + /** + * Disconnects the current peer and connects a new one + */ + CONNECT_NEW_PEER, + + /** + * Only disconnects the peer, does not initiate a new connection with + * anyone. + */ + DISCONNECT, + + /** + * Download a new piece (this is only valid if unchoked) + */ + DOWNLOAD, + + /** + * Do nothing, ignore. + */ + NONE; + } +} diff --git a/src/main/java/net/torrent/protocol/algorithm/TorrentPieceDownloadAlgorithm.java b/src/main/java/net/torrent/protocol/algorithm/TorrentPieceDownloadAlgorithm.java new file mode 100644 index 0000000..f30ac0c --- /dev/null +++ b/src/main/java/net/torrent/protocol/algorithm/TorrentPieceDownloadAlgorithm.java @@ -0,0 +1,127 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.algorithm; + +import net.torrent.protocol.peerwire.handler.PeerWireAlgorithmHandler; +import net.torrent.torrent.TorrentPart; +import net.torrent.torrent.TorrentPiece; +import net.torrent.torrent.context.TorrentPeer; + +/** + * This algorithm is used to return the {@link TorrentPart part} for download + * and validates if an certain {@link TorrentPiece piece} is complete. Please + * note that this algorithm DOES NOT do the checksum in the piece! The checksum + * is done at the {@link PeerWireAlgorithmHandler handler} level. + * + * @author Rogiel Josias Sulzbach + */ +public interface TorrentPieceDownloadAlgorithm { + /** + * Return the next part desired for download + * + * @param peer + * the peer + * @param part + * the part which has completed. Might be null! + * @return the new part + * @see TorrentPart + */ + TorrentPart getNextPart(TorrentPeer peer, TorrentPart part); + + /** + * Issued when an suggestion is received. If wished to accept it, return the + * first part of it, otherwise return null. + * + * @param peer + * the suggesting peer + * @param piece + * the suggested piece + */ + TorrentPart sugested(TorrentPeer peer, TorrentPiece piece); + + /** + * Issued when allowed to fast download an given piece, even while choked. + * If multiple pieces are allowed, one call per piece will be done. If + * willing to download pieces, return the first part of the piece. + * + * @param peer + * the allowing peer + * @param piece + * the allowed piece + */ + TorrentPart allowedFast(TorrentPeer peer, TorrentPiece piece); + + /** + * Test if an certain piece has all its parts already download. If true, a + * checksum will be performed and a message informing we have this piece + * will be broadcasted. This call is only valid once the next part has + * already been taken. + * + * @param peer + * the peer + * @param piece + * the piece to test + * @return true if complete, false otherwise. + */ + boolean isComplete(TorrentPeer peer, TorrentPiece piece); + + /** + * Called when an piece is complete but found to be corrupted + * + * @param peer + * the peer who send the piece (more precisely the completing + * part) + * @param piece + * the piece + * @return the action to be performed + * @see CorruptedAction + */ + CorruptedAction corrupted(TorrentPeer peer, TorrentPiece piece); + + /** + * Actions to be taken when a corrupted piece is downloaded. + * + * @author Rogiel Josias Sulzbach (http://www.rogiel.com/) + */ + public enum CorruptedAction { + /** + * Only disconnects the peer, does not initiate a new connection with + * anyone. + */ + DISCONNECT, + + /** + * Disconnects the current peer and connects a new one + */ + CONNECT_NEW_PEER, + + /** + * Choke this peer + */ + CHOKE, + + /** + * Retry to download the piece + */ + CONTINUE, + + /** + * Do nothing, ignore. Might cause peer to become idle. + */ + CANCEL; + } +} diff --git a/src/main/java/net/torrent/protocol/algorithm/TorrentPieceUploadAlgorithm.java b/src/main/java/net/torrent/protocol/algorithm/TorrentPieceUploadAlgorithm.java new file mode 100644 index 0000000..857b90e --- /dev/null +++ b/src/main/java/net/torrent/protocol/algorithm/TorrentPieceUploadAlgorithm.java @@ -0,0 +1,83 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.algorithm; + +import net.torrent.torrent.TorrentPart; +import net.torrent.torrent.context.TorrentPeer; + +/** + * Algorithm used for upload management. It verifies if we have interest in + * uploading an piece and handles cancel requests. TODO how to handle cancels? + * + * @author Rogiel Josias Sulzbach + */ +public interface TorrentPieceUploadAlgorithm { + /** + * Called when an peer has requested a piece to be uploaded. + * + * @param peer + * the peer requesting this piece + * @param part + * the part requested + * @return true if allowed to upload, false otherwise. + */ + RequestAction request(TorrentPeer peer, TorrentPart part); + + public enum RequestAction { + /** + * Only disconnects the peer, does not initiate a new connection with + * anyone. + */ + DISCONNECT, + + /** + * Disconnects the current peer and connects a new one + */ + CONNECT_NEW_PEER, + + /** + * Reject this request (only if supports Fast Extension). If not + * supported will fall back to {@link RequestAction#NONE} + */ + REJECT, + + /** + * Choke this peer + */ + CHOKE, + + /** + * Upload a new piece (this is only valid if unchoked) + */ + UPLOAD, + + /** + * Do nothing, ignore. + */ + NONE; + } + + /** + * Cancels a part request. + * + * @param peer + * the peer + * @param part + * the part + * @return TODO + */ + boolean cancel(TorrentPeer peer, TorrentPart part); +} diff --git a/src/main/java/net/torrent/protocol/algorithm/impl/TorrentStdAlgorithm.java b/src/main/java/net/torrent/protocol/algorithm/impl/TorrentStdAlgorithm.java new file mode 100644 index 0000000..9b63f01 --- /dev/null +++ b/src/main/java/net/torrent/protocol/algorithm/impl/TorrentStdAlgorithm.java @@ -0,0 +1,62 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.algorithm.impl; + +import net.torrent.protocol.algorithm.TorrentAlgorithm; +import net.torrent.protocol.algorithm.TorrentInterestAlgorithm; +import net.torrent.protocol.algorithm.TorrentPeerAlgorithm; +import net.torrent.protocol.algorithm.TorrentPieceDownloadAlgorithm; +import net.torrent.protocol.algorithm.TorrentPieceUploadAlgorithm; +import net.torrent.protocol.peerwire.manager.TorrentManager; + +/** + * Standard torrent algorithm + * + * @author Rogiel Josias Sulzbach + */ +public class TorrentStdAlgorithm implements TorrentAlgorithm { + private final TorrentPeerAlgorithm peerAlgorithm; + private final TorrentInterestAlgorithm interestAlgorithm; + private final TorrentPieceDownloadAlgorithm downloadAlgorithm; + private final TorrentPieceUploadAlgorithm uploadAlgorithm; + + public TorrentStdAlgorithm(final TorrentManager manager) { + peerAlgorithm = new TorrentStdPeerAlgorithm(manager); + interestAlgorithm = new TorrentStdInterestAlgorithm(manager); + downloadAlgorithm = new TorrentStdPieceDownloadAlgorithm(manager); + uploadAlgorithm = new TorrentStdPieceUploadAlgorithm(manager); + } + + @Override + public TorrentPeerAlgorithm getPeerAlgorithm() { + return peerAlgorithm; + } + + @Override + public TorrentInterestAlgorithm getInterestAlgorithm() { + return interestAlgorithm; + } + + @Override + public TorrentPieceDownloadAlgorithm getDownloadAlgorithm() { + return downloadAlgorithm; + } + + @Override + public TorrentPieceUploadAlgorithm getUploadAlgorithm() { + return uploadAlgorithm; + } +} diff --git a/src/main/java/net/torrent/protocol/algorithm/impl/TorrentStdInterestAlgorithm.java b/src/main/java/net/torrent/protocol/algorithm/impl/TorrentStdInterestAlgorithm.java new file mode 100644 index 0000000..cb512b2 --- /dev/null +++ b/src/main/java/net/torrent/protocol/algorithm/impl/TorrentStdInterestAlgorithm.java @@ -0,0 +1,48 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.algorithm.impl; + +import net.torrent.protocol.algorithm.TorrentInterestAlgorithm; +import net.torrent.protocol.peerwire.manager.TorrentManager; +import net.torrent.torrent.context.TorrentPeer; +import net.torrent.torrent.context.TorrentPeer.ChokingState; +import net.torrent.torrent.context.TorrentPeer.InterestState; + +/** + * Standard torrent interest algorithm + * + * @author Rogiel Josias Sulzbach + */ +public class TorrentStdInterestAlgorithm implements TorrentInterestAlgorithm { + @SuppressWarnings("unused") + private final TorrentManager manager; + + public TorrentStdInterestAlgorithm(TorrentManager manager) { + this.manager = manager; + } + + @Override + public InterestState interested(TorrentPeer peer) { + // if(peer.getPort() == 25944) + // return InterestState.UNINTERESTED; + return InterestState.INTERESTED; + } + + @Override + public ChokingState choke(TorrentPeer peer) { + return ChokingState.UNCHOKED; + } +} diff --git a/src/main/java/net/torrent/protocol/algorithm/impl/TorrentStdPeerAlgorithm.java b/src/main/java/net/torrent/protocol/algorithm/impl/TorrentStdPeerAlgorithm.java new file mode 100644 index 0000000..a132cd0 --- /dev/null +++ b/src/main/java/net/torrent/protocol/algorithm/impl/TorrentStdPeerAlgorithm.java @@ -0,0 +1,73 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.algorithm.impl; + +import net.torrent.protocol.algorithm.TorrentPeerAlgorithm; +import net.torrent.protocol.peerwire.manager.TorrentManager; +import net.torrent.torrent.context.TorrentPeer; +import net.torrent.torrent.context.TorrentPeer.ChokingState; +import net.torrent.torrent.context.TorrentPeer.InterestState; + +/** + * Standard torrent peer algorithm + * + * @author Rogiel Josias Sulzbach + */ +public class TorrentStdPeerAlgorithm implements TorrentPeerAlgorithm { + @SuppressWarnings("unused") + private final TorrentManager manager; + + public TorrentStdPeerAlgorithm(TorrentManager manager) { + this.manager = manager; + } + + @Override + public TorrentPeer connect() { + return null; + } + + @Override + public PeerDiscoveredAction discovered(TorrentPeer peer) { + return PeerDiscoveredAction.CONNECT; + } + + @Override + public KeepAliveAction keepAlive(TorrentPeer peer) { + return KeepAliveAction.KEEP_ALIVE; + } + + @Override + public ChokingState interested(TorrentPeer peer, InterestState interest) { + switch (interest) { + case INTERESTED: + return ChokingState.UNCHOKED; + case UNINTERESTED: + return ChokingState.CHOKED; + } + return null; + } + + @Override + public PeerChokedAction choked(TorrentPeer peer, ChokingState state) { + switch (state) { + case CHOKED: + return PeerChokedAction.NONE; + case UNCHOKED: + return PeerChokedAction.DOWNLOAD; + } + return null; + } +} diff --git a/src/main/java/net/torrent/protocol/algorithm/impl/TorrentStdPieceDownloadAlgorithm.java b/src/main/java/net/torrent/protocol/algorithm/impl/TorrentStdPieceDownloadAlgorithm.java new file mode 100644 index 0000000..084138b --- /dev/null +++ b/src/main/java/net/torrent/protocol/algorithm/impl/TorrentStdPieceDownloadAlgorithm.java @@ -0,0 +1,112 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.algorithm.impl; + +import java.util.HashSet; +import java.util.Set; + +import net.torrent.protocol.algorithm.TorrentPieceDownloadAlgorithm; +import net.torrent.protocol.peerwire.manager.TorrentManager; +import net.torrent.torrent.TorrentPart; +import net.torrent.torrent.TorrentPiece; +import net.torrent.torrent.context.TorrentPeer; +import net.torrent.torrent.piece.PieceSelector; +import net.torrent.torrent.piece.RandomPieceSelector; + +/** + * This standard implementation of {@link TorrentPieceDownloadAlgorithm} chooses + * a random missing piece and tries to download all the parts from the same + * peer, following the standard behavior of most of torrent clients. + * + * @author Rogiel Josias Sulzbach + */ +public class TorrentStdPieceDownloadAlgorithm implements + TorrentPieceDownloadAlgorithm { + /** + * The torrent manager + */ + private final TorrentManager manager; + // private final TorrentContext context; + // private final Torrent torrent; + + /** + * This selector is used to find the next piece to be downloaded. Parts are + * managed inside this algorithm. + */ + private final PieceSelector selector; + /** + * Maps all unchecked completed pieces. The piece is removed from the list + * once + * {@link TorrentPieceDownloadAlgorithm#isComplete(TorrentPeer, TorrentPiece)} + * is called. + */ + private Set completedPieces = new HashSet(); + + /** + * Creates a new instance of this algorithm. + * + * @param manager + * the torrent manager instance. With this object is possible to + * retrieve current downloads/uploads and connections. + */ + public TorrentStdPieceDownloadAlgorithm(TorrentManager manager) { + this.manager = manager; + // this.context = this.manager.getContext(); + // this.torrent = this.manager.getTorrent(); + selector = new RandomPieceSelector(manager); + } + + @Override + public TorrentPart getNextPart(TorrentPeer peer, TorrentPart part) { + if (part != null) { + if (part.isLast()) { + completedPieces.add(part.getPiece()); + } else { + return part.getNextPart(); + } + } + TorrentPiece piece = selector.select(peer); + if (piece == null) + // no piece, return null. The default handler will check, again, the + // interest on this peer. + return null; + return piece.getFirstPart(); + } + + @Override + public TorrentPart sugested(TorrentPeer peer, TorrentPiece piece) { + return piece.getFirstPart(); + } + + @Override + public TorrentPart allowedFast(TorrentPeer peer, TorrentPiece piece) { + return piece.getFirstPart(); + } + + @Override + public boolean isComplete(TorrentPeer peer, TorrentPiece piece) { + if (manager.getContext().getBitfield().hasPiece(piece)) + return true; + // minimum overhead possible, will return true if was on list + return completedPieces.remove(piece); + } + + @Override + public CorruptedAction corrupted(TorrentPeer peer, TorrentPiece piece) { + // TODO ban peer sending many corrupted pieces + return CorruptedAction.CANCEL; + } +} diff --git a/src/main/java/net/torrent/protocol/algorithm/impl/TorrentStdPieceUploadAlgorithm.java b/src/main/java/net/torrent/protocol/algorithm/impl/TorrentStdPieceUploadAlgorithm.java new file mode 100644 index 0000000..f01a4b0 --- /dev/null +++ b/src/main/java/net/torrent/protocol/algorithm/impl/TorrentStdPieceUploadAlgorithm.java @@ -0,0 +1,47 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.algorithm.impl; + +import net.torrent.protocol.algorithm.TorrentPieceUploadAlgorithm; +import net.torrent.protocol.peerwire.manager.TorrentManager; +import net.torrent.torrent.TorrentPart; +import net.torrent.torrent.context.TorrentPeer; + +/** + * Standard torrent upload algorithm + * + * @author Rogiel Josias Sulzbach + */ +public class TorrentStdPieceUploadAlgorithm implements + TorrentPieceUploadAlgorithm { + @SuppressWarnings("unused") + private final TorrentManager manager; + + public TorrentStdPieceUploadAlgorithm(TorrentManager manager) { + this.manager = manager; + } + + @Override + public RequestAction request(TorrentPeer peer, TorrentPart part) { + return RequestAction.NONE; + } + + @Override + public boolean cancel(TorrentPeer peer, TorrentPart part) { + // TODO Auto-generated method stub + return false; + } +} diff --git a/src/main/java/net/torrent/protocol/datastore/AbstractTorrentDatastore.java b/src/main/java/net/torrent/protocol/datastore/AbstractTorrentDatastore.java new file mode 100644 index 0000000..49426cb --- /dev/null +++ b/src/main/java/net/torrent/protocol/datastore/AbstractTorrentDatastore.java @@ -0,0 +1,46 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.datastore; + +import java.io.IOException; +import java.security.MessageDigest; + +import net.torrent.protocol.datastore.impl.FileAwareTorrentDatastore; +import net.torrent.torrent.TorrentPiece; + +/** + * Abstract implementation of {@link TorrentDatastore}. The only implemented + * method is {@link TorrentDatastore#checksum(TorrentPiece)} since it always the + * same. + *

+ * Since it is noticeable that this interface does not use the concept of files, + * it might be much more useful implementing {@link FileAwareTorrentDatastore} + * class instead of this one. + * + * @author Rogiel Josias Sulzbach + */ +public abstract class AbstractTorrentDatastore implements TorrentDatastore { + @Override + public boolean checksum(TorrentPiece piece) throws IOException { + final MessageDigest digest = piece.getHash().getType() + .getMessageDigest(); + if (digest == null) + throw new NullPointerException("Digest is null"); + byte[] hash = digest.digest(this.read(piece.asSinglePart()).array()); + return piece.getHash().compare(hash); + } + +} diff --git a/src/main/java/net/torrent/protocol/datastore/TorrentDatastore.java b/src/main/java/net/torrent/protocol/datastore/TorrentDatastore.java new file mode 100644 index 0000000..d573bad --- /dev/null +++ b/src/main/java/net/torrent/protocol/datastore/TorrentDatastore.java @@ -0,0 +1,75 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.datastore; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import net.torrent.protocol.datastore.impl.FileAwareTorrentDatastore; +import net.torrent.torrent.TorrentPart; +import net.torrent.torrent.TorrentPiece; + +/** + * The datastore is responsible for storing data downloaded from peers and read + * them for further upload. The storage should be done as fast as possible, + * since slow operations might and will slowdown download and upload rates. + *

+ * Since it is noticeable that this interface does not use the concept of files, + * it might be much more useful implementing {@link FileAwareTorrentDatastore} + * class instead of this interface. + * + * @author Rogiel Josias Sulzbach + */ +public interface TorrentDatastore { + /** + * Read an segment of data relative to {@link TorrentPart part}. + *

+ * Warning: Synchronization MUST be dealt by implementations. + * + * @param part + * the part readed + * @return {@link ByteBuffer} with the contents + * @throws IOException + * if exception occur at IO level + */ + ByteBuffer read(TorrentPart part) throws IOException; + + /** + * Stores an segment of data relative to {@link TorrentPart part}. + *

+ * Warning: Synchronization MUST be dealt by implementations. + * + * @param part + * the part to written + * @param buffer + * the buffer containing the data + * @return {@link ByteBuffer} with the contents + * @throws IOException + * if exception occur at IO level + */ + boolean write(TorrentPart part, ByteBuffer buffer) throws IOException; + + /** + * Calculates the checksum of an {@link TorrentPiece piece}. + * + * @param piece + * the piece to be tested + * @return true if checksum is correct, false otherwise. + * @throws IOException + * if exception occur at IO level + */ + boolean checksum(TorrentPiece piece) throws IOException; +} diff --git a/src/main/java/net/torrent/protocol/datastore/impl/FileAwareTorrentDatastore.java b/src/main/java/net/torrent/protocol/datastore/impl/FileAwareTorrentDatastore.java new file mode 100644 index 0000000..b195aa6 --- /dev/null +++ b/src/main/java/net/torrent/protocol/datastore/impl/FileAwareTorrentDatastore.java @@ -0,0 +1,118 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.datastore.impl; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import net.torrent.protocol.datastore.AbstractTorrentDatastore; +import net.torrent.protocol.datastore.TorrentDatastore; +import net.torrent.torrent.Torrent; +import net.torrent.torrent.TorrentFile; +import net.torrent.torrent.TorrentPart; +import net.torrent.util.Range; + +/** + * Since {@link TorrentDatastore} does not uses the concept of files when + * storing and/or retrieving data, this abstract class provides abstraction for + * that issue and allows, easily to store and/or retrieve data in separated + * files. + * + * @author Rogiel Josias Sulzbach + */ +public abstract class FileAwareTorrentDatastore extends + AbstractTorrentDatastore { + /** + * Read data contained withing file in the given range + * + * @param buffer + * the buffer in which data should be readed. + * @param file + * the file to be read + * @param range + * range of data being readed. + * @throws IOException + * if any exception occur at IO level + */ + protected abstract void read(ByteBuffer buffer, TorrentFile file, + Range range) throws IOException; + + /** + * Write data contained in buffer to file in the given + * range + * + * @param buffer + * the buffer in which data should be written. + * @param file + * the file to be written + * @param range + * range of data being written. + * @throws IOException + * if any exception occur at IO level + */ + protected abstract boolean write(ByteBuffer buffer, TorrentFile file, + Range range) throws IOException; + + @Override + public ByteBuffer read(TorrentPart part) throws IOException { + final ByteBuffer buffer = allocate(part); + + final Torrent torrent = part.getTorrent(); + Range partRange = part.asTorrentRange(); + + for (final TorrentFile file : torrent.getFiles()) { + final Range fileRange = file.asTorrentRange(); + if (fileRange.intersects(partRange)) { + final Range range = part.asFileRange(file).intersection( + fileRange); + buffer.limit((int) (buffer.position() + range.getLength())); + this.read(buffer, file, range); + } + } + + buffer.flip(); + return buffer; + } + + @Override + public boolean write(TorrentPart part, ByteBuffer buffer) + throws IOException { + final Torrent torrent = part.getTorrent(); + Range partRange = part.asTorrentRange(); + + for (final TorrentFile file : torrent.getFiles()) { + final Range fileRange = file.asFileRange(); + if (fileRange.intersects(partRange)) { + final Range range = part.asFileRange(file); + buffer.limit((int) (buffer.position() + range.getLength())); + if (!this.write(buffer, file, range)) + return false; + } + } + return true; + } + + /** + * Allocates a new {@link ByteBuffer}. + * + * @param part + * the {@link TorrentPart}s + * @return a newly allocated ByteBuffer + */ + private ByteBuffer allocate(TorrentPart part) { + return ByteBuffer.allocate(part.getLength()); + } +} diff --git a/src/main/java/net/torrent/protocol/datastore/impl/PlainTorrentDatastore.java b/src/main/java/net/torrent/protocol/datastore/impl/PlainTorrentDatastore.java new file mode 100644 index 0000000..86b2b71 --- /dev/null +++ b/src/main/java/net/torrent/protocol/datastore/impl/PlainTorrentDatastore.java @@ -0,0 +1,87 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.datastore.impl; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +import net.torrent.protocol.datastore.AbstractTorrentDatastore; +import net.torrent.torrent.TorrentPart; + +/** + * Stores data into a single plain file, ignoring any file metadata into the + * original .torrent file. + *

+ * Unless for a single torrent file, data might (and possibly will) not be + * readable outside this library. + * + * @author Rogiel Josias Sulzbach + */ +public class PlainTorrentDatastore extends AbstractTorrentDatastore { + /** + * The target file + */ + private final File file; + /** + * The file channel + */ + private FileChannel channel; + + /** + * Creates a new datastore instance + * + * @param file + * the single file to be used + */ + public PlainTorrentDatastore(File file) { + this.file = file; + try { + this.channel = new RandomAccessFile(this.file, "rw").getChannel(); + } catch (FileNotFoundException e) { + } + } + + @Override + public synchronized ByteBuffer read(TorrentPart part) throws IOException { + final ByteBuffer buffer = ByteBuffer.allocate(part.getLength()); + synchronized (channel) { + channel.position(part.getOffset()); + while (buffer.hasRemaining()) { + if (channel.read(buffer) == -1) // EOF + break; + } + } + buffer.flip(); + return buffer; + } + + @Override + public synchronized boolean write(TorrentPart part, ByteBuffer buffer) + throws IOException { + synchronized (channel) { + channel.position(part.getOffset()); + while (buffer.hasRemaining()) { + if (channel.write(buffer) == -1) + return false; + } + } + return true; + } +} diff --git a/src/main/java/net/torrent/protocol/datastore/impl/SimpleTorrentDatastore.java b/src/main/java/net/torrent/protocol/datastore/impl/SimpleTorrentDatastore.java new file mode 100644 index 0000000..db97555 --- /dev/null +++ b/src/main/java/net/torrent/protocol/datastore/impl/SimpleTorrentDatastore.java @@ -0,0 +1,94 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.datastore.impl; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.HashMap; +import java.util.Map; + +import net.torrent.torrent.TorrentFile; +import net.torrent.util.Range; + +/** + * An simple implementation for torrent. Stores files in local file system. + * + * @author Rogiel Josias Sulzbach + */ +public class SimpleTorrentDatastore extends FileAwareTorrentDatastore { + private final Map cache = new HashMap(); + private final File root; + + public SimpleTorrentDatastore(File root) { + this.root = root; + } + + public SimpleTorrentDatastore() { + this(new File(".")); + } + + @Override + protected void read(ByteBuffer buffer, TorrentFile file, Range range) + throws IOException { + final FileChannel channel = cache(file); + synchronized (channel) { + channel.position(file.getOffset() + range.getStart()); + while (buffer.hasRemaining()) { + if (channel.read(buffer) == -1) // EOF + break; + } + } + // don't flip the buffer! + } + + @Override + protected boolean write(ByteBuffer buffer, TorrentFile file, Range range) + throws IOException { + final FileChannel channel = cache(file); + synchronized (channel) { + channel.position(file.getOffset() + range.getStart()); + while (buffer.hasRemaining()) { + if (channel.write(buffer) == -1) + return false; + } + } + return true; + } + + /** + * Retrieve an cached {@link FileChannel channel}. If not cached, a new will + * be open in read/write mode and cached. + * + * @param file + * the {@link TorrentFile file} + * @return the cached {@link FileChannel channel} + * @throws FileNotFoundException + * if parent folder does not exists. + */ + private FileChannel cache(TorrentFile file) throws FileNotFoundException { + FileChannel channel = cache.get(file); + if (channel == null) { + channel = new RandomAccessFile(new File(root, + file.getRelativePath()), "rw").getChannel(); + cache.put(file, channel); + } + return channel; + } +} diff --git a/src/main/java/net/torrent/protocol/datastore/impl/VoidTorrentDatastore.java b/src/main/java/net/torrent/protocol/datastore/impl/VoidTorrentDatastore.java new file mode 100644 index 0000000..7180187 --- /dev/null +++ b/src/main/java/net/torrent/protocol/datastore/impl/VoidTorrentDatastore.java @@ -0,0 +1,47 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.datastore.impl; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import net.torrent.protocol.datastore.AbstractTorrentDatastore; +import net.torrent.torrent.TorrentPart; +import net.torrent.torrent.TorrentPiece; + +/** + * This is much like the /dev/null and /dev/zero pseudo-file + * in UNIX systems. All data written is discarded and all readed data is + * composed of NULL (0x00). Checksums always succeed. + * + * @author Rogiel Josias Sulzbach + */ +public class VoidTorrentDatastore extends AbstractTorrentDatastore { + @Override + public ByteBuffer read(TorrentPart part) throws IOException { + return ByteBuffer.allocate(part.getLength()); + } + + @Override + public boolean write(TorrentPart part, ByteBuffer buffer) { + return true; + } + + @Override + public boolean checksum(TorrentPiece piece) throws IOException { + return true; + } +} diff --git a/src/main/java/net/torrent/protocol/dht/DHTClient.java b/src/main/java/net/torrent/protocol/dht/DHTClient.java new file mode 100644 index 0000000..cd234c9 --- /dev/null +++ b/src/main/java/net/torrent/protocol/dht/DHTClient.java @@ -0,0 +1,23 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.dht; + +/** + * @author Rogiel Josias Sulzbach + */ +public class DHTClient { + +} diff --git a/src/main/java/net/torrent/protocol/peerwire/PeerWireManager.java b/src/main/java/net/torrent/protocol/peerwire/PeerWireManager.java new file mode 100644 index 0000000..69196d9 --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/PeerWireManager.java @@ -0,0 +1,197 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire; + +import java.net.InetSocketAddress; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import net.torrent.protocol.algorithm.TorrentAlgorithm; +import net.torrent.protocol.peerwire.handler.PeerWireAlgorithmHandler; +import net.torrent.protocol.peerwire.manager.TorrentManager; + +import org.jboss.netty.bootstrap.ClientBootstrap; +import org.jboss.netty.bootstrap.ServerBootstrap; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelHandler; +import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; +import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; + +/** + * Manager for the peerwire protocol. Can be used to start or stop the server + * and initiate new connections. + * + * @author Rogiel Josias Sulzbach + */ +public class PeerWireManager { + /** + * The Netty client's {@link ClientBootstrap bootstrap} + */ + private final ClientBootstrap client = new ClientBootstrap( + new NioClientSocketChannelFactory(Executors.newCachedThreadPool(), + Executors.newCachedThreadPool())); + /** + * The Netty server's {@link ServerBootstrap bootstrap} + */ + private final ServerBootstrap server = new ServerBootstrap( + new NioServerSocketChannelFactory(Executors.newCachedThreadPool(), + Executors.newCachedThreadPool())); + + /** + * The server's listen address + */ + private InetSocketAddress listenAddress; + /** + * The server's channel + */ + private Channel serverChannel; + + /** + * The pipeline channel factory + */ + private final PeerWirePipelineFactory pipelineFactory; + + /** + * Creates a new instance + * + * @param manager + * the torrent manager + * @param algorithm + * the torrent algorithm + * @param listenAddress + * the server's listen address + */ + public PeerWireManager(TorrentManager manager, ChannelHandler algorithm, + InetSocketAddress listenAddress) { + pipelineFactory = new PeerWirePipelineFactory(manager, algorithm); + + client.setPipelineFactory(pipelineFactory); + server.setPipelineFactory(pipelineFactory); + } + + /** + * Creates a new instance + * + * @param manager + * the torrent manager + * @param algorithm + * the torrent algorithm + */ + public PeerWireManager(TorrentManager manager, TorrentAlgorithm algorithm) { + this(manager, new PeerWireAlgorithmHandler(manager, algorithm), null); + } + + /** + * Listen the server to the given port + * + * @param port + * the port + * @return the server {@link Channel}. + */ + public Channel listen(int port) { + if (listenAddress == null) + listenAddress = new InetSocketAddress(port); + return (serverChannel = server.bind(listenAddress)); + } + + /** + * Connect to a new client + * + * @param address + * the address + * @return the {@link ChannelFuture} for monitoring the connection progress + */ + public ChannelFuture connect(InetSocketAddress address) { + return client.connect(address); + } + + /** + * Connect to a new client and wait until is complete. + * + * @param address + * the peer address + * @param wait + * wait time + * @param unit + * unit of wait + * @return the newly created {@link Channel} + */ + public Channel connectWait(InetSocketAddress address, long wait, + TimeUnit unit) { + final ChannelFuture future = connect(address); + if (future.awaitUninterruptibly(wait, unit)) { + if (future.isSuccess()) + return future.getChannel(); + } + return null; + } + + /** + * Close the server and the client, and then release the external resources. + */ + public void close() { + if (serverChannel != null) + serverChannel.close().awaitUninterruptibly(); + client.releaseExternalResources(); + } + + /** + * Get the server's listening channel + * + * @return + */ + public Channel getServerChannel() { + return serverChannel; + } + + /** + * Get the Netty client's {@link ClientBootstrap bootstrap} + * + * @return the {@link ClientBootstrap} + */ + protected ClientBootstrap getClientBootstrap() { + return client; + } + + /** + * Get the Netty server's {@link ServerBootstrap bootstrap} + * + * @return the {@link ServerBootstrap} + */ + protected ServerBootstrap getServerBootstrap() { + return server; + } + + /** + * Return the current server's listening address + * + * @return the listen address + */ + public InetSocketAddress getListenAddress() { + return listenAddress; + } + + /** + * Get the current server's listening address + * + * @param listenAddress + * the listen address + */ + public void setListenAddress(InetSocketAddress listenAddress) { + this.listenAddress = listenAddress; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/PeerWirePeer.java b/src/main/java/net/torrent/protocol/peerwire/PeerWirePeer.java new file mode 100644 index 0000000..9b1fdb9 --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/PeerWirePeer.java @@ -0,0 +1,401 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire; + +import java.nio.ByteBuffer; +import java.util.BitSet; + +import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage; +import net.torrent.protocol.peerwire.message.BitfieldMessage; +import net.torrent.protocol.peerwire.message.CancelMessage; +import net.torrent.protocol.peerwire.message.ChokeMessage; +import net.torrent.protocol.peerwire.message.HandshakeMessage; +import net.torrent.protocol.peerwire.message.HaveMessage; +import net.torrent.protocol.peerwire.message.InterestedMessage; +import net.torrent.protocol.peerwire.message.KeepAliveMessage; +import net.torrent.protocol.peerwire.message.NotInterestedMessage; +import net.torrent.protocol.peerwire.message.PieceMessage; +import net.torrent.protocol.peerwire.message.PortMessage; +import net.torrent.protocol.peerwire.message.RequestMessage; +import net.torrent.protocol.peerwire.message.UnchokeMessage; +import net.torrent.protocol.peerwire.message.fast.AllowedFastMessage; +import net.torrent.protocol.peerwire.message.fast.HaveAllMessage; +import net.torrent.protocol.peerwire.message.fast.HaveNoneMessage; +import net.torrent.protocol.peerwire.message.fast.RejectMessage; +import net.torrent.protocol.peerwire.message.fast.SuggestPieceMessage; +import net.torrent.torrent.context.TorrentPeer; +import net.torrent.torrent.context.TorrentPeer.ChokingState; +import net.torrent.torrent.context.TorrentPeer.InterestState; +import net.torrent.torrent.context.TorrentPeerCapabilities.TorrentPeerCapability; + +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelFutureListener; + +/** + * An PeerWire Peer manages the {@link Channel channel} (the current peer + * connection) and the {@link TorrentPeer torrent peer} object. + *

+ * This object contains handy methods to send messages to the peer. + * + * @author Rogiel Josias Sulzbach + */ +public class PeerWirePeer { + /** + * The active {@link Channel} + */ + private final Channel channel; + /** + * The active {@link TorrentPeer}. It might no be available until handshake. + */ + private TorrentPeer peer; + + /** + * Creates a new instance + * + * @param channel + * the channel + * @param peer + * the peer. Can be null. + */ + public PeerWirePeer(Channel channel, TorrentPeer peer) { + this.channel = channel; + this.peer = peer; + } + + /** + * Send an handshake message + * + * @param infohash + * the info hash + * @param peerId + * the peer id + * @param reserved + * the capabilities {@link BitSet} + * @return the {@link ChannelFuture} for the message + */ + public ChannelFuture handshake(byte[] infohash, byte[] peerId, + BitSet reserved) { + return write(new HandshakeMessage(infohash, peerId, reserved)); + } + + /** + * Send an bitfield message + * + * @param bitset + * the bitfield bits + * @return the {@link ChannelFuture} for the message + */ + public ChannelFuture bitfield(BitSet bitset) { + return write(new BitfieldMessage(bitset)); + } + + /** + * Send an choke message + */ + public void choke() { + if (peer.getChokingState() == ChokingState.CHOKED) + return; + write(new ChokeMessage()).addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) + throws Exception { + if (future.isSuccess()) { + peer.setChokingState(ChokingState.CHOKED); + } + } + }); + } + + /** + * Send an unchoke message + */ + public void unchoke() { + if (peer.getChokingState() == ChokingState.UNCHOKED) + return; + write(new UnchokeMessage()).addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) + throws Exception { + if (future.isSuccess()) { + peer.setChokingState(ChokingState.UNCHOKED); + } + } + }); + } + + /** + * Send an interest message + */ + public void interested() { + if (peer.getInterestState() == InterestState.INTERESTED) + return; + write(new InterestedMessage()).addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) + throws Exception { + if (future.isSuccess()) { + peer.setInterestState(InterestState.INTERESTED); + } + } + }); + } + + /** + * Send an not interest message + */ + public void uninterested() { + if (peer.getInterestState() == InterestState.UNINTERESTED) + return; + write(new NotInterestedMessage()).addListener( + new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) + throws Exception { + if (future.isSuccess()) { + peer.setInterestState(InterestState.UNINTERESTED); + } + } + }); + } + + /** + * Send an request message + * + * @param index + * the piece index + * @param start + * the part start + * @param length + * the part length + * @return the {@link ChannelFuture} for this message + */ + public ChannelFuture request(int index, int start, int length) { + return write(new RequestMessage(index, start, length)); + } + + /** + * Send an upload message + * + * @param index + * the index + * @param start + * the start + * @param length + * the length + * @param data + * the upload {@link ByteBuffer content} + */ + public void upload(int index, int start, int length, ByteBuffer data) { + this.unchoke(); + write(new PieceMessage(index, start, length, data)); + } + + /** + * Send an have message + * + * @param index + * the piece index + */ + public void have(int index) { + write(new HaveMessage(index)); + } + + /** + * Send an cancel message + * + * @param index + * the index + * @param start + * the start + * @param length + * the length + */ + public void cancel(int index, int start, int length) { + write(new CancelMessage(index, start, length)); + } + + /** + * Send an keep alive message + */ + public void keepAlive() { + write(new KeepAliveMessage()); + } + + // ////////////////////////////////////////////////////////////////////// + + /** + * *** REQUIRES {@link TorrentPeerCapability#DHT DHT Protocol} *** + *

+ * Send and port message. The port is used for UDP. + * + * @param port + * the port number + */ + public void port(short port) { + write(new PortMessage(port)); + } + + /** + * *** REQUIRES {@link TorrentPeerCapability#EXTENSION_PROTOCOL Extension + * Protocol} *** + *

+ * Send an have none message. + */ + public void haveNone() { + write(new HaveNoneMessage()); + } + + /** + * *** REQUIRES {@link TorrentPeerCapability#EXTENSION_PROTOCOL Extension + * Protocol} *** + *

+ * Send an have all message. + */ + public void haveAll() { + write(new HaveAllMessage()); + } + + /** + * *** REQUIRES {@link TorrentPeerCapability#EXTENSION_PROTOCOL Extension + * Protocol} *** + *

+ * Send an reject message. + * + * @param index + * the piece index + * @param start + * the part start + * @param length + * the part length + */ + public void reject(int index, int start, int length) { + this.choke(); + write(new RejectMessage(index, start, length)); + } + + /** + * *** REQUIRES {@link TorrentPeerCapability#EXTENSION_PROTOCOL Extension + * Protocol} *** + *

+ * Send an suggest message + * + * @param index + * the piece index + */ + public void suggest(int index) { + write(new SuggestPieceMessage(index)); + } + + /** + * *** REQUIRES {@link TorrentPeerCapability#EXTENSION_PROTOCOL Extension + * Protocol} *** + *

+ * Send an list of pieces allowed to download fast (download even when + * choked) + * + * @param indexes + * the piece indexes + */ + public void allowedFast(int... indexes) { + write(new AllowedFastMessage(indexes)); + } + + // ////////////////////////////////////////////////////////////////////// + + /** + * Write an message to the peer + * + * @return an {@link ChannelFuture} to monitor write progress + * @see Channel#write(Object) + */ + public ChannelFuture write(PeerWireWritableMessage message) { + return channel.write(message); + } + + /** + * Disconnect the peer + * + * @return an {@link ChannelFuture} to monitor disconnect progress + * @see Channel#diconnect() + */ + public ChannelFuture disconnect() { + return channel.disconnect(); + } + + /** + * Close the peer {@link Channel channel} + * + * @return an {@link ChannelFuture} to monitor close progress + * @see Channel#close() + */ + public ChannelFuture close() { + return channel.close(); + } + + /** + * Get the peer {@link Channel channel} + * + * @return the peer active {@link Channel} + */ + public Channel getChannel() { + return channel; + } + + /** + * Get the active {@link TorrentPeer} + * + * @return the active peer + */ + public TorrentPeer getTorrentPeer() { + return peer; + } + + /** + * Set the active peer + * + * @param peer + * the active peer + */ + public void setTorrentPeer(TorrentPeer peer) { + this.peer = peer; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((channel == null) ? 0 : channel.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PeerWirePeer other = (PeerWirePeer) obj; + if (channel == null) { + if (other.channel != null) + return false; + } else if (!channel.equals(other.channel)) + return false; + return true; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/PeerWirePipelineFactory.java b/src/main/java/net/torrent/protocol/peerwire/PeerWirePipelineFactory.java new file mode 100644 index 0000000..154d36d --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/PeerWirePipelineFactory.java @@ -0,0 +1,88 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire; + +import static org.jboss.netty.channel.Channels.pipeline; +import net.torrent.protocol.peerwire.codec.PeerWireDecoder; +import net.torrent.protocol.peerwire.codec.PeerWireEncoder; +import net.torrent.protocol.peerwire.handler.PeerWireManagerHeadHandler; +import net.torrent.protocol.peerwire.handler.PeerWireManagerTailHandler; +import net.torrent.protocol.peerwire.manager.TorrentManager; + +import org.jboss.netty.channel.ChannelHandler; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.handler.logging.LoggingHandler; +import org.jboss.netty.logging.InternalLogLevel; + +/** + * The {@link ChannelPipeline} factory for all PeerWire connections. + * + * @author Rogiel Josias Sulzbach + */ +public class PeerWirePipelineFactory implements ChannelPipelineFactory { + /** + * The logging handler + */ + private final LoggingHandler loggingHandler = new LoggingHandler( + InternalLogLevel.WARN); + /** + * The algorithm handler + */ + private final ChannelHandler algorithmHandler; + /** + * The head manager handler + */ + private final PeerWireManagerHeadHandler headManagerHandler; + /** + * The tail manager handler + */ + private final PeerWireManagerTailHandler tailManagerHandler; + + /** + * Creates a new instance + * + * @param manager + * the torrent manager + * @param algorithmHandler + * the algorithm handler + */ + public PeerWirePipelineFactory(TorrentManager manager, + ChannelHandler algorithmHandler) { + this.algorithmHandler = algorithmHandler; + this.headManagerHandler = new PeerWireManagerHeadHandler(manager); + this.tailManagerHandler = new PeerWireManagerTailHandler(manager); + } + + @Override + public ChannelPipeline getPipeline() throws Exception { + final ChannelPipeline pipeline = pipeline(); + + // TODO create traffic shape handler + // TODO create firewall handler - block connections from unwanted peers + + pipeline.addLast("decoder", new PeerWireDecoder()); + pipeline.addLast("encoder", new PeerWireEncoder()); + + pipeline.addLast("logging", loggingHandler); + + pipeline.addLast("head-handler", headManagerHandler); + pipeline.addLast("algorithm", algorithmHandler); + pipeline.addLast("tail-handler", tailManagerHandler); + + return pipeline; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/codec/PeerWireDecoder.java b/src/main/java/net/torrent/protocol/peerwire/codec/PeerWireDecoder.java new file mode 100644 index 0000000..09d82eb --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/codec/PeerWireDecoder.java @@ -0,0 +1,144 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.codec; + +import net.torrent.protocol.peerwire.message.BitfieldMessage; +import net.torrent.protocol.peerwire.message.CancelMessage; +import net.torrent.protocol.peerwire.message.ChokeMessage; +import net.torrent.protocol.peerwire.message.HandshakeMessage; +import net.torrent.protocol.peerwire.message.HaveMessage; +import net.torrent.protocol.peerwire.message.InterestedMessage; +import net.torrent.protocol.peerwire.message.KeepAliveMessage; +import net.torrent.protocol.peerwire.message.NotInterestedMessage; +import net.torrent.protocol.peerwire.message.PieceMessage; +import net.torrent.protocol.peerwire.message.PortMessage; +import net.torrent.protocol.peerwire.message.RequestMessage; +import net.torrent.protocol.peerwire.message.UnchokeMessage; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.frame.CorruptedFrameException; +import org.jboss.netty.handler.codec.frame.FrameDecoder; + +/** + * BitTorrent has two types of message headers: + *

+ *

Handshake message

Handshake messages are composed of 1 byte, which + * indicates the size of protocol string ("BitTorrent protocol"). + *

+ *

Messages

All other messages are 4-byte integer containing the + * message length, followed by 1 byte opcode. + *

+ *

+ * Instances of this class keep channel state content and must not be shared nor + * cached. + * + * @author Rogiel Josias Sulzbach + */ +public class PeerWireDecoder extends FrameDecoder { + private boolean handshaked = false; + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, + ChannelBuffer buffer) throws Exception { + buffer.markReaderIndex(); + + if (!handshaked) { + if (buffer.readableBytes() <= 47) // at least 47 bytes + return null; + + final int pstrlen = buffer.readByte(); + if (buffer.readableBytes() < pstrlen + 47) { + buffer.resetReaderIndex(); + return null; + } + buffer.readerIndex(buffer.readerIndex() - 1); + + final HandshakeMessage message = new HandshakeMessage(); + message.read(buffer); + handshaked = true; + + return message; + } else { + if (buffer.readableBytes() <= 4) { + buffer.resetReaderIndex(); + return null; + } + + int len = buffer.readInt(); + if (len == 0) { + return new KeepAliveMessage(); + } else if (buffer.readableBytes() < len) { + buffer.resetReaderIndex(); + return null; + } + + final byte id = buffer.readByte(); + final PeerWireReadableMessage message = getMessage(id); + if (message == null) + // force connection to be closed + throw new CorruptedFrameException("unknown message " + id); + message.read(buffer); + return message; + } + } + + /** + * Return the message represented by id. Will return null if + * message id is unknown. + * + * @param id + * the id of the message + * @return the message + */ + private PeerWireReadableMessage getMessage(byte id) { + PeerWireReadableMessage message = null; + switch (id) { + case BitfieldMessage.MESSAGE_ID: + message = new BitfieldMessage(); + break; + case CancelMessage.MESSAGE_ID: + message = new CancelMessage(); + break; + case ChokeMessage.MESSAGE_ID: + message = new ChokeMessage(); + break; + case HaveMessage.MESSAGE_ID: + message = new HaveMessage(); + break; + case InterestedMessage.MESSAGE_ID: + message = new InterestedMessage(); + break; + case NotInterestedMessage.MESSAGE_ID: + message = new NotInterestedMessage(); + break; + case PieceMessage.MESSAGE_ID: + message = new PieceMessage(); + break; + case PortMessage.MESSAGE_ID: + message = new PortMessage(); + break; + case RequestMessage.MESSAGE_ID: + message = new RequestMessage(); + break; + case UnchokeMessage.MESSAGE_ID: + message = new UnchokeMessage(); + break; + } + return message; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/codec/PeerWireEncoder.java b/src/main/java/net/torrent/protocol/peerwire/codec/PeerWireEncoder.java new file mode 100644 index 0000000..d4534ad --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/codec/PeerWireEncoder.java @@ -0,0 +1,52 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.codec; + +import net.torrent.protocol.peerwire.message.HandshakeMessage; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.oneone.OneToOneEncoder; + +/** + * Messages are encoded in {@link PeerWireWritableMessage#write(ChannelBuffer)} + * method. Message length is measured automatically by the encoder. + * + * @author Rogiel Josias Sulzbach + */ +public class PeerWireEncoder extends OneToOneEncoder { + @Override + protected Object encode(ChannelHandlerContext ctx, Channel channel, + Object msg) throws Exception { + ChannelBuffer buffer = ChannelBuffers.dynamicBuffer(); + if (!(msg instanceof PeerWireWritableMessage)) + return msg; + + if (msg instanceof HandshakeMessage) { + final HandshakeMessage message = (HandshakeMessage) msg; + message.write(buffer); + } else { + final PeerWireWritableMessage message = (PeerWireWritableMessage) msg; + buffer.writeInt(0); // allocate 4 bytes for header + message.write(buffer); + int len = buffer.readableBytes(); + buffer.setInt(0, len - 4); + } + return buffer; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/codec/PeerWireReadableMessage.java b/src/main/java/net/torrent/protocol/peerwire/codec/PeerWireReadableMessage.java new file mode 100644 index 0000000..3e492c2 --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/codec/PeerWireReadableMessage.java @@ -0,0 +1,36 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.codec; + +import java.io.IOException; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + * An readable message in the BitTorrent protocol + * + * @author Rogiel Josias Sulzbach + */ +public interface PeerWireReadableMessage { + /** + * Read the content of the message contained in this buffer. + * + * @param buffer + * the buffer + * @throws IOException + */ + void read(ChannelBuffer buffer) throws IOException; +} diff --git a/src/main/java/net/torrent/protocol/peerwire/codec/PeerWireWritableMessage.java b/src/main/java/net/torrent/protocol/peerwire/codec/PeerWireWritableMessage.java new file mode 100644 index 0000000..cb70ae8 --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/codec/PeerWireWritableMessage.java @@ -0,0 +1,36 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.codec; + +import java.io.IOException; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + * An writable message in the BitTorrent protocol + * + * @author Rogiel Josias Sulzbach + */ +public interface PeerWireWritableMessage { + /** + * Write the content of the message to this buffer. + * + * @param buffer + * the buffer + * @throws IOException + */ + void write(ChannelBuffer buffer) throws IOException; +} diff --git a/src/main/java/net/torrent/protocol/peerwire/handler/PeerWireAlgorithmHandler.java b/src/main/java/net/torrent/protocol/peerwire/handler/PeerWireAlgorithmHandler.java new file mode 100644 index 0000000..27e2e10 --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/handler/PeerWireAlgorithmHandler.java @@ -0,0 +1,481 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.handler; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.BitSet; + +import net.torrent.protocol.algorithm.TorrentAlgorithm; +import net.torrent.protocol.algorithm.TorrentInterestAlgorithm; +import net.torrent.protocol.algorithm.TorrentPeerAlgorithm; +import net.torrent.protocol.algorithm.TorrentPieceDownloadAlgorithm; +import net.torrent.protocol.algorithm.TorrentPieceUploadAlgorithm; +import net.torrent.protocol.datastore.TorrentDatastore; +import net.torrent.protocol.peerwire.PeerWirePeer; +import net.torrent.protocol.peerwire.manager.TorrentManager; +import net.torrent.protocol.peerwire.message.BitfieldMessage; +import net.torrent.protocol.peerwire.message.ChokeMessage; +import net.torrent.protocol.peerwire.message.HaveMessage; +import net.torrent.protocol.peerwire.message.InterestedMessage; +import net.torrent.protocol.peerwire.message.KeepAliveMessage; +import net.torrent.protocol.peerwire.message.NotInterestedMessage; +import net.torrent.protocol.peerwire.message.PieceMessage; +import net.torrent.protocol.peerwire.message.RequestMessage; +import net.torrent.protocol.peerwire.message.UnchokeMessage; +import net.torrent.protocol.peerwire.message.fast.AllowedFastMessage; +import net.torrent.protocol.peerwire.message.fast.SuggestPieceMessage; +import net.torrent.torrent.Torrent; +import net.torrent.torrent.TorrentPart; +import net.torrent.torrent.TorrentPiece; +import net.torrent.torrent.context.TorrentPeer; +import net.torrent.torrent.context.TorrentPeer.ChokingState; +import net.torrent.torrent.context.TorrentPeer.InterestState; +import net.torrent.torrent.context.TorrentPeerCapabilities.TorrentPeerCapability; +import net.torrent.util.PeerCallback; + +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.handler.timeout.IdleStateAwareChannelHandler; +import org.jboss.netty.handler.timeout.IdleStateEvent; + +/** + * Standard handler responsible for forwarding calls to {@link TorrentAlgorithm} + * methods. This class handles low-level protocol specific behavior. + * + * @author Rogiel Josias Sulzbach + */ +public class PeerWireAlgorithmHandler extends IdleStateAwareChannelHandler { + /** + * The torrent manager + */ + private final TorrentManager manager; + /** + * The torrent datastore + */ + private final TorrentDatastore datastore; + + /** + * The peer algorithm + */ + private final TorrentPeerAlgorithm peerAlgorithm; + /** + * The interest algorithm + */ + private final TorrentInterestAlgorithm interestAlgorithm; + /** + * The download algorithm + */ + private final TorrentPieceDownloadAlgorithm downloadAlgorithm; + /** + * The upload algorithm + */ + private final TorrentPieceUploadAlgorithm uploadAlgorithm; + + /** + * Creates a new handler + * + * @param manager + * the torrent manager + * @param algorithm + * the algorithm + */ + public PeerWireAlgorithmHandler(TorrentManager manager, + final TorrentAlgorithm algorithm) { + this.manager = manager; + this.datastore = manager.getDatastore(); + this.peerAlgorithm = algorithm.getPeerAlgorithm(); + this.interestAlgorithm = algorithm.getInterestAlgorithm(); + this.downloadAlgorithm = algorithm.getDownloadAlgorithm(); + this.uploadAlgorithm = algorithm.getUploadAlgorithm(); + } + + @Override + public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + final PeerWirePeer peer = manager.getPeerManager().getPeer( + e.getChannel()); + // TODO handshake with random peer id + peer.handshake(manager.getTorrent().getInfoHash().toByteArray(), + "-TR2050-mcm14ye4h2mq".getBytes(), manager.getContext() + .getCapabilites().toBitSet()); + peer.port((short) 1541); + super.channelConnected(ctx, e); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + final PeerWirePeer peer = manager.getPeerManager().getPeer( + e.getChannel()); + final Object msg = e.getMessage(); + + if (msg instanceof BitfieldMessage) { + final BitfieldMessage bitfield = (BitfieldMessage) msg; + final BitSet bitset = bitfield.getBitfield(); + peer.getTorrentPeer().getBitfield().setBits(bitset); + testInterest(peer); + } else if (msg instanceof InterestedMessage) { + peer.getTorrentPeer() + .setPeerInterestState(InterestState.INTERESTED); + peerIntrestUpdate(peer, peer.getTorrentPeer() + .getPeerInterestState()); + } else if (msg instanceof NotInterestedMessage) { + peer.getTorrentPeer().setPeerInterestState( + InterestState.UNINTERESTED); + peerIntrestUpdate(peer, peer.getTorrentPeer() + .getPeerInterestState()); + } else if (msg instanceof UnchokeMessage) { + peer.getTorrentPeer().setPeerChokingState(ChokingState.UNCHOKED); + peerChokeUpdate(peer, peer.getTorrentPeer().getPeerChokingState()); + } else if (msg instanceof ChokeMessage) { + peer.getTorrentPeer().setPeerChokingState(ChokingState.CHOKED); + peerChokeUpdate(peer, peer.getTorrentPeer().getPeerChokingState()); + } else if (msg instanceof RequestMessage) { + final RequestMessage request = (RequestMessage) msg; + final Torrent torrent = manager.getTorrent(); + final TorrentPart part = torrent.getPart(request.getIndex(), + request.getStart(), request.getLength()); + processRequest(peer, part); + } else if (msg instanceof PieceMessage) { + final PieceMessage pieceMsg = (PieceMessage) msg; + final Torrent torrent = manager.getTorrent(); + final TorrentPart part = torrent.getPart(pieceMsg.getIndex(), + pieceMsg.getStart(), pieceMsg.getLength()); + processDownload(peer, part, pieceMsg.getBlock()); + } else if (msg instanceof HaveMessage) { + final HaveMessage have = (HaveMessage) msg; + final Torrent torrent = manager.getTorrent(); + final TorrentPiece piece = torrent.getPiece(have.getPiece()); + peer.getTorrentPeer().getBitfield().setPiece(piece, true); + testInterest(peer); + } else if (msg instanceof KeepAliveMessage) { + keepAlive(peer); + } else if (msg instanceof AllowedFastMessage) { + final AllowedFastMessage fast = (AllowedFastMessage) msg; + final Torrent torrent = manager.getTorrent(); + for (final int index : fast.getPieces()) { + final TorrentPiece piece = torrent.getPiece(index); + allowedFast(peer, piece); + } + } else if (msg instanceof SuggestPieceMessage) { + final SuggestPieceMessage suggest = (SuggestPieceMessage) msg; + final Torrent torrent = manager.getTorrent(); + final TorrentPiece piece = torrent.getPiece(suggest.getPiece()); + suggested(peer, piece); + } + super.messageReceived(ctx, e); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) + throws Exception { + // any exception thrown, the channel is disconnected. + e.getCause().printStackTrace(); + e.getChannel().disconnect(); + super.exceptionCaught(ctx, e); + } + + @Override + public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e) + throws Exception { + final PeerWirePeer peer = manager.getPeerManager().getPeer( + e.getChannel()); + keepAlive(peer); + super.channelIdle(ctx, e); + } + + /** + * Test the interest in the peer + * + * @param peer + * the peer + */ + private void testInterest(PeerWirePeer peer) { + switch (interestAlgorithm.interested(peer.getTorrentPeer())) { + case INTERESTED: + peer.interested(); + return; + case UNINTERESTED: + testChoke(peer); + return; + } + } + + /** + * Test choke interest to this peer + * + * @param peer + * the peer + */ + private void testChoke(PeerWirePeer peer) { + switch (interestAlgorithm.choke(peer.getTorrentPeer())) { + case CHOKED: + peer.choke(); + return; + case UNCHOKED: + peer.unchoke(); + return; + } + } + + /** + * Handles the peer choke interest change. + * + * @param peer + * the peer + * @param state + * the new interest state + */ + private void peerIntrestUpdate(PeerWirePeer peer, InterestState state) { + switch (peerAlgorithm.interested(peer.getTorrentPeer(), state)) { + case CHOKED: + peer.choke(); + return; + + case UNCHOKED: + peer.unchoke(); + return; + } + } + + /** + * Handles the peer choke change. + * + * @param peer + * the peer + * @param state + * the choke state + */ + private void peerChokeUpdate(PeerWirePeer peer, ChokingState state) { + switch (peerAlgorithm.choked(peer.getTorrentPeer(), state)) { + case DISCONNECT: + peer.disconnect(); + break; + + case CONNECT_NEW_PEER: + peer.disconnect(); + connect(peerAlgorithm.connect()); + return; + + case DOWNLOAD: + download(peer, + downloadAlgorithm.getNextPart(peer.getTorrentPeer(), null)); + return; + } + } + + /** + * Process the upload request + * + * @param peer + * the peer + * @param part + * the part + * @throws IOException + */ + private void processRequest(PeerWirePeer peer, TorrentPart part) + throws IOException { + switch (uploadAlgorithm.request(peer.getTorrentPeer(), part)) { + case DISCONNECT: + peer.disconnect(); + break; + case REJECT: + if (!peer.getTorrentPeer().getCapabilities() + .supports(TorrentPeerCapability.FAST_PEERS)) + return; + peer.reject(part.getPiece().getIndex(), part.getStart(), + part.getLength()); + break; + case CONNECT_NEW_PEER: + peer.disconnect(); + connect(peerAlgorithm.connect()); + break; + case CHOKE: + peer.choke(); + break; + case UPLOAD: + upload(peer, part); + break; + } + } + + /** + * Processes an download + * + * @param peer + * the uploader peer + * @param part + * the part + * @param data + * the downloaded content + * @throws IOException + */ + private void processDownload(final PeerWirePeer peer, + final TorrentPart part, ByteBuffer data) throws IOException { + final TorrentPart nextPart = downloadAlgorithm.getNextPart( + peer.getTorrentPeer(), part); + boolean complete = downloadAlgorithm.isComplete(peer.getTorrentPeer(), + part.getPiece()); + final ChannelFuture future = download(peer, nextPart); + + // store piece + if (!datastore.write(part, data)) + return; + if (!complete) + return; + + if (datastore.checksum(part.getPiece())) { + manager.getContext().getBitfield().setPiece(part.getPiece(), true); + manager.getPeerManager().executeActive(new PeerCallback() { + @Override + public void callback(PeerWirePeer peer) { + peer.have(part.getPiece().getIndex()); + } + }); + } else { + System.exit(0); + manager.getContext().getBitfield().setPiece(part.getPiece(), false); + switch (downloadAlgorithm.corrupted(peer.getTorrentPeer(), + part.getPiece())) { + case CHOKE: + if (future != null && !future.cancel()) + peer.cancel(nextPart.getPiece().getIndex(), + nextPart.getStart(), nextPart.getLength()); + peer.choke(); + break; + case DISCONNECT: + peer.disconnect(); + break; + case CONNECT_NEW_PEER: + peer.disconnect(); + connect(peerAlgorithm.connect()); + break; + case CONTINUE: + break; + case CANCEL: + if (future != null && !future.cancel()) + peer.cancel(nextPart.getPiece().getIndex(), + nextPart.getStart(), nextPart.getLength()); + return; + } + } + } + + /** + * Check keep alive interest in this peer. + * + * @param peer + * the peer + */ + private void keepAlive(PeerWirePeer peer) { + switch (peerAlgorithm.keepAlive(peer.getTorrentPeer())) { + case DISCONNECT: + peer.disconnect(); + break; + case CONNECT_NEW_PEER: + peer.disconnect(); + connect(peerAlgorithm.connect()); + break; + case KEEP_ALIVE: + peer.keepAlive(); + break; + } + } + + /** + * Do the part request + * + * @param peer + * the uploader peer + * @param part + * the part + * @return an {@link ChannelFuture}. Can be used to cancel the message send. + */ + private ChannelFuture download(PeerWirePeer peer, TorrentPart part) { + if (part == null) { + testInterest(peer); + return null; + } + return peer.request(part.getPiece().getIndex(), part.getStart(), + part.getLength()); + } + + /** + * Do the part upload + * + * @param peer + * the downloader peer + * @param part + * the part + */ + private void upload(PeerWirePeer peer, TorrentPart part) throws IOException { + if (part == null) { + testChoke(peer); + return; + } + final ByteBuffer data = datastore.read(part); + peer.upload(part.getPiece().getIndex(), part.getStart(), + part.getLength(), data); + } + + /** + * Tries to establish an connection to peer + * + * @param peer + * the peer to be connected. + */ + private void connect(TorrentPeer peer) { + if (peer == null) + return; + // TODO + } + + /** + * Processes the suggested piece + * + * @param peer + * the suggesting peer + * @param piece + * the suggested piece + */ + private void suggested(PeerWirePeer peer, TorrentPiece piece) { + final TorrentPart part = downloadAlgorithm.sugested( + peer.getTorrentPeer(), piece); + if (part == null) + return; + download(peer, part); + } + + /** + * Processes the allowed fast piece + * + * @param peer + * the allowing peer + * @param piece + * the allowed piece + */ + private void allowedFast(PeerWirePeer peer, TorrentPiece piece) { + final TorrentPart part = downloadAlgorithm.allowedFast( + peer.getTorrentPeer(), piece); + if (part == null) + return; + download(peer, part); + } +} \ No newline at end of file diff --git a/src/main/java/net/torrent/protocol/peerwire/handler/PeerWireManagerHeadHandler.java b/src/main/java/net/torrent/protocol/peerwire/handler/PeerWireManagerHeadHandler.java new file mode 100644 index 0000000..ddad293 --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/handler/PeerWireManagerHeadHandler.java @@ -0,0 +1,102 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.handler; + +import java.net.InetSocketAddress; + +import net.torrent.protocol.peerwire.PeerWirePeer; +import net.torrent.protocol.peerwire.manager.TorrentManager; +import net.torrent.protocol.peerwire.message.HandshakeMessage; +import net.torrent.torrent.context.TorrentPeer; +import net.torrent.torrent.context.TorrentPeerID; +import net.torrent.torrent.context.TorrentPeerCapabilities.TorrentPeerCapability; + +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelHandler; + +/** + * Handles pre-algoritihm handler stuff. + *

+ * Updates {@link TorrentManager} state. + * + * @author Rogiel Josias Sulzbach + */ +public class PeerWireManagerHeadHandler extends SimpleChannelHandler { + /** + * The torrent manager + */ + private final TorrentManager manager; + + /** + * Creates a new instance + * + * @param manager + * the torrent manager + */ + public PeerWireManagerHeadHandler(TorrentManager manager) { + this.manager = manager; + } + + @Override + public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + manager.getConnectionManager().add(e.getChannel()); + manager.getPeerManager().add(e.getChannel(), null); + super.channelOpen(ctx, e); + } + + @Override + public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + if (!manager.getConnectionManager().update(e.getChannel())) { + e.getChannel().close(); + } + manager.getPeerManager().update(e.getChannel()); + super.channelConnected(ctx, e); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + final Channel channel = e.getChannel(); + final PeerWirePeer pwpeer = manager.getPeerManager().getPeer( + e.getChannel()); + final Object msg = e.getMessage(); + + if (msg instanceof HandshakeMessage) { + final HandshakeMessage handshake = (HandshakeMessage) msg; + final TorrentPeer peer = manager.getContext().getPeer( + TorrentPeerID.create(handshake.getPeerId()), + (InetSocketAddress) channel.getRemoteAddress()); + pwpeer.setTorrentPeer(peer); + peer.setSocketAddress((InetSocketAddress) channel + .getRemoteAddress()); + peer.getCapabilities().setCapabilities(handshake.getReserved()); + + // TODO send bitfield + if (peer.getCapabilities().supports( + TorrentPeerCapability.FAST_PEERS)) { + pwpeer.haveAll(); + } else { + // pwpeer.bitfield(manager.getContext().getBitfield().getBits()); + } + } + super.messageReceived(ctx, e); + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/handler/PeerWireManagerTailHandler.java b/src/main/java/net/torrent/protocol/peerwire/handler/PeerWireManagerTailHandler.java new file mode 100644 index 0000000..dfa4e2d --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/handler/PeerWireManagerTailHandler.java @@ -0,0 +1,145 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.handler; + +import net.torrent.protocol.peerwire.PeerWirePeer; +import net.torrent.protocol.peerwire.manager.TorrentManager; +import net.torrent.protocol.peerwire.message.CancelMessage; +import net.torrent.protocol.peerwire.message.PieceMessage; +import net.torrent.protocol.peerwire.message.RequestMessage; +import net.torrent.protocol.peerwire.message.fast.RejectMessage; +import net.torrent.torrent.Torrent; +import net.torrent.torrent.TorrentPart; + +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelFutureListener; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelHandler; + +/** + * Handles post-algorithm handler stuff. + *

+ * Updates {@link TorrentManager} state. + * + * @author Rogiel Josias Sulzbach + */ +public class PeerWireManagerTailHandler extends SimpleChannelHandler { + /** + * The torrent manager + */ + private final TorrentManager manager; + + /** + * Creates a new instance + * + * @param manager + * the torrent manager + */ + public PeerWireManagerTailHandler(TorrentManager manager) { + this.manager = manager; + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + final Object msg = e.getMessage(); + + if (msg instanceof PieceMessage) { + final PieceMessage pieceMsg = (PieceMessage) msg; + final Torrent torrent = manager.getTorrent(); + final TorrentPart part = torrent.getPart(pieceMsg.getIndex(), + pieceMsg.getStart(), pieceMsg.getLength()); + manager.getDownloadManager().remove(part); + } else if (msg instanceof RejectMessage) { + final RejectMessage reject = (RejectMessage) msg; + final Torrent torrent = manager.getTorrent(); + final TorrentPart part = torrent.getPart(reject.getIndex(), + reject.getStart(), reject.getLength()); + manager.getDownloadManager().remove(part); + } + super.messageReceived(ctx, e); + } + + @Override + public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + final PeerWirePeer peer = manager.getPeerManager().getPeer( + e.getChannel()); + final Object msg = e.getMessage(); + + if (msg instanceof PieceMessage) { + final PieceMessage message = (PieceMessage) msg; + final Torrent torrent = manager.getContext().getTorrent(); + final TorrentPart part = torrent.getPart(message.getIndex(), + message.getStart(), message.getLength()); + manager.getUploadManager().add(part, peer.getTorrentPeer()); + e.getFuture().addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) + throws Exception { + manager.getUploadManager().remove(part); + } + }); + } else if (msg instanceof RequestMessage) { + final RequestMessage message = (RequestMessage) msg; + final Torrent torrent = manager.getContext().getTorrent(); + final TorrentPart part = torrent.getPart(message.getIndex(), + message.getStart(), message.getLength()); + manager.getDownloadManager().add(part, peer.getTorrentPeer()); + } else if (msg instanceof CancelMessage) { + final CancelMessage message = (CancelMessage) msg; + final Torrent torrent = manager.getContext().getTorrent(); + final TorrentPart part = torrent.getPart(message.getIndex(), + message.getStart(), message.getLength()); + e.getFuture().addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) + throws Exception { + manager.getDownloadManager().remove(part); + } + }); + } + super.writeRequested(ctx, e); + } + + @Override + public void channelDisconnected(ChannelHandlerContext ctx, + ChannelStateEvent e) throws Exception { + manager.getConnectionManager().update(e.getChannel()); + final PeerWirePeer peer = manager.getPeerManager().update( + e.getChannel()); + if (peer.getTorrentPeer() != null) { + manager.getDownloadManager().remove(peer.getTorrentPeer()); + manager.getUploadManager().remove(peer.getTorrentPeer()); + } + super.channelDisconnected(ctx, e); + } + + @Override + public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) + throws Exception { + manager.getConnectionManager().remove(e.getChannel()); + final PeerWirePeer peer = manager.getPeerManager().remove( + e.getChannel()); + if (peer.getTorrentPeer() != null) { + manager.getDownloadManager().remove(peer.getTorrentPeer()); + manager.getUploadManager().remove(peer.getTorrentPeer()); + } + super.channelClosed(ctx, e); + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/manager/ConnectionManager.java b/src/main/java/net/torrent/protocol/peerwire/manager/ConnectionManager.java new file mode 100644 index 0000000..42ee6f7 --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/manager/ConnectionManager.java @@ -0,0 +1,165 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.manager; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import net.torrent.torrent.context.TorrentContext; + +import org.jboss.netty.channel.Channel; + +/** + * Connection manager: manages active and inactive connections. + * + * @author Rogiel Josias Sulzbach + */ +public class ConnectionManager implements Iterable { + /** + * The torrent context + */ + private final TorrentContext context; + + /** + * The list of active channels + */ + private final Set activeChannels = new HashSet(); + /** + * The list of inactive channels + */ + private final Set inactiveChannels = new HashSet(); + + /** + * Creates a new instance + * + * @param context + * the torrent context + */ + public ConnectionManager(TorrentContext context) { + this.context = context; + } + + /** + * Registers a new channel + * + * @param channel + * the channel + * @return true if channel was added. False if was already in list. + */ + public boolean add(Channel channel) { + if (channel.isConnected()) { + return activeChannels.add(channel); + } else { + return inactiveChannels.add(channel); + } + } + + /** + * Test if contains the channel. + * + * @param channel + * the channel + * @return true if channel is registered + */ + public boolean contains(Channel channel) { + if (activeChannels.contains(channel)) + return true; + if (inactiveChannels.contains(channel)) + return true; + return false; + } + + /** + * Remove the channel + * + * @param channel + * the channel + * @return true if channel was registered + */ + public boolean remove(Channel channel) { + if (activeChannels.remove(channel)) + return true; + if (inactiveChannels.remove(channel)) + return true; + return false; + } + + /** + * Updates the state of the channel. In practice, remove and adds again the + * channel. + * + * @param channel + * the channel + * @return true if channel has been added successfully. + */ + public boolean update(Channel channel) { + if (!remove(channel)) + return false; + return add(channel); + } + + /** + * Get to count of active connections + * + * @return the amount of active connections + */ + public int getActiveConnections() { + return activeChannels.size(); + } + + /** + * Get to count of inactive connections + * + * @return the amount of inactive connections + */ + public int getInactiveConnections() { + return inactiveChannels.size(); + } + + /** + * Get the list of active channels + * + * @return list of active channels + */ + public Set getActiveChannels() { + return Collections.unmodifiableSet(activeChannels); + } + + /** + * Get the list of inactive channels + * + * @return list of inactive channels + */ + public Set getInactiveChannels() { + return Collections.unmodifiableSet(inactiveChannels); + } + + @Override + public Iterator iterator() { + return activeChannels.iterator(); + } + + /** + * Get the torent context + * + * @return the torrent context + */ + public TorrentContext getContext() { + return context; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/manager/DownloadManager.java b/src/main/java/net/torrent/protocol/peerwire/manager/DownloadManager.java new file mode 100644 index 0000000..9eecd4f --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/manager/DownloadManager.java @@ -0,0 +1,107 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.manager; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import net.torrent.torrent.TorrentPart; +import net.torrent.torrent.TorrentPiece; +import net.torrent.torrent.context.TorrentContext; +import net.torrent.torrent.context.TorrentPeer; + +public class DownloadManager implements Iterable { + private final TorrentContext context; + + private final Map activeParts = new HashMap(); + + public DownloadManager(TorrentContext context) { + this.context = context; + } + + public boolean isDownloading(TorrentPart torrentPart) { + return activeParts.containsKey(torrentPart); + } + + public boolean isDownloading(TorrentPiece piece) { + for (final TorrentPart part : activeParts.keySet()) { + if (part.getPiece().equals(piece)) + return true; + } + return false; + } + + public boolean isDownloading(TorrentPeer peer) { + return activeParts.containsValue(peer); + } + + public TorrentPeer getPeer(TorrentPart torrentPart) { + return activeParts.get(torrentPart); + } + + public Set getTorrentParts(TorrentPeer peer) { + final Set parts = new HashSet(); + for (final Entry entry : activeParts + .entrySet()) { + if (entry.getValue().equals(peer)) + parts.add(entry.getKey()); + } + return parts; + } + + public boolean isInactive() { + return activeParts.isEmpty(); + } + + public TorrentPeer add(TorrentPart torrentPart, TorrentPeer peer) { + return activeParts.put(torrentPart, peer); + } + + public TorrentPeer remove(TorrentPart torrentPart) { + return activeParts.remove(torrentPart); + } + + public Set remove(TorrentPeer peer) { + final Set parts = new HashSet(); + for (TorrentPart part : getTorrentParts(peer)) { + if (activeParts.remove(part) != null) + parts.add(part); + } + return parts; + } + + public int getActiveDownloadsCount() { + return activeParts.size(); + } + + public Map getActiveDownloads() { + return Collections.unmodifiableMap(activeParts); + } + + @Override + public Iterator iterator() { + return activeParts.keySet().iterator(); + } + + public TorrentContext getContext() { + return context; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/manager/PeerManager.java b/src/main/java/net/torrent/protocol/peerwire/manager/PeerManager.java new file mode 100644 index 0000000..bd621c5 --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/manager/PeerManager.java @@ -0,0 +1,174 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.manager; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import net.torrent.protocol.peerwire.PeerWirePeer; +import net.torrent.torrent.context.TorrentContext; +import net.torrent.torrent.context.TorrentPeer; +import net.torrent.util.PeerCallback; + +import org.jboss.netty.channel.Channel; + +public class PeerManager implements Iterable { + private final TorrentContext context; + private final ConnectionManager connectionManager; + + private final Map activePeers = new HashMap(); + private final Map inactivePeers = new HashMap(); + + public PeerManager(TorrentContext context, + ConnectionManager connectionManager) { + this.context = context; + this.connectionManager = connectionManager; + } + + public boolean contains(Channel channel) { + if (activePeers.containsKey(channel)) + return true; + if (inactivePeers.containsKey(channel)) + return true; + return false; + } + + public boolean contains(PeerWirePeer peer) { + if (activePeers.containsValue(peer)) + return true; + if (inactivePeers.containsValue(peer)) + return true; + return false; + } + + public PeerWirePeer getPeer(Channel channel) { + PeerWirePeer peer = activePeers.get(channel); + if (peer == null) + peer = inactivePeers.get(channel); + return peer; + } + + public Channel getChannel(PeerWirePeer peer) { + for (final Entry entry : activePeers.entrySet()) { + if (entry.getValue().equals(peer)) + return entry.getKey(); + } + for (final Entry entry : inactivePeers + .entrySet()) { + if (entry.getValue().equals(peer)) + return entry.getKey(); + } + return null; + } + + public boolean isEmpty() { + return activePeers.isEmpty(); + } + + public PeerWirePeer add(Channel channel, TorrentPeer peer) { + if (channel.isConnected()) { + return activePeers.put(channel, new PeerWirePeer(channel, peer)); + } else { + return inactivePeers.put(channel, new PeerWirePeer(channel, peer)); + } + } + + public PeerWirePeer remove(Channel channel) { + PeerWirePeer peer; + if ((peer = activePeers.remove(channel)) != null) + return peer; + if ((peer = inactivePeers.remove(channel)) != null) + return peer; + return null; + } + + public PeerWirePeer remove(PeerWirePeer peer) { + final Channel channel = getChannel(peer); + PeerWirePeer peerRemoved; + if ((peerRemoved = activePeers.remove(channel)) != null) + return peerRemoved; + if ((peerRemoved = inactivePeers.remove(channel)) != null) + return peerRemoved; + return null; + } + + public PeerWirePeer update(Channel channel) { + PeerWirePeer peer; + if ((peer = remove(channel)) == null) + return null; + return add(channel, peer.getTorrentPeer()); + } + + public int getActivePeersCount() { + return activePeers.size(); + } + + public int getImactivePeersCount() { + return activePeers.size(); + } + + public Map getActivePeers() { + return Collections.unmodifiableMap(activePeers); + } + + public Map getInactivePeers() { + return Collections.unmodifiableMap(inactivePeers); + } + + public Set getActiveChannels() { + return Collections.unmodifiableSet(activePeers.keySet()); + } + + public Set getInactiveChannels() { + return Collections.unmodifiableSet(inactivePeers.keySet()); + } + + public void executeActive(PeerCallback callback) { + for (final Entry entry : this.activePeers + .entrySet()) { + callback.callback(entry.getValue()); + } + } + + public void executeInactive(PeerCallback callback) { + for (final Entry entry : this.inactivePeers + .entrySet()) { + callback.callback(entry.getValue()); + } + } + + public void execute(PeerCallback callback) { + executeActive(callback); + executeInactive(callback); + } + + @Override + public Iterator iterator() { + return activePeers.values().iterator(); + } + + public TorrentContext getContext() { + return context; + } + + public ConnectionManager getConnectionManager() { + return connectionManager; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/manager/TorrentManager.java b/src/main/java/net/torrent/protocol/peerwire/manager/TorrentManager.java new file mode 100644 index 0000000..7adb839 --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/manager/TorrentManager.java @@ -0,0 +1,68 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.manager; + +import net.torrent.protocol.datastore.TorrentDatastore; +import net.torrent.torrent.Torrent; +import net.torrent.torrent.context.TorrentContext; + +public class TorrentManager { + private final TorrentContext context; + + private final ConnectionManager connectionManager; + private final PeerManager peerManager; + private final DownloadManager downloadManager; + private final UploadManager uploadManager; + + private final TorrentDatastore datastore; + + public TorrentManager(TorrentContext context, TorrentDatastore datastore) { + this.context = context; + this.datastore = datastore; + connectionManager = new ConnectionManager(context); + peerManager = new PeerManager(context, connectionManager); + downloadManager = new DownloadManager(context); + uploadManager = new UploadManager(context); + } + + public TorrentContext getContext() { + return context; + } + + public TorrentDatastore getDatastore() { + return datastore; + } + + public ConnectionManager getConnectionManager() { + return connectionManager; + } + + public PeerManager getPeerManager() { + return peerManager; + } + + public DownloadManager getDownloadManager() { + return downloadManager; + } + + public UploadManager getUploadManager() { + return uploadManager; + } + + public Torrent getTorrent() { + return context.getTorrent(); + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/manager/UploadManager.java b/src/main/java/net/torrent/protocol/peerwire/manager/UploadManager.java new file mode 100644 index 0000000..a0863ad --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/manager/UploadManager.java @@ -0,0 +1,98 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.manager; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import net.torrent.torrent.TorrentPart; +import net.torrent.torrent.context.TorrentContext; +import net.torrent.torrent.context.TorrentPeer; + +public class UploadManager implements Iterable { + private final TorrentContext context; + + private final Map activeParts = new HashMap(); + + public UploadManager(TorrentContext context) { + this.context = context; + } + + public boolean isUploading(TorrentPart torrentPart) { + return activeParts.containsKey(torrentPart); + } + + public boolean isUploading(TorrentPeer peer) { + return activeParts.containsValue(peer); + } + + public TorrentPeer getPeer(TorrentPart torrentPart) { + return activeParts.get(torrentPart); + } + + public Set getTorrentParts(TorrentPeer peer) { + final Set parts = new HashSet(); + for (final Entry entry : activeParts + .entrySet()) { + if (entry.getValue().equals(peer)) + parts.add(entry.getKey()); + } + return parts; + } + + public boolean isInactive() { + return activeParts.isEmpty(); + } + + public TorrentPeer add(TorrentPart torrentPart, TorrentPeer peer) { + return activeParts.put(torrentPart, peer); + } + + public TorrentPeer remove(TorrentPart torrentPart) { + return activeParts.remove(torrentPart); + } + + public Set remove(TorrentPeer peer) { + final Set parts = new HashSet(); + for (TorrentPart part : getTorrentParts(peer)) { + if (activeParts.remove(part) != null) + parts.add(part); + } + return parts; + } + + public int getActiveUploadsCount() { + return activeParts.size(); + } + + public Map getActiveUploads() { + return Collections.unmodifiableMap(activeParts); + } + + @Override + public Iterator iterator() { + return activeParts.keySet().iterator(); + } + + public TorrentContext getContext() { + return context; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/message/BitfieldMessage.java b/src/main/java/net/torrent/protocol/peerwire/message/BitfieldMessage.java new file mode 100644 index 0000000..aa25c18 --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/message/BitfieldMessage.java @@ -0,0 +1,119 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.message; + +import java.io.IOException; +import java.util.BitSet; + +import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage; +import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + *

+ * bitfield: [len=0001+X][id=5][bitfield]
+ * 
+ * + * The bitfield message may only be sent immediately after the handshaking + * sequence is completed, and before any other messages are sent. It is + * optional, and need not be sent if a client has no pieces. The bitfield + * message is variable length, where X is the length of the bitfield. The + * payload is a bitfield representing the pieces that have been successfully + * downloaded. The high bit in the first byte corresponds to piece index 0. Bits + * that are cleared indicated a missing piece, and set bits indicate a valid and + * available piece. Spare bits at the end are set to zero. A bitfield of the + * wrong length is considered an error. Clients should drop the connection if + * they receive bitfields that are not of the correct size, or if the bitfield + * has any of the spare bits set. + * + * @author Rogiel Josias Sulzbach + * @source BitTorrent documentation + */ +public class BitfieldMessage implements PeerWireWritableMessage, + PeerWireReadableMessage { + public static final byte MESSAGE_ID = 0x05; + + /** + * A set of bits with the following scheme: + *
    + *
  • true: has the piece
  • + * false: piece is missing + *
+ */ + private BitSet bitfield; + + public BitfieldMessage(BitSet bitfield) { + this.bitfield = bitfield; + } + + public BitfieldMessage() { + } + + @Override + public void read(ChannelBuffer buffer) throws IOException { + buffer.readerIndex(buffer.readerIndex() - 5); + int len = buffer.readInt() - 2; + buffer.readByte(); // unk + + bitfield = new BitSet(len * 8); + int i = 0; + int read = 0; + while (read <= len) { + byte b = buffer.readByte(); + for (int j = 128; j > 0; j >>= 1) { + bitfield.set(i++, (b & j) != 0); + } + read++; + } + } + + @Override + public void write(ChannelBuffer buffer) throws IOException { + buffer.writeByte(MESSAGE_ID); + // byte[] bytes = new byte[bitfield.size() / 8 - 4]; + // for (int i = 0; i < bitfield.size(); i++) { + // if((bytes.length - i / 8 - 1) > bytes.length) + // break; + // if (bitfield.get(i)) { + // bytes[bytes.length - i / 8 - 1] |= 1 << (i % 8); + // } + // } + // buffer.put(bytes); + for (int i = 0; i < bitfield.size();) { + byte data = 0; + for (int j = 128; i < bitfield.size() && j > 0; j >>= 1, i++) { + if (bitfield.get(i)) { + data |= j; + } + } + buffer.writeByte(data); + } + } + + public BitSet getBitfield() { + return bitfield; + } + + public void setBitfield(BitSet bitfield) { + this.bitfield = bitfield; + } + + @Override + public String toString() { + return "BitfieldMessage [bitfield=" + bitfield + "]"; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/message/CancelMessage.java b/src/main/java/net/torrent/protocol/peerwire/message/CancelMessage.java new file mode 100644 index 0000000..a438578 --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/message/CancelMessage.java @@ -0,0 +1,107 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.message; + +import java.io.IOException; + +import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage; +import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + *
+ * cancel: [len=0013][id=8][index][begin][length]
+ * 
+ * + * The cancel message is fixed length, and is used to cancel block requests. The + * payload is identical to that of the "request" message. It is typically used + * during "End Game" (check for algorithms). + * + * @author Rogiel Josias Sulzbach + * @source BitTorrent documentation + */ +public class CancelMessage implements PeerWireWritableMessage, + PeerWireReadableMessage { + public static final byte MESSAGE_ID = 0x08; + + /** + * The piece index + */ + private int index; + /** + * The start offset + */ + private int start; + /** + * The length + */ + private int length; + + public CancelMessage() { + } + + public CancelMessage(int index, int start, int length) { + this.index = index; + this.start = start; + this.length = length; + } + + @Override + public void read(ChannelBuffer buffer) throws IOException { + this.index = buffer.readInt(); + this.start = buffer.readInt(); + this.length = buffer.readInt(); + } + + @Override + public void write(ChannelBuffer buffer) throws IOException { + buffer.writeByte(MESSAGE_ID); + buffer.writeInt(index); + buffer.writeInt(start); + buffer.writeInt(length); + } + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + } + + public int getStart() { + return start; + } + + public void setStart(int start) { + this.start = start; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } + + @Override + public String toString() { + return "CancelMessage [index=" + index + ", start=" + start + + ", length=" + length + "]"; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/message/ChokeMessage.java b/src/main/java/net/torrent/protocol/peerwire/message/ChokeMessage.java new file mode 100644 index 0000000..07a559e --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/message/ChokeMessage.java @@ -0,0 +1,52 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.message; + +import java.io.IOException; + +import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage; +import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + *
+ * choke: [len=0001][id=0]
+ * 
+ * + * The choke message is fixed-length and has no payload. + * + * @author Rogiel Josias Sulzbach + * @source BitTorrent documentation + */ +public class ChokeMessage implements PeerWireWritableMessage, + PeerWireReadableMessage { + public static final byte MESSAGE_ID = 0x00; + + @Override + public void read(ChannelBuffer buffer) throws IOException { + } + + @Override + public void write(ChannelBuffer buffer) throws IOException { + buffer.writeByte(MESSAGE_ID); + } + + @Override + public String toString() { + return "ChokeMessage []"; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/message/HandshakeMessage.java b/src/main/java/net/torrent/protocol/peerwire/message/HandshakeMessage.java new file mode 100644 index 0000000..87dc18c --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/message/HandshakeMessage.java @@ -0,0 +1,167 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.message; + +import java.io.IOException; +import java.util.Arrays; +import java.util.BitSet; + +import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage; +import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + * TODO documentation + * + * @author Rogiel Josias Sulzbach + */ +public class HandshakeMessage implements PeerWireWritableMessage, + PeerWireReadableMessage { + /** + * Length of "BitTorrent protocol" string. + */ + private int pstrlen = 19; + /** + * The protocol string, must match to validate the protocol connection + */ + private String pstr = "BitTorrent protocol"; + /** + * Addons supported by the client (bitfield) + */ + private BitSet reserved = new BitSet(64); + /** + * The torrent's hash + */ + private byte[] infohash; + /** + * The peer id + */ + private byte[] peerId; + + public HandshakeMessage(byte[] infohash, byte[] peerId, BitSet reserved) { + this.infohash = infohash; + this.peerId = peerId; + this.reserved = reserved; + } + + public HandshakeMessage() { + } + + @Override + public void read(ChannelBuffer buffer) throws IOException { + this.pstrlen = buffer.readByte(); + this.pstr = new String(buffer.readBytes(pstrlen).array()); + + for (int i = 0; i < 8; i++) { + byte b = buffer.readByte(); + for (int j = 0; j < 8; j++) { + if ((b & (1 << j)) > 0) { + reserved.set(i * 8 + j); + } + } + } + System.out.println(reserved); + // int bit = 0; + // for (int i = 0; i < 8; i++) { + // byte b = buffer.readByte(); + // for (int j = 128; j > 0; j >>= 1) { + // reserved.set(bit++, (b & j) != 0); + // } + // } + + // this.reserved = buffer.readBytes(8).array(); + this.infohash = buffer.readBytes(20).array(); + this.peerId = buffer.readBytes(20).array(); + } + + @Override + public void write(ChannelBuffer buffer) throws IOException { + buffer.writeByte((byte) this.pstr.length()); + buffer.writeBytes(this.pstr.getBytes()); + + for (int i = 0; i < 64;) { + byte data = 0; + for (int j = 128; i < reserved.size() && j > 0; j >>= 1, i++) { + if (reserved.get(i)) { + data |= j; + } + } + buffer.writeByte(data); + } + + // buffer.writeBytes(reserved); + buffer.writeBytes(this.infohash); + buffer.writeBytes(this.peerId); + } + + public int getPstrlen() { + return pstrlen; + } + + public void setPstrlen(int pstrlen) { + this.pstrlen = pstrlen; + } + + public String getPstr() { + return pstr; + } + + public void setPstr(String pstr) { + this.pstr = pstr; + } + + public BitSet getReserved() { + return reserved; + } + + public void setReserved(BitSet reserved) { + this.reserved = reserved; + } + + public byte[] getInfohash() { + return infohash; + } + + public void setInfohash(byte[] infohash) { + this.infohash = infohash; + } + + public byte[] getPeerId() { + return peerId; + } + + public void setPeerId(byte[] peerId) { + this.peerId = peerId; + } + + @Override + public String toString() { + final int maxLen = 10; + return "HandshakeMessage [pstrlen=" + + pstrlen + + ", pstr=" + + pstr + + ", reserved=" + + reserved + + ", infohash=" + + (infohash != null ? Arrays.toString(Arrays.copyOf(infohash, + Math.min(infohash.length, maxLen))) : null) + + ", peerId=" + + (peerId != null ? Arrays.toString(Arrays.copyOf(peerId, + Math.min(peerId.length, maxLen))) : null) + "]"; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/message/HaveMessage.java b/src/main/java/net/torrent/protocol/peerwire/message/HaveMessage.java new file mode 100644 index 0000000..9a185ca --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/message/HaveMessage.java @@ -0,0 +1,87 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.message; + +import java.io.IOException; + +import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage; +import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + *
+ * have: [len=0005][id=4][piece index(int)]
+ * 
+ * + * The have message is fixed length. The payload is the zero-based index of a + * that has just been successfully downloaded and verified via the hash.
+ *
+ * Implementer's Note: That is the strict definition, in reality some + * games may be played. In particular because peers are extremely unlikely to + * download pieces that they already have, a peer may choose not to advertise + * having a piece to a peer that already has that piece. At a minimum + * "HAVE supression" will result in a 50% reduction in the number of HAVE + * messages, this translates to around a 25-35% reduction in protocol overhead. + * At the same time, it may be worthwhile to send a HAVE message to a peer that + * has that piece already since it will be useful in determining which piece is + * rare. A malicious peer might also choose to advertise having pieces that it + * knows the peer will never download. Due to this attempting to model peers + * using this information is a bad idea. + * + * @author Rogiel Josias Sulzbach + * @source BitTorrent documentation + */ +public class HaveMessage implements PeerWireWritableMessage, + PeerWireReadableMessage { + public static final byte MESSAGE_ID = 0x04; + + /** + * The new obtained piece index + */ + private int piece; + + public HaveMessage(int piece) { + this.piece = piece; + } + + public HaveMessage() { + } + + @Override + public void read(ChannelBuffer buffer) throws IOException { + this.piece = buffer.readInt(); + } + + @Override + public void write(ChannelBuffer buffer) throws IOException { + buffer.writeByte(MESSAGE_ID); + buffer.writeInt(piece); + } + + public int getPiece() { + return piece; + } + + public void setPiece(int piece) { + this.piece = piece; + } + + @Override + public String toString() { + return "HaveMessage [piece=" + piece + "]"; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/message/InterestedMessage.java b/src/main/java/net/torrent/protocol/peerwire/message/InterestedMessage.java new file mode 100644 index 0000000..c840107 --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/message/InterestedMessage.java @@ -0,0 +1,52 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.message; + +import java.io.IOException; + +import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage; +import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + *
+ * interested: [len=0001][id=2]
+ * 
+ * + * The interested message is fixed-length and has no payload. + * + * @author Rogiel Josias Sulzbach + * @source BitTorrent documentation + */ +public class InterestedMessage implements PeerWireWritableMessage, + PeerWireReadableMessage { + public static final byte MESSAGE_ID = 0x02; + + @Override + public void read(ChannelBuffer buffer) throws IOException { + } + + @Override + public void write(ChannelBuffer buffer) throws IOException { + buffer.writeByte(MESSAGE_ID); + } + + @Override + public String toString() { + return "InterestedMessage []"; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/message/KeepAliveMessage.java b/src/main/java/net/torrent/protocol/peerwire/message/KeepAliveMessage.java new file mode 100644 index 0000000..a431dac --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/message/KeepAliveMessage.java @@ -0,0 +1,54 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.message; + +import java.io.IOException; + +import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage; +import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + *
+ * keep-alive: [len=0000]
+ * 
+ * + * The keep-alive message is a message with zero bytes, specified with the + * length prefix set to zero. There is no message ID and no payload. Peers may + * close a connection if they receive no messages (keep-alive or any other + * message) for a certain period of time, so a keep-alive message must be sent + * to maintain the connection alive if no command have been sent for a given + * amount of time. This amount of time is generally two minutes. + * + * @author Rogiel Josias Sulzbach + * @source BitTorrent documentation + */ +public class KeepAliveMessage implements PeerWireWritableMessage, + PeerWireReadableMessage { + @Override + public void read(ChannelBuffer buffer) throws IOException { + } + + @Override + public void write(ChannelBuffer buffer) throws IOException { + } + + @Override + public String toString() { + return "KeepAliveMessage []"; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/message/NotInterestedMessage.java b/src/main/java/net/torrent/protocol/peerwire/message/NotInterestedMessage.java new file mode 100644 index 0000000..b65c4c4 --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/message/NotInterestedMessage.java @@ -0,0 +1,52 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.message; + +import java.io.IOException; + +import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage; +import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + *
+ * not interested: [len=0001][id=3]
+ * 
+ * + * The not interested message is fixed-length and has no payload. + * + * @author Rogiel Josias Sulzbach + * @source BitTorrent documentation + */ +public class NotInterestedMessage implements PeerWireWritableMessage, + PeerWireReadableMessage { + public static final byte MESSAGE_ID = 0x03; + + @Override + public void read(ChannelBuffer buffer) throws IOException { + } + + @Override + public void write(ChannelBuffer buffer) throws IOException { + buffer.writeByte(MESSAGE_ID); + } + + @Override + public String toString() { + return "NotInterestedMessage []"; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/message/PieceMessage.java b/src/main/java/net/torrent/protocol/peerwire/message/PieceMessage.java new file mode 100644 index 0000000..ffb5119 --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/message/PieceMessage.java @@ -0,0 +1,131 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.message; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage; +import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + *
+ * piece: [len=0009+X][id=7][index][begin][block]
+ * 
+ * + * The piece message is variable length, where X is the length of the block. The + * payload contains the following information:
+ * index: integer specifying the zero-based piece index
+ * begin: integer specifying the zero-based byte offset within the piece
+ * block: block of data, which is a subset of the piece specified by index. + * + * @author Rogiel Josias Sulzbach + * @source BitTorrent documentation + */ +public class PieceMessage implements PeerWireWritableMessage, + PeerWireReadableMessage { + public static final byte MESSAGE_ID = 0x07; + + /** + * The piece index + */ + private int index; + /** + * The part start offset + */ + private int start; + /** + * The part length + */ + private int length; + /** + * The downloaded/uploaded data + */ + private ByteBuffer block; + + public PieceMessage(RequestMessage request, ByteBuffer block) { + this.index = request.getIndex(); + this.start = request.getStart(); + this.length = request.getLength(); + this.block = block; + } + + public PieceMessage(int index, int start, int length, ByteBuffer block) { + this.index = index; + this.start = start; + this.length = length; + this.block = block; + } + + public PieceMessage() { + } + + @Override + public void read(ChannelBuffer buffer) throws IOException { + this.index = buffer.readInt(); + this.start = buffer.readInt(); + this.length = buffer.readableBytes(); + this.block = buffer.readBytes(length).toByteBuffer(); + } + + @Override + public void write(ChannelBuffer buffer) throws IOException { + buffer.writeByte(MESSAGE_ID); + buffer.writeInt(index); + buffer.writeInt(start); + buffer.writeBytes(block); + } + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + } + + public int getStart() { + return start; + } + + public void setStart(int start) { + this.start = start; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } + + public ByteBuffer getBlock() { + return block; + } + + public void setBlock(ByteBuffer block) { + this.block = block; + } + + @Override + public String toString() { + return "PieceMessage [index=" + index + ", start=" + start + + ", length=" + length + ", block=" + block + "]"; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/message/PortMessage.java b/src/main/java/net/torrent/protocol/peerwire/message/PortMessage.java new file mode 100644 index 0000000..3482c66 --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/message/PortMessage.java @@ -0,0 +1,77 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.message; + +import java.io.IOException; + +import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage; +import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + *
+ * port: [len=0003][id=9][listen-port]
+ * 
+ * + * The port message is sent by newer versions of the Mainline that implements a + * DHT tracker. The listen port is the port this peer's DHT node is listening + * on. This peer should be inserted in the local routing table (if DHT tracker + * is supported). + * + * @author Rogiel Josias Sulzbach + * @source BitTorrent documentation + */ +public class PortMessage implements PeerWireWritableMessage, + PeerWireReadableMessage { + public static final byte MESSAGE_ID = 0x09; + + /** + * The DHT port + */ + private short port; + + public PortMessage() { + } + + public PortMessage(short port) { + this.port = port; + } + + @Override + public void read(ChannelBuffer buffer) throws IOException { + this.port = buffer.readShort(); + } + + @Override + public void write(ChannelBuffer buffer) throws IOException { + buffer.writeByte(MESSAGE_ID); + buffer.writeShort(port); + } + + public short getPort() { + return port; + } + + public void setPort(short port) { + this.port = port; + } + + @Override + public String toString() { + return "PortMessage [port=" + port + "]"; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/message/RequestMessage.java b/src/main/java/net/torrent/protocol/peerwire/message/RequestMessage.java new file mode 100644 index 0000000..aef508e --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/message/RequestMessage.java @@ -0,0 +1,145 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.message; + +import java.io.IOException; + +import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage; +import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + *
+ * request: [len=0013][id=6][index][begin][length]
+ * 
+ * + * The request message is fixed length, and is used to request a block. The + * payload contains the following information:
+ * index: integer specifying the zero-based piece index
+ * begin: integer specifying the zero-based byte offset within the piece
+ * length: integer specifying the requested length.
+ * View #1 According to the official specification, "All current implementations + * use 2^15 (32KB), and close connections which request an amount greater than + * 2^17 (128KB)." As early as version 3 or 2004, this behavior was changed to + * use 2^14 (16KB) blocks. As of version 4.0 or mid-2005, the mainline + * disconnected on requests larger than 2^14 (16KB); and some clients have + * followed suit. Note that block requests are smaller than pieces (>=2^18 + * bytes), so multiple requests will be needed to download a whole piece.
+ * Strictly, the specification allows 2^15 (32KB) requests. The reality is near + * all clients will now use 2^14 (16KB) requests. Due to clients that enforce + * that size, it is recommended that implementations make requests of that size. + * Due to smaller requests resulting in higher overhead due to tracking a + * greater number of requests, implementers are advised against going below 2^14 + * (16KB). The choice of request block size limit enforcement is not nearly so + * clear cut. With mainline version 4 enforcing 16KB requests, most clients will + * use that size.
+ * At the same time 2^14 (16KB) is the semi-official (only semi because the + * official protocol document has not been updated) limit now, so enforcing that + * isn't wrong. At the same time, allowing larger requests enlarges the set of + * possible peers, and except on very low bandwidth connections (<256kbps) + * multiple blocks will be downloaded in one choke-timeperiod, thus merely + * enforcing the old limit causes minimal performance degradation. Due to this + * factor, it is recommended that only the older 2^17 (128KB) maximum size limit + * be enforced. View #2 This section has contained falsehoods for a large + * portion of the time this page has existed. This is the third time I (uau) am + * correcting this same section for incorrect information being added, so I + * won't rewrite it completely since it'll probably be broken again... Current + * version has at least the following errors: Mainline started using 2^14 + * (16384) byte requests when it was still the only client in existence; only + * the "official specification" still talked about the obsolete 32768 byte value + * which was in reality neither the default size nor maximum allowed. In version + * 4 the request behavior did not change, but the maximum allowed size did + * change to equal the default size. In latest mainline versions the max has + * changed to 32768 (note that this is the first appearance of 32768 for either + * default or max size since the first ancient versions).
+ * "Most older clients use 32KB requests" is false. Discussion of larger + * requests fails to take latency effects into account. + * + * @author Rogiel Josias Sulzbach + * @source BitTorrent documentation + */ +public class RequestMessage implements PeerWireWritableMessage, + PeerWireReadableMessage { + public static final byte MESSAGE_ID = 0x06; + + /** + * The piece index + */ + private int index; + /** + * The piece start offset + */ + private int start; + /** + * The part length + */ + private int length; + + public RequestMessage(int index, int start, int length) { + this.index = index; + this.start = start; + this.length = length; + } + + public RequestMessage() { + } + + @Override + public void read(ChannelBuffer buffer) throws IOException { + this.index = buffer.readInt(); + this.start = buffer.readInt(); + this.length = buffer.readInt(); + } + + @Override + public void write(ChannelBuffer buffer) throws IOException { + buffer.writeByte(MESSAGE_ID); + buffer.writeInt(index); + buffer.writeInt(start); + buffer.writeInt(length); + } + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + } + + public int getStart() { + return start; + } + + public void setStart(int start) { + this.start = start; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } + + @Override + public String toString() { + return "RequestMessage [index=" + index + ", start=" + start + + ", length=" + length + "]"; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/message/UnchokeMessage.java b/src/main/java/net/torrent/protocol/peerwire/message/UnchokeMessage.java new file mode 100644 index 0000000..a8e26e7 --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/message/UnchokeMessage.java @@ -0,0 +1,52 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.message; + +import java.io.IOException; + +import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage; +import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + *
+ * unchoke: [len=0001][id=1]
+ * 
+ * + * The unchoke message is fixed-length and has no payload. + * + * @author Rogiel Josias Sulzbach + * @source BitTorrent documentation + */ +public class UnchokeMessage implements PeerWireWritableMessage, + PeerWireReadableMessage { + public static final byte MESSAGE_ID = 0x01; + + @Override + public void read(ChannelBuffer buffer) throws IOException { + } + + @Override + public void write(ChannelBuffer buffer) throws IOException { + buffer.writeByte(MESSAGE_ID); + } + + @Override + public String toString() { + return "UnchokeMessage []"; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/message/fast/AllowedFastMessage.java b/src/main/java/net/torrent/protocol/peerwire/message/fast/AllowedFastMessage.java new file mode 100644 index 0000000..83fcac9 --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/message/fast/AllowedFastMessage.java @@ -0,0 +1,114 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.message.fast; + +import java.io.IOException; +import java.util.Arrays; + +import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage; +import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + *
+ * AllowedFast: [len=0x0004+pieces(4 bytes each)][id=0x11][piece indexes(int)]*
+ * 
+ * + * With the BitTorrent protocol specified in BEP 0003 [2], new peers take + * several minutes to ramp up before they can effectively engage in BitTorrent's + * tit-for-tat. The reason is simple: starting peers have few pieces to trade. + *

+ * Allowed Fast is an advisory message which means + * "if you ask for this piece, I'll give it to you even if you're choked." + * Allowed Fast thus shortens the awkward stage during which the peer obtains + * occasional optimistic unchokes but cannot sufficiently reciprocate to remain + * unchoked. + *

+ * The pieces that can be downloaded when choked constitute a peer's allowed + * fast set. The set is generated using a canonical algorithm that produces + * piece indices unique to the message receiver so that if two peers offer k + * pieces fast it will be the same k, and if one offers k+1 it will be the same + * k plus one more. k should be small enough to avoid abuse, but large enough to + * ramp up tit-for-tat. We currently set k to 10, but peers are free to change + * this number, e.g., to suit load. + *

+ * The message sender MAY list pieces that the message sender does not have. The + * receiver MUST NOT interpret an Allowed Fast message as meaning that the + * message sender has the piece. This allows peers to generate and communicate + * allowed fast sets at the beginning of a connection. However, a peer MAY send + * Allowed Fast messages at any time. + *

+ * A peer SHOULD send Allowed Fast messages to any starting peer unless the + * local peer lacks sufficient resources. A peer MAY reject requests for already + * Allowed Fast pieces if the local peer lacks sufficient resources, if the + * requested piece has already been sent to the requesting peer, or if the + * requesting peer is not a starting peer. Our current implementation rejects + * requests for Allowed Fast messages whenever the requesting peer has more than + * * k * pieces. + *

+ * When the fast extension is disabled, if a peer recieves an Allowed Fast + * message then the peer MUST close the connection. + * + * @author Rogiel Josias Sulzbach + * @source http://www.bittorrent.org/beps/bep_0006.html + */ +public class AllowedFastMessage implements PeerWireWritableMessage, + PeerWireReadableMessage { + public static final byte MESSAGE_ID = 0x0D; + + /** + * The pieces indexes + */ + private int[] pieces; + + public AllowedFastMessage(int... pieces) { + this.pieces = pieces; + } + + public AllowedFastMessage() { + } + + @Override + public void read(ChannelBuffer buffer) throws IOException { + this.pieces = new int[buffer.readableBytes() / 4]; + for (int i = 0; i < pieces.length; i++) { + pieces[i] = buffer.readInt(); + } + } + + @Override + public void write(ChannelBuffer buffer) throws IOException { + buffer.writeByte(MESSAGE_ID); + for (final int piece : pieces) { + buffer.writeInt(piece); + } + } + + public int[] getPieces() { + return pieces; + } + + public void setPieces(int[] pieces) { + this.pieces = pieces; + } + + @Override + public String toString() { + return "AllowedFastMessage [pieces=" + + (pieces != null ? Arrays.toString(pieces) : null) + "]"; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/message/fast/HaveAllMessage.java b/src/main/java/net/torrent/protocol/peerwire/message/fast/HaveAllMessage.java new file mode 100644 index 0000000..cf13f95 --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/message/fast/HaveAllMessage.java @@ -0,0 +1,60 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.message.fast; + +import java.io.IOException; + +import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage; +import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + *

+ * HaveAll: [len=0x0001][id=0x0E]
+ * 
+ * + * Have All and Have None specify that the message sender has all or none of the + * pieces respectively. When present, Have All or Have None replace the Have + * Bitfield. Exactly one of Have All, Have None, or Have Bitfield MUST appear + * and only immediately after the handshake. The reason for these messages is to + * save bandwidth. Also slightly to remove the idiosyncrasy of sending no + * message when a peer has no pieces. + *

+ * When the fast extension is disabled, if a peer receives Have All or Have None + * then the peer MUST close the connection. + * + * @author Rogiel Josias Sulzbach + * @source http://www.bittorrent.org/beps/bep_0006.html + */ +public class HaveAllMessage implements PeerWireWritableMessage, + PeerWireReadableMessage { + public static final byte MESSAGE_ID = 0x0E; + + @Override + public void read(ChannelBuffer buffer) throws IOException { + } + + @Override + public void write(ChannelBuffer buffer) throws IOException { + buffer.writeByte(MESSAGE_ID); + } + + @Override + public String toString() { + return "HaveAllMessage []"; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/message/fast/HaveNoneMessage.java b/src/main/java/net/torrent/protocol/peerwire/message/fast/HaveNoneMessage.java new file mode 100644 index 0000000..c0f1812 --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/message/fast/HaveNoneMessage.java @@ -0,0 +1,60 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.message.fast; + +import java.io.IOException; + +import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage; +import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + *

+ * HaveNone: [len=0x0001][id=0x0F]
+ * 
+ * + * Have All and Have None specify that the message sender has all or none of the + * pieces respectively. When present, Have All or Have None replace the Have + * Bitfield. Exactly one of Have All, Have None, or Have Bitfield MUST appear + * and only immediately after the handshake. The reason for these messages is to + * save bandwidth. Also slightly to remove the idiosyncrasy of sending no + * message when a peer has no pieces. + *

+ * When the fast extension is disabled, if a peer receives Have All or Have None + * then the peer MUST close the connection. + * + * @author Rogiel Josias Sulzbach + * @source http://www.bittorrent.org/beps/bep_0006.html + */ +public class HaveNoneMessage implements PeerWireWritableMessage, + PeerWireReadableMessage { + public static final byte MESSAGE_ID = 0x0F; + + @Override + public void read(ChannelBuffer buffer) throws IOException { + } + + @Override + public void write(ChannelBuffer buffer) throws IOException { + buffer.writeByte(MESSAGE_ID); + } + + @Override + public String toString() { + return "HaveNoneMessage []"; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/message/fast/RejectMessage.java b/src/main/java/net/torrent/protocol/peerwire/message/fast/RejectMessage.java new file mode 100644 index 0000000..fddb7cc --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/message/fast/RejectMessage.java @@ -0,0 +1,127 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.message.fast; + +import java.io.IOException; + +import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage; +import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + *

+ * Reject: [len=0013][id=0x10][index][begin][length]
+ * 
+ * + * Reject Request notifies a requesting peer that its request will not be + * satisfied. + * + * If the fast extension is disabled and a peer receives a reject request then + * the peer MUST close the connection. + *

+ * When the fast extension is enabled: + *

    + *
  • If a peer receives a reject for a request that was never sent then the + * peer SHOULD close the connection.
  • + *
  • If a peer sends a choke, it MUST reject all requests from the peer to + * whom the choke was sent except it SHOULD NOT reject requests for pieces that + * are in the allowed fast set. A peer SHOULD choke first and then reject + * requests so that the peer receiving the choke does not re-request the pieces. + *
  • + *
  • If a peer receives a request from a peer its choking, the peer receiving + * the request SHOULD send a reject unless the piece is in the allowed fast set. + *
  • + *
  • If a peer receives an excessive number of requests from a peer it is + * choking, the peer receiving the requests MAY close the connection rather than + * reject the request. However, consider that it can take several seconds for + * buffers to drain and messages to propagate once a peer is choked. + *
+ * + * @author Rogiel Josias Sulzbach + * @source http://www.bittorrent.org/beps/bep_0006.html + */ +public class RejectMessage implements PeerWireWritableMessage, + PeerWireReadableMessage { + public static final byte MESSAGE_ID = 0x06; + + /** + * The piece index + */ + private int index; + /** + * The piece start offset + */ + private int start; + /** + * The part length + */ + private int length; + + public RejectMessage(int index, int start, int length) { + this.index = index; + this.start = start; + this.length = length; + } + + public RejectMessage() { + } + + @Override + public void read(ChannelBuffer buffer) throws IOException { + this.index = buffer.readInt(); + this.start = buffer.readInt(); + this.length = buffer.readInt(); + } + + @Override + public void write(ChannelBuffer buffer) throws IOException { + buffer.writeByte(MESSAGE_ID); + buffer.writeInt(index); + buffer.writeInt(start); + buffer.writeInt(length); + } + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + } + + public int getStart() { + return start; + } + + public void setStart(int start) { + this.start = start; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } + + @Override + public String toString() { + return "RejectMessage [index=" + index + ", start=" + start + + ", length=" + length + "]"; + } +} diff --git a/src/main/java/net/torrent/protocol/peerwire/message/fast/SuggestPieceMessage.java b/src/main/java/net/torrent/protocol/peerwire/message/fast/SuggestPieceMessage.java new file mode 100644 index 0000000..67b742d --- /dev/null +++ b/src/main/java/net/torrent/protocol/peerwire/message/fast/SuggestPieceMessage.java @@ -0,0 +1,85 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire.message.fast; + +import java.io.IOException; + +import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage; +import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** + *
+ * SuggestPiece: [len=0x0005][id=0x0D][piece index(int)]
+ * 
+ * + * Suggest Piece is an advisory message meaning + * "you might like to download this piece." The intended usage is for + * 'super-seeding' without throughput reduction, to avoid redundant downloads, + * and so that a seed which is disk I/O bound can upload continguous or + * identical pieces to avoid excessive disk seeks. In all cases, the seed SHOULD + * operate to maintain a roughly equal number of copies of each piece in the + * network. A peer MAY send more than one suggest piece message at any given + * time. A peer receiving multiple suggest piece messages MAY interpret this as + * meaning that all of the suggested pieces are equally appropriate. + *

+ * When the fast extension is disabled, if a peer receives a Suggest Piece + * message, the peer MUST close the connection. + * + * @author Rogiel Josias Sulzbach + * @source http://www.bittorrent.org/beps/bep_0006.html + */ +public class SuggestPieceMessage implements PeerWireWritableMessage, + PeerWireReadableMessage { + public static final byte MESSAGE_ID = 0x0D; + + /** + * The new obtained piece index + */ + private int piece; + + public SuggestPieceMessage(int piece) { + this.piece = piece; + } + + public SuggestPieceMessage() { + } + + @Override + public void read(ChannelBuffer buffer) throws IOException { + this.piece = buffer.readInt(); + } + + @Override + public void write(ChannelBuffer buffer) throws IOException { + buffer.writeByte(MESSAGE_ID); + buffer.writeInt(piece); + } + + public int getPiece() { + return piece; + } + + public void setPiece(int piece) { + this.piece = piece; + } + + @Override + public String toString() { + return "SuggestPieceMessage [piece=" + piece + "]"; + } +} diff --git a/src/main/java/net/torrent/protocol/tracker/HttpTorrentTrackerAnnouncer.java b/src/main/java/net/torrent/protocol/tracker/HttpTorrentTrackerAnnouncer.java new file mode 100644 index 0000000..90b59ab --- /dev/null +++ b/src/main/java/net/torrent/protocol/tracker/HttpTorrentTrackerAnnouncer.java @@ -0,0 +1,58 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.tracker; + +import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import net.torrent.protocol.tracker.message.AnnounceMessage; +import net.torrent.protocol.tracker.message.AnnounceMessage.Event; +import net.torrent.torrent.Torrent; +import net.torrent.torrent.TorrentTracker; + +import org.jboss.netty.bootstrap.ClientBootstrap; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; + +public class HttpTorrentTrackerAnnouncer { + private final ClientBootstrap client = new ClientBootstrap( + new NioClientSocketChannelFactory(Executors.newCachedThreadPool(), + Executors.newCachedThreadPool())); + + public HttpTorrentTrackerAnnouncer() { + client.setPipelineFactory(new HttpTorrentTrackerPipelineFactory()); + } + + public boolean announce(Torrent torrent, TorrentTracker tracker) + throws UnsupportedEncodingException, MalformedURLException { + final AnnounceMessage announceMessage = new AnnounceMessage(tracker + .getURL().toString(), torrent.getInfoHash().toByteArray(), + torrent.getInfoHash().toByteArray(), 10, 0, 0, 0, true, false, + Event.STARTED); + int port = (tracker.getURL().getPort() > 0 ? tracker.getURL().getPort() + : tracker.getURL().getDefaultPort()); + final ChannelFuture chFuture = client.connect(new InetSocketAddress( + tracker.getURL().getHost(), port)); + chFuture.awaitUninterruptibly(60, TimeUnit.SECONDS); + if (!chFuture.isSuccess()) + return false; + return chFuture.getChannel().write(announceMessage.write()) + .awaitUninterruptibly(60, TimeUnit.SECONDS); + } +} diff --git a/src/main/java/net/torrent/protocol/tracker/HttpTorrentTrackerPipelineFactory.java b/src/main/java/net/torrent/protocol/tracker/HttpTorrentTrackerPipelineFactory.java new file mode 100644 index 0000000..68875ee --- /dev/null +++ b/src/main/java/net/torrent/protocol/tracker/HttpTorrentTrackerPipelineFactory.java @@ -0,0 +1,54 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.tracker; + +import static org.jboss.netty.channel.Channels.pipeline; +import net.torrent.protocol.tracker.codec.ISO8859HttpRequestEncoder; +import net.torrent.protocol.tracker.codec.TorrentTrackerBDecoder; +import net.torrent.protocol.tracker.codec.TorrentTrackerDecoder; +import net.torrent.protocol.tracker.codec.TorrentTrackerEncoder; + +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.handler.codec.http.HttpResponseDecoder; +import org.jboss.netty.handler.logging.LoggingHandler; +import org.jboss.netty.logging.InternalLogLevel; + +public class HttpTorrentTrackerPipelineFactory implements + ChannelPipelineFactory { + @Override + public ChannelPipeline getPipeline() throws Exception { + final ChannelPipeline pipeline = pipeline(); + + // log binary data input and object output + // pipeline.addFirst("logging", new LoggingHandler()); + + pipeline.addLast("tracker.encoder", new TorrentTrackerEncoder()); + pipeline.addLast("encoder", new ISO8859HttpRequestEncoder()); + + pipeline.addLast("interceptor", new Interceptor()); + + pipeline.addLast("decoder", new HttpResponseDecoder()); + pipeline.addLast("bdecoder", new TorrentTrackerBDecoder()); + pipeline.addLast("tracker.decoder", new TorrentTrackerDecoder()); + + pipeline.addLast("handler", new TrackerHandler()); + + pipeline.addLast("logging", new LoggingHandler(InternalLogLevel.WARN)); + + return pipeline; + } +} diff --git a/src/main/java/net/torrent/protocol/tracker/Interceptor.java b/src/main/java/net/torrent/protocol/tracker/Interceptor.java new file mode 100644 index 0000000..3879660 --- /dev/null +++ b/src/main/java/net/torrent/protocol/tracker/Interceptor.java @@ -0,0 +1,36 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.tracker; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.oneone.OneToOneEncoder; + +public class Interceptor extends OneToOneEncoder { + @Override + protected Object encode(ChannelHandlerContext ctx, Channel channel, + Object msg) throws Exception { + System.out.println(">" + msg); + if (!(msg instanceof ChannelBuffer)) + return msg; + + final ChannelBuffer buffer = (ChannelBuffer) msg; + System.out.println(new String(buffer.array())); + + return msg; + } +} diff --git a/src/main/java/net/torrent/protocol/tracker/TrackerHandler.java b/src/main/java/net/torrent/protocol/tracker/TrackerHandler.java new file mode 100644 index 0000000..519660c --- /dev/null +++ b/src/main/java/net/torrent/protocol/tracker/TrackerHandler.java @@ -0,0 +1,29 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.tracker; + +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelUpstreamHandler; + +public class TrackerHandler extends SimpleChannelUpstreamHandler { + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) + throws Exception { + System.out.println(e.getMessage()); + super.messageReceived(ctx, e); + } +} diff --git a/src/main/java/net/torrent/protocol/tracker/codec/ISO8859HttpRequestEncoder.java b/src/main/java/net/torrent/protocol/tracker/codec/ISO8859HttpRequestEncoder.java new file mode 100644 index 0000000..6170586 --- /dev/null +++ b/src/main/java/net/torrent/protocol/tracker/codec/ISO8859HttpRequestEncoder.java @@ -0,0 +1,40 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.tracker.codec; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.handler.codec.http.HttpMessage; +import org.jboss.netty.handler.codec.http.HttpRequest; +import org.jboss.netty.handler.codec.http.HttpRequestEncoder; + +public class ISO8859HttpRequestEncoder extends HttpRequestEncoder { + static final byte SP = 32; + static final byte CR = 13; + static final byte LF = 10; + + @Override + protected void encodeInitialLine(ChannelBuffer buf, HttpMessage message) + throws Exception { + HttpRequest request = (HttpRequest) message; + buf.writeBytes(request.getMethod().toString().getBytes()); + buf.writeByte(SP); + buf.writeBytes(request.getUri().getBytes()); + buf.writeByte(SP); + buf.writeBytes(request.getProtocolVersion().toString().getBytes()); + buf.writeByte(CR); + buf.writeByte(LF); + } +} diff --git a/src/main/java/net/torrent/protocol/tracker/codec/TorrentTrackerBDecoder.java b/src/main/java/net/torrent/protocol/tracker/codec/TorrentTrackerBDecoder.java new file mode 100644 index 0000000..0a2d7cf --- /dev/null +++ b/src/main/java/net/torrent/protocol/tracker/codec/TorrentTrackerBDecoder.java @@ -0,0 +1,45 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.tracker.codec; + +import java.io.ByteArrayInputStream; + +import net.torrent.util.bencoding.BEncodedInputStream; +import net.torrent.util.bencoding.BMap; + +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.http.HttpChunk; +import org.jboss.netty.handler.codec.oneone.OneToOneDecoder; + +public class TorrentTrackerBDecoder extends OneToOneDecoder { + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, + Object msg) throws Exception { + if (!(msg instanceof HttpChunk)) + return msg; + final HttpChunk message = (HttpChunk) msg; + + System.out.println(new String(message.getContent().array())); + if (message.getContent().readableBytes() <= 0) + return null; + + final BEncodedInputStream in = new BEncodedInputStream( + new ByteArrayInputStream(message.getContent().array())); + final BMap map = (BMap) in.readElement(); + return map; + } +} diff --git a/src/main/java/net/torrent/protocol/tracker/codec/TorrentTrackerDecoder.java b/src/main/java/net/torrent/protocol/tracker/codec/TorrentTrackerDecoder.java new file mode 100644 index 0000000..1fecc15 --- /dev/null +++ b/src/main/java/net/torrent/protocol/tracker/codec/TorrentTrackerDecoder.java @@ -0,0 +1,37 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.tracker.codec; + +import net.torrent.protocol.tracker.message.PeerListMessage; +import net.torrent.util.bencoding.BMap; + +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.oneone.OneToOneDecoder; + +public class TorrentTrackerDecoder extends OneToOneDecoder { + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, + Object msg) throws Exception { + if (!(msg instanceof BMap)) + return msg; + + final BMap bencodedData = (BMap) msg; + final TorrentTrackerResponseMessage message = new PeerListMessage(); + message.read(bencodedData); + return message; + } +} diff --git a/src/main/java/net/torrent/protocol/tracker/codec/TorrentTrackerEncoder.java b/src/main/java/net/torrent/protocol/tracker/codec/TorrentTrackerEncoder.java new file mode 100644 index 0000000..d48d4d3 --- /dev/null +++ b/src/main/java/net/torrent/protocol/tracker/codec/TorrentTrackerEncoder.java @@ -0,0 +1,32 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.tracker.codec; + +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.handler.codec.oneone.OneToOneEncoder; + +public class TorrentTrackerEncoder extends OneToOneEncoder { + @Override + protected Object encode(ChannelHandlerContext ctx, Channel channel, + Object msg) throws Exception { + if (!(msg instanceof TorrentTrackerRequestMessage)) + return msg; + + final TorrentTrackerRequestMessage message = (TorrentTrackerRequestMessage) msg; + return message.write(); + } +} diff --git a/src/main/java/net/torrent/protocol/tracker/codec/TorrentTrackerRequestMessage.java b/src/main/java/net/torrent/protocol/tracker/codec/TorrentTrackerRequestMessage.java new file mode 100644 index 0000000..307dabd --- /dev/null +++ b/src/main/java/net/torrent/protocol/tracker/codec/TorrentTrackerRequestMessage.java @@ -0,0 +1,26 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.tracker.codec; + +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; + +import org.jboss.netty.handler.codec.http.HttpRequest; + +public interface TorrentTrackerRequestMessage { + HttpRequest write() throws UnsupportedEncodingException, + MalformedURLException; +} diff --git a/src/main/java/net/torrent/protocol/tracker/codec/TorrentTrackerResponseMessage.java b/src/main/java/net/torrent/protocol/tracker/codec/TorrentTrackerResponseMessage.java new file mode 100644 index 0000000..e3e614d --- /dev/null +++ b/src/main/java/net/torrent/protocol/tracker/codec/TorrentTrackerResponseMessage.java @@ -0,0 +1,23 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.tracker.codec; + +import net.torrent.util.bencoding.BMap; +import net.torrent.util.bencoding.BTypeException; + +public interface TorrentTrackerResponseMessage { + void read(BMap map) throws BTypeException; +} diff --git a/src/main/java/net/torrent/protocol/tracker/message/AnnounceMessage.java b/src/main/java/net/torrent/protocol/tracker/message/AnnounceMessage.java new file mode 100644 index 0000000..30d8aab --- /dev/null +++ b/src/main/java/net/torrent/protocol/tracker/message/AnnounceMessage.java @@ -0,0 +1,173 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.tracker.message; + +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; + +import net.torrent.protocol.tracker.codec.TorrentTrackerRequestMessage; + +import org.jboss.netty.handler.codec.http.DefaultHttpRequest; +import org.jboss.netty.handler.codec.http.HttpMethod; +import org.jboss.netty.handler.codec.http.HttpRequest; +import org.jboss.netty.handler.codec.http.HttpVersion; + +public class AnnounceMessage implements TorrentTrackerRequestMessage { + private String url; + private byte[] infoHash; + private byte[] peerId; + private int port; + + private long uploaded; + private long downloaded; + private long left; + + private boolean compact; + private boolean noPeerId; + + private Event event; + + public enum Event { + UPDATE(null), STARTED("started"), STOPPED("stopped"), COMPLETED( + "completed"); + private String urlArg; + + Event(String arg) { + urlArg = arg; + } + + public String urlArg() { + return urlArg; + } + } + + private String ip; + private Integer numWant = 10; + private String key = "avtbyit8"; + private String trackerId; + + public AnnounceMessage(String url, byte[] infoHash, byte[] peerId, + int port, long uploaded, long downloaded, long left, + boolean compact, boolean noPeerId, Event event, String ip, + Integer numWant, String key, String trackerId) { + this.url = url; + this.infoHash = infoHash; + this.peerId = peerId; + this.port = port; + this.uploaded = uploaded; + this.downloaded = downloaded; + this.left = left; + this.compact = compact; + this.noPeerId = noPeerId; + this.event = event; + this.ip = ip; + this.numWant = numWant; + this.key = key; + this.trackerId = trackerId; + } + + public AnnounceMessage(String url, byte[] infoHash, byte[] peerId, + int port, long uploaded, long downloaded, long left, + boolean compact, boolean noPeerId, Event event) { + this.url = url; + this.infoHash = infoHash; + this.peerId = peerId; + this.port = port; + this.uploaded = uploaded; + this.downloaded = downloaded; + this.left = left; + this.compact = compact; + this.noPeerId = noPeerId; + this.event = event; + } + + @Override + public HttpRequest write() throws UnsupportedEncodingException, + MalformedURLException { + StringBuilder builder = new StringBuilder(url); + if (url.contains("?")) { + builder.append("&"); + } else { + builder.append("?"); + } + + addLowerCase(builder, "info_hash", infoHash); + add(builder, "peer_id", "-TR2130-g4mvcv2iyehf"); + add(builder, "port", port); + + add(builder, "uploaded", Long.toString(uploaded)); + add(builder, "downloaded", Long.toString(downloaded)); + add(builder, "left", Long.toString(left)); + + add(builder, "compact", compact); + add(builder, "no_peer_id", noPeerId); + if (event != Event.UPDATE) + add(builder, "event", event.urlArg()); + + add(builder, "ip", ip); + add(builder, "numwant", numWant); + add(builder, "key", key); + add(builder, "trackerid", trackerId); + + builder.setLength(builder.length() - 1);// trim last character it is an + // unnecessary &. + final URL url = new URL(builder.toString()); + + return new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, + url.getPath() + "?" + url.getQuery()); + } + + private void add(StringBuilder builder, String key, String value) + throws UnsupportedEncodingException { + if (value == null) + return; + builder.append(key + "=" + value).append("&"); + } + + private void add(StringBuilder builder, String key, byte[] value) + throws UnsupportedEncodingException { + if (value == null) + return; + add(builder, key, URLEncoder.encode(new String(value), "ISO-8859-1") + .replaceAll("\\+", "%20")); + } + + private void addLowerCase(StringBuilder builder, String key, byte[] value) + throws UnsupportedEncodingException { + if (value == null) + return; + add(builder, key, + URLEncoder + .encode(new String(value, "ISO-8859-1"), "ISO-8859-1") + .replaceAll("\\+", "%20").toLowerCase()); + } + + private void add(StringBuilder builder, String key, Number value) + throws UnsupportedEncodingException { + if (value == null) + return; + add(builder, key, value.toString()); + } + + private void add(StringBuilder builder, String key, Boolean value) + throws UnsupportedEncodingException { + if (value == null) + return; + add(builder, key, (value ? "1" : "0")); + } +} diff --git a/src/main/java/net/torrent/protocol/tracker/message/PeerListMessage.java b/src/main/java/net/torrent/protocol/tracker/message/PeerListMessage.java new file mode 100644 index 0000000..df524be --- /dev/null +++ b/src/main/java/net/torrent/protocol/tracker/message/PeerListMessage.java @@ -0,0 +1,181 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.tracker.message; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.security.InvalidParameterException; +import java.util.ArrayList; +import java.util.List; + +import net.torrent.protocol.tracker.codec.TorrentTrackerResponseMessage; +import net.torrent.util.bencoding.BList; +import net.torrent.util.bencoding.BMap; +import net.torrent.util.bencoding.BTypeException; + +public class PeerListMessage implements TorrentTrackerResponseMessage { + private static final String FAILURE_REASON = "failure reason"; + private static final String WARNING_MESSAGE = "warning message"; + private static final String INTERVAL = "interval"; + private static final String MIN_INTERVAL = "min interval"; + private static final String TRACKER_ID = "tracker id"; + private static final String COMPLETE = "complete"; + private static final String INCOMPLETE = "incomplete"; + private static final String PEERS = "peers"; + + private String failureReason; + private String warningMessage; + + private Integer interval; + private Integer minInterval; + private byte[] trackerID; + private Integer complete; + private Integer incomplete; + private List peerList; + private boolean compact; + + @Override + public void read(BMap response) throws BTypeException { + if (response == null) + throw new InvalidParameterException("Tracker response is null"); + if (response.containsKey(FAILURE_REASON)) { + failureReason = response.getString(FAILURE_REASON); + return; + } + this.interval = response.getInteger(INTERVAL); + + warningMessage = response.getString(WARNING_MESSAGE); + interval = response.getInteger(INTERVAL); + minInterval = response.getInteger(MIN_INTERVAL); + trackerID = (byte[]) response.get(TRACKER_ID); + + complete = response.getInteger(COMPLETE); + incomplete = response.getInteger(INCOMPLETE); + + Object peers = response.get(PEERS); + if (peers instanceof BList) { + BList list = (BList) peers; + peerList = new ArrayList(list.size()); + for (int i = 0; i < list.size(); i++) { + peerList.add(PeerInfo.fromBMap(list.getMap(i))); + } + } else { + byte[] list = (byte[]) peers; + if (list.length % 6 != 0) + throw new IllegalStateException( + "Peerlist not in format IPv4:port!"); + peerList = new ArrayList(list.length / 6); + for (int i = 0; i < list.length; i += 6) { + peerList.add(PeerInfo.fromRawIP(list, i, 6)); + } + compact = true; + } + } + + public String getFailureReason() { + return failureReason; + } + + public String getWarningMessage() { + return warningMessage; + } + + public Integer getInterval() { + return interval; + } + + public Integer getMinInterval() { + return minInterval; + } + + public byte[] getTrackerID() { + return trackerID; + } + + public Integer getComplete() { + return complete; + } + + public Integer getIncomplete() { + return incomplete; + } + + public List getPeerList() { + return peerList; + } + + public boolean isCompact() { + return compact; + } + + public static class PeerInfo { + private byte[] peerId; + private String ip; + private int port; + + public PeerInfo(byte[] peerId, String ip, int port) { + this.peerId = peerId; + this.ip = ip; + this.port = port; + } + + public byte[] getPeerId() { + return peerId; + } + + public void setPeerId(byte[] peerId) { + this.peerId = peerId; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public static PeerInfo fromBMap(BMap map) throws BTypeException { + return new PeerInfo((byte[]) map.get("peer id"), + map.getString("peer ip"), map.getInteger("port")); + } + + public static PeerInfo fromRawIP(byte[] list, int i, int j) { + byte[] addr = new byte[4]; + System.arraycopy(list, i, addr, 0, 4); + InetAddress address; + try { + address = InetAddress.getByAddress(addr); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + InetSocketAddress socketAddress = new InetSocketAddress( + address.getHostAddress(), ((list[i + j - 2] & 0xFF) << 8) + + (list[i + j - 1] & 0xFF)); + return new PeerInfo(null, socketAddress.getHostName(), + socketAddress.getPort()); + } + } +} diff --git a/src/main/java/net/torrent/torrent/Torrent.java b/src/main/java/net/torrent/torrent/Torrent.java new file mode 100644 index 0000000..4edbd3f --- /dev/null +++ b/src/main/java/net/torrent/torrent/Torrent.java @@ -0,0 +1,362 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.torrent; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.InvalidParameterException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import net.torrent.torrent.TorrentInfo.TorrentType; +import net.torrent.util.bencoding.BEncodedInputStream; +import net.torrent.util.bencoding.BList; +import net.torrent.util.bencoding.BMap; +import net.torrent.util.bencoding.BTypeException; + +/** + * An class representing an .torrent file. + * + * @author Rogiel Josias Sulzbach + */ +public class Torrent { + /** + * Dictionary key for announce + */ + private static final String ANNOUNCE = "announce"; + /** + * Dictionary key for announce-list + */ + private static final String ANNOUNCE_LIST = "announce-list"; + /** + * Dictionary key for info + */ + private static final String INFO = "info"; + /** + * Dictionary key for comment + */ + private static final String COMMENT = "comment"; + /** + * Dictionary key for creation date + */ + private static final String CREATION_DATE = "creation date"; + /** + * Dictionary key for created by + */ + private static final String CREATED_BY = "created by"; + + /** + * The torrent information + */ + private final TorrentInfo info; + /** + * The list of trackers + */ + private final Set trackers = new HashSet(); + + /** + * The torrent comment + */ + private final String comment; + /** + * The creation date of the torrent + */ + private final Date creationDate; + /** + * The torrent creator + */ + private final String createdBy; + + /** + * Creates a new instance + * + * @param info + * the torrent information + * @param comment + * the comment + * @param creationDate + * the date of creation + * @param createdBy + * the creator + * @param trackers + * the list of trackers + */ + public Torrent(TorrentInfo info, String comment, Date creationDate, + String createdBy, TorrentTracker... trackers) { + if (info == null) + throw new InvalidParameterException("Torrent info is null"); + if (trackers == null || trackers.length == 0) + throw new InvalidParameterException( + "Trackers null or empty: DHT is not yet implemented, a tracker is needed."); + + Collections.addAll(this.trackers, trackers); + + this.info = info; + this.comment = comment; + this.creationDate = creationDate; + this.createdBy = createdBy; + } + + /** + * Creates a new instance + * + * @param info + * the torrent information + * @param trackers + * the list o trackers + */ + public Torrent(TorrentInfo info, TorrentTracker... trackers) { + this(info, null, null, null, trackers); + } + + /** + * Creates a new instance + * + * @param torrent + * the torrent bencoded map + * @throws BTypeException + * @throws URISyntaxException + */ + protected Torrent(BMap torrent) throws BTypeException, URISyntaxException { + this.comment = torrent.getString(COMMENT); + + final Long time = torrent.getLong(CREATION_DATE); + this.creationDate = (time != null ? new Date(time * 1000) : null); + + this.createdBy = torrent.getString(CREATED_BY); + + BList aLists = torrent.getList(ANNOUNCE_LIST); + if (aLists != null) { + for (int i = 0; i < aLists.size(); i++) { + BList list = aLists.getList(i); + URI primary = null; + URI[] backup = new URI[(list.size() > 1 ? list.size() - 1 : 0)]; + int backups = 0; + for (int j = 0; j < list.size(); j++) { + if (j == 0) { + final String url = list.getString(j); + if (!url.startsWith("http")) + break; + primary = new URI(url); + } else { + final String url = list.getString(j); + if (url.startsWith("http")) + backup[backups++] = new URI(url); + } + } + this.trackers.add(new TorrentTracker(this, primary, Arrays + .copyOf(backup, backups))); + } + } else { + if (torrent.containsKey(ANNOUNCE)) { + this.trackers.add(new TorrentTracker(this, new URI(torrent + .getString(ANNOUNCE)))); + } + } + + info = new TorrentInfo(this, torrent.getMap(INFO)); + } + + /** + * Get torrent information + * + * @return the torrent information + */ + public TorrentInfo getInfo() { + return info; + } + + /** + * The list of trackers + * + * @return the trackers + */ + public Set getTrackers() { + return Collections.unmodifiableSet(trackers); + } + + /** + * Get the torrent comment + * + * @return the torrent comment + */ + public String getComment() { + return comment; + } + + /** + * Get the creation adte + * + * @return the creation date + */ + public Date getCreationDate() { + return creationDate; + } + + /** + * Get the torrent creator + * + * @return the torrent creator + */ + public String getCreatedBy() { + return createdBy; + } + + /** + * Get the torrent info hash + * + * @return the torrent info hash + */ + public TorrentHash getInfoHash() { + return info.getInfoHash(); + } + + /** + * Get list of files + * + * @return the file list + */ + public List getFiles() { + return info.getFiles(); + } + + /** + * Get the list of pieces + * + * @return the list of pieces + */ + public TorrentPiece[] getPieces() { + return info.getPieces(); + } + + /** + * Get the torrent type + * + * @return the torrent type + */ + public TorrentType getType() { + return info.getType(); + } + + /** + * Get the piece by index + * + * @param index + * the piece index + * @return the found piece + */ + public TorrentPiece getPiece(int index) { + return info.getPiece(index); + } + + /** + * Get an certain part + * + * @param piece + * the piece + * @param start + * the part start + * @param len + * the part length + * @return the found part + */ + public TorrentPart getPart(TorrentPiece piece, int start, int len) { + return info.getPart(piece, start, len); + } + + /** + * Get an certain part + * + * @param piece + * the piece index + * @param start + * the part start + * @param len + * the part length + * @return the found part + */ + public TorrentPart getPart(int index, int start, int len) { + return info.getPart(index, start, len); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((info == null) ? 0 : info.getInfoHash().hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Torrent other = (Torrent) obj; + if (info == null) { + if (other.info != null) + return false; + } else if (!info.equals(other.info)) + return false; + return true; + } + + /** + * Load an torrent from an {@link InputStream} + * + * @param in + * the {@link InputStream} + * @return the loaded {@link Torrent} instance + * @throws IOException + * @throws URISyntaxException + */ + public static Torrent load(InputStream in) throws IOException, + URISyntaxException { + if (in == null) + throw new InvalidParameterException("InputStream cannot be null"); + + final BMap map = (BMap) new BEncodedInputStream(in).readElement(); + return new Torrent(map); + } + + /** + * Load an torrent from an {@link File} + * + * @param file + * the {@link File} + * @return the loaded {@link Torrent} instance + * @throws IOException + * @throws URISyntaxException + */ + public static Torrent load(File file) throws IOException, + URISyntaxException { + if (file == null) + throw new InvalidParameterException("File cannot be null"); + return load(new FileInputStream(file)); + } +} diff --git a/src/main/java/net/torrent/torrent/TorrentFile.java b/src/main/java/net/torrent/torrent/TorrentFile.java new file mode 100644 index 0000000..eeec3a4 --- /dev/null +++ b/src/main/java/net/torrent/torrent/TorrentFile.java @@ -0,0 +1,172 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.torrent; + +import net.torrent.util.Range; + +/** + * An file inside an {@link Torrent torrent} file + * + * @author Rogiel Josias Sulzbach + */ +public class TorrentFile { + /** + * The torrent information + */ + private final TorrentInfo info; + /** + * The filename + */ + private final String name; + /** + * The file length + */ + private final long length; + /** + * The file offset + */ + private final long offset; + + /** + * Creates a new instance + * + * @param info + * the torrent information + * @param name + * the filename + * @param length + * the file length + * @param offset + * the file offset + */ + public TorrentFile(TorrentInfo info, String name, long length, long offset) { + this.info = info; + this.name = name; + this.length = length; + this.offset = offset; + } + + /** + * Get the torrent informatiom + * + * @return the torrent information + */ + public TorrentInfo getInfo() { + return info; + } + + /** + * Get the filename + * + * @return the filename + */ + public String getName() { + return name; + } + + /** + * Get the filesize + * + * @return the filesize + */ + public long getLength() { + return length; + } + + /** + * Get the file offset + * + * @return the offset + */ + public long getOffset() { + return offset; + } + + /** + * Get the base directory name + * + * @return the base directory name + * @see TorrentInfo#getDirectoryName() + */ + public String getBaseDirectoryName() { + return info.getDirectoryName(); + } + + /** + * Get the relative name. This is the same as: + *

+ * + * String name = getBaseDirectoryName() + "/" + getName(); + * + * + * @return the relative name + */ + public String getRelativePath() { + if (getBaseDirectoryName() != null) { + return getBaseDirectoryName() + "/" + name; + } + return name; + } + + /** + * Get the file as torrent range + * + * @return the torrent range representing the file + */ + public Range asTorrentRange() { + return Range.getRangeByLength(offset, length); + } + + /** + * Get the file as file range + * + * @return the file range representing the file + */ + public Range asFileRange() { + return Range.getRangeByLength(0, length); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (int) (length ^ (length >>> 32)); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + (int) (offset ^ (offset >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TorrentFile other = (TorrentFile) obj; + if (length != other.length) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (offset != other.offset) + return false; + return true; + } +} diff --git a/src/main/java/net/torrent/torrent/TorrentHash.java b/src/main/java/net/torrent/torrent/TorrentHash.java new file mode 100644 index 0000000..4e71f94 --- /dev/null +++ b/src/main/java/net/torrent/torrent/TorrentHash.java @@ -0,0 +1,221 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.torrent; + +import java.security.InvalidParameterException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +/** + * Represents an {@link TorrentPiece} hash. + * + * @author Rogiel Josias Sulzbach + */ +public class TorrentHash { + /** + * An table containing hexadecimal characters + */ + private static final char[] HEX_TABLE = { '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + /** + * The hash + */ + private final byte[] hash; + /** + * The hash type + */ + private final HashType type; + + /** + * The hashes type + * + * @author Rogiel Josias Sulzbach + */ + public enum HashType { + /** + * SHA1 -> 20 bytes + */ + SHA1(20, "SHA1"), + + /** + * MD5 -> 16 bytes + */ + MD5(16, "MD5"); + + /** + * The hash length + */ + private final int length; + /** + * The digest algorithm + */ + private final String digestAlgorithm; + /** + * The {@link MessageDigest} instance + */ + private MessageDigest digest; + + /** + * Creates a new instance + * + * @param length + * the hash length + * @param digestAlgorithm + * the algorithm + */ + HashType(int length, String digestAlgorithm) { + this.length = length; + this.digestAlgorithm = digestAlgorithm; + try { + this.digest = MessageDigest.getInstance(digestAlgorithm); + } catch (NoSuchAlgorithmException e) { + this.digest = null; + } + } + + /** + * Get the hash length + * + * @return the hash length + */ + public int getLength() { + return length; + } + + /** + * Get the hash algorithm + * + * @return the hash algorithm + */ + public String getDigestAlgorithm() { + return digestAlgorithm; + } + + /** + * Get the {@link MessageDigest} + * + * @return the {@link MessageDigest} + */ + public MessageDigest getMessageDigest() { + return digest; + } + } + + /** + * Creates a new instance + * + * @param hash + * the hash + * @param type + * the hash type + */ + public TorrentHash(byte[] hash, HashType type) { + if (hash == null) + throw new IllegalArgumentException("hash is null"); + if (type == null) + throw new IllegalArgumentException("type is null"); + if (hash.length != type.length) + throw new IllegalArgumentException("hash does not have " + + type.length + " bytes"); + this.hash = Arrays.copyOf(hash, hash.length); + this.type = type; + } + + /** + * Get hash as an hexadecimal string + * + * @return + */ + public String asHexString() { + StringBuilder b = new StringBuilder(hash.length * 2); + for (int i = 0; i < hash.length; i++) { + b.append(HEX_TABLE[((hash[i] & 0xff) >> 4)]); + b.append(HEX_TABLE[hash[i] & 15]); + } + return b.toString(); + } + + /** + * Compare the two hashes + * + * @param hash + * the hash + * @return true if are equal + */ + public boolean compare(byte[] hash) { + if (hash == null) + throw new InvalidParameterException("Hash is null"); + return Arrays.equals(this.hash, hash); + } + + /** + * Get the hash byte array + * + * @return the hash byte array + */ + public byte[] toByteArray() { + return Arrays.copyOf(hash, hash.length); + } + + /** + * Get the hash length + * + * @return the hash length + */ + public int getHashLength() { + return hash.length; + } + + /** + * Get the hash type + * + * @return the hash type + */ + public HashType getType() { + return type; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(hash); + result = prime * result + type.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TorrentHash other = (TorrentHash) obj; + if (!Arrays.equals(hash, other.hash)) + return false; + if (type != other.type) + return false; + return true; + } + + @Override + public String toString() { + return type + ":" + asHexString(); + } +} diff --git a/src/main/java/net/torrent/torrent/TorrentInfo.java b/src/main/java/net/torrent/torrent/TorrentInfo.java new file mode 100644 index 0000000..0c25e6f --- /dev/null +++ b/src/main/java/net/torrent/torrent/TorrentInfo.java @@ -0,0 +1,198 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.torrent; + +import java.io.File; +import java.math.BigInteger; +import java.security.InvalidParameterException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import net.torrent.torrent.TorrentHash.HashType; +import net.torrent.util.bencoding.BEncodedOutputStream; +import net.torrent.util.bencoding.BList; +import net.torrent.util.bencoding.BMap; +import net.torrent.util.bencoding.BTypeException; + +public class TorrentInfo { + private static final String PIECE_LENGTH = "piece length"; + private static final String PIECES = "pieces"; + private static final String NAME = "name"; + private static final String LENGTH = "length"; + private static final String FILES = "files"; + private static final String PATH = "path"; + private static final String PRIVATE = "private"; + + private final Torrent torrent; + private final TorrentHash infohash; + + private final List files = new ArrayList(); + private final TorrentPiece[] pieces; + + private long size; + private final int pieceLength; + private final boolean privTracker; + private final TorrentType type; + + public enum TorrentType { + SINGLE_FILE, DIRECTORY; + } + + private final String directoryName; + + public TorrentInfo(Torrent torrent, TorrentHash infohash, + TorrentPiece[] pieces, int pieceLength, boolean privTracker, + TorrentType type, String directoryName) { + this.torrent = torrent; + this.infohash = infohash; + this.pieces = pieces; + this.pieceLength = pieceLength; + this.privTracker = privTracker; + this.type = type; + this.directoryName = directoryName; + } + + public TorrentInfo(Torrent torrent, BMap info) throws BTypeException { + if (torrent == null) + throw new InvalidParameterException("Torrent is null"); + if (info == null) + throw new InvalidParameterException("Info BMap is null"); + this.torrent = torrent; + + infohash = calculateInfoHash(info); + privTracker = Integer.valueOf(1).equals(info.getInteger(PRIVATE)); + pieceLength = ((BigInteger) info.get(PIECE_LENGTH)).intValue(); + + String base = info.getString(NAME); + if (base == null) + throw new IllegalStateException("Missing a required key: " + NAME); + if (info.containsKey(LENGTH)) { // single file mode + type = TorrentType.SINGLE_FILE; + directoryName = null; + files.add(new TorrentFile(this, base, ((BigInteger) info + .get(LENGTH)).longValue(), 0)); + } else { + type = TorrentType.DIRECTORY; + directoryName = base; + + long offset = 0; + BList fList = info.getList(FILES); + for (int i = 0; i < fList.size(); i++) { + BMap file = fList.getMap(i); + StringBuilder path = new StringBuilder(); + BList elements = file.getList(PATH); + for (int j = 0; j < elements.size(); j++) { + String element = elements.getString(j); + if (path.length() > 0) { + path.append(File.separatorChar); + } + path.append(element); + } + if (files.add(new TorrentFile(this, path.toString(), + ((BigInteger) file.get(LENGTH)).longValue(), offset))) { + offset += ((BigInteger) file.get(LENGTH)).longValue(); + } + } + } + calculateSize(); + + byte[] hashes = (byte[]) info.get(PIECES); + if (hashes == null) + throw new IllegalStateException("Pieces hash is null"); + if (hashes.length % 20 != 0) + throw new IllegalStateException( + "Pieces hash dictionary has an invalid size: " + + hashes.length); + pieces = new TorrentPiece[(hashes.length / 20)]; + for (int index = 0; index < pieces.length; index++) { + int len = pieceLength; + if (pieces.length - 1 == index) // last piece + len = (int) (size - (pieceLength * (pieces.length - 1))); + + pieces[index] = new TorrentPiece(this, Arrays.copyOfRange(hashes, + index * 20, index * 20 + 20), index, len); + } + // Arrays.sort(pieces, PieceIndexComparator.SHARED_INSTANCE); + } + + private void calculateSize() { + for (TorrentFile file : files) { + size += file.getLength(); + } + } + + private TorrentHash calculateInfoHash(Map info) { + try { + MessageDigest sha1 = MessageDigest.getInstance("SHA1"); + sha1.update(BEncodedOutputStream.bencode(info)); + return new TorrentHash(sha1.digest(), HashType.SHA1); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + public Torrent getTorrent() { + return torrent; + } + + public TorrentHash getInfoHash() { + return infohash; + } + + public List getFiles() { + return files; + } + + public TorrentPiece[] getPieces() { + return pieces; + } + + public int getPieceLength() { + return pieceLength; + } + + public boolean isPrivTracker() { + return privTracker; + } + + public TorrentType getType() { + return type; + } + + public String getDirectoryName() { + return directoryName; + } + + public TorrentPiece getPiece(int index) { + for (final TorrentPiece piece : pieces) { + if (piece.getIndex() == index) + return piece; + } + return null; + } + + public TorrentPart getPart(TorrentPiece piece, int start, int len) { + return piece.getPart(start, len); + } + + public TorrentPart getPart(int index, int start, int len) { + return getPart(getPiece(index), start, len); + } +} diff --git a/src/main/java/net/torrent/torrent/TorrentPart.java b/src/main/java/net/torrent/torrent/TorrentPart.java new file mode 100644 index 0000000..43ba984 --- /dev/null +++ b/src/main/java/net/torrent/torrent/TorrentPart.java @@ -0,0 +1,239 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.torrent; + +import net.torrent.protocol.peerwire.message.RequestMessage; +import net.torrent.util.Range; + +/** + * BitTorrent files are divided in small chunks of hashes, each hash represent + * an piece and each of those pieces are divided into smaller parts. Each + * part can be requested to an peer and received afterwards. + * + * @author Rogiel Josias Sulzbach + * @see RequestMessage RequestMessage for more about requests and parts + */ +public class TorrentPart { + /** + * The standard part length -- most clients respect this! + */ + public static final int PART_LENGTH = 16 * 1024; + + /** + * The piece index, only for internal management + */ + private int index; // internal use only + /** + * The piece of this part + */ + private final TorrentPiece piece; + /** + * The start offset of data + */ + private final int start; + /** + * The length of data + */ + private final int length; + + /** + * Creates a new instance + * + * @param piece + * the piece + * @param start + * the start offset + * @param length + * the length + */ + public TorrentPart(TorrentPiece piece, int start, int length) { + this.index = start / length; + this.piece = piece; + this.start = start; + this.length = length; + } + + /** + * Creates a new instance + * + * @param index + * the part index. Note: internal use only! + * @param piece + * the piece + * @param start + * the start offset + * @param length + * the length + */ + public TorrentPart(int index, TorrentPiece piece, int start, int length) { + this.index = index; + this.piece = piece; + this.start = start; + this.length = length; + } + + /** + * Get the piece + * + * @return the piece + */ + public TorrentPiece getPiece() { + return piece; + } + + /** + * Get the torrent + * + * @return the torrent + */ + public Torrent getTorrent() { + return piece.getTorrent(); + } + + /** + * Get the data start offset + * + * @return the start offset + */ + public int getStart() { + return start; + } + + /** + * Get the data length + * + * @return the data length + */ + public int getLength() { + return length; + } + + /** + * Get the offset of part inside the torrent. + * + * @return the part's torrent offset + */ + public long getOffset() { + return piece.getOffset() + start; + } + + /** + * Check if this is the last part in this piece. + *

+ * + * TorrentPart part = ...;
+ * boolean first = (part.getStart() + part.getLength() == part.getPiece().getLength()); + *
+ * + * @return true if last + */ + public boolean isLast() { + return (start + length == piece.getLength()); + } + + /** + * Check if this is the first part in this piece. + *

+ * + * TorrentPart part = ...;
+ * boolean first = (part.getStart() == 0); + *
+ * + * @return true if first + */ + public boolean isFirst() { + return start == 0; + } + + /** + * Get the next part. Null if last. + * + * @return the next part. Might be null. + */ + public TorrentPart getNextPart() { + if (isLast()) + return null; + return piece.getParts()[index + 1]; + } + + /** + * Get the range inside the piece. + * + * @return the range in piece + */ + public Range asPieceRange() { + return Range.getRangeByLength(start, length); + } + + /** + * Get the range inside the torrent + * + * @return the range in torrent + */ + public Range asTorrentRange() { + return Range.getRangeByLength(piece.getOffset() + start, length); + } + + /** + * Get the range inside the file + * + * @param file + * the file + * @return the range in file + */ + public Range asFileRange(TorrentFile file) { + return Range.getRangeByLength( + piece.getOffset() - file.getOffset() + start, length) + .intersection(file.asFileRange()); + } + + @Override + public String toString() { + return "TorrentPart [piece=" + piece + ", start=" + start + ", length=" + + length + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + length; + result = prime * result + ((piece == null) ? 0 : piece.hashCode()); + result = prime * result + start; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TorrentPart other = (TorrentPart) obj; + if (length != other.length) + return false; + if (piece == null) { + if (other.piece != null) + return false; + } else if (!piece.equals(other.piece)) + return false; + if (start != other.start) + return false; + return true; + } +} diff --git a/src/main/java/net/torrent/torrent/TorrentPiece.java b/src/main/java/net/torrent/torrent/TorrentPiece.java new file mode 100644 index 0000000..c9e3d24 --- /dev/null +++ b/src/main/java/net/torrent/torrent/TorrentPiece.java @@ -0,0 +1,252 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.torrent; + +import net.torrent.torrent.TorrentHash.HashType; + +/** + * Torrent files are divided into small chunks of data called pieces. + * Each piece has the same size, except for the last which can be equal or + * smaller (enough to fit all data). + *

+ * This class handles this distribution and takes care of creating + * {@link TorrentPart parts}. + * + * @author Rogiel Josias Sulzbach + */ +public class TorrentPiece { + /** + * The Torrent information + */ + private final TorrentInfo info; + /** + * The piece hash + */ + private final TorrentHash hash; + /** + * The piece index + */ + private final int index; + /** + * The piece length + */ + private final int length; + /** + * The torrent parts. All parts have the standard length ( + * {@link TorrentPart#PART_LENGTH}) + */ + private final TorrentPart[] parts; + + /** + * Create a new instance + * + * @param info + * the torrent information + * @param hash + * the torrent hash (as {@link TorrentHash}) + * @param index + * the piece index + * @param length + * the piece length + */ + public TorrentPiece(TorrentInfo info, final TorrentHash hash, int index, + int length) { + this.info = info; + this.hash = hash; + this.index = index; + this.length = length; + + int len = TorrentPart.PART_LENGTH; + if (len > length) + len = length; + int partCount = (length / len); + if ((length % len) > 0) + partCount += 1; + + parts = new TorrentPart[partCount]; + int remain = length; + for (int i = 0; i < parts.length; i++) { + int plen = remain; + if (remain > len) + plen = len; + parts[i] = new TorrentPart(i, this, length - remain, plen); + remain -= plen; + } + } + + /** + * Creates a new instance + * + * @param info + * the torrent info + * @param hash + * the info hash (as byte array) + * @param index + * the piece index + * @param length + * the piece length + */ + public TorrentPiece(TorrentInfo info, final byte[] hash, int index, + int length) { + this(info, new TorrentHash(hash, HashType.SHA1), index, length); + } + + /** + * Get the torrent information + * + * @return the torrent information + */ + public TorrentInfo getInfo() { + return info; + } + + /** + * Get the torrent + * + * @return the torrent + */ + public Torrent getTorrent() { + return info.getTorrent(); + } + + /** + * Get the piece hash + * + * @return the piece hash + */ + public TorrentHash getHash() { + return hash; + } + + /** + * Get the piece index + * + * @return the piece index + */ + public int getIndex() { + return index; + } + + /** + * Get the piece length + * + * @return the piece length + */ + public int getLength() { + return length; + } + + /** + * Get the piece offset + * + * @return the piece offset (relative to torrent) + */ + public long getOffset() { + return (long) index * (long) info.getPieceLength(); + } + + /** + * Get the next sequential part + * + * @return the next part + */ + public TorrentPiece getNextPiece() { + return info.getPiece(index + 1); + } + + /** + * Get all parts + * + * @return all the parts + */ + public TorrentPart[] getParts() { + return parts; + } + + /** + * Get the first part + * + * @return the first part + */ + public TorrentPart getFirstPart() { + return parts[0]; + } + + /** + * Get the last part + * + * @return the last part + */ + public TorrentPart getLastPart() { + return parts[parts.length - 1]; + } + + /** + * The the part starting at start with length + * + * @param start + * @param length + * @return the found or created part + */ + public TorrentPart getPart(int start, int length) { + for (final TorrentPart part : parts) { + if (part.getStart() == start && part.getLength() == length) + return part; + } + return new TorrentPart(this, start, length); + } + + /** + * Get this piece as an entire part + * + * @return the part + */ + public TorrentPart asSinglePart() { + return getPart(0, length); + } + + @Override + public String toString() { + return "TorrentPiece [info=" + info + ", hash=" + hash + ", index=" + + index + ", length=" + length + ", parts=" + + (parts != null ? parts.length : null) + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((hash == null) ? 0 : hash.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TorrentPiece other = (TorrentPiece) obj; + if (hash == null) { + if (other.hash != null) + return false; + } else if (!hash.equals(other.hash)) + return false; + return true; + } +} diff --git a/src/main/java/net/torrent/torrent/TorrentTracker.java b/src/main/java/net/torrent/torrent/TorrentTracker.java new file mode 100644 index 0000000..2203c4a --- /dev/null +++ b/src/main/java/net/torrent/torrent/TorrentTracker.java @@ -0,0 +1,146 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.torrent; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * An tracker has the responsibility to discover new peers. Each peer, once + * begin downloading, send its own peer id and got registered into an peer table + * in the tracker. Once the next peer starts, it receives an copy of that peer + * table and connect to some or all peers in that table. + * + * @author Rogiel Josias Sulzbach + */ +public class TorrentTracker { + /** + * The torrent + */ + private final Torrent torrent; + /** + * The tracker URI. There are several types of trackers: + *

    + *
  • HTTP
  • + *
  • HTTPS
  • + *
  • UDP
  • + *
+ * Each type have its own protocol and methods to retrieve peers. + */ + private final URI uri; + /** + * The tracker backups uris + */ + private final Set backup = new HashSet(); + + /** + * Creates a new instance + * + * @param torrent + * the torrent + * @param uri + * the tracker uri + * @param backup + * the tracker's backup uris + */ + public TorrentTracker(Torrent torrent, URI uri, URI... backup) { + this.torrent = torrent; + this.uri = uri; + if (backup != null && backup.length > 0) + Collections.addAll(this.backup, backup); + } + + /** + * Creates a new instance + * + * @param torrent + * the torrent + * @param uri + * the tracker uri + */ + public TorrentTracker(Torrent torrent, URI uri) { + this(torrent, uri, (URI[]) null); + } + + /** + * Get the torrent + * + * @return the torrent + */ + public Torrent getTorrent() { + return torrent; + } + + /** + * Get the tracker {@link URL} + * + * @return the url + */ + public URL getURL() { + try { + return uri.toURL(); + } catch (MalformedURLException e) { + return null; + } + } + + /** + * Get the tracker {@link URI} + * + * @return the tracker uri + */ + public URI getURI() { + return uri; + } + + /** + * Get the backup {@link URI}s + * + * @return the tracker's backup URIS + */ + public Set getBackup() { + return Collections.unmodifiableSet(backup); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((uri == null) ? 0 : uri.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TorrentTracker other = (TorrentTracker) obj; + if (uri == null) { + if (other.uri != null) + return false; + } else if (!uri.equals(other.uri)) + return false; + return true; + } +} diff --git a/src/main/java/net/torrent/torrent/context/TorrentBitfield.java b/src/main/java/net/torrent/torrent/context/TorrentBitfield.java new file mode 100644 index 0000000..5becd3c --- /dev/null +++ b/src/main/java/net/torrent/torrent/context/TorrentBitfield.java @@ -0,0 +1,260 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.torrent.context; + +import java.security.InvalidParameterException; +import java.util.BitSet; +import java.util.Iterator; + +import net.torrent.torrent.TorrentPiece; + +/** + * This is a part bitfield. The only way to check if there is a full piece is to + * test all parts. + * + * @author Rogiel Josias Sulzbach + */ +public class TorrentBitfield implements Iterable { + /** + * The torrent context + */ + private final TorrentContext context; + /** + * {@link BitSet} containing all completed pieces + */ + private final BitSet bits; + + /** + * Creates a new instance given the {@link BitSet}. + * + * @param context + * the torrent context + * @param bits + * the {@link BitSet}. + */ + public TorrentBitfield(TorrentContext context, BitSet bits) { + this.context = context; + this.bits = bits; + } + + /** + * Creates a new instance + * + * @param context + * the torrent context + */ + public TorrentBitfield(TorrentContext context) { + this(context, new BitSet((context.getTorrent() != null ? context + .getTorrent().getPieces().length : 0))); + } + + /** + * Test if this bitfield contains the given piece. Redirect the call to + * {@link TorrentBitfield#hasPiece(int)}. + * + * @param piece + * the piece to test + * @return true if piece is set, false otherwise + */ + public boolean hasPiece(TorrentPiece piece) { + if (piece == null) + throw new InvalidParameterException("Piece is null"); + return hasPiece(piece.getIndex()); + } + + /** + * Test if this bitfield contains the given piece index + * + * @param index + * the piece index + * @return true if piece is set, false otherwise + */ + public boolean hasPiece(int index) { + return bits.get(index); + } + + /** + * Set the state of the given piece. + * + * @param piece + * the piece + * @param state + * the state + */ + public void setPiece(TorrentPiece piece, boolean state) { + if (piece == null) + throw new InvalidParameterException("Piece is null"); + bits.set(piece.getIndex(), state); + } + + /** + * Set the bits into this bitfield. + * + * @param bitSet + * the bits + */ + public void setBits(BitSet bitSet) { + bits.xor(bitSet); + } + + /** + * Get the torrent context + * + * @return the torrent context + */ + public TorrentContext getContext() { + return context; + } + + /** + * Get the backing {@link BitSet}. + * + * @return the bit set + */ + public BitSet getBits() { + return bits; + } + + /** + * Return an Bitfield representing remaining pieces. + * + * @return the remain bitfield + */ + public TorrentBitfield getRemainingPieces() { + BitSet set = (BitSet) bits.clone(); + set.flip(0, bits.size()); + return new TorrentBitfield(context, set); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((bits == null) ? 0 : bits.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TorrentBitfield other = (TorrentBitfield) obj; + if (bits == null) { + if (other.bits != null) + return false; + } else if (!bits.equals(other.bits)) + return false; + return true; + } + + @Override + public Iterator iterator() { + return iterator(true); + } + + /** + * Iterate to all pieces with type. True if set, false if not. + * + * @param type + * the type + * @return {@link Iterator} for those pieces + */ + public Iterator iterator(final boolean type) { + return new BitfieldIterator(type); + } + + /** + * Iterate to all pieces with type. True if set, false if not. + * + * @param type + * the type + * @return {@link Iterable} for those pieces + * @see TorrentBitfield#iterator(boolean) + */ + public Iterable iterate(final boolean type) { + return new Iterable() { + @Override + public Iterator iterator() { + return TorrentBitfield.this.iterator(type); + } + }; + } + + /** + * The bitfield iterator + * + * @author Rogiel Josias Sulzbach (http://www.rogiel.com/) + */ + public class BitfieldIterator implements Iterator { + /** + * The desired value + */ + private boolean value = true; + + /** + * The current index + */ + private int index = 0; + + /** + * Creates a new instance + * + * @param value + * the desired state + */ + public BitfieldIterator(boolean value) { + this.value = value; + } + + @Override + public boolean hasNext() { + return getIndex() >= 0; + } + + @Override + public TorrentPiece next() { + int index = -2; + try { + index = getIndex(); + return context.getTorrent().getPiece(getIndex()); + } finally { + this.index = index + 1; + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + /** + * Find the next index, without increasing it. + * + * @return the next index, -1 if non existent. + */ + private int getIndex() { + if (value) + return index = bits.nextSetBit(index); + else + return bits.nextClearBit(index); + } + } +} diff --git a/src/main/java/net/torrent/torrent/context/TorrentContext.java b/src/main/java/net/torrent/torrent/context/TorrentContext.java new file mode 100644 index 0000000..8d86ac1 --- /dev/null +++ b/src/main/java/net/torrent/torrent/context/TorrentContext.java @@ -0,0 +1,188 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.torrent.context; + +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import net.torrent.protocol.tracker.message.PeerListMessage.PeerInfo; +import net.torrent.torrent.Torrent; +import net.torrent.torrent.context.TorrentPeerCapabilities.TorrentPeerCapability; + +public class TorrentContext { + /** + * The torrent metadata object + */ + private final Torrent torrent; + /** + * The bitfield + */ + private final TorrentBitfield bitfield = new TorrentBitfield(this); + + /** + * The capabilities + */ + private final TorrentPeerCapabilities capabilites = new TorrentPeerCapabilities( + TorrentPeerCapability.DHT, TorrentPeerCapability.FAST_PEERS); + + private final Set peers = new HashSet(); + /** + * Unknown peers does not have their IDs, consequently they cannot be + * queried using their Id and must be done through IP. + */ + private final Set unknownPeers = new HashSet(); + + /** + * Creates a new context + * + * @param torrent + * the torrent metadata + */ + public TorrentContext(Torrent torrent) { + this.torrent = torrent; + } + + /** + * Get the torrent metadata object + * + * @return metadata object + */ + public Torrent getTorrent() { + return torrent; + } + + /** + * Get the context bitfield + * + * @return the bitfield + */ + public TorrentBitfield getBitfield() { + return bitfield; + } + + /** + * Get the capabilities of this context + * + * @return the capabilities + */ + public TorrentPeerCapabilities getCapabilites() { + return capabilites; + } + + /** + * Tests if both peer and this context support an given capability. + * + * @param peer + * the peer + * @param capability + * the capability + * @return true if both support this capability + */ + public boolean supports(TorrentPeer peer, TorrentPeerCapability capability) { + return capabilites.supports(capability) + && peer.getCapabilities().supports(capability); + } + + /** + * Get the list of known peers (have known peerid) + * + * @return the list of peers + */ + public Set getPeers() { + return Collections.unmodifiableSet(peers); + } + + /** + * Get the list of unknown peers (don't have known peerid) + * + * @return the list of peers + */ + public Set getUnknownPeers() { + return Collections.unmodifiableSet(unknownPeers); + } + + /** + * Get an peer by its PeerID + * + * @param peerId + * the peer id + * @return the found peer. Null if not found. + */ + public TorrentPeer getPeer(TorrentPeerID peerId) { + for (final TorrentPeer peer : peers) { + if (peer.getPeerID().equals(peerId)) + return peer; + } + return null; + } + + /** + * Get an peer by its address + * + * @param address + * the address + * @return the found peer. Null if not found. + */ + public TorrentPeer getPeer(InetSocketAddress address) { + for (final TorrentPeer peer : peers) { + if (peer.getSocketAddress().equals(address)) + return peer; + } + return null; + } + + /** + * Lookup for a peer first by its id, then by address, if still not found, + * creates a new entry. + * + * @param id + * the peer id + * @param address + * the address + * @return the found or newly created peer + */ + public TorrentPeer getPeer(TorrentPeerID id, InetSocketAddress address) { + TorrentPeer peer = getPeer(id); + if (peer == null) { + peer = getPeer(address); + if (peer != null) { + if (peers.remove(peer)) + peer = peer.createWithID(id); + } else { + peer = new TorrentPeer(this, id, null); + } + peers.add(peer); + } + return peer; + } + + /** + * If this peer already exists, will update its IP. + * + * @param peerInfo + * the peer info object, returned from the tracker + */ + public TorrentPeer addPeerByPeerInfo(PeerInfo peerInfo) { + final TorrentPeerID id = TorrentPeerID.create(peerInfo.getPeerId()); + final InetSocketAddress address = new InetSocketAddress( + peerInfo.getIp(), peerInfo.getPort()); + TorrentPeer peer = getPeer(id, address); + peer.setSocketAddress(address); + return peer; + } +} diff --git a/src/main/java/net/torrent/torrent/context/TorrentPeer.java b/src/main/java/net/torrent/torrent/context/TorrentPeer.java new file mode 100644 index 0000000..bbb482b --- /dev/null +++ b/src/main/java/net/torrent/torrent/context/TorrentPeer.java @@ -0,0 +1,369 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.torrent.context; + +import java.net.InetSocketAddress; +import java.util.Date; + +/** + * Object representing a peer in the swarm. + * + * @author Rogiel Josias Sulzbach + */ +public class TorrentPeer { + /** + * The torrent context + */ + private final TorrentContext context; + /** + * The peer id + */ + private final TorrentPeerID peerID; + + /** + * Set of peer capabilities + */ + private final TorrentPeerCapabilities capabilities = new TorrentPeerCapabilities(); + + /** + * The bitfield of the peer + */ + private final TorrentBitfield bitfield; + /** + * Local choking state to this peer. By default is + * {@link ChokingState#CHOKED choked}. + */ + private ChokingState chokingState = ChokingState.CHOKED; + /** + * Remote choking state of this peer. By default is + * {@link ChokingState#CHOKED choked}. + */ + private ChokingState peerChokingState = ChokingState.CHOKED; + + /** + * The choking states + * + * @author Rogiel Josias Sulzbach (http://www.rogiel.com/) + */ + public enum ChokingState { + /** + * Choked: communication is "blocked". No pieces can be requested nor + * sent. By stantard, this is the default interest once the connection + * is made. + */ + CHOKED, + /** + * Unchoked: communication is "open": pieces can be requested and + * uploaded. + */ + UNCHOKED; + } + + /** + * Local interest to this peer. By default is + * {@link InterestState#UNINTERESTED not interested} + */ + private InterestState interestState = InterestState.UNINTERESTED; + /** + * Remote interest of this peer. By default is + * {@link InterestState#UNINTERESTED not interested} + */ + private InterestState peerInterestState = InterestState.UNINTERESTED; + + /** + * The interest states + * + * @author Rogiel Josias Sulzbach (http://www.rogiel.com/) + */ + public enum InterestState { + /** + * The peer is interested in the other pieces. If the peer is willing to + * upload, the remote peer is unchoked and data transfer can begin. + */ + INTERESTED, + + /** + * The peer has not interest in the remote peer pieces. By stantard, + * this is the default interest once the connection is made. + */ + UNINTERESTED; + } + + /** + * The peer socket address. For incoming connections. + */ + private InetSocketAddress socketAddress; + /** + * Last time the peer was found online. + */ + private Date lastReached = null; + /** + * State of the peer connectivity. Peer behind firewall or NAT will have + * false. By default all peers are accessible. + */ + private boolean accessible = true; + + /** + * Creates a new peer + * + * @param context + * the torrent context + * @param peerID + * the peer id + * @param socketAddress + * the peer address + */ + public TorrentPeer(TorrentContext context, TorrentPeerID peerID, + InetSocketAddress socketAddress) { + if (peerID == null && socketAddress == null) + throw new IllegalArgumentException( + "PeerID or SocketAddress must be not null"); + if (context == null) + throw new IllegalArgumentException("Context is null"); + + this.context = context; + this.peerID = peerID; + this.socketAddress = socketAddress; + this.bitfield = new TorrentBitfield(context); + } + + /** + * Get the peer id + * + * @return the peer id + */ + public TorrentPeerID getPeerID() { + return peerID; + } + + /** + * Set the peer address + * + * @param address + * the address + */ + public void setSocketAddress(InetSocketAddress address) { + this.socketAddress = address; + } + + /** + * Get the peer address + * + * @return the address + */ + public InetSocketAddress getSocketAddress() { + return socketAddress; + } + + /** + * Get the peer bitfield + * + * @return the bitfield + */ + public TorrentBitfield getBitfield() { + return bitfield; + } + + /** + * Get the peer capabilities + * + * @return the capabilities + */ + public TorrentPeerCapabilities getCapabilities() { + return capabilities; + } + + /** + * Get the local choking state to this peer. By default is + * {@link ChokingState#CHOKED choked}. + * + * @return the choking state + */ + public ChokingState getChokingState() { + return chokingState; + } + + /** + * Set the local choking state to this peer. By default is + * {@link ChokingState#CHOKED choked}. + * + * @param chokingState + * the choking state + */ + public void setChokingState(ChokingState chokingState) { + this.chokingState = chokingState; + } + + /** + * + * Get the remote choking state of this peer. By default is + * {@link ChokingState#CHOKED choked}. + * + * @return the peer choking state + */ + public ChokingState getPeerChokingState() { + return peerChokingState; + } + + /** + * Set the remote choking state of this peer. By default is + * {@link ChokingState#CHOKED choked}. + * + * @param peerChokingState + * the peer choking state + */ + public void setPeerChokingState(ChokingState peerChokingState) { + this.peerChokingState = peerChokingState; + } + + /** + * Get the local interest to this peer. By default is + * {@link InterestState#UNINTERESTED not interested} + * + * @return the local interest + */ + public InterestState getInterestState() { + return interestState; + } + + /** + * Set the local interest to this peer. By default is + * {@link InterestState#UNINTERESTED not interested} + * + * @param interestState + * the local interest + */ + public void setInterestState(InterestState interestState) { + this.interestState = interestState; + } + + /** + * Get the peer remote interest of this peer. By default is + * {@link InterestState#UNINTERESTED not interested} + * + * @return the peer remote interest + */ + public InterestState getPeerInterestState() { + return peerInterestState; + } + + /** + * Set the peer remote interest of this peer. By default is + * {@link InterestState#UNINTERESTED not interested} + * + * @param peerInterestState + * the peer remote interest + */ + public void setPeerInterestState(InterestState peerInterestState) { + this.peerInterestState = peerInterestState; + } + + /** + * Get the last time the peer has been reached by an connection + * + * @return the date of last connection + */ + public Date getLastReached() { + return lastReached; + } + + /** + * Set the last time the peer has been reached by an connection + * + * @param lastReached + * the date of last connection + */ + public void setLastReached(Date lastReached) { + this.lastReached = lastReached; + } + + /** + * State of the peer connectivity. Peer behind firewall or NAT will have + * false. By default all peers are accessible. + * + * @return true if is accessible, false otherwise. + */ + public boolean isAccessible() { + return accessible; + } + + /** + * State of the peer connectivity. Peer behind firewall or NAT will have + * false. By default all peers are accessible. + * + * @param accessible + * true if is accessible, false otherwise. + */ + public void setAccessible(boolean accessible) { + this.accessible = accessible; + } + + /** + * Get the torrent context + * + * @return the torrent context + */ + public TorrentContext getContext() { + return context; + } + + /** + * Create an clone of this peer with the new {@link TorrentPeerID id}. + * + * @param id + * the new peer id + * @return the new peer with id. + */ + public TorrentPeer createWithID(TorrentPeerID id) { + return new TorrentPeer(context, id, socketAddress); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TorrentPeer other = (TorrentPeer) obj; + if (peerID == null) { + if (other.peerID != null) + return false; + if (socketAddress == null) { + if (other.socketAddress != null) + return false; + } else if (!socketAddress.equals(other.socketAddress)) + return false; + } else if (!peerID.equals(other.peerID)) + return false; + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + if (peerID != null) { + result = prime * result + peerID.hashCode(); + } else if (socketAddress != null) { + result = prime * result + socketAddress.hashCode(); + } + return result; + } +} diff --git a/src/main/java/net/torrent/torrent/context/TorrentPeerCapabilities.java b/src/main/java/net/torrent/torrent/context/TorrentPeerCapabilities.java new file mode 100644 index 0000000..f3c7444 --- /dev/null +++ b/src/main/java/net/torrent/torrent/context/TorrentPeerCapabilities.java @@ -0,0 +1,154 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.torrent.context; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; + +/** + * Object containing peers support for certain capabilities. + * + * @author Rogiel Josias Sulzbach + */ +public class TorrentPeerCapabilities { + /** + * BitSet containing available capabilities + */ + private BitSet capabilities = new BitSet(64); + + /** + * Create a new instance + */ + public TorrentPeerCapabilities() { + } + + /** + * Create a new instance with the given capabilities + * + * @param capabilities + * the capabilities supported + */ + public TorrentPeerCapabilities(TorrentPeerCapability... capabilities) { + for (final TorrentPeerCapability capability : capabilities) { + this.capabilities.set(capability.bit); + } + } + + /** + * Create a new instance with the given capabilities + * + * @param capabilities + * the capabilities {@link BitSet} supported + */ + public TorrentPeerCapabilities(BitSet capabilitites) { + this.capabilities = capabilitites; + } + + /** + * Tests if the given capability is supported. + * + * @param capability + * the capability + * @return true if capability is supported, false otherwise. + */ + public boolean supports(TorrentPeerCapability capability) { + return capabilities.get(capability.getBit()); + } + + /** + * Get an {@link List} of capabilities supported. + * + * @return the list os capabilities + */ + public List getCapabilities() { + final List capabilitites = new ArrayList(); + for (final TorrentPeerCapability capability : TorrentPeerCapability + .values()) { + if (supports(capability)) + capabilitites.add(capability); + } + return capabilitites; + } + + /** + * Set the capabilities {@link BitSet}. + * + * @param capabilitites + * the bitset + */ + public void setCapabilities(BitSet capabilitites) { + this.capabilities = capabilitites; + } + + /** + * Convert this matrix of capabilties to a {@link BitSet}. + * + * @return an {@link BitSet}. + */ + public BitSet toBitSet() { + return capabilities; + } + + /** + * Enumeration of known capabilities. + * + * @author Rogiel Josias Sulzbach (http://www.rogiel.com/) + */ + public enum TorrentPeerCapability { + /** + * Location aware protocol + */ + LOCATION_AWARE_PROTOCOL(21), + /** + * Extension protocol + */ + EXTENSION_PROTOCOL(44), + /** + * Fast peers support + */ + FAST_PEERS(62), + /** + * DHT Support + */ + DHT(64); + + /** + * The bit index for this capability + */ + private final int bit; + + /** + * Creates a new capability + * + * @param bit + * the bit marking this capability + */ + TorrentPeerCapability(int bit) { + this.bit = bit; + } + + /** + * Get the bit marking this capability + * + * @return the bit marking the capability + */ + public int getBit() { + return bit; + } + } +} diff --git a/src/main/java/net/torrent/torrent/context/TorrentPeerID.java b/src/main/java/net/torrent/torrent/context/TorrentPeerID.java new file mode 100644 index 0000000..52a5dcc --- /dev/null +++ b/src/main/java/net/torrent/torrent/context/TorrentPeerID.java @@ -0,0 +1,87 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.torrent.context; + +import java.util.Arrays; + +/** + * Creates a new Peer id for an peer. + * + * @author Rogiel Josias Sulzbach + */ +public class TorrentPeerID { + /** + * The peer id in bytes (20 bytes) + */ + private byte[] peerID; + + /** + * Creates a new PeerID. + * + * @param peerID + * the bytes of the peer id + */ + protected TorrentPeerID(byte[] peerID) { + this.peerID = peerID; + } + + @Override + public String toString() { + return new String(peerID); + } + + /** + * Convert to a byte array + * + * @return the id in bytes + */ + public byte[] toByteArray() { + return Arrays.copyOf(peerID, peerID.length); + } + + /** + * Creates a new ID + * + * @param peerID + * the id byte array + * @return the new instance of {@link TorrentPeerID peer id} + */ + public static final TorrentPeerID create(byte[] peerID) { + return new TorrentPeerID(peerID); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(peerID); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TorrentPeerID other = (TorrentPeerID) obj; + if (!Arrays.equals(peerID, other.peerID)) + return false; + return true; + } +} \ No newline at end of file diff --git a/src/main/java/net/torrent/torrent/piece/PieceSelector.java b/src/main/java/net/torrent/torrent/piece/PieceSelector.java new file mode 100644 index 0000000..eeeb418 --- /dev/null +++ b/src/main/java/net/torrent/torrent/piece/PieceSelector.java @@ -0,0 +1,37 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.torrent.piece; + +import net.torrent.torrent.TorrentPiece; +import net.torrent.torrent.context.TorrentPeer; + +/** + * The {@link PieceSelector} is used to select the desired piece to be + * downloaded. Implementations must make sure the piece is not being downloaded + * already and that the peer has it. + * + * @author Rogiel Josias Sulzbach + */ +public interface PieceSelector { + /** + * Selects the next piece suitable for download. + * + * @param peer + * the peer chosen for download + * @return the {@link TorrentPiece piece} selected for download. + */ + public TorrentPiece select(TorrentPeer peer); +} diff --git a/src/main/java/net/torrent/torrent/piece/RandomPieceSelector.java b/src/main/java/net/torrent/torrent/piece/RandomPieceSelector.java new file mode 100644 index 0000000..a0506cc --- /dev/null +++ b/src/main/java/net/torrent/torrent/piece/RandomPieceSelector.java @@ -0,0 +1,47 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.torrent.piece; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import net.torrent.protocol.peerwire.manager.TorrentManager; +import net.torrent.torrent.TorrentPiece; + +/** + * Selects randomly an piece. Once this instance is created it takes all pieces + * and put them into an list, then the list is sorted by an random algorithm. + * Once the next piece is called the pieces are returned in-list order. + * + * @author Rogiel Josias Sulzbach + */ +public class RandomPieceSelector extends SortedListPieceSelector { + /** + * Creates a new instance + * + * @param manager + * the torrent manager + */ + public RandomPieceSelector(TorrentManager manager) { + super(manager, Arrays.asList(manager.getTorrent().getPieces())); + } + + @Override + protected void sort(List pieces) { + Collections.shuffle(pieces); + } +} diff --git a/src/main/java/net/torrent/torrent/piece/SequentialPieceSelector.java b/src/main/java/net/torrent/torrent/piece/SequentialPieceSelector.java new file mode 100644 index 0000000..a640936 --- /dev/null +++ b/src/main/java/net/torrent/torrent/piece/SequentialPieceSelector.java @@ -0,0 +1,46 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.torrent.piece; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import net.torrent.protocol.peerwire.manager.TorrentManager; +import net.torrent.torrent.TorrentPiece; +import net.torrent.util.comparator.PieceIndexComparator; + +/** + * Select pieces in sequential order. Can be used for streaming purposes. + * + * @author Rogiel Josias Sulzbach + */ +public class SequentialPieceSelector extends SortedListPieceSelector { + /** + * Creates a new instance + * + * @param manager + * the torrent manager + */ + public SequentialPieceSelector(TorrentManager manager) { + super(manager, Arrays.asList(manager.getTorrent().getPieces())); + } + + @Override + protected void sort(List pieces) { + Collections.sort(pieces, PieceIndexComparator.SHARED_INSTANCE); + } +} diff --git a/src/main/java/net/torrent/torrent/piece/SortedListPieceSelector.java b/src/main/java/net/torrent/torrent/piece/SortedListPieceSelector.java new file mode 100644 index 0000000..ceba524 --- /dev/null +++ b/src/main/java/net/torrent/torrent/piece/SortedListPieceSelector.java @@ -0,0 +1,78 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.torrent.piece; + +import java.util.List; + +import net.torrent.protocol.peerwire.manager.TorrentManager; +import net.torrent.torrent.TorrentPiece; +import net.torrent.torrent.context.TorrentPeer; + +/** + * An sorted {@link PieceSelector} select pieces sequentially (whenever + * possible) in a pre-sorted list. The list is sorted on construction by the + * {@link SortedListPieceSelector#sort(List) sort} method. + * + * @author Rogiel Josias Sulzbach + */ +public abstract class SortedListPieceSelector implements PieceSelector { + /** + * The torrent manager + */ + private final TorrentManager manager; + /** + * The sorted list of pieces + */ + private final List pieces; + + /** + * Creates a new instance + * + * @param manager + * the torrent manager + * @param pieces + * the unsorted list of pieces + */ + protected SortedListPieceSelector(TorrentManager manager, + List pieces) { + this.manager = manager; + this.pieces = pieces; + this.sort(this.pieces); + } + + @Override + public synchronized TorrentPiece select(TorrentPeer peer) { + for (int index = 0; index < pieces.size(); index++) { + final TorrentPiece piece = pieces.get(index); + if (manager.getContext().getBitfield().hasPiece(piece)) + continue; + if (!peer.getBitfield().hasPiece(piece)) + continue; + if (manager.getDownloadManager().isDownloading(piece)) + continue; + return piece; + } + return null; + } + + /** + * Sorts the set using an implementation specific algorithm. + * + * @param pieces + * the unsorted pieces list that will be sorted. + */ + protected abstract void sort(List pieces); +} diff --git a/src/main/java/net/torrent/util/PeerCallback.java b/src/main/java/net/torrent/util/PeerCallback.java new file mode 100644 index 0000000..8fb998f --- /dev/null +++ b/src/main/java/net/torrent/util/PeerCallback.java @@ -0,0 +1,34 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.util; + +import net.torrent.protocol.peerwire.PeerWirePeer; +import net.torrent.protocol.peerwire.manager.PeerManager; + +/** + * Callback used in {@link PeerManager#execute(PeerCallback)} + * + * @author Rogiel Josias Sulzbach + */ +public interface PeerCallback { + /** + * Execute the desired action for peer + * + * @param peer + * the peer + */ + void callback(PeerWirePeer peer); +} diff --git a/src/main/java/net/torrent/util/Range.java b/src/main/java/net/torrent/util/Range.java new file mode 100644 index 0000000..4441d7f --- /dev/null +++ b/src/main/java/net/torrent/util/Range.java @@ -0,0 +1,178 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.util; + +import java.io.Serializable; + +/** + * This class represents an interval. + * + * @author dante + */ +public final class Range implements Serializable { + private static final long serialVersionUID = 1L; + + private final long start, length; + + /** + * Creates a range with the given parameters + * + * @param start + * the beginning of the range + * @param length + * the length of the range + * @return a range + */ + public static Range getRangeByLength(long start, long length) { + return new Range(start, length); + } + + /** + * Creates the smallest possible range containing the given numbers. + * + * @param number1 + * number to be within the range + * @param number2 + * number to be within the range + * @return + */ + public static Range getRangeByNumbers(long number1, long number2) { + long s = Math.min(number1, number2); + return new Range(s, Math.max(number1, number2) - s + 1); + } + + /** + * Creates a new range + * + * @param start + * @param length + */ + private Range(long start, long length) { + if (start < 0) + throw new IllegalArgumentException("start must be >= 0"); + if (length < 0) + throw new IllegalArgumentException("length must be >= 0"); + + this.start = start; + this.length = length; + } + + /** + * @param range + * @return true if the given range is contained within this range + */ + public boolean contains(Range range) { + return getStart() <= range.getStart() && getEnd() >= range.getEnd(); + } + + /** + * @param pos + * @return true if the given point is contained within this range + */ + public boolean contains(long pos) { + return getStart() <= pos && getEnd() >= pos; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Range) { + Range r = (Range) obj; + return getStart() == r.getStart() && getLength() == r.getLength(); + } + return false; + } + + /** + * @return the last index contained in this range + */ + public long getEnd() { + return start + length - 1; + } + + /** + * @return the length of this range + */ + public long getLength() { + return length; + } + + /** + * @return the first index contained in this range + */ + public long getStart() { + return start; + } + + /** + * Test if this range defines a zero-sized range. + * + * @return true if zero-sized. + */ + public boolean isEmpty() { + return length == 0; + } + + @Override + public int hashCode() { + return (int) ((getStart() * 13) & (getEnd() * 137)); + } + + /** + * Creates a range which contains only the indices contained in the + * intersection of this range and the given range. + * + * @param range + * the range to intersect with + * @return the intersected range or null if the ranges don't overlap + */ + public Range intersection(Range range) { + if (!intersects(range)) { + return null; + } + return getRangeByNumbers(Math.max(getStart(), range.getStart()), + Math.min(getEnd(), range.getEnd())); + } + + /** + * Returns the number of indices which are in this range and the given + * range. + * + * @param r + * @return 0 if the ranges don't overlap, the length of the intersection + * between them otherwise + */ + public long intersectionLength(Range r) { + if (!intersects(r)) { + return 0; + } + return Math.min(getEnd(), r.getEnd()) + - Math.max(getStart(), r.getStart()) + 1; + } + + /** + * @param range + * the range to intersect test with + * @return true if the ranges overlap + */ + public boolean intersects(Range range) { + return getStart() <= range.getEnd() && getEnd() >= range.getStart(); + } + + @Override + public String toString() { + return "[" + getStart() + " - " + getEnd() + "]"; + } +} diff --git a/src/main/java/net/torrent/util/bencoding/BDecodingException.java b/src/main/java/net/torrent/util/bencoding/BDecodingException.java new file mode 100644 index 0000000..ff16c99 --- /dev/null +++ b/src/main/java/net/torrent/util/bencoding/BDecodingException.java @@ -0,0 +1,21 @@ +package net.torrent.util.bencoding; + +import java.io.IOException; + +/** + * BDecodingException: an error has occured when decoding bencoded data. + * + * @author Dennis "Bytekeeper" Waldherr + */ +public class BDecodingException extends IOException { + private static final long serialVersionUID = 1L; + + public BDecodingException(Exception e) { + super(e); + } + + public BDecodingException(String message) { + super(message); + } + +} diff --git a/src/main/java/net/torrent/util/bencoding/BEncodedInputStream.java b/src/main/java/net/torrent/util/bencoding/BEncodedInputStream.java new file mode 100644 index 0000000..ca88c5c --- /dev/null +++ b/src/main/java/net/torrent/util/bencoding/BEncodedInputStream.java @@ -0,0 +1,320 @@ +package net.torrent.util.bencoding; + +import java.io.ByteArrayInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.PushbackInputStream; +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Helper class to decode "bencoded" data. + * + * @author Dennis "Bytekeeper" Waldherr + * + */ +public class BEncodedInputStream extends InputStream { + private static final int MAX_STRING_LENGTH = 1024 * 1024; + + private final PushbackInputStream in; + + public BEncodedInputStream(InputStream in) { + this.in = new PushbackInputStream(in); + } + + /** + * Utility method to decode exactly one bencoded element. + * + * @param bencode + * @return + * @throws BDecodingException + * @throws IOException + */ + public static Object bdecode(byte[] bencode) throws BDecodingException { + BEncodedInputStream in = new BEncodedInputStream( + new ByteArrayInputStream(bencode)); + try { + return in.readElement(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Reads one bencoded element. + * + * @param in + * @return + * @throws IOException + * @throws BDecodingException + */ + public Object readElement() throws IOException, BDecodingException { + return bdecode(); + } + + /** + * Converts a string result from bencoding to a java String. If the data is + * already a String or null it is simply returned. + * + * @param data + * @return + * @throws BTypeException + */ + private static String stringOf(Object data) throws BTypeException { + if (data == null) { + return null; + } + try { + if (data instanceof byte[]) { + return new String((byte[]) data, "UTF-8"); + } else if (data instanceof String) { + return (String) data; + } else { + throw new BTypeException("Unsupported type: " + data.getClass()); + } + } catch (UnsupportedEncodingException e) { + throw new Error(e); + } + } + + /** + * Converts a constructed or decoded integer to a java Integer. If the data + * is already an Integer or null it is simply returned. + * + * @param data + * @return + * @throws BTypeException + */ + private static Integer intOf(Object data) throws BTypeException { + if (data == null) { + return null; + } + if (data instanceof BigInteger) { + return ((BigInteger) data).intValue(); + } else if (data instanceof Integer) { + return (Integer) data; + } else { + throw new BTypeException("Unsupported type: " + data.getClass()); + } + } + + /** + * Converts a constructed or decoded integer to a java Integer. If the data + * is already an Integer or null it is simply returned. + * + * @param data + * @return + * @throws BTypeException + */ + private static Long longOf(Object data) throws BTypeException { + if (data == null) { + return null; + } + if (data instanceof BigInteger) { + return ((BigInteger) data).longValue(); + } else if (data instanceof Integer) { + return Long.valueOf((Integer) data); + } else if (data instanceof Long) { + return (Long) data; + } else { + throw new BTypeException("Unsupported type: " + data.getClass()); + } + } + + private Object bdecode() throws IOException, BDecodingException { + int head = in.read(); + switch (head) { + case -1: + throw new EOFException(); + case 'l': + return bdecodeList(); + case 'i': + return bdecodeInteger(); + case 'd': + return bdecodeDictionary(); + default: + if (Character.isDigit(head)) { + in.unread(head); + return bdecodeString(); + } + } + throw new BDecodingException("Parameter is not bencoded data."); + } + + private BMap bdecodeDictionary() throws IOException, BDecodingException { + assert in != null; + + BMap map = new BMapImpl(); + int head; + while ((head = in.read()) != 'e') { + if (head < 0) { + throw new EOFException(); + } + in.unread(head); + String key; + try { + key = stringOf(bdecodeString()); + } catch (BTypeException e) { + throw new BDecodingException(e); + } + map.put(key, bdecode()); + } + return map; + } + + private byte[] bdecodeString() throws IOException, BDecodingException { + assert in != null; + int len = 0; + int head; + while ((head = in.read()) != ':') { + if (head < 0) { + throw new EOFException(); + } + len = len * 10 + (head - '0'); + if (len > MAX_STRING_LENGTH) { + throw new BDecodingException("Encoded string length exceeds " + + MAX_STRING_LENGTH + " bytes!"); + } + } + byte data[] = new byte[len]; + int off = 0; + while (len > 0) { + int read = in.read(data, off, len); + len -= read; + off += read; + } + return data; + } + + private Object bdecodeInteger() throws IOException, BDecodingException { + assert in != null; + + StringBuilder b = new StringBuilder(); + int head; + while ((head = in.read()) != 'e') { + if (head < 0) { + throw new EOFException(); + } + if (!Character.isDigit(head)) { + throw new BDecodingException("Expected digit but got: " + head); + } + b.append((char) head); + } + return new BigInteger(b.toString()); + } + + private BList bdecodeList() throws IOException, BDecodingException { + assert in != null; + + BList list = new BListImpl(); + int head; + while ((head = in.read()) != 'e') { + if (head < 0) { + throw new EOFException(); + } + in.unread(head); + list.add(bdecode()); + } + return list; + } + + @Override + public int read() throws IOException { + return in.read(); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return in.read(b, off, len); + } + + private static class BListImpl extends ArrayList implements BList { + + /** + * + */ + private static final long serialVersionUID = 1L; + + @Override + public Integer getInteger(int index) throws BTypeException { + return intOf(get(index)); + } + + @Override + public Long getLong(int index) throws BTypeException { + return longOf(get(index)); + } + + @Override + public String getString(int index) throws BTypeException { + return stringOf(get(index)); + } + + @Override + public BList getList(int index) throws BTypeException { + Object tmp = get(index); + if (tmp instanceof BList) { + return (BList) tmp; + } + throw new BTypeException("Unexpected type: " + tmp.getClass()); + } + + @Override + public BMap getMap(int index) throws BTypeException { + Object tmp = get(index); + if (tmp instanceof BMap) { + return (BMap) tmp; + } + throw new BTypeException("Unexpected type: " + tmp.getClass()); + } + + } + + private static class BMapImpl extends HashMap implements + BMap { + + /** + * + */ + private static final long serialVersionUID = 1L; + + @Override + public Integer getInteger(String key) throws BTypeException { + return intOf(get(key)); + } + + @Override + public Long getLong(String key) throws BTypeException { + return longOf(get(key)); + } + + @Override + public String getString(String key) throws BTypeException { + return stringOf(get(key)); + } + + @Override + public BList getList(String key) throws BTypeException { + Object tmp = get(key); + if (tmp instanceof BList) { + return (BList) tmp; + } + if (tmp != null) + throw new BTypeException("Unexpected type: " + tmp.getClass()); + return null; + } + + @Override + public BMap getMap(String key) throws BTypeException { + Object tmp = get(key); + if (tmp instanceof BMap) { + return (BMap) tmp; + } + throw new BTypeException("Unexpected type: " + tmp.getClass()); + } + } + +} diff --git a/src/main/java/net/torrent/util/bencoding/BEncodedOutputStream.java b/src/main/java/net/torrent/util/bencoding/BEncodedOutputStream.java new file mode 100644 index 0000000..c38ccd3 --- /dev/null +++ b/src/main/java/net/torrent/util/bencoding/BEncodedOutputStream.java @@ -0,0 +1,168 @@ +package net.torrent.util.bencoding; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.security.InvalidParameterException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +/** + * Helper class to "BEncode" data. + * + * @author Dennis "Bytekeeper" Waldherr + * + */ +public final class BEncodedOutputStream extends OutputStream { + static final Comparator BYTE_COMPARATOR = new Comparator() { + @Override + public int compare(String o1, String o2) { + byte b1[] = bytesOf(o1); + byte b2[] = bytesOf(o2); + int len = Math.min(b1.length, b2.length); + for (int i = 0; i < len; i++) { + if (b1[i] > b2[i]) { + return 1; + } + if (b1[i] < b2[i]) { + return -1; + } + } + return b1.length - b2.length; + } + }; + + private final OutputStream out; + + /** + * Converts a string into the raw byte array representation used to store + * into bencoded data. + * + * @param s + * @return + */ + public static byte[] bytesOf(String s) { + try { + return s.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new Error(e); + } + } + + private BEncodedOutputStream(OutputStream out) { + if (out == null) + throw new InvalidParameterException("output strean is null"); + this.out = out; + } + + /** + * @param obj + * @return + */ + public static byte[] bencode(Object obj) { + if (obj == null) + throw new InvalidParameterException("Object to encode is null!"); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + BEncodedOutputStream bout = new BEncodedOutputStream(out); + bout.writeElement(obj); + bout.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + return out.toByteArray(); + } + + /** + * BEncodes data and outputs it to an OutputStream. + * + * @param out + * @param obj + * @throws IOException + */ + @SuppressWarnings("unchecked") + public void writeElement(Object obj) throws IOException { + if (obj == null) + throw new InvalidParameterException("Object to encode is null!"); + + if (obj instanceof String) { + bencodeString((String) obj); + } else if (obj instanceof byte[]) { + bencodeString((byte[]) obj); + } else if (obj instanceof Integer) { + bencodeInteger(BigInteger.valueOf((Integer) obj)); + } else if (obj instanceof Long) { + bencodeInteger(BigInteger.valueOf((Long) obj)); + } else if (obj instanceof BigInteger) { + bencodeInteger((BigInteger) obj); + } else if (obj instanceof Collection) { + bencodeList((Collection) obj); + } else if (obj instanceof Map) { + bencodeDictionary((Map) obj); + } else { + throw new IllegalArgumentException("Type " + obj.getClass() + + " is not bencodable."); + } + } + + private void bencodeDictionary(Map dict) throws IOException { + assert out != null; + assert dict != null; + + out.write('d'); + List sorted = new ArrayList(dict.keySet()); + Collections.sort(sorted, BYTE_COMPARATOR); + for (String key : sorted) { + writeElement(key); + writeElement(dict.get(key)); + } + out.write('e'); + } + + private void bencodeList(Collection list) throws IOException { + assert out != null; + assert list != null; + out.write('l'); + for (Object child : list) { + writeElement(child); + } + out.write('e'); + } + + private void bencodeInteger(BigInteger integer) throws IOException { + assert integer != null; + out.write('i'); + out.write(bytesOf(integer.toString())); + out.write('e'); + } + + private void bencodeString(String string) throws IOException { + assert string != null; + byte[] bytes = bytesOf(string); + bencodeString(bytes); + } + + private void bencodeString(byte[] data) throws IOException { + assert data != null; + out.write(bytesOf(Integer.toString(data.length))); + out.write(':'); + out.write(data); + } + + @Override + public void write(int arg0) throws IOException { + out.write(arg0); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + out.write(b, off, len); + } + +} diff --git a/src/main/java/net/torrent/util/bencoding/BList.java b/src/main/java/net/torrent/util/bencoding/BList.java new file mode 100644 index 0000000..d0aa44c --- /dev/null +++ b/src/main/java/net/torrent/util/bencoding/BList.java @@ -0,0 +1,21 @@ +package net.torrent.util.bencoding; + +import java.util.List; + +/** + * Representation of a bencoded list. + * + * @author Bytekeeper + * + */ +public interface BList extends List { + String getString(int index) throws BTypeException; + + Integer getInteger(int index) throws BTypeException; + + Long getLong(int index) throws BTypeException; + + BList getList(int index) throws BTypeException; + + BMap getMap(int index) throws BTypeException; +} diff --git a/src/main/java/net/torrent/util/bencoding/BMap.java b/src/main/java/net/torrent/util/bencoding/BMap.java new file mode 100644 index 0000000..3c51ba5 --- /dev/null +++ b/src/main/java/net/torrent/util/bencoding/BMap.java @@ -0,0 +1,21 @@ +package net.torrent.util.bencoding; + +import java.util.Map; + +/** + * Representation of a bencoded map. + * + * @author Bytekeeper + * + */ +public interface BMap extends Map { + String getString(String key) throws BTypeException; + + Integer getInteger(String key) throws BTypeException; + + Long getLong(String key) throws BTypeException; + + BList getList(String key) throws BTypeException; + + BMap getMap(String key) throws BTypeException; +} diff --git a/src/main/java/net/torrent/util/bencoding/BTypeException.java b/src/main/java/net/torrent/util/bencoding/BTypeException.java new file mode 100644 index 0000000..1cb4fcb --- /dev/null +++ b/src/main/java/net/torrent/util/bencoding/BTypeException.java @@ -0,0 +1,11 @@ +package net.torrent.util.bencoding; + +import java.io.IOException; + +public class BTypeException extends IOException { + private static final long serialVersionUID = 1L; + + public BTypeException(String msg) { + super(msg); + } +} diff --git a/src/main/java/net/torrent/util/bencoding/HashBMap.java b/src/main/java/net/torrent/util/bencoding/HashBMap.java new file mode 100644 index 0000000..f0355e9 --- /dev/null +++ b/src/main/java/net/torrent/util/bencoding/HashBMap.java @@ -0,0 +1,32 @@ +package net.torrent.util.bencoding; + +import java.util.HashMap; + +public class HashBMap extends HashMap implements BMap { + private static final long serialVersionUID = 1L; + + @Override + public Integer getInteger(String key) { + return (Integer) get(key); + } + + @Override + public BList getList(String key) { + return (BList) get(key); + } + + @Override + public Long getLong(String key) { + return (Long) get(key); + } + + @Override + public BMap getMap(String key) { + return (BMap) get(key); + } + + @Override + public String getString(String key) { + return (String) get(key); + } +} diff --git a/src/main/java/net/torrent/util/comparator/PieceIndexComparator.java b/src/main/java/net/torrent/util/comparator/PieceIndexComparator.java new file mode 100644 index 0000000..2b1fd86 --- /dev/null +++ b/src/main/java/net/torrent/util/comparator/PieceIndexComparator.java @@ -0,0 +1,41 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.util.comparator; + +import java.util.Comparator; + +import net.torrent.torrent.TorrentPiece; + +/** + * Compare two pieces indexes. + * + * @author Rogiel Josias Sulzbach + */ +public class PieceIndexComparator implements Comparator { + /** + * Shared instance + */ + public static final PieceIndexComparator SHARED_INSTANCE = new PieceIndexComparator(); + + @Override + public int compare(TorrentPiece piece1, TorrentPiece piece2) { + if (piece1 == null) + return Integer.MAX_VALUE; + if (piece2 == null) + return Integer.MAX_VALUE; + return piece2.getIndex() - piece1.getIndex(); + } +} diff --git a/src/main/java/net/torrent/util/comparator/RandomComparator.java b/src/main/java/net/torrent/util/comparator/RandomComparator.java new file mode 100644 index 0000000..f5bae7c --- /dev/null +++ b/src/main/java/net/torrent/util/comparator/RandomComparator.java @@ -0,0 +1,41 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.util.comparator; + +import java.util.Comparator; +import java.util.Random; + +/** + * Random comparator, values returned on each call are always different. + * + * @author Rogiel Josias Sulzbach + */ +public class RandomComparator implements Comparator { + /** + * Shared instance + */ + public static final RandomComparator SHARED_INSTANCE = new RandomComparator(); + + /** + * Random number generator + */ + private final Random random = new Random(); + + @Override + public int compare(Object o1, Object o2) { + return random.nextInt(250 * 10 * (random.nextInt(100) + 1)); + } +} diff --git a/src/test/java/net/bittorrent/protocol/tracker/HttpTorrentTrackerAnnouncerTest.java b/src/test/java/net/bittorrent/protocol/tracker/HttpTorrentTrackerAnnouncerTest.java new file mode 100644 index 0000000..575efe2 --- /dev/null +++ b/src/test/java/net/bittorrent/protocol/tracker/HttpTorrentTrackerAnnouncerTest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.bittorrent.protocol.tracker; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; + +import net.torrent.protocol.tracker.HttpTorrentTrackerAnnouncer; +import net.torrent.torrent.Torrent; + +import org.junit.Test; + +public class HttpTorrentTrackerAnnouncerTest { + @Test + public void testAnnounce() throws IOException, URISyntaxException, + InterruptedException { + final Torrent torrent = Torrent + .load(new File( + "src/test/resources/Tim Besamusca - Running Away EP Urban Sickness Audio USA1008.torrent")); + final HttpTorrentTrackerAnnouncer announcer = new HttpTorrentTrackerAnnouncer(); + System.out.println(announcer.announce(torrent, torrent.getTrackers() + .iterator().next())); + + Thread.sleep(10 * 60 * 1000); + } +} diff --git a/src/test/java/net/bittorrent/util/comparator/RandomComparatorTest.java b/src/test/java/net/bittorrent/util/comparator/RandomComparatorTest.java new file mode 100644 index 0000000..70fcc3e --- /dev/null +++ b/src/test/java/net/bittorrent/util/comparator/RandomComparatorTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.bittorrent.util.comparator; + +import junit.framework.Assert; + +import net.torrent.util.comparator.RandomComparator; + +import org.junit.Test; + +public class RandomComparatorTest { + @Test + public void testCompare() { + final RandomComparator comparator = RandomComparator.SHARED_INSTANCE; + + Assert.assertNotSame(comparator.compare(null, null), + comparator.compare(null, null)); + } + + @Test + public void testSorting() { + /* + * List list1 = Arrays.asList("a", "b", "c", "d"); List + * list2 = Arrays.asList("a", "b", "c", "d"); Collections.sort(list1, + * RandomComparator.SHARED_INSTANCE); Collections.sort(list2, + * RandomComparator.SHARED_INSTANCE); + * Assert.assertFalse(list1.equals(list2)); + */ + } +} diff --git a/src/test/java/net/torrent/protocol/peerwire/PeerWireManagerTest.java b/src/test/java/net/torrent/protocol/peerwire/PeerWireManagerTest.java new file mode 100644 index 0000000..b6b0430 --- /dev/null +++ b/src/test/java/net/torrent/protocol/peerwire/PeerWireManagerTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire; + +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.URISyntaxException; + +import net.torrent.BitTorrentClient; +import net.torrent.BitTorrentClientFactory; +import net.torrent.torrent.Torrent; + +import org.junit.Test; + +public class PeerWireManagerTest { + @Test + public void testPeerWire() throws IOException, InterruptedException, + URISyntaxException { + final Torrent torrent = Torrent.load(new File( + "src/test/resources/oth.s01e13.avi.torrent")); + System.out.println(torrent.getInfoHash()); + + final BitTorrentClient client = new BitTorrentClientFactory(torrent) + .newBitTorrentClient(); + client.start(new InetSocketAddress("192.168.1.100", 25944)); + // client.start(new InetSocketAddress("192.168.1.110", 51413)); + + Thread.sleep(60 * 1000 * 30); + } +} diff --git a/src/test/java/net/torrent/protocol/peerwire/TorrentProfile.java b/src/test/java/net/torrent/protocol/peerwire/TorrentProfile.java new file mode 100644 index 0000000..0d1d6c5 --- /dev/null +++ b/src/test/java/net/torrent/protocol/peerwire/TorrentProfile.java @@ -0,0 +1,46 @@ +/* + * Copyright 2011 Rogiel Josias Sulzbach + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.torrent.protocol.peerwire; + +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.URISyntaxException; + +import net.torrent.BitTorrentClient; +import net.torrent.BitTorrentClientFactory; +import net.torrent.torrent.Torrent; + +public class TorrentProfile { + public static void main(String[] args) throws IOException, + InterruptedException, URISyntaxException { + final Torrent torrent = Torrent.load(new File( + "src/test/resources/oth.s01e13.avi.torrent")); + System.out.println(torrent.getInfoHash()); + + // System.out.println(torrent.getPiece(700).getParts()[3]); + // for (final TorrentPart part : torrent.getPiece(700).getParts()) { + // System.out.println(part); + // } + + final BitTorrentClient client = new BitTorrentClientFactory(torrent) + .newBitTorrentClient(); + client.start(new InetSocketAddress("192.168.1.100", 25944)); + // client.start(new InetSocketAddress("192.168.1.110", 51413)); + + Thread.sleep(60 * 1000 * 30); + } +} diff --git a/src/test/resources/.gitignore b/src/test/resources/.gitignore new file mode 100644 index 0000000..6f2ab87 --- /dev/null +++ b/src/test/resources/.gitignore @@ -0,0 +1,3 @@ +/oth.s01e13.avi.torrent +/One Tree Hill - 01x13 - (HD) - Hanging By A Mome.(Português).srt.torrent +/Tim Besamusca - Running Away EP Urban Sickness Audio USA1008.torrent