1
0
mirror of https://github.com/Rogiel/torrent4j synced 2026-01-26 21:32:46 +00:00

Change-Id: I0f4f7ffe65dcfbaaaa792ebb43566c7d07c7569d

This commit is contained in:
rogiel
2011-04-27 21:55:23 -03:00
commit e9684fcdfd
104 changed files with 10553 additions and 0 deletions

11
.classpath Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

23
.project Normal file
View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>libtorrent-java</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.maven.ide.eclipse.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.maven.ide.eclipse.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,3 @@
#Wed Apr 27 21:19:52 BRT 2011
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning

View File

@@ -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

202
LICENSE Normal file
View File

@@ -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.

0
README Normal file
View File

28
pom.xml Normal file
View File

@@ -0,0 +1,28 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.bittorrent</groupId>
<artifactId>bittorrent-java</artifactId>
<version>1.0.0-alpha</version>
<name>Java Bittorrent Library</name>
<description>Download and upload torrents with this embeddable library!</description>
<repositories>
<repository>
<id>repository.jboss.org</id>
<url>https://repository.jboss.org/nexus/content/repositories/releases/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.jboss.netty</groupId>
<artifactId>netty</artifactId>
<version>3.2.4.Final</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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 <tt>addrs</tt>.
*
* @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 (<a
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
*/
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;
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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;
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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.<br />
* 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;
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @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();
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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);
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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 (<a
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
*/
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 (<a
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
*/
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, <b>when the
* peer choke or unchoke</b>!).
*
* @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 (<a
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
*/
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;
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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 (<a
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
*/
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;
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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);
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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;
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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;
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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;
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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<TorrentPiece> completedPieces = new HashSet<TorrentPiece>();
/**
* 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;
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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;
}
}

View File

@@ -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.
* <p>
* 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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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);
}
}

View File

@@ -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.
* <p>
* 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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public interface TorrentDatastore {
/**
* Read an segment of data relative to {@link TorrentPart part}.
* <p>
* <b>Warning</b>: Synchronization <b>MUST</b> 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}.
* <p>
* <b>Warning</b>: Synchronization <b>MUST</b> 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;
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public abstract class FileAwareTorrentDatastore extends
AbstractTorrentDatastore {
/**
* Read data contained withing <tt>file</tt> in the given <tt>range</tt>
*
* @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 <tt>buffer</tt> to <tt>file</tt> in the given
* <tt>range</tt>
*
* @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());
}
}

View File

@@ -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.
* <p>
* Unless for a single torrent file, data might (and possibly will) not be
* readable outside this library.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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;
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class SimpleTorrentDatastore extends FileAwareTorrentDatastore {
private final Map<TorrentFile, FileChannel> cache = new HashMap<TorrentFile, FileChannel>();
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;
}
}

View File

@@ -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 <tt>/dev/null</tt> and <tt>/dev/zero</tt> pseudo-file
* in UNIX systems. All data written is discarded and all readed data is
* composed of <tt>NULL</tt> (<tt>0x00</tt>). Checksums always succeed.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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;
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class DHTClient {
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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 <tt>wait</tt>
* @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;
}
}

View File

@@ -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.
* <p>
* This object contains handy methods to send messages to the peer.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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} ***
* <p>
* 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} ***
* <p>
* Send an have none message.
*/
public void haveNone() {
write(new HaveNoneMessage());
}
/**
* *** REQUIRES {@link TorrentPeerCapability#EXTENSION_PROTOCOL Extension
* Protocol} ***
* <p>
* Send an have all message.
*/
public void haveAll() {
write(new HaveAllMessage());
}
/**
* *** REQUIRES {@link TorrentPeerCapability#EXTENSION_PROTOCOL Extension
* Protocol} ***
* <p>
* 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} ***
* <p>
* 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} ***
* <p>
* 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;
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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;
}
}

View File

@@ -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:
* <p>
* <h1>Handshake message</h1> Handshake messages are composed of 1 byte, which
* indicates the size of protocol string ("BitTorrent protocol").
* <p>
* <h1>Messages</h1> All other messages are 4-byte integer containing the
* message length, followed by 1 byte opcode.
* <p>
* <p>
* Instances of this class keep channel state content and must not be shared nor
* cached.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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 <tt>id</tt>. 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;
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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;
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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;
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public interface PeerWireWritableMessage {
/**
* Write the content of the message to this buffer.
*
* @param buffer
* the buffer
* @throws IOException
*/
void write(ChannelBuffer buffer) throws IOException;
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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 <tt>peer</tt>
*
* @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);
}
}

View File

@@ -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.
* <p>
* Updates {@link TorrentManager} state.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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);
}
}

View File

@@ -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.
* <p>
* Updates {@link TorrentManager} state.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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);
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class ConnectionManager implements Iterable<Channel> {
/**
* The torrent context
*/
private final TorrentContext context;
/**
* The list of active channels
*/
private final Set<Channel> activeChannels = new HashSet<Channel>();
/**
* The list of inactive channels
*/
private final Set<Channel> inactiveChannels = new HashSet<Channel>();
/**
* 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<Channel> getActiveChannels() {
return Collections.unmodifiableSet(activeChannels);
}
/**
* Get the list of inactive channels
*
* @return list of inactive channels
*/
public Set<Channel> getInactiveChannels() {
return Collections.unmodifiableSet(inactiveChannels);
}
@Override
public Iterator<Channel> iterator() {
return activeChannels.iterator();
}
/**
* Get the torent context
*
* @return the torrent context
*/
public TorrentContext getContext() {
return context;
}
}

View File

@@ -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<TorrentPart> {
private final TorrentContext context;
private final Map<TorrentPart, TorrentPeer> activeParts = new HashMap<TorrentPart, TorrentPeer>();
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<TorrentPart> getTorrentParts(TorrentPeer peer) {
final Set<TorrentPart> parts = new HashSet<TorrentPart>();
for (final Entry<TorrentPart, TorrentPeer> 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<TorrentPart> remove(TorrentPeer peer) {
final Set<TorrentPart> parts = new HashSet<TorrentPart>();
for (TorrentPart part : getTorrentParts(peer)) {
if (activeParts.remove(part) != null)
parts.add(part);
}
return parts;
}
public int getActiveDownloadsCount() {
return activeParts.size();
}
public Map<TorrentPart, TorrentPeer> getActiveDownloads() {
return Collections.unmodifiableMap(activeParts);
}
@Override
public Iterator<TorrentPart> iterator() {
return activeParts.keySet().iterator();
}
public TorrentContext getContext() {
return context;
}
}

View File

@@ -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<PeerWirePeer> {
private final TorrentContext context;
private final ConnectionManager connectionManager;
private final Map<Channel, PeerWirePeer> activePeers = new HashMap<Channel, PeerWirePeer>();
private final Map<Channel, PeerWirePeer> inactivePeers = new HashMap<Channel, PeerWirePeer>();
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<Channel, PeerWirePeer> entry : activePeers.entrySet()) {
if (entry.getValue().equals(peer))
return entry.getKey();
}
for (final Entry<Channel, PeerWirePeer> 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<Channel, PeerWirePeer> getActivePeers() {
return Collections.unmodifiableMap(activePeers);
}
public Map<Channel, PeerWirePeer> getInactivePeers() {
return Collections.unmodifiableMap(inactivePeers);
}
public Set<Channel> getActiveChannels() {
return Collections.unmodifiableSet(activePeers.keySet());
}
public Set<Channel> getInactiveChannels() {
return Collections.unmodifiableSet(inactivePeers.keySet());
}
public void executeActive(PeerCallback callback) {
for (final Entry<Channel, PeerWirePeer> entry : this.activePeers
.entrySet()) {
callback.callback(entry.getValue());
}
}
public void executeInactive(PeerCallback callback) {
for (final Entry<Channel, PeerWirePeer> entry : this.inactivePeers
.entrySet()) {
callback.callback(entry.getValue());
}
}
public void execute(PeerCallback callback) {
executeActive(callback);
executeInactive(callback);
}
@Override
public Iterator<PeerWirePeer> iterator() {
return activePeers.values().iterator();
}
public TorrentContext getContext() {
return context;
}
public ConnectionManager getConnectionManager() {
return connectionManager;
}
}

View File

@@ -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();
}
}

View File

@@ -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<TorrentPart> {
private final TorrentContext context;
private final Map<TorrentPart, TorrentPeer> activeParts = new HashMap<TorrentPart, TorrentPeer>();
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<TorrentPart> getTorrentParts(TorrentPeer peer) {
final Set<TorrentPart> parts = new HashSet<TorrentPart>();
for (final Entry<TorrentPart, TorrentPeer> 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<TorrentPart> remove(TorrentPeer peer) {
final Set<TorrentPart> parts = new HashSet<TorrentPart>();
for (TorrentPart part : getTorrentParts(peer)) {
if (activeParts.remove(part) != null)
parts.add(part);
}
return parts;
}
public int getActiveUploadsCount() {
return activeParts.size();
}
public Map<TorrentPart, TorrentPeer> getActiveUploads() {
return Collections.unmodifiableMap(activeParts);
}
@Override
public Iterator<TorrentPart> iterator() {
return activeParts.keySet().iterator();
}
public TorrentContext getContext() {
return context;
}
}

View File

@@ -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;
/**
* <pre>
* bitfield: [len=0001+X][id=5][bitfield]
* </pre>
*
* 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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @source BitTorrent documentation
*/
public class BitfieldMessage implements PeerWireWritableMessage,
PeerWireReadableMessage {
public static final byte MESSAGE_ID = 0x05;
/**
* A set of bits with the following scheme:
* <ul>
* <li><b>true</b>: has the piece</li>
* </li><b>false</b>: piece is missing</li>
* </ul>
*/
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 + "]";
}
}

View File

@@ -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;
/**
* <pre>
* cancel: [len=0013][id=8][index][begin][length]
* </pre>
*
* 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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @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 + "]";
}
}

View File

@@ -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;
/**
* <pre>
* choke: [len=0001][id=0]
* </pre>
*
* The choke message is fixed-length and has no payload.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @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 []";
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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) + "]";
}
}

View File

@@ -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;
/**
* <pre>
* have: [len=0005][id=4][piece index(int)]
* </pre>
*
* 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.<br>
* <br>
* <b>Implementer's Note</b>: 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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @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 + "]";
}
}

View File

@@ -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;
/**
* <pre>
* interested: [len=0001][id=2]
* </pre>
*
* The interested message is fixed-length and has no payload.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @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 []";
}
}

View File

@@ -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;
/**
* <pre>
* keep-alive: [len=0000]
* </pre>
*
* 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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @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 []";
}
}

View File

@@ -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;
/**
* <pre>
* not interested: [len=0001][id=3]
* </pre>
*
* The not interested message is fixed-length and has no payload.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @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 []";
}
}

View File

@@ -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;
/**
* <pre>
* piece: [len=0009+X][id=7][index][begin][block]
* </pre>
*
* The piece message is variable length, where X is the length of the block. The
* payload contains the following information:<br>
* index: integer specifying the zero-based piece index<br>
* begin: integer specifying the zero-based byte offset within the piece<br>
* block: block of data, which is a subset of the piece specified by index.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @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 + "]";
}
}

View File

@@ -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;
/**
* <pre>
* port: [len=0003][id=9][listen-port]
* </pre>
*
* 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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @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 + "]";
}
}

View File

@@ -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;
/**
* <pre>
* request: [len=0013][id=6][index][begin][length]
* </pre>
*
* The request message is fixed length, and is used to request a block. The
* payload contains the following information:<br>
* index: integer specifying the zero-based piece index<br>
* begin: integer specifying the zero-based byte offset within the piece<br>
* length: integer specifying the requested length.<br>
* 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.<br>
* 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.<br>
* 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).<br>
* "Most older clients use 32KB requests" is false. Discussion of larger
* requests fails to take latency effects into account.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @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 + "]";
}
}

View File

@@ -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;
/**
* <pre>
* unchoke: [len=0001][id=1]
* </pre>
*
* The unchoke message is fixed-length and has no payload.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @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 []";
}
}

View File

@@ -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;
/**
* <pre>
* AllowedFast: [len=0x0004+pieces(4 bytes each)][id=0x11][piece indexes(int)]*
* </pre>
*
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* When the fast extension is disabled, if a peer recieves an Allowed Fast
* message then the peer MUST close the connection.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @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) + "]";
}
}

View File

@@ -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;
/**
* <pre>
* HaveAll: [len=0x0001][id=0x0E]
* </pre>
*
* 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.
* <p>
* When the fast extension is disabled, if a peer receives Have All or Have None
* then the peer MUST close the connection.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @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 []";
}
}

View File

@@ -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;
/**
* <pre>
* HaveNone: [len=0x0001][id=0x0F]
* </pre>
*
* 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.
* <p>
* When the fast extension is disabled, if a peer receives Have All or Have None
* then the peer MUST close the connection.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @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 []";
}
}

View File

@@ -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;
/**
* <pre>
* Reject: [len=0013][id=0x10][index][begin][length]
* </pre>
*
* 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.
* <p>
* When the fast extension is enabled:
* <ul>
* <li>If a peer receives a reject for a request that was never sent then the
* peer SHOULD close the connection.</li>
* <li>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.
* </li>
* <li>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.
* </li>
* <li>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.
* </ul>
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @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 + "]";
}
}

View File

@@ -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;
/**
* <pre>
* SuggestPiece: [len=0x0005][id=0x0D][piece index(int)]
* </pre>
*
* 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.
* <p>
* When the fast extension is disabled, if a peer receives a Suggest Piece
* message, the peer MUST close the connection.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @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 + "]";
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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"));
}
}

View File

@@ -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<PeerInfo> 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<PeerInfo>(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<PeerInfo>(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<PeerInfo> 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());
}
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class Torrent {
/**
* Dictionary key for <tt>announce</tt>
*/
private static final String ANNOUNCE = "announce";
/**
* Dictionary key for <tt>announce-list</tt>
*/
private static final String ANNOUNCE_LIST = "announce-list";
/**
* Dictionary key for <tt>info</tt>
*/
private static final String INFO = "info";
/**
* Dictionary key for <tt>comment</tt>
*/
private static final String COMMENT = "comment";
/**
* Dictionary key for <tt>creation date</tt>
*/
private static final String CREATION_DATE = "creation date";
/**
* Dictionary key for <tt>created by</tt>
*/
private static final String CREATED_BY = "created by";
/**
* The torrent information
*/
private final TorrentInfo info;
/**
* The list of trackers
*/
private final Set<TorrentTracker> trackers = new HashSet<TorrentTracker>();
/**
* 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<TorrentTracker> 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<TorrentFile> 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));
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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:
* <p>
* <code>
* String name = getBaseDirectoryName() + "/" + getName();
* </code>
*
* @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;
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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();
}
}

View File

@@ -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<TorrentFile> files = new ArrayList<TorrentFile>();
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<String, ?> 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<TorrentFile> 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);
}
}

View File

@@ -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 <b>piece</b> and each of those pieces are divided into smaller parts. Each
* part can be requested to an peer and received afterwards.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @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. <b>Note</b>: 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.
* <p>
* <code>
* TorrentPart part = ...;<br>
* boolean first = (part.getStart() + part.getLength() == part.getPiece().getLength());
* </code>
*
* @return true if last
*/
public boolean isLast() {
return (start + length == piece.getLength());
}
/**
* Check if this is the first part in this piece.
* <p>
* <code>
* TorrentPart part = ...;<br>
* boolean first = (part.getStart() == 0);
* </code>
*
* @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;
}
}

View File

@@ -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 <tt>pieces</tt>.
* Each piece has the same size, except for the last which can be equal or
* smaller (enough to fit all data).
* <p>
* This class handles this distribution and takes care of creating
* {@link TorrentPart parts}.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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 <tt>start</tt> with <tt>length</tt>
*
* @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;
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class TorrentTracker {
/**
* The torrent
*/
private final Torrent torrent;
/**
* The tracker URI. There are several types of trackers:
* <ul>
* <li>HTTP</li>
* <li>HTTPS</li>
* <li>UDP</li>
* </ul>
* Each type have its own protocol and methods to retrieve peers.
*/
private final URI uri;
/**
* The tracker backups uris
*/
private final Set<URI> backup = new HashSet<URI>();
/**
* 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<URI> 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;
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class TorrentBitfield implements Iterable<TorrentPiece> {
/**
* 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 <tt>index</tt>
*
* @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<TorrentPiece> iterator() {
return iterator(true);
}
/**
* Iterate to all pieces with <tt>type</tt>. True if set, false if not.
*
* @param type
* the type
* @return {@link Iterator} for those pieces
*/
public Iterator<TorrentPiece> iterator(final boolean type) {
return new BitfieldIterator(type);
}
/**
* Iterate to all pieces with <tt>type</tt>. True if set, false if not.
*
* @param type
* the type
* @return {@link Iterable} for those pieces
* @see TorrentBitfield#iterator(boolean)
*/
public Iterable<TorrentPiece> iterate(final boolean type) {
return new Iterable<TorrentPiece>() {
@Override
public Iterator<TorrentPiece> iterator() {
return TorrentBitfield.this.iterator(type);
}
};
}
/**
* The bitfield iterator
*
* @author Rogiel Josias Sulzbach (<a
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
*/
public class BitfieldIterator implements Iterator<TorrentPiece> {
/**
* 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);
}
}
}

View File

@@ -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<TorrentPeer> peers = new HashSet<TorrentPeer>();
/**
* Unknown peers does not have their IDs, consequently they cannot be
* queried using their Id and must be done through IP.
*/
private final Set<TorrentPeer> unknownPeers = new HashSet<TorrentPeer>();
/**
* 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<TorrentPeer> getPeers() {
return Collections.unmodifiableSet(peers);
}
/**
* Get the list of unknown peers (don't have known peerid)
*
* @return the list of peers
*/
public Set<TorrentPeer> 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;
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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 (<a
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
*/
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 (<a
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
*/
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;
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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 <tt>capabilities</tt>
*
* @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 <tt>capabilities</tt>
*
* @param capabilities
* the capabilities {@link BitSet} supported
*/
public TorrentPeerCapabilities(BitSet capabilitites) {
this.capabilities = capabilitites;
}
/**
* Tests if the given <tt>capability</tt> 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<TorrentPeerCapability> getCapabilities() {
final List<TorrentPeerCapability> capabilitites = new ArrayList<TorrentPeerCapability>();
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 (<a
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
*/
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;
}
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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;
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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);
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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<TorrentPiece> pieces) {
Collections.shuffle(pieces);
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
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<TorrentPiece> pieces) {
Collections.sort(pieces, PieceIndexComparator.SHARED_INSTANCE);
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public abstract class SortedListPieceSelector implements PieceSelector {
/**
* The torrent manager
*/
private final TorrentManager manager;
/**
* The sorted list of pieces
*/
private final List<TorrentPiece> pieces;
/**
* Creates a new instance
*
* @param manager
* the torrent manager
* @param pieces
* the <b>unsorted</b> list of pieces
*/
protected SortedListPieceSelector(TorrentManager manager,
List<TorrentPiece> 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<TorrentPiece> pieces);
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public interface PeerCallback {
/**
* Execute the desired action for <tt>peer</tt>
*
* @param peer
* the peer
*/
void callback(PeerWirePeer peer);
}

View File

@@ -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() + "]";
}
}

View File

@@ -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);
}
}

View File

@@ -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<Object> 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<String, Object> 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());
}
}
}

View File

@@ -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<String> BYTE_COMPARATOR = new Comparator<String>() {
@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<String, ?>) obj);
} else {
throw new IllegalArgumentException("Type " + obj.getClass()
+ " is not bencodable.");
}
}
private void bencodeDictionary(Map<String, ?> dict) throws IOException {
assert out != null;
assert dict != null;
out.write('d');
List<String> sorted = new ArrayList<String>(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);
}
}

View File

@@ -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<Object> {
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;
}

View File

@@ -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, Object> {
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;
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,32 @@
package net.torrent.util.bencoding;
import java.util.HashMap;
public class HashBMap extends HashMap<String, Object> 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);
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class PieceIndexComparator implements Comparator<TorrentPiece> {
/**
* 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();
}
}

View File

@@ -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 <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class RandomComparator implements Comparator<Object> {
/**
* 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));
}
}

View File

@@ -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);
}
}

Some files were not shown because too many files have changed in this diff Show More