1
0
mirror of https://github.com/Rogiel/torrent4j synced 2025-12-06 07:32:47 +00:00

Initial commit

This commit is contained in:
2012-01-14 17:14:53 -02:00
commit 5cc4b215d1
81 changed files with 5778 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/.project
/.classpath
/.settings

263
pom.xml Normal file
View File

@@ -0,0 +1,263 @@
<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>com.rogiel</groupId>
<artifactId>torrent4j</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>torrent4j</name>
<description>Bittorrent java library</description>
<inceptionYear>2012</inceptionYear>
<organization>
<name>Rogiel</name>
<url>http://www.rogiel.com/</url>
</organization>
<url>http://www.rogiel.com/</url>
<scm>
<developerConnection>scm:git:git@github.com:torrent4j/torrent4j.git</developerConnection>
<connection>scm:git:github.com:torrent4j/torrent4j.git</connection>
</scm>
<distributionManagement>
<downloadUrl>https://github.com/torrent4j/torrent4j/downloads</downloadUrl>
<repository>
<id>torrent4j-repository</id>
<name>torrent4j-repository</name>
<url>scp://rogiels@rogiel.com/home/rogiels/maven.rogiel.com</url>
<layout>default</layout>
<uniqueVersion>false</uniqueVersion>
</repository>
<site>
<id>torrent4j-site</id>
<name>torrent4j-site</name>
<url>scp://rogiels@rogiel.com/home/rogiels/torrent4j.rogiel.com</url>
</site>
</distributionManagement>
<issueManagement>
<system>GitHub</system>
<url>https://github.com/torrent4j/torrent4j/issues</url>
</issueManagement>
<ciManagement>
<system>GitHub</system>
<url>https://github.com/torrent4j/torrent4j</url>
</ciManagement>
<developers>
<developer>
<id>Rogiel</id>
<name>Rogiel</name>
<email>rogiel@rogiel.com</email>
<url>http://www.rogiel.com/</url>
<timezone>-3</timezone>
<roles>
<role>Creator</role>
<role>API Designer</role>
</roles>
</developer>
</developers>
<licenses>
<license>
<name>GNU General Public License version 3</name>
<url>http://www.gnu.org/licenses/gpl.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<build>
<defaultGoal>package</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>attached</goal>
</goals>
</execution>
</executions>
<configuration>
<descriptors>
<descriptor>src/main/assembly/distribution-bin.xml</descriptor>
</descriptors>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.7</version>
<configuration>
<uniqueVersion>false</uniqueVersion>
</configuration>
<dependencies>
<dependency>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ssh</artifactId>
<version>2.1</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>com.github.github</groupId>
<artifactId>downloads-maven-plugin</artifactId>
<version>0.4</version>
<executions>
<execution>
<phase>deploy</phase>
<goals>
<goal>upload</goal>
</goals>
</execution>
</executions>
<configuration>
<description>${project.version} release of ${project.name}</description>
<override>true</override>
<includeAttached>true</includeAttached>
<repositoryOwner>torrent4j</repositoryOwner>
<repositoryName>torrent4j</repositoryName>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.2.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.0</version>
<configuration>
<outputEncoding>UTF-8</outputEncoding>
<reportPlugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.8</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>2.4</version>
<configuration>
<developerConnection>scm:git@github.com:torrent4j/torrent4j.git</developerConnection>
<anonymousConnection>scm:git://github.com/torrent4j/torrent4j.git</anonymousConnection>
<webAccessUrl>https://github.com/torrent4j/torrent4j/tree/master/</webAccessUrl>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-changelog-plugin</artifactId>
<version>2.2</version>
<configuration>
<outputEncoding>UTF-8</outputEncoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jxr-plugin</artifactId>
<version>2.3</version>
<configuration>
<inputEncoding>UTF-8</inputEncoding>
<outputEncoding>UTF-8</outputEncoding>
</configuration>
</plugin>
</reportPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ssh</artifactId>
<version>2.1</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>com.github.github</groupId>
<artifactId>site-maven-plugin</artifactId>
<version>0.4</version>
<configuration>
<message>Creating site for ${project.version}</message>
<repositoryOwner>torrent4j</repositoryOwner>
<repositoryName>torrent4j.github.com</repositoryName>
</configuration>
<executions>
<execution>
<phase>site</phase>
<goals>
<goal>site</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty</artifactId>
<version>4.0.0.Alpha1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>20041127.091804</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>jboss-snapshots</id>
<url>https://repository.jboss.org/nexus/content/repositories/snapshots/</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>oss-sonatype-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>oss-sonatype-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>

View File

@@ -0,0 +1,25 @@
<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>bin</id>
<formats>
<format>zip</format>
</formats>
<files>
<file>
<source>${project.build.directory}/${project.artifactId}-${project.version}.jar</source>
<outputDirectory>/</outputDirectory>
<destName>torrent4j-${project.version}.jar</destName>
<fileMode>0755</fileMode>
</file>
</files>
<dependencySets>
<dependencySet>
<outputDirectory>/libs</outputDirectory>
<useProjectArtifact>false</useProjectArtifact>
<unpack>false</unpack>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
</assembly>

View File

@@ -0,0 +1,26 @@
<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>src</id>
<formats>
<format>zip</format>
</formats>
<fileSets>
<fileSet>
<directory>${project.basedir}</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>src/**</include>
</includes>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<outputDirectory>/libs</outputDirectory>
<useProjectArtifact>false</useProjectArtifact>
<unpack>false</unpack>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
</assembly>

View File

@@ -0,0 +1,184 @@
package com.torrent4j;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.torrent4j.model.Torrent;
import com.torrent4j.model.TorrentPiece;
import com.torrent4j.net.TorrentProtocol;
import com.torrent4j.net.peerwire.PeerWireProtocol;
import com.torrent4j.storage.NIOTorrentStorage;
import com.torrent4j.storage.TorrentStorage;
import com.torrent4j.util.Hash;
/**
* The main class for starting torrent transfers. Each torrent instance need to
* be registered into an controller. Once registered, connections can be
* established to start downloading the file (or files) from other peers in the
* BitTorrent network.
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public class TorrentController {
/**
* The controller configuration
*/
private final TorrentControllerConfiguration config = new TorrentControllerConfiguration();
/**
* The torrent protocol
*/
private final TorrentProtocol protocol;
/**
* The torrent storage
*/
private final TorrentStorage storage;
/**
* The list of torrents controlled on this controller
*/
private final List<Torrent> torrents = new ArrayList<>();
/**
* Creates a new controller
*
* @param protocol
* the protocol to use
* @param storage
* the storage to use
*/
public TorrentController(TorrentProtocol protocol, TorrentStorage storage) {
this.protocol = protocol;
this.storage = storage;
}
/**
* Creates a new controller with {@link NIOTorrentStorage} as default
* storage.
*
* @param protocol
* thw protocol to use
*/
public TorrentController(TorrentProtocol protocol) {
this(protocol, new NIOTorrentStorage());
}
/**
* Creates a new controller with {@link PeerWireProtocol} as default
* protocol.
*
* @param storage
* the storage to use
*/
public TorrentController(TorrentStorage storage) {
this(new PeerWireProtocol(), storage);
}
/**
* Creates a new controller with {@link PeerWireProtocol} as default
* protocol and {@link NIOTorrentStorage} as default storage.
*/
public TorrentController() {
this(new PeerWireProtocol(), new NIOTorrentStorage());
}
/**
* Retrieves data from the storage and validate all piece information
* already downloaded, if any. Automatically updates the torrent internal
* state to match the real download progress.
*
* @param torrent
* the torrent to check existing data
* @throws IOException
* if any IO error occur
*/
public void checkExistingData(Torrent torrent) throws IOException {
for (final TorrentPiece piece : torrent.getPieces()) {
final Hash hash = storage.checksum(piece);
if (!piece.getHash().equals(hash))
continue;
torrent.getCompletedPieces().addPiece(piece);
}
}
/**
* Registers a new torrent on this controller
*
* @param torrent
* the torrent to be registered
*/
public void registerTorrent(Torrent torrent) {
torrents.add(torrent);
torrent.setController(this);
}
/**
* Removes an already registered torrent from this controller
*
* @param torrent
* the torrent to be removed
*/
public void removeTorrent(Torrent torrent) {
torrents.remove(torrent);
torrent.setController(null);
}
/**
* Tries to locate the torrent represented by <code>hash</code>.
*
* @param hash
* the torrent hash to look for
* @return the torrent with the given <code>hash</code>, if any
*/
public Torrent findTorrent(Hash hash) {
for (final Torrent torrent : torrents) {
if (torrent.getHash().equals(hash))
return torrent;
}
return null;
}
/**
* Stars the controller
*
* @param port
* the listen port
*/
public void start(int port) {
protocol.start(this, port);
}
/**
* Stops the controller
*/
public void stop() {
protocol.stop();
}
/**
* @return this controller configuration object
*/
public TorrentControllerConfiguration getConfig() {
return config;
}
/**
* @return this controller protocol
*/
public TorrentProtocol getProtocol() {
return protocol;
}
/**
* @return this controller storage
*/
public TorrentStorage getStorage() {
return storage;
}
/**
* @return this controller torrent list
*/
public List<Torrent> getTorrents() {
return torrents;
}
}

View File

@@ -0,0 +1,15 @@
package com.torrent4j;
import com.torrent4j.util.PeerIDGenerator;
public class TorrentControllerConfiguration {
private String peerID = PeerIDGenerator.generateRandomPeerID();
public String getPeerID() {
return peerID;
}
public void setPeerID(String peerID) {
this.peerID = peerID;
}
}

View File

@@ -0,0 +1,99 @@
package com.torrent4j.model;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Iterator;
import java.util.List;
import com.torrent4j.util.BitField;
public abstract class AbstractTorrentPiecesContainer implements Iterable<TorrentPiece> {
protected final Torrent torrent;
protected final BitField bitSet;
public AbstractTorrentPiecesContainer(Torrent torrent) {
this.torrent = torrent;
this.bitSet = new BitField(torrent.getPieces().size());
}
public boolean hasPiece(TorrentPiece piece) {
return bitSet.get(piece.getIndex());
}
public void addPiece(TorrentPiece piece) {
bitSet.set(piece.getIndex(), true);
}
public void removePiece(TorrentPiece piece) {
bitSet.set(piece.getIndex(), false);
}
public boolean isSeeder() {
return bitSet.cardinality() == torrent.getPieces().size();
}
public boolean isLeecher() {
return !isSeeder();
}
public void clear() {
bitSet.clear();
}
public List<TorrentPiece> getPieces() {
return createList(bitSet);
}
public boolean hasMissingPieces(AbstractTorrentPiecesContainer other) {
final BitSet difference = (BitSet) bitSet.clone();
difference.andNot(other.bitSet);
return difference.cardinality() != 0;
}
public List<TorrentPiece> getMissingPieces(AbstractTorrentPiecesContainer other) {
final BitSet difference = (BitSet) bitSet.clone();
difference.andNot(other.bitSet);
return createList(difference);
}
public List<TorrentPiece> createList(BitSet bits) {
final List<TorrentPiece> list = new ArrayList<>();
for (int i = bits.nextSetBit(0); i >= 0; i = bits.nextSetBit(i + 1)) {
list.add(getTorrent().getPiece(i));
}
return list;
}
@Override
public Iterator<TorrentPiece> iterator() {
return new Iterator<TorrentPiece>() {
private int lastIndex = 0;
private int index = 0;
@Override
public boolean hasNext() {
return bitSet.nextSetBit(lastIndex) >= 0;
}
@Override
public TorrentPiece next() {
lastIndex = index;
return getTorrent().getPiece(
(index = bitSet.nextSetBit(lastIndex)));
}
@Override
public void remove() {
removePiece(getTorrent().getPiece(lastIndex));
}
};
}
public BitField getBitSet() {
return bitSet;
}
public Torrent getTorrent() {
return torrent;
}
}

View File

@@ -0,0 +1,359 @@
package com.torrent4j.model;
import static com.torrent4j.util.HashType.MD5;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.torrent4j.TorrentController;
import com.torrent4j.model.metadata.MetadataFile;
import com.torrent4j.model.metadata.MetadataInfo;
import com.torrent4j.model.metadata.TorrentMetadata;
import com.torrent4j.net.TorrentProtocol;
import com.torrent4j.strategy.TorrentStrategy;
import com.torrent4j.strategy.standard.StandardTorrentStrategy;
import com.torrent4j.util.Range;
import com.torrent4j.util.bencoding.BDecoder;
import com.torrent4j.util.bencoding.BMap;
/**
* The main Torrent instance class. With this class, all torrent state is stored
* and external interaction can be performed.
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public class Torrent {
/**
* The torrent strategy
*/
private final TorrentStrategy strategy;
/**
* The torrent meta data
*/
private final TorrentMetadata metadata;
/**
* The torrent hash
*/
private final TorrentHash hash;
/**
* The torrent pieces
*/
private final List<TorrentPiece> pieces = new ArrayList<>();
/**
* The torrent files
*/
private final List<TorrentFile> files = new ArrayList<>();
/**
* The torrent traffic control
*/
private final TorrentTrafficControl trafficControl = new TorrentTrafficControl(this);
/**
* The torrent swarm (list of all peers)
*/
private final TorrentSwarm swarm = new TorrentSwarm(this);
/**
* The torrent already downloaded pieces
*/
private final TorrentCompletePieces completedPieces;
/**
* The current controller for this torrent object. If not attached to any
* controller, its value is <code>null</code>.
*/
private TorrentController controller;
/**
* Creates a new torrent instance
*
* @param strategy
* the torrent strategy
* @param metadata
* the torrent meta data
*/
public Torrent(TorrentStrategy strategy, TorrentMetadata metadata) {
this.strategy = strategy;
this.metadata = metadata;
this.hash = new TorrentHash(this, metadata.getInfoHash());
// parse file list
final MetadataInfo info = metadata.getInfo();
final byte[] pieceHashes = info.getPieceHashes();
final int pieces = pieceHashes.length / 20;
long piecesLength = 0;
for (int i = 0; i < pieces; i++) {
final byte[] hash = Arrays.copyOfRange(pieceHashes, i * 20,
(i + 1) * 20);
final int length = (int) (i + 1 == pieces ? (info.getLength() - piecesLength)
: info.getPieceLength());
this.pieces.add(new TorrentPiece(this, hash, i, info
.getPieceLength() * i, length));
piecesLength += length;
}
final Path torrentPath = Paths.get(info.getName());
long offset = 0;
for (final MetadataFile metaFile : info.getFiles()) {
byte[] hash = null;
if (metaFile.getHash() != null)
hash = MD5.fromString(metaFile.getHash());
final List<TorrentPiece> filePieces = getPieces(Range
.getRangeByLength(offset, metaFile.getLength()));
final TorrentFile file = new TorrentFile(this, offset,
metaFile.getLength(), filePieces,
torrentPath.resolve(metaFile.getFileName()), hash);
files.add(file);
for (final TorrentPiece piece : filePieces) {
piece.addFile(file);
}
offset += metaFile.getLength();
}
completedPieces = new TorrentCompletePieces(this);
// try {
// this.localPeer.setPeerID(new String(Hex
// .decodeHex("851054102530302d9c640cd409c769266ad3a04f"
// .toCharArray())));
// } catch (DecoderException e) {
// }
}
/**
* Creates a new torrent instance with {@link StandardTorrentStrategy} as
* default strategy
*
* @param metadata
* the torrent meta data
*/
public Torrent(TorrentMetadata metadata) {
this(new StandardTorrentStrategy(), metadata);
}
/**
* @return this torrent's strategy
*/
public TorrentStrategy getStrategy() {
return strategy;
}
/**
* @return this torrent's meta data
*/
public TorrentMetadata getMetadata() {
return metadata;
}
/**
* @return this torrent's hash
*/
public TorrentHash getHash() {
return hash;
}
/**
* @return this torrent's pieces
*/
public List<TorrentPiece> getPieces() {
return pieces;
}
/**
* Determines all the pieces that contains at least one byte inside the
* requested range.
*
* @param range
* the range to look for pieces
* @return the pieces inside the requested <code>range</code>
*/
public List<TorrentPiece> getPieces(Range range) {
final List<TorrentPiece> pieces = new ArrayList<>();
for (final TorrentPiece piece : this.pieces) {
if (piece.getTorrentRange().intersects(range))
pieces.add(piece);
}
return pieces;
}
/**
* @param index
* the piece index
* @return the piece at the requested index, if any.
*/
public TorrentPiece getPiece(int index) {
if ((index + 1) > pieces.size())
return null;
return pieces.get(index);
}
/**
* @return all the files for this torrent
*/
public List<TorrentFile> getFiles() {
return files;
}
/**
* Determines all the files that contains at least one byte inside the
* requested range.
*
* @param range
* the range to search for files
* @return the list of files inside the requested <code>range</code>
*/
public List<TorrentFile> getFiles(Range range) {
final List<TorrentFile> files = new ArrayList<>();
for (final TorrentFile file : this.files) {
if (file.getTorrentRange().intersects(range))
files.add(file);
}
return files;
}
/**
* @return the sum of all file sizes
*/
public long getTorrentSize() {
long size = 0;
for (final TorrentFile file : files) {
size += file.getLength();
}
return size;
}
/**
* @return the trafficControl
*/
public TorrentTrafficControl getTrafficControl() {
return trafficControl;
}
/**
* @return the torrent swarm
*/
public TorrentSwarm getSwarm() {
return swarm;
}
/**
* @return the downloaded pieces
*/
public TorrentCompletePieces getCompletedPieces() {
return completedPieces;
}
/**
* @return this torrent's controller, if any.
*/
public TorrentController getController() {
return controller;
}
/**
* Sets the current controller. <b>This method should not be invoked
* manually!</b>
*
* @param controller
* the controller to set
*/
public void setController(TorrentController controller) {
this.controller = controller;
}
/**
* @return the torrent protocol
*/
public TorrentProtocol getProtocol() {
return controller.getProtocol();
}
@Override
public String toString() {
return "Torrent [hash=" + hash + "]";
}
/**
* Load an torrent from an {@link InputStream}
*
* @param in
* the {@link InputStream}
* @return the loaded {@link Torrent} instance
* @throws IOException
* if any error occur while reading the torrent file
*/
public static Torrent load(InputStream in) throws IOException {
final Object node = new BDecoder(in).readElement();
final TorrentMetadata metadata = new TorrentMetadata((BMap) node);
return new Torrent(metadata);
}
/**
* Load an torrent from an {@link InputStream}
*
* @param file
* the file {@link Path}
* @return the loaded {@link Torrent} instance
* @throws IOException
* if any error occur while reading the torrent file
*/
public static Torrent load(Path file) throws IOException {
return load(Files.newInputStream(file));
}
/**
* Load an torrent from an {@link File}
*
* @param file
* the {@link File}
* @return the loaded {@link Torrent} instance
* @throws IOException
* if any error occur while reading the torrent file
*/
public static Torrent load(File file) throws IOException {
if (file == null)
throw new InvalidParameterException("File cannot be null");
return load(new FileInputStream(file));
}
/**
* Load an torrent from an {@link Byte} array
*
* @param content
* the {@link Byte} array
* @return the loaded {@link Torrent} instance
* @throws IOException
* if any error occur while reading the torrent file
*/
public static Torrent load(byte[] content) throws IOException {
return load(new ByteArrayInputStream(content));
}
/**
* Load an torrent from an {@link File}
*
* @param url
* the {@link URL}
* @return the loaded {@link Torrent} instance
* @throws IOException
* if any error occur while reading the torrent file
*/
public static Torrent load(URL url) throws IOException {
if (url == null)
throw new InvalidParameterException("File cannot be null");
return load(url.openStream());
}
}

View File

@@ -0,0 +1,37 @@
package com.torrent4j.model;
/**
* An {@link AbstractTorrentPiecesContainer} dedicated to storing an
* {@link Torrent} already downloaded pieces.
* <p>
* This instance overrides the {@link #addPiece(TorrentPiece)} and
* {@link #removePiece(TorrentPiece)} in order to set
* {@link TorrentPiece#setDownloaded(boolean)} accordingly, after that, the call
* is redirected to the super implementation.
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public class TorrentCompletePieces extends AbstractTorrentPiecesContainer {
/**
* Creates a new instance
*
* @param torrent
* the torrent
*/
public TorrentCompletePieces(Torrent torrent) {
super(torrent);
}
@Override
public void addPiece(TorrentPiece piece) {
super.addPiece(piece);
piece.setDownloaded(true);
}
@Override
public void removePiece(TorrentPiece piece) {
super.removePiece(piece);
piece.setDownloaded(false);
}
}

View File

@@ -0,0 +1,162 @@
package com.torrent4j.model;
import java.nio.file.Path;
import java.util.List;
import com.torrent4j.storage.TorrentStorage;
import com.torrent4j.util.Range;
/**
* An file that can be downloaded from a torrent peer.
* <p>
* Though protocol implementations does not know about files, files themselves
* are composed from several pieces (multiple files can be represented by a
* single piece and (normally) a file is represented by several pieces -- both
* are valid, even simultaneously) that are downloaded and can later be
* assembled into a file by an {@link TorrentStorage}.
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public class TorrentFile {
/**
* The torrent containing this file
*/
private final Torrent torrent;
/**
* The file hash, if present at the metadata file
*/
private TorrentFileHash hash;
/**
* The file offset (relative to the torrent)
*/
private final long offset;
/**
* The file length
*/
private final long length;
/**
* The list of pieces that contains at least one byte of the file data
*/
private final List<TorrentPiece> pieces;
/**
* The file path relative to the torrent
*/
private Path path;
/**
* Creates a new instance
*
* @param torrent
* the torrent
* @param offset
* the file offset (relative to the torrent)
* @param length
* the file length
* @param pieces
* the list of pieces that contains at least one byte of the file
* data
* @param path
* the file path relative to the torrent
* @param hash
* the file hash, if present at the metadata file
*/
public TorrentFile(Torrent torrent, long offset, long length,
List<TorrentPiece> pieces, Path path, byte[] hash) {
this.torrent = torrent;
this.offset = offset;
this.length = length;
this.pieces = pieces;
this.path = path;
if (hash != null)
this.hash = new TorrentFileHash(this, hash);
}
/**
* @return the file hash, if present at the metadata file
*/
public TorrentFileHash getHash() {
return hash;
}
/**
* @param hash
* the file hash
*/
public void setHash(TorrentFileHash hash) {
this.hash = hash;
}
/**
* @return the file offset (relative to the torrent)
*/
public long getOffset() {
return offset;
}
/**
* @return the file length
*/
public long getLength() {
return length;
}
/**
* @return the range of this file inside the torrent
*/
public Range getTorrentRange() {
return Range.getRangeByLength(offset, length);
}
/**
* @return the range of this file as a single file
*/
public Range getFileRange() {
return Range.getRangeByLength(0, length);
}
/**
* @return the list of pieces that contains at least one byte of the file
* data
*/
public List<TorrentPiece> getPieces() {
return pieces;
}
/**
* @return the file path relative to the torrent
*/
public Path getPath() {
return path;
}
/**
* @param path
* the file path relative to the torrent
*/
public void setPath(Path path) {
this.path = path;
}
/**
* @return the file name
*/
public String getFileName() {
return path.getFileName().toString();
}
/**
* @return the parent torrent
*/
public Torrent getTorrent() {
return torrent;
}
@Override
public String toString() {
return "TorrentFile [torrent=" + torrent + ", hash=" + hash
+ ", length=" + length + ", path=" + path + "]";
}
}

View File

@@ -0,0 +1,21 @@
package com.torrent4j.model;
import com.torrent4j.util.Hash;
import com.torrent4j.util.HashType;
public class TorrentFileHash extends Hash {
private final TorrentFile file;
public TorrentFileHash(TorrentFile file, byte[] hash) {
super(HashType.MD5, hash);
this.file = file;
}
public TorrentFile getFile() {
return file;
}
public Torrent getTorrent() {
return file.getTorrent();
}
}

View File

@@ -0,0 +1,18 @@
package com.torrent4j.model;
import com.torrent4j.util.Hash;
import com.torrent4j.util.HashType;
public class TorrentHash extends Hash {
private final Torrent torrent;
public TorrentHash(Torrent torrent, byte[] hash) {
super(HashType.SHA1, hash);
this.torrent = torrent;
}
public Torrent getTorrent() {
return torrent;
}
}

View File

@@ -0,0 +1,233 @@
package com.torrent4j.model;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import com.torrent4j.model.TorrentPeerState.TorrentPeerChoking;
import com.torrent4j.model.TorrentPeerState.TorrentPeerInterest;
import com.torrent4j.net.TorrentProtocolPeer;
/**
* Represents a peer on the BitTorrent network. This object contains information
* necessary to locate and connect to a peer.
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public class TorrentPeer {
/**
* The parent torrent for this peer
*/
private final Torrent torrent;
/**
* The pieces this peer has complete
*/
private final TorrentPeerPieces pieces;
/**
* The peer ID
*/
private String peerID;
/**
* The protocol peer used to communicate with it
*/
private TorrentProtocolPeer protocolPeer;
/**
* The socket address
*/
private InetSocketAddress address;
/**
* Whether the peer can be contacted directly (has public accessible IP)
*/
private boolean accessible;
/**
* This peer's traffic control
*/
private final TorrentPeerTrafficControl trafficControl = new TorrentPeerTrafficControl(
this);
/**
* The peer state
*/
private TorrentPeerState state = new TorrentPeerState(this);
/**
* Creates a new instance
*
* @param torrent
* the torrent
*/
public TorrentPeer(Torrent torrent) {
this.torrent = torrent;
this.pieces = new TorrentPeerPieces(this);
}
public TorrentPeerPieces getPieces() {
return pieces;
}
public String getPeerID() {
return peerID;
}
public void setPeerID(String peerID) {
this.peerID = peerID;
}
public TorrentProtocolPeer getProtocolPeer() {
return protocolPeer;
}
public void setProtocolPeer(TorrentProtocolPeer protocolPeer) {
this.protocolPeer = protocolPeer;
}
public boolean connect() {
if (protocolPeer == null) {
if (torrent.getProtocol() == null)
return false;
return torrent.getProtocol().connect(this);
} else {
return protocolPeer.connect();
}
}
public boolean isConnectable() {
return address != null;
}
public boolean isConnected() {
if (protocolPeer == null)
return false;
return protocolPeer.isConnected();
}
public boolean disconnect() {
if (protocolPeer == null)
return false;
return protocolPeer.disconnect();
}
public InetSocketAddress getAddress() {
return address;
}
public void setAddress(InetSocketAddress address) {
this.address = address;
}
public final InetAddress getIP() {
return address.getAddress();
}
public final int getPort() {
return address.getPort();
}
public final String getHostName(boolean resolve) {
return resolve ? address.getHostName() : address.getHostString();
}
public final String getHostName() {
return getHostName(false);
}
public boolean isAccessible() {
return accessible;
}
public void setAccessible(boolean accessible) {
this.accessible = accessible;
}
/**
* @return the traffic shaper
*/
public TorrentPeerTrafficControl getTrafficControl() {
return trafficControl;
}
public TorrentPeerState getState() {
return state;
}
public void resetState() {
state = new TorrentPeerState(this);
}
// NETWORK RELATED THINGS!
public void handshake() {
// FIXME sent another peer id
protocolPeer.handshake(torrent.getHash().getHash(), torrent
.getController().getConfig().getPeerID());
}
public void declareInterest() {
if (state.isLocalllyInterested())
return;
protocolPeer.interested();
state.setLocalInterest(TorrentPeerInterest.INTERESTED);
}
public void withdrawInterest() {
if (!state.isLocalllyInterested())
return;
protocolPeer.notInterested();
state.setLocalInterest(TorrentPeerInterest.NOT_INTERESTED);
}
public void choke() {
if (state.isLocallyChoked())
return;
protocolPeer.choke();
state.setLocallyChoked(TorrentPeerChoking.CHOKED);
}
public void unchoke() {
if (!state.isLocallyChoked())
return;
protocolPeer.unchoke();
state.setLocallyChoked(TorrentPeerChoking.UNCHOKED);
}
public void have(TorrentPiece piece) {
protocolPeer.have(piece.getIndex());
}
public void bitField() {
protocolPeer.bitField(torrent.getCompletedPieces().getBitSet());
}
public void requestBlock(TorrentPieceBlock block) {
protocolPeer.requestBlock(block.getPiece().getIndex(),
block.getOffset(), block.getLength());
}
public void cancelRequestedBlock(TorrentPieceBlock block) {
protocolPeer.cancelRequestedBlock(block.getPiece().getIndex(),
block.getOffset(), block.getLength());
}
public void sendBlock(TorrentPieceBlock block, ByteBuffer data) {
protocolPeer.sendBlock(block.getPiece().getIndex(), block.getOffset(),
data);
}
public void sendPort(int port) {
protocolPeer.port(port);
}
public void keepAlive() {
protocolPeer.keepAlive();
}
public Torrent getTorrent() {
return torrent;
}
@Override
public String toString() {
return "TorrentPeer [torrent=" + torrent + ", address=" + address + "]";
}
}

View File

@@ -0,0 +1,24 @@
package com.torrent4j.model;
import java.util.BitSet;
public class TorrentPeerPieces extends AbstractTorrentPiecesContainer {
private final TorrentPeer peer;
public void load(BitSet bitSet) {
this.bitSet.set(0, this.bitSet.size(), false);
for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {
this.bitSet.set(i, true);
}
}
public TorrentPeerPieces(TorrentPeer peer) {
super(peer.getTorrent());
this.peer = peer;
}
public TorrentPeer getPeer() {
return peer;
}
}

View File

@@ -0,0 +1,266 @@
package com.torrent4j.model;
import java.util.Date;
public class TorrentPeerState {
private final TorrentPeer peer;
private TorrentPeerInterest remoteInterest = TorrentPeerInterest.NOT_INTERESTED;
private TorrentPeerInterest localInterest = TorrentPeerInterest.NOT_INTERESTED;
public enum TorrentPeerInterest {
INTERESTED, NOT_INTERESTED;
}
private TorrentPeerChoking remoteChoked = TorrentPeerChoking.CHOKED;
private TorrentPeerChoking locallyChoked = TorrentPeerChoking.CHOKED;
public enum TorrentPeerChoking {
CHOKED, UNCHOKED;
}
private TorrentPieceBlock downloadRequestedBlock;
private Date downloadRequestedDate;
private TorrentPieceBlock lastDownloadedBlock;
private Date lastDownloadedBlockDate;
private TorrentPieceBlock uploadRequestedBlock;
private Date uploadRequestedDate;
private TorrentPieceBlock lastUploadedBlock;
private Date lastUploadedBlockDate;
public TorrentPeerState(TorrentPeer peer) {
this.peer = peer;
}
/**
* @return the remoteInterest
*/
public TorrentPeerInterest getRemoteInterest() {
return remoteInterest;
}
/**
* @return the remoteInterest
*/
public boolean isRemotellyInterested() {
return remoteInterest == TorrentPeerInterest.INTERESTED;
}
/**
* @param remoteInterest
* the remoteInterest to set
*/
public void setRemoteInterest(TorrentPeerInterest remoteInterest) {
this.remoteInterest = remoteInterest;
}
/**
* @return the localInterest
*/
public TorrentPeerInterest getLocalInterest() {
return localInterest;
}
/**
* @return the localInterest
*/
public boolean isLocalllyInterested() {
return localInterest == TorrentPeerInterest.INTERESTED;
}
/**
* @param localInterest
* the localInterest to set
*/
public void setLocalInterest(TorrentPeerInterest localInterest) {
this.localInterest = localInterest;
}
/**
* @return the remoteChoked
*/
public TorrentPeerChoking getRemoteChoked() {
return remoteChoked;
}
/**
* @return the remoteChoked
*/
public boolean isRemotellyChoked() {
return remoteChoked == TorrentPeerChoking.CHOKED;
}
/**
* @param remoteChoked
* the remoteChoked to set
*/
public void setRemoteChoked(TorrentPeerChoking remoteChoked) {
this.remoteChoked = remoteChoked;
}
/**
* @return the locallyChoked
*/
public TorrentPeerChoking getLocallyChoked() {
return locallyChoked;
}
/**
* @return the locallyChoked
*/
public boolean isLocallyChoked() {
return locallyChoked == TorrentPeerChoking.CHOKED;
}
/**
* @param locallyChoked
* the locallyChoked to set
*/
public void setLocallyChoked(TorrentPeerChoking locallyChoked) {
this.locallyChoked = locallyChoked;
}
/**
* @return the downloadRequestedBlock
*/
public TorrentPieceBlock getDownloadRequestedBlock() {
return downloadRequestedBlock;
}
/**
* @return the downloadRequestedBlock
*/
public boolean hasDownloadRequestedBlock() {
return downloadRequestedBlock != null;
}
/**
* @param downloadRequestedBlock
* the downloadRequestedBlock to set
*/
public void setDownloadRequestedBlock(
TorrentPieceBlock downloadRequestedBlock) {
this.downloadRequestedBlock = downloadRequestedBlock;
}
/**
* @return the downloadRequestedDate
*/
public Date getDownloadRequestedDate() {
return downloadRequestedDate;
}
/**
* @param downloadRequestedDate
* the downloadRequestedDate to set
*/
public void setDownloadRequestedDate(Date downloadRequestedDate) {
this.downloadRequestedDate = downloadRequestedDate;
}
/**
* @return the lastDownloadedBlock
*/
public TorrentPieceBlock getLastDownloadedBlock() {
return lastDownloadedBlock;
}
/**
* @param lastDownloadedBlock
* the lastDownloadedBlock to set
*/
public void setLastDownloadedBlock(TorrentPieceBlock lastDownloadedBlock) {
this.lastDownloadedBlock = lastDownloadedBlock;
}
/**
* @return the lastDownloadedBlockDate
*/
public Date getLastDownloadedBlockDate() {
return lastDownloadedBlockDate;
}
/**
* @param lastDownloadedBlockDate
* the lastDownloadedBlockDate to set
*/
public void setLastDownloadedBlockDate(Date lastDownloadedBlockDate) {
this.lastDownloadedBlockDate = lastDownloadedBlockDate;
}
/**
* @return the uploadRequestedBlock
*/
public TorrentPieceBlock getUploadRequestedBlock() {
return uploadRequestedBlock;
}
/**
* @return the uploadRequestedBlock
*/
public boolean hasUploadRequestedBlock() {
return uploadRequestedBlock != null;
}
/**
* @param uploadRequestedBlock
* the uploadRequestedBlock to set
*/
public void setUploadRequestedBlock(TorrentPieceBlock uploadRequestedBlock) {
this.uploadRequestedBlock = uploadRequestedBlock;
}
/**
* @return the uploadRequestedDate
*/
public Date getUploadRequestedDate() {
return uploadRequestedDate;
}
/**
* @param uploadRequestedDate
* the uploadRequestedDate to set
*/
public void setUploadRequestedDate(Date uploadRequestedDate) {
this.uploadRequestedDate = uploadRequestedDate;
}
/**
* @return the lastUploadedBlock
*/
public TorrentPieceBlock getLastUploadedBlock() {
return lastUploadedBlock;
}
/**
* @param lastUploadedBlock
* the lastUploadedBlock to set
*/
public void setLastUploadedBlock(TorrentPieceBlock lastUploadedBlock) {
this.lastUploadedBlock = lastUploadedBlock;
}
/**
* @return the lastUploadedBlockDate
*/
public Date getLastUploadedBlockDate() {
return lastUploadedBlockDate;
}
/**
* @param lastUploadedBlockDate
* the lastUploadedBlockDate to set
*/
public void setLastUploadedBlockDate(Date lastUploadedBlockDate) {
this.lastUploadedBlockDate = lastUploadedBlockDate;
}
public TorrentPeer getPeer() {
return peer;
}
public Torrent getTorrent() {
return peer.getTorrent();
}
}

View File

@@ -0,0 +1,17 @@
package com.torrent4j.model;
public class TorrentPeerTrafficControl extends TorrentTrafficControl {
private final TorrentPeer peer;
public TorrentPeerTrafficControl(TorrentPeer peer) {
super(peer.getTorrent());
this.peer = peer;
}
/**
* @return the peer
*/
public TorrentPeer getPeer() {
return peer;
}
}

View File

@@ -0,0 +1,128 @@
package com.torrent4j.model;
import java.util.ArrayList;
import java.util.List;
import com.torrent4j.util.Range;
public class TorrentPiece {
private final Torrent torrent;
private final TorrentPieceHash hash;
private final int index;
private final int offset;
private final int length;
private final List<TorrentPieceBlock> blocks = new ArrayList<>();
private final List<TorrentFile> files = new ArrayList<>();
public TorrentPiece(Torrent torrent, byte[] hash, int index, int offset,
int length) {
this.torrent = torrent;
this.hash = new TorrentPieceHash(this, hash);
this.index = index;
this.offset = offset;
this.length = length;
final int blocks = (int) Math.ceil((double) length
/ TorrentPieceBlock.BLOCK_LENGTH);
for (int i = 0; i < blocks; i++) {
final int blockOffset = TorrentPieceBlock.BLOCK_LENGTH * i;
int len = TorrentPieceBlock.BLOCK_LENGTH;
if (i == blocks - 1 && length % TorrentPieceBlock.BLOCK_LENGTH != 0)
len = length - blockOffset;
this.blocks.add(new TorrentPieceBlock(this, blockOffset, len));
}
}
public TorrentPieceHash getHash() {
return hash;
}
public int getIndex() {
return index;
}
public long getOffset() {
return offset;
}
public int getLength() {
return length;
}
public Range getTorrentRange() {
return Range.getRangeByLength(offset, length);
}
public boolean isFirst() {
return index == 0;
}
public boolean isLast() {
return torrent.getPieces().size() == index + 1;
}
public List<TorrentPieceBlock> getBlocks() {
return blocks;
}
public TorrentPieceBlock getBlock(int offset, int length) {
for (final TorrentPieceBlock block : blocks) {
if (block.getOffset() == offset && block.getLength() == length)
return block;
}
return null;
}
public TorrentPieceBlock getFirstBlock() {
return blocks.get(0);
}
public TorrentPieceBlock getLastBlock() {
return blocks.get(blocks.size() - 1);
}
public TorrentPieceBlock getNextBlock(TorrentPieceBlock block) {
int next = blocks.indexOf(block) + 1;
if (blocks.size() == next)
return null;
return blocks.get(next);
}
public boolean isDownloaded() {
for (final TorrentPieceBlock block : blocks) {
if (!block.isDownloaded())
return false;
}
return true;
}
public void setDownloaded(boolean downloaded) {
for (final TorrentPieceBlock block : blocks) {
block.setDownloaded(downloaded);
}
}
public List<TorrentFile> getFiles() {
return files;
}
/* package protected! */void addFile(TorrentFile file) {
files.add(file);
}
public TorrentPiece getNextPiece() {
return torrent.getPiece(index + 1);
}
public Torrent getTorrent() {
return torrent;
}
@Override
public String toString() {
return "TorrentPiece [torrent=" + torrent + ", hash=" + hash
+ ", index=" + index + ", length=" + length + "]";
}
}

View File

@@ -0,0 +1,81 @@
package com.torrent4j.model;
import com.torrent4j.util.Range;
public class TorrentPieceBlock {
public static final int BLOCK_LENGTH = 16 * 1024;
private final TorrentPiece piece;
private final int offset;
private final int length;
private boolean downloaded = false;
public TorrentPieceBlock(TorrentPiece piece, int offset, int length) {
this.piece = piece;
this.offset = offset;
this.length = length;
}
public int getOffset() {
return offset;
}
public int getLength() {
return length;
}
public Range getTorrentRange() {
return Range.getRangeByLength(piece.getOffset() + offset, length);
}
/**
* Get the range inside the piece.
*
* @return the range in piece
*/
public Range getPieceRange() {
return Range.getRangeByLength(offset, length);
}
/**
* Get the range inside the file
*
* @param file
* the file
* @return the range in file
*/
public Range getFileRange(TorrentFile file) {
if (piece.getOffset() - file.getOffset() + offset < 0)
return null;
return Range.getRangeByLength(
piece.getOffset() - file.getOffset() + offset, length)
.intersection(file.getFileRange());
}
public boolean isDownloaded() {
return downloaded;
}
public void setDownloaded(boolean downloaded) {
this.downloaded = downloaded;
}
public TorrentPieceBlock getNextBlock() {
return piece.getNextBlock(this);
}
public TorrentPiece getPiece() {
return piece;
}
public Torrent getTorrent() {
return piece.getTorrent();
}
@Override
public String toString() {
return "TorrentPieceBlock [piece=" + piece + ", offset=" + offset
+ ", length=" + length + "]";
}
}

View File

@@ -0,0 +1,21 @@
package com.torrent4j.model;
import com.torrent4j.util.Hash;
import com.torrent4j.util.HashType;
public class TorrentPieceHash extends Hash {
private final TorrentPiece piece;
public TorrentPieceHash(TorrentPiece piece, byte[] hash) {
super(HashType.SHA1, hash);
this.piece = piece;
}
public TorrentPiece getPiece() {
return piece;
}
public Torrent getTorrent() {
return piece.getTorrent();
}
}

View File

@@ -0,0 +1,69 @@
package com.torrent4j.model;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class TorrentSwarm {
private final Torrent torrent;
private final List<TorrentPeer> peers = new ArrayList<>();
public TorrentSwarm(Torrent torrent) {
this.torrent = torrent;
}
public void broadcast(SwarmBroadcastHandler handler) {
for (final TorrentPeer peer : getConnectedPeers()) {
try {
handler.broadcast(peer);
} catch (Exception e) {
if (handler.exception(e))
throw new RuntimeException(e);
}
}
}
public interface SwarmBroadcastHandler {
void broadcast(TorrentPeer peer);
boolean exception(Exception e);
}
public List<TorrentPeer> getPeers() {
return Collections.unmodifiableList(peers);
}
public List<TorrentPeer> getConnectedPeers() {
final List<TorrentPeer> list = new ArrayList<>();
for (final TorrentPeer peer : peers) {
if (peer.isConnected())
list.add(peer);
}
return Collections.unmodifiableList(list);
}
public void addPeer(TorrentPeer peer) {
peers.add(peer);
torrent.getStrategy().getPeerStrategy().peerDiscovered(torrent, peer);
}
public void removePeer(TorrentPeer peer) {
peers.remove(peer);
torrent.getStrategy().getPeerStrategy().peerRemoved(torrent, peer);
}
public TorrentPeer findPeer(InetSocketAddress address, String peerID) {
for (final TorrentPeer peer : peers) {
if (peerID != null && peerID.equals(peer.getPeerID()))
return peer;
if (address != null && address.equals(peer.getAddress()))
return peer;
}
return null;
}
public Torrent getTorrent() {
return torrent;
}
}

View File

@@ -0,0 +1,13 @@
package com.torrent4j.model;
public class TorrentTracker {
private final Torrent torrent;
public TorrentTracker(Torrent torrent) {
this.torrent = torrent;
}
public Torrent getTorrent() {
return torrent;
}
}

View File

@@ -0,0 +1,91 @@
package com.torrent4j.model;
public class TorrentTrafficControl {
private final Torrent torrent;
private long downloadSpeedLimit = 0;
private long uploadSpeedLimit = 0;
private long currentDownloadSpeed = 0;
private long currentUploadSpeed = 0;
public TorrentTrafficControl(Torrent torrent) {
this.torrent = torrent;
}
/**
* @return the downloadSpeedLimit
*/
public long getDownloadSpeedLimit() {
return downloadSpeedLimit;
}
/**
* @param downloadSpeedLimit
* the downloadSpeedLimit to set
*/
public void setDownloadSpeedLimit(long downloadSpeedLimit) {
this.downloadSpeedLimit = downloadSpeedLimit;
}
/**
* @return the uploadSpeedLimit
*/
public long getUploadSpeedLimit() {
return uploadSpeedLimit;
}
/**
* @param uploadSpeedLimit
* the uploadSpeedLimit to set
*/
public void setUploadSpeedLimit(long uploadSpeedLimit) {
this.uploadSpeedLimit = uploadSpeedLimit;
}
/**
* @return the currentDownloadSpeed
*/
public long getCurrentDownloadSpeed() {
return currentDownloadSpeed;
}
/**
* @param currentDownloadSpeed
* the currentDownloadSpeed to set
*/
public void setCurrentDownloadSpeed(long currentDownloadSpeed) {
this.currentDownloadSpeed = currentDownloadSpeed;
}
/**
* @return the currentUploadSpeed
*/
public long getCurrentUploadSpeed() {
return currentUploadSpeed;
}
/**
* @param currentUploadSpeed
* the currentUploadSpeed to set
*/
public void setCurrentUploadSpeed(long currentUploadSpeed) {
this.currentUploadSpeed = currentUploadSpeed;
}
@Override
public String toString() {
return "TorrentTrafficControl [torrent=" + torrent
+ ", downloadSpeedLimit=" + downloadSpeedLimit
+ ", uploadSpeedLimit=" + uploadSpeedLimit
+ ", currentDownloadSpeed=" + currentDownloadSpeed
+ ", currentUploadSpeed=" + currentUploadSpeed + "]";
}
/**
* @return the torrent
*/
public Torrent getTorrent() {
return torrent;
}
}

View File

@@ -0,0 +1,51 @@
package com.torrent4j.model.metadata;
import java.io.IOException;
import com.torrent4j.util.bencoding.BList;
import com.torrent4j.util.bencoding.BMap;
public class MetadataFile {
private String fileName;
private long length;
private String hash;
public MetadataFile(BMap file) throws IOException {
if (file.get("path") != null) {
final BList path = file.getList("path");
final StringBuilder builder = new StringBuilder();
for (final Object pathPart : path) {
builder.append(new String((byte[]) pathPart)).append("/");
}
this.fileName = builder.substring(0, builder.length() - 1);
} else {
this.fileName = file.getString("name");
}
this.length = file.getLong("length");
this.hash = file.getString("md5sum");
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
public String getHash() {
return hash;
}
public void setHash(String hash) {
this.hash = hash;
}
}

View File

@@ -0,0 +1,83 @@
package com.torrent4j.model.metadata;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.torrent4j.util.bencoding.BList;
import com.torrent4j.util.bencoding.BMap;
public class MetadataInfo {
private int pieceLength;
private byte[] pieceHashes;
private boolean privateTorrent;
private String name;
private List<MetadataFile> files = new ArrayList<>();
public MetadataInfo(BMap dictionary) throws IOException {
this.pieceLength = dictionary.getInteger("piece length");
this.pieceHashes = (byte[]) dictionary.get("pieces");
if (dictionary.get("private") != null)
this.privateTorrent = dictionary.getInteger("private") == 1;
this.name = dictionary.getString("name");
if (dictionary.get("files") != null) {
final BList files = dictionary
.getList("files");
for (final Object file : files) {
this.files.add(new MetadataFile((BMap) file));
}
} else {
this.files.add(new MetadataFile(dictionary));
}
}
public int getPieceLength() {
return pieceLength;
}
public void setPieceLength(int pieceLength) {
this.pieceLength = pieceLength;
}
public byte[] getPieceHashes() {
return pieceHashes;
}
public void setPieceHashes(byte[] pieceHashes) {
this.pieceHashes = pieceHashes;
}
public boolean isPrivateTorrent() {
return privateTorrent;
}
public void setPrivateTorrent(boolean privateTorrent) {
this.privateTorrent = privateTorrent;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<MetadataFile> getFiles() {
return files;
}
public void setFiles(List<MetadataFile> files) {
this.files = files;
}
public long getLength() {
long length = 0;
for (final MetadataFile file : files) {
length += file.getLength();
}
return length;
}
}

View File

@@ -0,0 +1,48 @@
package com.torrent4j.model.metadata;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import com.torrent4j.util.bencoding.BList;
public class MetadataTracker {
private String url;
private List<String> backupUrls;
public MetadataTracker(String url, List<String> backupUrls) {
this.url = url;
this.backupUrls = backupUrls;
}
public MetadataTracker(String url, String... backupUrls) {
this(url, Arrays.asList(backupUrls));
}
public MetadataTracker(String url) {
this.url = url;
}
public MetadataTracker(BList urls) throws IOException {
this.url = urls.getString(0);
if(urls.size() > 1) {
//TODO
}
}
public String getURL() {
return url;
}
public void setURL(String url) {
this.url = url;
}
public List<String> getBackupURLs() {
return backupUrls;
}
public void setBackupURLs(List<String> backupUrls) {
this.backupUrls = backupUrls;
}
}

View File

@@ -0,0 +1,73 @@
package com.torrent4j.model.metadata;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.torrent4j.util.HashType;
import com.torrent4j.util.bencoding.BEncoder;
import com.torrent4j.util.bencoding.BList;
import com.torrent4j.util.bencoding.BMap;
public class TorrentMetadata {
private MetadataInfo info;
private byte[] infoHash;
private List<MetadataTracker> trackers = new ArrayList<>();
private Date creationDate;
public TorrentMetadata(BMap dictionary) throws IOException {
this.trackers
.add(new MetadataTracker(dictionary.getString("announce")));
final BList announceListNode = dictionary.getList("announce-list");
if (announceListNode != null) {
for (final Object trackerGroup : announceListNode) {
trackers.add(new MetadataTracker((BList) trackerGroup));
}
}
if (dictionary.get("creation date") != null)
this.creationDate = new Date(
dictionary.getInteger("creation date") * 1000);
this.infoHash = HashType.SHA1.hash(BEncoder.bencode(dictionary
.get("info")));
this.info = new MetadataInfo(dictionary.getMap("info"));
}
public byte[] getInfoHash() {
return infoHash;
}
public void setInfoHash(byte[] infoHash) {
this.infoHash = infoHash;
}
public MetadataInfo getInfo() {
return info;
}
public void setInfo(MetadataInfo info) {
this.info = info;
}
public List<MetadataTracker> getTrackers() {
return trackers;
}
public MetadataTracker getMainTracker() {
return trackers.get(0);
}
public void setTrackers(List<MetadataTracker> trackers) {
this.trackers = trackers;
}
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
}

View File

@@ -0,0 +1,12 @@
package com.torrent4j.net;
import com.torrent4j.TorrentController;
import com.torrent4j.model.TorrentPeer;
public interface TorrentProtocol {
void start(TorrentController controller, int listenPort);
void stop();
boolean connect(TorrentPeer peer);
}

View File

@@ -0,0 +1,43 @@
package com.torrent4j.net;
import java.nio.ByteBuffer;
import java.util.BitSet;
import com.torrent4j.model.Torrent;
import com.torrent4j.model.TorrentPeer;
public interface TorrentProtocolPeer {
public TorrentPeer getTorrentPeer();
public Torrent getTorrent();
boolean connect();
boolean isConnected();
boolean disconnect();
void handshake(byte[] torrentHash, String peerID);
void requestBlock(int pieceIndex, int start, int length);
void cancelRequestedBlock(int pieceIndex, int start, int length);
void sendBlock(int pieceIndex, int start, ByteBuffer data);
void bitField(BitSet bitSet);
void have(int pieceIndex);
void choke();
void unchoke();
void interested();
void notInterested();
void port(int dhtPort);
void keepAlive();
}

View File

@@ -0,0 +1,8 @@
package com.torrent4j.net;
public interface TorrentTrafficShaper {
void update(long writeLimit, long readLimit);
long getDownloadSpeed();
long getUploadSpeed();
}

View File

@@ -0,0 +1,28 @@
package com.torrent4j.net.peerwire;
import io.netty.buffer.ChannelBuffer;
public abstract class AbstractPeerWireMessage implements PeerWireMessage {
public final int messageID;
public AbstractPeerWireMessage(int messageID) {
this.messageID = messageID;
}
@Override
public final void write(ChannelBuffer buffer) {
buffer.writeByte(messageID);
writeImpl(buffer);
}
public void writeImpl(ChannelBuffer buffer) {
}
@Override
public final void read(ChannelBuffer buffer) {
readImpl(buffer);
}
public void readImpl(ChannelBuffer buffer) {
}
}

View File

@@ -0,0 +1,270 @@
package com.torrent4j.net.peerwire;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.MessageEvent;
import io.netty.channel.SimpleChannelHandler;
import java.net.InetSocketAddress;
import java.util.Date;
import com.torrent4j.TorrentController;
import com.torrent4j.model.Torrent;
import com.torrent4j.model.TorrentPeer;
import com.torrent4j.model.TorrentPeerState.TorrentPeerChoking;
import com.torrent4j.model.TorrentPeerState.TorrentPeerInterest;
import com.torrent4j.model.TorrentPiece;
import com.torrent4j.model.TorrentPieceBlock;
import com.torrent4j.net.peerwire.codec.PeerWireFrameDecoder;
import com.torrent4j.net.peerwire.codec.PeerWireFrameEncoder;
import com.torrent4j.net.peerwire.codec.PeerWireMessageDecoder;
import com.torrent4j.net.peerwire.codec.PeerWireMessageEncoder;
import com.torrent4j.net.peerwire.messages.BitFieldMessage;
import com.torrent4j.net.peerwire.messages.BlockMessage;
import com.torrent4j.net.peerwire.messages.CancelMessage;
import com.torrent4j.net.peerwire.messages.ChokeMessage;
import com.torrent4j.net.peerwire.messages.HandshakeMessage;
import com.torrent4j.net.peerwire.messages.HaveMessage;
import com.torrent4j.net.peerwire.messages.InterestedMessage;
import com.torrent4j.net.peerwire.messages.NotInterestedMessage;
import com.torrent4j.net.peerwire.messages.RequestMessage;
import com.torrent4j.net.peerwire.messages.UnchokeMessage;
import com.torrent4j.net.peerwire.traffic.PeerTrafficShapingHandler;
import com.torrent4j.net.peerwire.traffic.TorrentTrafficShapingHandler;
import com.torrent4j.util.Hash;
import com.torrent4j.util.HashType;
public class PeerWireHandler extends SimpleChannelHandler {
private final TorrentController controller;
private PeerWireProtocolPeer peer;
public PeerWireHandler(TorrentController controller) {
this.controller = controller;
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
throws Exception {
try {
final Object msg = e.getMessage();
if (!(msg instanceof PeerWireMessage))
return;
if (msg instanceof HandshakeMessage) {
final HandshakeMessage message = (HandshakeMessage) msg;
((PeerWireFrameDecoder) e.getChannel().getPipeline()
.get("frame-decoder")).setHandshaked(true);
((PeerWireMessageDecoder) e.getChannel().getPipeline()
.get("message-decoder")).setHandshaked(true);
final Hash hash = new Hash(HashType.SHA1, message.torrentHash);
final Torrent torrent = controller.findTorrent(hash);
if (torrent == null) {
e.getChannel().disconnect();
return;
}
TorrentPeer peer = torrent.getSwarm().findPeer(
(InetSocketAddress) e.getChannel().getRemoteAddress(),
message.peerID);
if (peer == null) {
peer = new TorrentPeer(torrent);
peer.setPeerID(message.peerID);
peer.setAddress((InetSocketAddress) e.getChannel()
.getRemoteAddress());
}
this.peer = (PeerWireProtocolPeer) peer.getProtocolPeer();
e.getChannel().getPipeline().get(PeerTrafficShapingHandler.class)
.setPeer(peer);
e.getChannel().getPipeline()
.get(TorrentTrafficShapingHandler.class)
.setTorrent(torrent);
peer.resetState();
this.peer.getStrategy().getPeerStrategy()
.peerConnected(torrent, peer);
} else if (msg instanceof HaveMessage) {
peer.getStrategy()
.getPeerStrategy()
.havePiece(
peer.getTorrent(),
peer.getTorrentPeer(),
peer.getTorrent().getPiece(
((HaveMessage) msg).pieceIndex));
} else if (msg instanceof UnchokeMessage) {
peer.getTorrentPeer().getState()
.setRemoteChoked(TorrentPeerChoking.UNCHOKED);
peer.getStrategy().getPeerStrategy()
.unchoked(peer.getTorrent(), peer.getTorrentPeer());
} else if (msg instanceof ChokeMessage) {
peer.getTorrentPeer().getState()
.setRemoteChoked(TorrentPeerChoking.CHOKED);
peer.getStrategy().getPeerStrategy()
.choked(peer.getTorrent(), peer.getTorrentPeer());
} else if (msg instanceof InterestedMessage) {
peer.getTorrentPeer().getState()
.setRemoteInterest(TorrentPeerInterest.INTERESTED);
peer.getStrategy().getPeerStrategy()
.interested(peer.getTorrent(), peer.getTorrentPeer());
} else if (msg instanceof NotInterestedMessage) {
peer.getTorrentPeer().getState()
.setRemoteInterest(TorrentPeerInterest.NOT_INTERESTED);
peer.getStrategy()
.getPeerStrategy()
.notInterested(peer.getTorrent(), peer.getTorrentPeer());
} else if (msg instanceof BitFieldMessage) {
peer.getTorrentPeer().getPieces()
.load(((BitFieldMessage) msg).bitSet);
peer.getStrategy().getPeerStrategy()
.bitField(peer.getTorrent(), peer.getTorrentPeer());
} else if (msg instanceof RequestMessage) {
final RequestMessage message = (RequestMessage) msg;
final TorrentPiece piece = peer.getTorrent().getPiece(
message.pieceIndex);
final TorrentPieceBlock block = piece.getBlock(message.begin,
message.length);
peer.getTorrentPeer().getState().setUploadRequestedBlock(block);
peer.getTorrentPeer().getState()
.setUploadRequestedDate(new Date());
peer.getStrategy()
.getUploadStrategy()
.blockRequested(peer.getTorrent(), block,
peer.getTorrentPeer());
} else if (msg instanceof CancelMessage) {
final CancelMessage message = (CancelMessage) msg;
final TorrentPiece piece = peer.getTorrent().getPiece(
message.pieceIndex);
final TorrentPieceBlock block = piece.getBlock(message.begin,
message.length);
peer.getTorrentPeer().getState().setUploadRequestedBlock(null);
peer.getTorrentPeer().getState().setUploadRequestedDate(null);
peer.getStrategy()
.getUploadStrategy()
.blockRequestCancelled(peer.getTorrent(), block,
peer.getTorrentPeer());
} else if (msg instanceof BlockMessage) {
final BlockMessage message = (BlockMessage) msg;
final TorrentPiece piece = peer.getTorrent().getPiece(
message.pieceIndex);
final TorrentPieceBlock block = piece.getBlock(message.begin,
message.data.remaining());
peer.getTorrentPeer().getState().setLastDownloadedBlock(block);
peer.getTorrentPeer().getState()
.setLastDownloadedBlockDate(new Date());
peer.getTorrentPeer().getState()
.setDownloadRequestedBlock(null);
peer.getTorrentPeer().getState().setDownloadRequestedDate(null);
controller.getStorage().write(piece.getTorrent(),
block.getTorrentRange(), message.data);
block.setDownloaded(true);
if (piece.isDownloaded()) {
final Hash pieceHash = controller.getStorage().checksum(
piece);
if (!piece.getHash().equals(pieceHash)) {
piece.getTorrent()
.getStrategy()
.getDownloadStrategy()
.pieceChecksumFailed(piece.getTorrent(), piece,
peer.getTorrentPeer());
} else {
piece.getTorrent().getCompletedPieces().addPiece(piece);
piece.getTorrent()
.getStrategy()
.getDownloadStrategy()
.pieceComplete(piece.getTorrent(), piece,
peer.getTorrentPeer());
}
} else {
piece.getTorrent()
.getStrategy()
.getDownloadStrategy()
.blockReceived(block.getTorrent(), block,
peer.getTorrentPeer());
}
} else {
System.out.println(msg);
}
} finally {
ctx.sendUpstream(e);
}
}
@Override
public void writeRequested(ChannelHandlerContext ctx, MessageEvent e)
throws Exception {
try {
final Object msg = e.getMessage();
if (!(msg instanceof PeerWireMessage))
return;
if (msg instanceof HandshakeMessage) {
e.getFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future)
throws Exception {
((PeerWireFrameEncoder) future.getChannel()
.getPipeline().get("frame-encoder"))
.setHandshaked(true);
((PeerWireMessageEncoder) future.getChannel()
.getPipeline().get("message-encoder"))
.setHandshaked(true);
}
});
} else if (msg instanceof BlockMessage) {
final BlockMessage message = (BlockMessage) msg;
final TorrentPiece piece = peer.getTorrent().getPiece(
message.pieceIndex);
final TorrentPieceBlock block = piece.getBlock(message.begin,
message.data.remaining());
peer.getTorrentPeer().getState().setLastUploadedBlock(block);
peer.getTorrentPeer().getState()
.setLastUploadedBlockDate(new Date());
peer.getTorrentPeer().getState().setUploadRequestedBlock(null);
peer.getTorrentPeer().getState().setUploadRequestedDate(null);
} else if (msg instanceof RequestMessage) {
final RequestMessage message = (RequestMessage) msg;
final TorrentPiece piece = peer.getTorrent().getPiece(
message.pieceIndex);
final TorrentPieceBlock block = piece.getBlock(message.begin,
message.length);
peer.getTorrentPeer().getState()
.setDownloadRequestedBlock(block);
peer.getTorrentPeer().getState()
.setDownloadRequestedDate(new Date());
} else if (msg instanceof CancelMessage) {
peer.getTorrentPeer().getState()
.setDownloadRequestedBlock(null);
peer.getTorrentPeer().getState().setDownloadRequestedDate(null);
}
} finally {
ctx.sendDownstream(e);
}
}
// @Override
// public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent
// e)
// throws Exception {
// try {
// peer = new PeerWireProtocolPeer(e.getChannel());
// } finally {
// ctx.sendUpstream(e);
// }
// }
}

View File

@@ -0,0 +1,9 @@
package com.torrent4j.net.peerwire;
import io.netty.buffer.ChannelBuffer;
public interface PeerWireMessage {
void write(ChannelBuffer buffer);
void read(ChannelBuffer buffer);
}

View File

@@ -0,0 +1,46 @@
package com.torrent4j.net.peerwire;
import static io.netty.channel.Channels.pipeline;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPipelineFactory;
import java.util.concurrent.Executor;
import com.torrent4j.TorrentController;
import com.torrent4j.net.peerwire.codec.PeerWireFrameDecoder;
import com.torrent4j.net.peerwire.codec.PeerWireFrameEncoder;
import com.torrent4j.net.peerwire.codec.PeerWireMessageDecoder;
import com.torrent4j.net.peerwire.codec.PeerWireMessageEncoder;
import com.torrent4j.net.peerwire.traffic.TorrentTrafficShapingHandler;
import com.torrent4j.net.peerwire.traffic.PeerTrafficShapingHandler;
public class PeerWirePipelineFactory implements ChannelPipelineFactory {
private final TorrentController controller;
private final Executor executor;
public PeerWirePipelineFactory(TorrentController controller,
Executor executor) {
this.controller = controller;
this.executor = executor;
}
@Override
public ChannelPipeline getPipeline() throws Exception {
final ChannelPipeline p = pipeline();
p.addLast("torrent-shaper", new TorrentTrafficShapingHandler(executor));
p.addLast("traffic-shaper", new PeerTrafficShapingHandler(executor));
p.addLast("frame-decoder", new PeerWireFrameDecoder());
p.addLast("frame-encoder", new PeerWireFrameEncoder());
p.addLast("message-decoder", new PeerWireMessageDecoder());
p.addLast("message-encoder", new PeerWireMessageEncoder());
// p.addLast("logging", new LoggingHandler(InternalLogLevel.WARN));
p.addLast("handler", new PeerWireHandler(controller));
return p;
}
}

View File

@@ -0,0 +1,65 @@
package com.torrent4j.net.peerwire;
import io.netty.bootstrap.ClientBootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.socket.nio.NioClientSocketChannelFactory;
import io.netty.channel.socket.nio.NioServerSocketChannelFactory;
import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import com.torrent4j.TorrentController;
import com.torrent4j.model.TorrentPeer;
import com.torrent4j.net.TorrentProtocol;
public class PeerWireProtocol implements TorrentProtocol {
private final Executor threadPool = Executors.newCachedThreadPool();
private final ServerBootstrap serverBootstrap = new ServerBootstrap(
new NioServerSocketChannelFactory(threadPool, threadPool));
private final ClientBootstrap clientBootstrap = new ClientBootstrap(
new NioClientSocketChannelFactory(threadPool, threadPool));
private Channel serverChannel;
@Override
public void start(TorrentController controller, int listenPort) {
serverBootstrap.setPipelineFactory(new PeerWirePipelineFactory(
controller, threadPool));
clientBootstrap.setPipelineFactory(new PeerWirePipelineFactory(
controller, threadPool));
try {
serverChannel = serverBootstrap.bind(new InetSocketAddress(
Inet4Address.getByName("0.0.0.0"), listenPort));
} catch (UnknownHostException e) {
}
}
@Override
public void stop() {
clientBootstrap.releaseExternalResources();
serverChannel.close();
serverBootstrap.releaseExternalResources();
}
@Override
public boolean connect(TorrentPeer peer) {
final ChannelFuture future = clientBootstrap.connect(peer.getAddress())
.awaitUninterruptibly();
if (future.isSuccess()) {
final PeerWireProtocolPeer protocolPeer = new PeerWireProtocolPeer(
future.getChannel());
protocolPeer.setTorrentPeer(peer);
peer.setProtocolPeer(protocolPeer);
return true;
} else {
return false;
}
}
}

View File

@@ -0,0 +1,129 @@
package com.torrent4j.net.peerwire;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import java.nio.ByteBuffer;
import java.util.BitSet;
import com.torrent4j.model.Torrent;
import com.torrent4j.model.TorrentPeer;
import com.torrent4j.net.TorrentProtocolPeer;
import com.torrent4j.net.peerwire.messages.BitFieldMessage;
import com.torrent4j.net.peerwire.messages.BlockMessage;
import com.torrent4j.net.peerwire.messages.CancelMessage;
import com.torrent4j.net.peerwire.messages.ChokeMessage;
import com.torrent4j.net.peerwire.messages.HandshakeMessage;
import com.torrent4j.net.peerwire.messages.HaveMessage;
import com.torrent4j.net.peerwire.messages.InterestedMessage;
import com.torrent4j.net.peerwire.messages.KeepAliveMessage;
import com.torrent4j.net.peerwire.messages.NotInterestedMessage;
import com.torrent4j.net.peerwire.messages.PortMessage;
import com.torrent4j.net.peerwire.messages.RequestMessage;
import com.torrent4j.net.peerwire.messages.UnchokeMessage;
import com.torrent4j.strategy.TorrentStrategy;
public class PeerWireProtocolPeer implements TorrentProtocolPeer {
private final Channel channel;
private TorrentPeer peer;
public PeerWireProtocolPeer(Channel channel) {
this.channel = channel;
}
@Override
public boolean connect() {
return channel.connect(channel.getRemoteAddress())
.awaitUninterruptibly().isSuccess();
}
@Override
public boolean isConnected() {
return channel.isConnected();
}
@Override
public boolean disconnect() {
return channel.disconnect().awaitUninterruptibly().isSuccess();
}
@Override
public void handshake(byte[] torrentHash, String peerID) {
write(new HandshakeMessage(torrentHash, peerID));
}
@Override
public void requestBlock(int pieceIndex, int start, int length) {
write(new RequestMessage(pieceIndex, start, length));
}
@Override
public void cancelRequestedBlock(int pieceIndex, int start, int length) {
write(new CancelMessage(pieceIndex, start, length));
}
@Override
public void sendBlock(int pieceIndex, int start, ByteBuffer data) {
write(new BlockMessage(pieceIndex, start, data));
}
@Override
public void bitField(BitSet bitSet) {
write(new BitFieldMessage(bitSet));
}
@Override
public void have(int pieceIndex) {
write(new HaveMessage(pieceIndex));
}
@Override
public void choke() {
write(new ChokeMessage());
}
@Override
public void unchoke() {
write(new UnchokeMessage());
}
@Override
public void interested() {
write(new InterestedMessage());
}
@Override
public void notInterested() {
write(new NotInterestedMessage());
}
@Override
public void port(int dhtPort) {
write(new PortMessage(dhtPort));
}
@Override
public void keepAlive() {
write(new KeepAliveMessage());
}
public ChannelFuture write(PeerWireMessage message) {
return channel.write(message);
}
public TorrentPeer getTorrentPeer() {
return peer;
}
public void setTorrentPeer(TorrentPeer peer) {
this.peer = peer;
}
public Torrent getTorrent() {
return peer.getTorrent();
}
public TorrentStrategy getStrategy() {
return peer.getTorrent().getStrategy();
}
}

View File

@@ -0,0 +1,40 @@
package com.torrent4j.net.peerwire.codec;
import io.netty.buffer.ChannelBuffer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.frame.FrameDecoder;
public class PeerWireFrameDecoder extends FrameDecoder {
private boolean handshaked = false;
@Override
protected Object decode(ChannelHandlerContext ctx, Channel channel,
ChannelBuffer buffer) throws Exception {
if (!handshaked) {
int pos = buffer.readerIndex();
buffer.skipBytes(68);
return buffer.copy(pos, 68);
}
if (buffer.readableBytes() < 4)
return null;
int pos = buffer.readerIndex();
final int len = buffer.readInt();
if (buffer.readableBytes() >= len) {
try {
return buffer.slice(buffer.readerIndex(), len);
} finally {
buffer.skipBytes(len);
}
} else {
buffer.readerIndex(pos);
return null;
}
}
public void setHandshaked(boolean handshaked) {
this.handshaked = handshaked;
}
}

View File

@@ -0,0 +1,30 @@
package com.torrent4j.net.peerwire.codec;
import io.netty.buffer.ChannelBuffer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.oneone.OneToOneEncoder;
public class PeerWireFrameEncoder extends OneToOneEncoder {
private boolean handshaked = false;
@Override
protected Object encode(ChannelHandlerContext ctx, Channel channel,
Object msg) throws Exception {
if (!(msg instanceof ChannelBuffer) || !handshaked)
return msg;
final ChannelBuffer buffer = (ChannelBuffer) msg;
buffer.readerIndex(0);
if (handshaked) {
final int len = buffer.readableBytes() - 4;
buffer.setInt(0, len);
}
return buffer;
}
public void setHandshaked(boolean handshaked) {
this.handshaked = handshaked;
}
}

View File

@@ -0,0 +1,85 @@
package com.torrent4j.net.peerwire.codec;
import io.netty.buffer.ChannelBuffer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.oneone.OneToOneDecoder;
import com.torrent4j.net.peerwire.PeerWireMessage;
import com.torrent4j.net.peerwire.messages.BitFieldMessage;
import com.torrent4j.net.peerwire.messages.BlockMessage;
import com.torrent4j.net.peerwire.messages.CancelMessage;
import com.torrent4j.net.peerwire.messages.ChokeMessage;
import com.torrent4j.net.peerwire.messages.HandshakeMessage;
import com.torrent4j.net.peerwire.messages.HaveMessage;
import com.torrent4j.net.peerwire.messages.InterestedMessage;
import com.torrent4j.net.peerwire.messages.KeepAliveMessage;
import com.torrent4j.net.peerwire.messages.NotInterestedMessage;
import com.torrent4j.net.peerwire.messages.PortMessage;
import com.torrent4j.net.peerwire.messages.RequestMessage;
import com.torrent4j.net.peerwire.messages.UnchokeMessage;
public class PeerWireMessageDecoder extends OneToOneDecoder {
private boolean handshaked = false;
@Override
protected Object decode(ChannelHandlerContext ctx, Channel channel,
Object msg) throws Exception {
if (!(msg instanceof ChannelBuffer))
return msg;
final ChannelBuffer buffer = (ChannelBuffer) msg;
if (!handshaked) {
final HandshakeMessage message = new HandshakeMessage();
message.read(buffer);
return message;
} else {
if(buffer.readableBytes() == 0)
return new KeepAliveMessage();
final byte opcode = buffer.readByte();
final PeerWireMessage message;
switch (opcode) {
case CancelMessage.MESSAGE_ID:
message = new CancelMessage();
break;
case BitFieldMessage.MESSAGE_ID:
message = new BitFieldMessage();
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 BlockMessage.MESSAGE_ID:
message = new BlockMessage();
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;
default:
return null;
}
message.read(buffer);
return message;
}
}
public void setHandshaked(boolean handshaked) {
this.handshaked = handshaked;
}
}

View File

@@ -0,0 +1,33 @@
package com.torrent4j.net.peerwire.codec;
import io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.oneone.OneToOneEncoder;
import com.torrent4j.net.peerwire.PeerWireMessage;
import com.torrent4j.net.peerwire.messages.KeepAliveMessage;
public class PeerWireMessageEncoder extends OneToOneEncoder {
private boolean handshaked = false;
@Override
protected Object encode(ChannelHandlerContext ctx, Channel channel,
Object msg) throws Exception {
if (!(msg instanceof PeerWireMessage))
return msg;
final PeerWireMessage message = (PeerWireMessage) msg;
final ChannelBuffer buffer = ChannelBuffers.dynamicBuffer();
if (handshaked && !(message instanceof KeepAliveMessage))
buffer.writeInt(0x00);
message.write(buffer);
return buffer;
}
public void setHandshaked(boolean handshaked) {
this.handshaked = handshaked;
}
}

View File

@@ -0,0 +1,58 @@
package com.torrent4j.net.peerwire.messages;
import io.netty.buffer.ChannelBuffer;
import java.util.BitSet;
import com.torrent4j.net.peerwire.AbstractPeerWireMessage;
public class BitFieldMessage extends AbstractPeerWireMessage {
public static final int MESSAGE_ID = 0x05;
public BitSet bitSet;
public BitFieldMessage() {
super(MESSAGE_ID);
}
public BitFieldMessage(BitSet bitSet) {
super(MESSAGE_ID);
this.bitSet = bitSet;
}
@Override
public void writeImpl(ChannelBuffer buffer) {
for (int i = 0; i < bitSet.size();) {
byte data = 0;
for (int j = 128; i < bitSet.size() && j > 0; j >>= 1, i++) {
if (bitSet.get(i)) {
data |= j;
}
}
buffer.writeByte(data);
}
}
@Override
public void readImpl(ChannelBuffer buffer) {
bitSet = new BitSet();
int i = 0;
while (buffer.readable()) {
byte b = buffer.readByte();
for (int j = 128; j > 0; j >>= 1) {
bitSet.set(i++, (b & j) != 0);
}
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "BITFIELD [bitSet=" + bitSet + "]";
}
}

View File

@@ -0,0 +1,51 @@
package com.torrent4j.net.peerwire.messages;
import io.netty.buffer.ChannelBuffer;
import java.nio.ByteBuffer;
import com.torrent4j.net.peerwire.AbstractPeerWireMessage;
public class BlockMessage extends AbstractPeerWireMessage {
public static final int MESSAGE_ID = 0x07;
public int pieceIndex;
public int begin;
public ByteBuffer data;
public BlockMessage() {
super(MESSAGE_ID);
}
public BlockMessage(int pieceIndex, int begin, ByteBuffer data) {
super(MESSAGE_ID);
this.pieceIndex = pieceIndex;
this.begin = begin;
this.data = data;
}
@Override
public void writeImpl(ChannelBuffer buffer) {
buffer.writeInt(pieceIndex);
buffer.writeInt(begin);
buffer.writeBytes(data);
}
@Override
public void readImpl(ChannelBuffer buffer) {
pieceIndex = buffer.readInt();
begin = buffer.readInt();
data = buffer.readBytes(buffer.readableBytes()).toByteBuffer();
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "BLOCK [pieceIndex=" + pieceIndex + ", begin=" + begin
+ ", data=" + data + "]";
}
}

View File

@@ -0,0 +1,49 @@
package com.torrent4j.net.peerwire.messages;
import io.netty.buffer.ChannelBuffer;
import com.torrent4j.net.peerwire.AbstractPeerWireMessage;
public class CancelMessage extends AbstractPeerWireMessage {
public static final int MESSAGE_ID = 0x08;
public int pieceIndex;
public int begin;
public int length;
public CancelMessage() {
super(MESSAGE_ID);
}
public CancelMessage(int pieceIndex, int begin, int length) {
super(MESSAGE_ID);
this.pieceIndex = pieceIndex;
this.begin = begin;
this.length = length;
}
@Override
public void writeImpl(ChannelBuffer buffer) {
buffer.writeInt(pieceIndex);
buffer.writeInt(begin);
buffer.writeInt(length);
}
@Override
public void readImpl(ChannelBuffer buffer) {
pieceIndex = buffer.readInt();
begin = buffer.readInt();
length = buffer.readInt();
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "CANCEL [pieceIndex=" + pieceIndex + ", begin=" + begin
+ ", length=" + length + "]";
}
}

View File

@@ -0,0 +1,19 @@
package com.torrent4j.net.peerwire.messages;
import com.torrent4j.net.peerwire.AbstractPeerWireMessage;
public class ChokeMessage extends AbstractPeerWireMessage {
public static final int MESSAGE_ID = 0x00;
public ChokeMessage() {
super(MESSAGE_ID);
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "CHOKE []";
}
}

View File

@@ -0,0 +1,54 @@
package com.torrent4j.net.peerwire.messages;
import io.netty.buffer.ChannelBuffer;
import java.util.Arrays;
import com.torrent4j.net.peerwire.PeerWireMessage;
public class HandshakeMessage implements PeerWireMessage {
public int protocolStringLength = 19;
public String protocolString = "BitTorrent protocol";
public long reserved = 0;
public byte[] torrentHash;
public String peerID;
public HandshakeMessage() {
}
public HandshakeMessage(byte[] torrentHash, String peerID) {
this.torrentHash = torrentHash;
this.peerID = peerID;
}
@Override
public void write(ChannelBuffer buffer) {
buffer.writeByte(protocolStringLength);
buffer.writeBytes(protocolString.getBytes());
buffer.writeLong(reserved);
buffer.writeBytes(torrentHash, 0, 20);
buffer.writeBytes(peerID.getBytes(), 0, 20);
}
@Override
public void read(ChannelBuffer buffer) {
protocolStringLength = buffer.readByte();
protocolString = buffer.readBytes(protocolStringLength).toString();
reserved = buffer.readLong();
torrentHash = buffer.readBytes(20).array();
peerID = buffer.readBytes(20).toString();
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "HANDSHAKE [protocolStringLength=" + protocolStringLength
+ ", protocolString=" + protocolString + ", reserved="
+ reserved + ", torrentHash=" + Arrays.toString(torrentHash)
+ ", peerID=" + peerID + "]";
}
}

View File

@@ -0,0 +1,40 @@
package com.torrent4j.net.peerwire.messages;
import io.netty.buffer.ChannelBuffer;
import com.torrent4j.net.peerwire.AbstractPeerWireMessage;
public class HaveMessage extends AbstractPeerWireMessage {
public static final int MESSAGE_ID = 0x04;
public int pieceIndex;
public HaveMessage() {
super(MESSAGE_ID);
}
public HaveMessage(int pieceIndex) {
super(MESSAGE_ID);
this.pieceIndex = pieceIndex;
}
@Override
public void writeImpl(ChannelBuffer buffer) {
buffer.writeInt(pieceIndex);
}
@Override
public void readImpl(ChannelBuffer buffer) {
pieceIndex = buffer.readInt();
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "HAVE [pieceIndex=" + pieceIndex + "]";
}
}

View File

@@ -0,0 +1,21 @@
package com.torrent4j.net.peerwire.messages;
import com.torrent4j.net.peerwire.AbstractPeerWireMessage;
public class InterestedMessage extends AbstractPeerWireMessage {
public static final int MESSAGE_ID = 0x02;
public InterestedMessage() {
super(MESSAGE_ID);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "INTERESTED []";
}
}

View File

@@ -0,0 +1,28 @@
package com.torrent4j.net.peerwire.messages;
import io.netty.buffer.ChannelBuffer;
import com.torrent4j.net.peerwire.PeerWireMessage;
public class KeepAliveMessage implements PeerWireMessage {
public KeepAliveMessage() {
}
@Override
public void write(ChannelBuffer buffer) {
}
@Override
public void read(ChannelBuffer buffer) {
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "KEEP_ALIVE []";
}
}

View File

@@ -0,0 +1,21 @@
package com.torrent4j.net.peerwire.messages;
import com.torrent4j.net.peerwire.AbstractPeerWireMessage;
public class NotInterestedMessage extends AbstractPeerWireMessage {
public static final int MESSAGE_ID = 0x03;
public NotInterestedMessage() {
super(MESSAGE_ID);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "NOT_INTERESTED []";
}
}

View File

@@ -0,0 +1,40 @@
package com.torrent4j.net.peerwire.messages;
import io.netty.buffer.ChannelBuffer;
import com.torrent4j.net.peerwire.AbstractPeerWireMessage;
public class PortMessage extends AbstractPeerWireMessage {
public static final int MESSAGE_ID = 0x09;
public int listenPort;
public PortMessage() {
super(MESSAGE_ID);
}
public PortMessage(int listenPort) {
super(MESSAGE_ID);
this.listenPort = listenPort;
}
@Override
public void writeImpl(ChannelBuffer buffer) {
buffer.writeInt(listenPort);
}
@Override
public void readImpl(ChannelBuffer buffer) {
listenPort = buffer.readInt();
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "PORT [listenPort=" + listenPort + "]";
}
}

View File

@@ -0,0 +1,44 @@
package com.torrent4j.net.peerwire.messages;
import io.netty.buffer.ChannelBuffer;
import com.torrent4j.net.peerwire.AbstractPeerWireMessage;
public class RequestMessage extends AbstractPeerWireMessage {
public static final int MESSAGE_ID = 0x06;
public int pieceIndex;
public int begin;
public int length;
public RequestMessage() {
super(MESSAGE_ID);
}
public RequestMessage(int pieceIndex, int begin, int length) {
super(MESSAGE_ID);
this.pieceIndex = pieceIndex;
this.begin = begin;
this.length = length;
}
@Override
public void writeImpl(ChannelBuffer buffer) {
buffer.writeInt(pieceIndex);
buffer.writeInt(begin);
buffer.writeInt(length);
}
@Override
public void readImpl(ChannelBuffer buffer) {
pieceIndex = buffer.readInt();
begin = buffer.readInt();
length = buffer.readInt();
}
@Override
public String toString() {
return "REQUEST [pieceIndex=" + pieceIndex + ", begin=" + begin
+ ", length=" + length + "]";
}
}

View File

@@ -0,0 +1,21 @@
package com.torrent4j.net.peerwire.messages;
import com.torrent4j.net.peerwire.AbstractPeerWireMessage;
public class UnchokeMessage extends AbstractPeerWireMessage {
public static final int MESSAGE_ID = 0x01;
public UnchokeMessage() {
super(MESSAGE_ID);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "UNCHOKE []";
}
}

View File

@@ -0,0 +1,60 @@
package com.torrent4j.net.peerwire.traffic;
import io.netty.handler.traffic.ChannelTrafficShapingHandler;
import io.netty.handler.traffic.TrafficCounter;
import java.util.concurrent.Executor;
import com.torrent4j.model.TorrentPeer;
import com.torrent4j.model.TorrentPeerTrafficControl;
public class PeerTrafficShapingHandler extends ChannelTrafficShapingHandler {
private long writeLimit;
private long readLimit;
private TorrentPeer peer;
public PeerTrafficShapingHandler(Executor executor) {
super(executor, 0, 0);
}
private void reconfigure() {
if (peer == null)
return;
final TorrentPeerTrafficControl peerTraffic = peer.getTrafficControl();
long readLimit = peerTraffic.getDownloadSpeedLimit();
long writeLimit = peerTraffic.getUploadSpeedLimit();
if (readLimit != this.readLimit || writeLimit != this.writeLimit) {
this.writeLimit = writeLimit;
this.readLimit = readLimit;
configure(writeLimit, readLimit);
}
}
@Override
protected void doAccounting(TrafficCounter counter) {
if (peer == null)
return;
reconfigure();
peer.getTrafficControl().setCurrentDownloadSpeed(
counter.getLastReadThroughput());
peer.getTrafficControl().setCurrentUploadSpeed(
counter.getLastWriteThroughput());
}
/**
* @return the peer
*/
public TorrentPeer getPeer() {
return peer;
}
/**
* @param peer
* the peer to set
*/
public void setPeer(TorrentPeer peer) {
this.peer = peer;
reconfigure();
}
}

View File

@@ -0,0 +1,60 @@
package com.torrent4j.net.peerwire.traffic;
import io.netty.handler.traffic.GlobalTrafficShapingHandler;
import io.netty.handler.traffic.TrafficCounter;
import java.util.concurrent.Executor;
import com.torrent4j.model.Torrent;
import com.torrent4j.model.TorrentTrafficControl;
public class TorrentTrafficShapingHandler extends GlobalTrafficShapingHandler {
private Torrent torrent;
private long writeLimit;
private long readLimit;
public TorrentTrafficShapingHandler(Executor executor) {
super(executor, 0, 0);
}
private void reconfigure() {
if (torrent == null)
return;
final TorrentTrafficControl traffic = torrent.getTrafficControl();
long readLimit = traffic.getDownloadSpeedLimit();
long writeLimit = traffic.getUploadSpeedLimit();
if (readLimit != this.readLimit || writeLimit != this.writeLimit) {
this.writeLimit = writeLimit;
this.readLimit = readLimit;
configure(writeLimit, readLimit);
}
}
@Override
protected void doAccounting(TrafficCounter counter) {
if (torrent == null)
return;
reconfigure();
torrent.getTrafficControl().setCurrentDownloadSpeed(
counter.getLastReadThroughput());
torrent.getTrafficControl().setCurrentUploadSpeed(
counter.getLastWriteThroughput());
}
/**
* @return the torrent
*/
public Torrent getTorrent() {
return torrent;
}
/**
* @param torrent
* the torrent to set
*/
public void setTorrent(Torrent torrent) {
this.torrent = torrent;
reconfigure();
}
}

View File

@@ -0,0 +1,25 @@
package com.torrent4j.storage;
import java.io.IOException;
import java.nio.ByteBuffer;
import com.torrent4j.model.TorrentPiece;
import com.torrent4j.util.Hash;
/**
* Abstract {@link TorrentStorage} that implements universal that methods that
* very likely don't need to be override by implementations.
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public abstract class AbstractTorrentStorage implements TorrentStorage {
@Override
public Hash checksum(TorrentPiece piece) throws IOException {
final ByteBuffer buffer = read(piece.getTorrent(),
piece.getTorrentRange());
if (buffer == null)
return null;
return new Hash(piece.getHash().getType(), piece.getHash().getType()
.hash(buffer));
}
}

View File

@@ -0,0 +1,101 @@
package com.torrent4j.storage;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import com.torrent4j.model.Torrent;
import com.torrent4j.model.TorrentFile;
import com.torrent4j.util.Range;
/**
* Provides an abstraction to storage implementations that allows data to be
* stored into multiple files, respecting data stored into the original .torrent
* meta data file. {@link #write(Torrent, Range, ByteBuffer)} and
* {@link #read(Torrent, Range)} detect the file (or files) contained in the
* requested range and for each file that needs to be written or redden, a call
* to {@link #write(TorrentFile, Range, ByteBuffer)} and
* {@link #read(TorrentFile, Range, ByteBuffer)} is made. On all cases, the same
* buffer is sent for several calls.
* <p>
* If any error occur while reading any of the files, the whole reading process
* fails.
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*
*/
public abstract class FileAwareTorrentStorage extends AbstractTorrentStorage
implements TorrentStorage {
@Override
public final boolean write(Torrent torrent, Range dataRange, ByteBuffer data)
throws IOException {
final List<TorrentFile> files = torrent.getFiles(dataRange);
for (final TorrentFile file : files) {
final Range rangeOnFile = file.getTorrentRange().intersection(
dataRange);
final Range range = file.getFileRange().intersection(
Range.getRangeByLength(
rangeOnFile.getStart() - file.getOffset(),
rangeOnFile.getLength()));
data.limit((int) (data.position() + range.getLength()));
if (!this.write(file, range, data))
return false;
}
return true;
}
@Override
public final ByteBuffer read(Torrent torrent, Range dataRange)
throws IOException {
final List<TorrentFile> files = torrent.getFiles(dataRange);
final ByteBuffer data = ByteBuffer
.allocate((int) dataRange.getLength());
for (final TorrentFile file : files) {
final Range rangeOnFile = file.getTorrentRange().intersection(
dataRange);
final Range range = file.getFileRange().intersection(
Range.getRangeByLength(
rangeOnFile.getStart() - file.getOffset(),
rangeOnFile.getLength()));
data.limit((int) (data.position() + range.getLength()));
if (!this.read(file, range, data))
return null;
}
data.flip();
return data;
}
/**
* Writes <code>data</code> into the given <code>file</code> at the
* requested <code>range</code>.
*
* @param file
* the file to write the data to
* @param range
* the range to write the data to
* @param data
* the data to be written
* @return <code>true</code> if write was successful
* @throws IOException
* if any exception occur while writing data
*/
protected abstract boolean write(TorrentFile file, Range range,
ByteBuffer data) throws IOException;
/**
* Reads <code>data</code> from the given <code>file</code> in the requested
* <code>range</code>.
*
* @param file
* the file to read the data from
* @param range
* the range to read the data from
* @param data
* the data to be redden
* @return <code>true</code> if read was successful
* @throws IOException
* if any exception occur while reading data
*/
protected abstract boolean read(TorrentFile file, Range range,
ByteBuffer data) throws IOException;
}

View File

@@ -0,0 +1,66 @@
package com.torrent4j.storage;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import com.torrent4j.model.Torrent;
import com.torrent4j.util.Range;
/**
* An simple {@link TorrentStorage} implementation that stores data into huge
* in-memory direct buffers. Please note that though it provides really fast
* write and reading, it should not be used in real world implementations.
* Storing data in-memory consumes a lot of memory and may cause the JVM to be
* terminated by some OSes.
* <p>
* <b>This class is intended for benchmarking and testing!</b>
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public class InMemoryTorrentStorage extends AbstractTorrentStorage implements
TorrentStorage {
/**
* The map containing all buffers for all active torrents
*/
private final Map<Torrent, ByteBuffer> buffers = new HashMap<>();
@Override
public boolean write(Torrent torrent, Range range, ByteBuffer data)
throws IOException {
final ByteBuffer buffer = getBuffer(torrent);
synchronized (buffer) {
buffer.position((int) range.getStart());
buffer.put(data);
}
return true;
}
@Override
public ByteBuffer read(Torrent torrent, Range range) throws IOException {
final ByteBuffer store = getBuffer(torrent);
synchronized (store) {
return (ByteBuffer) ((ByteBuffer) store.position((int) range
.getStart())).slice().limit((int) range.getLength());
}
}
/**
* Tries to return an existing buffer for the <code>torrent</code>. If none
* is found, creates a new buffer and returns it.
*
* @param torrent
* the torrent instance
* @return an {@link ByteBuffer} allocated with enough size to store all the
* torrent data into it
*/
private ByteBuffer getBuffer(Torrent torrent) {
ByteBuffer buffer = buffers.get(torrent);
if (buffer == null) {
buffer = ByteBuffer.allocateDirect((int) torrent.getTorrentSize());
buffers.put(torrent, buffer);
}
return buffer;
}
}

View File

@@ -0,0 +1,98 @@
package com.torrent4j.storage;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.READ;
import static java.nio.file.StandardOpenOption.WRITE;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import com.torrent4j.model.TorrentFile;
import com.torrent4j.util.Range;
/**
* This {@link TorrentStorage} instance implements
* {@link FileAwareTorrentStorage}, and thus, stores data into separated files
* backed by an NIO.2 {@link SeekableByteChannel} available on Java 7. The
* implementation tries to respect as much as possible from the original file
* and folder structure, however, if any invalid name is found, beware that this
* implementation might override it.
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public class NIOTorrentStorage extends FileAwareTorrentStorage {
/**
* The place where data files are stored
*/
private final Path root;
/**
* Creates a new instance
*
* @param root
* the place where torrent data is stored
*/
public NIOTorrentStorage(Path root) {
this.root = root;
}
/**
* Creates a new instance which stores data into the current working
* directory
*/
public NIOTorrentStorage() {
this(Paths.get(".").toAbsolutePath());
}
@Override
protected synchronized boolean write(TorrentFile file, Range range,
ByteBuffer data) throws IOException {
try (final SeekableByteChannel channel = openChannel(file, CREATE,
WRITE)) {
if (channel == null)
return false;
channel.position(range.getStart());
return channel.write(data) > 0;
}
}
@Override
protected synchronized boolean read(TorrentFile file, Range range,
ByteBuffer data) throws IOException {
try (final SeekableByteChannel channel = openChannel(file, READ)) {
if (channel == null)
return false;
channel.position(range.getStart());
return channel.read(data) > 0;
}
}
/**
* Opens a new channel ready for writing or reading (defines by
* <code>modes</code>) data into the torrent <code>file</code>.
*
* @param file
* the torrent file
* @param modes
* the file open mode
* @return an {@link SeekableByteChannel}
* @throws IOException
* if any error is thrown by NIO.2
*/
private SeekableByteChannel openChannel(TorrentFile file,
OpenOption... modes) throws IOException {
final Path filePath = root.resolve(file.getFileName());
try {
SeekableByteChannel channel = Files.newByteChannel(filePath, modes);
return channel;
} catch (NoSuchFileException e) {
return null;
}
}
}

View File

@@ -0,0 +1,61 @@
package com.torrent4j.storage;
import java.io.IOException;
import java.nio.ByteBuffer;
import com.torrent4j.model.Torrent;
import com.torrent4j.model.TorrentPiece;
import com.torrent4j.util.Hash;
import com.torrent4j.util.Range;
/**
* Provides an easy interface to access torrent data in an linear way. All
* torrent data is accessed by an single addressing, starting from the first
* piece to that last one. Implementations can create abstractions to support
* files (see {@link FileAwareTorrentStorage}) or can store data into a huge
* in-memory buffer (see {@link InMemoryTorrentStorage}).
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public interface TorrentStorage {
/**
* Writes data into the underlying storage
*
* @param torrent
* the torrent
* @param torrentRange
* the range in which the data is located
* @param data
* the data itself
* @return <code>true</code> if the write was successful
* @throws IOException
* if any error occur while writing
*/
boolean write(Torrent torrent, Range torrentRange, ByteBuffer data)
throws IOException;
/**
* Reads data from the underlying storage
*
* @param torrent
* the torrent
* @param torrentRange
* the range in which the data should be read from
* @return an {@link ByteBuffer}
* @throws IOException
* if any error occur while reading
*/
ByteBuffer read(Torrent torrent, Range torrentRange) throws IOException;
/**
* Calculates the checksum of an given piece
*
* @param piece
* the piece
* @return the {@link Hash} for the requested piece
* @throws IOException
* if any error occur while reading data or calculating the
* checksum
*/
Hash checksum(TorrentPiece piece) throws IOException;
}

View File

@@ -0,0 +1,39 @@
package com.torrent4j.storage;
import java.io.IOException;
import java.nio.ByteBuffer;
import com.torrent4j.model.Torrent;
import com.torrent4j.model.TorrentPiece;
import com.torrent4j.util.Hash;
import com.torrent4j.util.Range;
/**
* This {@link TorrentStorage} implementation is similar to a UNIX
* <code>/dev/null</code> for writing and <code>/dev/zero</code> for reading.
* Data on this storage are never persisted and are discarded immediately. All
* reads return a {@link ByteBuffer} composed entirely from <code>0</code>.
* <p>
* Also, in order for checksums to match, when checksumming an piece, the piece
* hash itself is returned. This means that the checksums will <b>never</b>
* fail, no matter what, independent from data contents.
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public class VoidTorrentStorage implements TorrentStorage {
@Override
public boolean write(Torrent torrent, Range range, ByteBuffer data)
throws IOException {
return true;
}
@Override
public ByteBuffer read(Torrent torrent, Range range) throws IOException {
return ByteBuffer.allocate((int) range.getLength());
}
@Override
public Hash checksum(TorrentPiece piece) {
return piece.getHash();
}
}

View File

@@ -0,0 +1,51 @@
package com.torrent4j.strategy;
import com.torrent4j.model.Torrent;
import com.torrent4j.model.TorrentPeer;
import com.torrent4j.model.TorrentPiece;
import com.torrent4j.model.TorrentPieceBlock;
/**
* Determines the actions that should be taken regarding downloads
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public interface TorrentDownloadStrategy {
/**
* Notifies that an block has been received and stored in the storage.
*
* @param torrent
* the torrent
* @param block
* the block that has been downloaded
* @param peer
* the peer who uploaded the block
*/
void blockReceived(Torrent torrent, TorrentPieceBlock block,
TorrentPeer peer);
/**
* Notifies that an piece is complete and its checksum its corrects
*
* @param torrent
* the torrent
* @param piece
* the completed piece
* @param peer
* the peer who uploaded the last block to complete this piece
*/
void pieceComplete(Torrent torrent, TorrentPiece piece, TorrentPeer peer);
/**
* Notifies that an piece has completed but its checksum data is incorrect.
*
* @param torrent
* the torrent
* @param piece
* the piece which is complete but with wrong data
* @param peer
* the peer who uploaded the last block of the corrupted piece
*/
void pieceChecksumFailed(Torrent torrent, TorrentPiece piece,
TorrentPeer peer);
}

View File

@@ -0,0 +1,136 @@
package com.torrent4j.strategy;
import com.torrent4j.model.Torrent;
import com.torrent4j.model.TorrentPeer;
import com.torrent4j.model.TorrentPiece;
/**
* Determine actions that the client should take regarding peers and their
* requests.
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public interface TorrentPeerStrategy {
/**
* Performs an action with a peer that has been recently discovered.
* Discovery can happen through tracker announcements, DHT, peer exchange or
* by manually adding them to the torrent object.
*
* @param torrent
* the torrent
* @param peer
* the discovered peer
*/
void peerDiscovered(Torrent torrent, TorrentPeer peer);
/**
* Performs an action once handshaked with a peer.
*
* @param torrent
* the torrent
* @param peer
* the connected peer
*/
void peerConnected(Torrent torrent, TorrentPeer peer);
/**
* Notifies that the bitfield for the given peer has been received.
*
* @param torrent
* the torrent
* @param peer
* the peer which sent his bitfield
*/
void bitField(Torrent torrent, TorrentPeer peer);
/**
* Notifies that the peer has gotten a new piece
*
* @param torrent
* the torrent
* @param peer
* the peer which downloaded a new piece
* @param piece
* the piece which the peer has received
*/
void havePiece(Torrent torrent, TorrentPeer peer, TorrentPiece piece);
/**
* Notifies that the peer has unchoked the connection
*
* @param torrent
* the torrent
* @param peer
* the peer
*/
void unchoked(Torrent torrent, TorrentPeer peer);
/**
* Notifies that the peer has choked the connection
*
* @param torrent
* the torrent
* @param peer
* the peer
*/
void choked(Torrent torrent, TorrentPeer peer);
/**
* Notifies that the peer has interest in any of our pieces. Normally, when
* this message is received, the remote peer is asking for an unchoke.
*
* @param torrent
* the torrent
* @param peer
* the peer that is interested in our pieces
*/
void interested(Torrent torrent, TorrentPeer peer);
/**
* Notifies that the peer no more has interest in any of our pieces.
* Normally, it is safe to choke the peer after this.
*
* @param torrent
* the torrent
* @param peer
* the peer that has no more intested in any of the pieces
*/
void notInterested(Torrent torrent, TorrentPeer peer);
/**
* Notifies that the peer has been idle for some time. If required, an
* <code>keepalive</code> message should be sent. It is also possible to
* close the connection if the peer is no longer interesting.
*
* @param torrent
* the torrent
* @param peer
* the idle peer
* @param idleTime
* the peer's idle time in milliseconds
*/
void peerIdle(Torrent torrent, TorrentPeer peer, long idleTime);
/**
* Notifies the the peer has been disconnected. Note that this method is
* called either if the client closed the connection or if we requested an
* disconnection.
*
* @param torrent
* the torrent
* @param peer
* the disconnected peer
*/
void peerDisconnected(Torrent torrent, TorrentPeer peer);
/**
* Notifies that the peer has been removed from the swarm list. Although it
* is possible to re-add the peer to the list, this is not recommended!
*
* @param torrent
* the torrent
* @param peer
* the removed peer
*/
void peerRemoved(Torrent torrent, TorrentPeer peer);
}

View File

@@ -0,0 +1,23 @@
package com.torrent4j.strategy;
/**
* Provides an easy method to access all torrent strategies
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public interface TorrentStrategy {
/**
* @return the download strategy
*/
TorrentDownloadStrategy getDownloadStrategy();
/**
* @return the upload strategy
*/
TorrentUploadStrategy getUploadStrategy();
/**
* @return the peer strategy
*/
TorrentPeerStrategy getPeerStrategy();
}

View File

@@ -0,0 +1,38 @@
package com.torrent4j.strategy;
import com.torrent4j.model.Torrent;
import com.torrent4j.model.TorrentPeer;
import com.torrent4j.model.TorrentPieceBlock;
/**
* Determines the actions that should be taken regarding uploads
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public interface TorrentUploadStrategy {
/**
* Notifies that an given peer is requesting an block upload
*
* @param torrent
* the torrent
* @param block
* the block requested for upload
* @param peer
* the peer requesting the block
*/
void blockRequested(Torrent torrent, TorrentPieceBlock block,
TorrentPeer peer);
/**
* Notifies that the peer has cancelled its previous request for the block
*
* @param torrent
* the torrent
* @param block
* the previous requested block (now cancelled)
* @param peer
* the peer canceling the block request
*/
void blockRequestCancelled(Torrent torrent, TorrentPieceBlock block,
TorrentPeer peer);
}

View File

@@ -0,0 +1,22 @@
package com.torrent4j.strategy.standard;
import com.torrent4j.model.TorrentPeer;
import com.torrent4j.model.TorrentPiece;
/**
* Selects an suitable piece for download
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public interface PieceSelector {
/**
* Applies an algorithm to determine the most suitable piece for download
* from the peer. If no pieces are available or none are worth to download
* from this peer, <code>null</code> should be returned.
*
* @param peer
* the peer to download the piece from
* @return the piece to start downloading, if any
*/
TorrentPiece selectPiece(TorrentPeer peer);
}

View File

@@ -0,0 +1,22 @@
package com.torrent4j.strategy.standard;
import java.util.List;
import com.torrent4j.model.TorrentPeer;
import com.torrent4j.model.TorrentPiece;
/**
* Randomly selects an piece from the available peer pieces
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public class RandomPieceSelector implements PieceSelector {
@Override
public TorrentPiece selectPiece(TorrentPeer peer) {
final List<TorrentPiece> pieces = peer.getPieces().getMissingPieces(
peer.getTorrent().getCompletedPieces());
if (pieces.isEmpty())
return null;
return pieces.get((int) (Math.random() * pieces.size()));
}
}

View File

@@ -0,0 +1,63 @@
package com.torrent4j.strategy.standard;
import com.torrent4j.model.Torrent;
import com.torrent4j.model.TorrentPeer;
import com.torrent4j.model.TorrentPiece;
import com.torrent4j.model.TorrentPieceBlock;
import com.torrent4j.model.TorrentSwarm.SwarmBroadcastHandler;
import com.torrent4j.strategy.TorrentDownloadStrategy;
public class StandardTorrentDownloadStrategy implements TorrentDownloadStrategy {
private final StandardTorrentStrategy strategy;
public StandardTorrentDownloadStrategy(StandardTorrentStrategy strategy) {
this.strategy = strategy;
}
@Override
public void blockReceived(Torrent torrent, TorrentPieceBlock block,
TorrentPeer peer) {
TorrentPieceBlock next = block.getNextBlock();
if (next == null) {
final TorrentPiece piece = strategy.getPieceSelector().selectPiece(
peer);
if (piece == null) {
peer.withdrawInterest();
return;
}
next = piece.getFirstBlock();
}
peer.requestBlock(next);
}
@Override
public void pieceComplete(Torrent torrent,
final TorrentPiece completePiece, TorrentPeer peer) {
torrent.getSwarm().broadcast(new SwarmBroadcastHandler() {
@Override
public void broadcast(TorrentPeer peer) {
peer.have(completePiece);
}
@Override
public boolean exception(Exception e) {
return false;
}
});
final TorrentPiece piece = strategy.getPieceSelector()
.selectPiece(peer);
if (piece == null) {
peer.withdrawInterest();
return;
}
peer.requestBlock(piece.getFirstBlock());
}
@Override
public void pieceChecksumFailed(Torrent torrent, TorrentPiece piece,
TorrentPeer peer) {
System.out.println("Checksum has failed!");
strategy.banPeer(peer);
}
}

View File

@@ -0,0 +1,99 @@
package com.torrent4j.strategy.standard;
import com.torrent4j.model.Torrent;
import com.torrent4j.model.TorrentPeer;
import com.torrent4j.model.TorrentPiece;
import com.torrent4j.strategy.TorrentPeerStrategy;
public class StandardTorrentPeerStrategy implements TorrentPeerStrategy {
private final StandardTorrentStrategy strategy;
public StandardTorrentPeerStrategy(StandardTorrentStrategy strategy) {
this.strategy = strategy;
}
@Override
public void peerDiscovered(Torrent torrent, TorrentPeer peer) {
peer.connect();
peer.handshake();
}
@Override
public void peerConnected(Torrent torrent, TorrentPeer peer) {
peer.bitField();
}
@Override
public void bitField(Torrent torrent, TorrentPeer peer) {
if (peer.getPieces().isSeeder()
&& torrent.getCompletedPieces().isSeeder()) {
peer.disconnect();
return;
}
if (peer.getPieces().hasMissingPieces(torrent.getCompletedPieces())) {
peer.declareInterest();
return;
}
}
@Override
public void havePiece(Torrent torrent, TorrentPeer peer, TorrentPiece piece) {
if (torrent.getCompletedPieces().hasPiece(piece))
return;
if (peer.getState().hasDownloadRequestedBlock())
return;
if (peer.getState().isRemotellyChoked())
return;
peer.requestBlock(piece.getFirstBlock());
}
@Override
public void unchoked(Torrent torrent, TorrentPeer peer) {
final TorrentPiece piece = strategy.getPieceSelector()
.selectPiece(peer);
if (piece == null) {
peer.withdrawInterest();
return;
}
peer.requestBlock(piece.getFirstBlock());
}
@Override
public void choked(Torrent torrent, TorrentPeer peer) {
if (peer.getPieces().isSeeder()
&& torrent.getCompletedPieces().isSeeder()) {
peer.disconnect();
return;
}
}
@Override
public void interested(Torrent torrent, TorrentPeer peer) {
peer.unchoke();
}
@Override
public void notInterested(Torrent torrent, TorrentPeer peer) {
peer.choke();
if (peer.getPieces().isSeeder()
&& torrent.getCompletedPieces().isSeeder()) {
peer.disconnect();
return;
}
}
@Override
public void peerIdle(Torrent torrent, TorrentPeer peer, long idleTime) {
peer.keepAlive();
}
@Override
public void peerDisconnected(Torrent torrent, TorrentPeer peer) {
}
@Override
public void peerRemoved(Torrent torrent, TorrentPeer peer) {
if (peer.isConnected())
peer.disconnect();
}
}

View File

@@ -0,0 +1,61 @@
package com.torrent4j.strategy.standard;
import java.util.ArrayList;
import java.util.List;
import com.torrent4j.model.TorrentPeer;
import com.torrent4j.strategy.TorrentDownloadStrategy;
import com.torrent4j.strategy.TorrentPeerStrategy;
import com.torrent4j.strategy.TorrentStrategy;
import com.torrent4j.strategy.TorrentUploadStrategy;
public class StandardTorrentStrategy implements TorrentStrategy {
private final TorrentDownloadStrategy downloadStrategy = new StandardTorrentDownloadStrategy(
this);
private final TorrentUploadStrategy uploadStrategy = new StandardTorrentUploadStrategy(
this);
private final TorrentPeerStrategy peerStrategy = new StandardTorrentPeerStrategy(
this);
private final PieceSelector pieceSelector;
private final List<TorrentPeer> bannedPeers = new ArrayList<>();
public StandardTorrentStrategy(PieceSelector pieceSelector) {
this.pieceSelector = pieceSelector;
}
public StandardTorrentStrategy() {
this.pieceSelector = new RandomPieceSelector();
}
@Override
public TorrentDownloadStrategy getDownloadStrategy() {
return downloadStrategy;
}
@Override
public TorrentUploadStrategy getUploadStrategy() {
return uploadStrategy;
}
@Override
public TorrentPeerStrategy getPeerStrategy() {
return peerStrategy;
}
public PieceSelector getPieceSelector() {
return pieceSelector;
}
public void banPeer(TorrentPeer peer) {
bannedPeers.add(peer);
}
public void unbanPeer(TorrentPeer peer) {
bannedPeers.remove(peer);
}
public boolean isBanned(TorrentPeer peer) {
return bannedPeers.contains(peer);
}
}

View File

@@ -0,0 +1,37 @@
package com.torrent4j.strategy.standard;
import java.io.IOException;
import com.torrent4j.model.Torrent;
import com.torrent4j.model.TorrentPeer;
import com.torrent4j.model.TorrentPieceBlock;
import com.torrent4j.storage.TorrentStorage;
import com.torrent4j.strategy.TorrentUploadStrategy;
public class StandardTorrentUploadStrategy implements TorrentUploadStrategy {
@SuppressWarnings("unused")
private final StandardTorrentStrategy strategy;
public StandardTorrentUploadStrategy(StandardTorrentStrategy strategy) {
this.strategy = strategy;
}
@Override
public void blockRequested(Torrent torrent, TorrentPieceBlock block,
TorrentPeer peer) {
final TorrentStorage storage = torrent.getController().getStorage();
try {
peer.sendBlock(block,
storage.read(torrent, block.getTorrentRange()));
} catch (IOException e) {
peer.disconnect();
return;
}
}
@Override
public void blockRequestCancelled(Torrent torrent, TorrentPieceBlock block,
TorrentPeer peer) {
// we don't queue uploads, so we don't need to worry about this
}
}

View File

@@ -0,0 +1,102 @@
package com.torrent4j.util;
import java.util.BitSet;
public class BitField extends BitSet {
private static final long serialVersionUID = 1L;
private final int length;
public BitField(int length) {
super(length);
this.length = length;
}
private void ensureBitFieldLength(int index) {
if (index > length)
throw new ArrayIndexOutOfBoundsException(index + " is bigger than "
+ length);
}
@Override
public int size() {
return length;
}
@Override
public void flip(int bitIndex) {
ensureBitFieldLength(bitIndex);
super.flip(bitIndex);
}
@Override
public void flip(int fromIndex, int toIndex) {
ensureBitFieldLength(fromIndex);
ensureBitFieldLength(toIndex);
super.flip(fromIndex, toIndex);
}
@Override
public void set(int bitIndex) {
ensureBitFieldLength(bitIndex);
super.set(bitIndex);
}
@Override
public void set(int bitIndex, boolean value) {
ensureBitFieldLength(bitIndex);
super.set(bitIndex, value);
}
@Override
public void set(int fromIndex, int toIndex) {
ensureBitFieldLength(fromIndex);
ensureBitFieldLength(toIndex);
super.set(fromIndex, toIndex);
}
@Override
public void set(int fromIndex, int toIndex, boolean value) {
ensureBitFieldLength(fromIndex);
ensureBitFieldLength(toIndex);
super.set(fromIndex, toIndex, value);
}
@Override
public void clear(int bitIndex) {
ensureBitFieldLength(bitIndex);
super.clear(bitIndex);
}
@Override
public void clear(int fromIndex, int toIndex) {
ensureBitFieldLength(fromIndex);
ensureBitFieldLength(toIndex);
super.clear(fromIndex, toIndex);
}
@Override
public boolean get(int bitIndex) {
ensureBitFieldLength(bitIndex);
return super.get(bitIndex);
}
@Override
public BitSet get(int fromIndex, int toIndex) {
ensureBitFieldLength(fromIndex);
ensureBitFieldLength(toIndex);
return super.get(fromIndex, toIndex);
}
@Override
public int nextSetBit(int fromIndex) {
ensureBitFieldLength(fromIndex);
return super.nextSetBit(fromIndex);
}
@Override
public int nextClearBit(int fromIndex) {
ensureBitFieldLength(fromIndex);
return super.nextClearBit(fromIndex);
}
}

View File

@@ -0,0 +1,55 @@
package com.torrent4j.util;
import java.util.Arrays;
public class Hash {
private final HashType type;
private final byte[] hash;
public Hash(HashType type, byte[] hash) {
this.type = type;
this.hash = hash;
}
public HashType getType() {
return type;
}
public byte[] getHash() {
return hash;
}
public String getString() {
return type.toString(hash);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(hash);
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Hash))
return false;
Hash other = (Hash) obj;
if (!Arrays.equals(hash, other.hash))
return false;
if (type != other.type)
return false;
return true;
}
@Override
public String toString() {
return type.name() + ":" + getString();
}
}

View File

@@ -0,0 +1,105 @@
package com.torrent4j.util;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
public enum HashType {
SHA1(new SimpleHashDelegate("SHA1")),
SHA256(new SimpleHashDelegate("SHA-256")),
SHA384(new SimpleHashDelegate("SHA-384")),
SHA512(new SimpleHashDelegate("SHA-512")),
MD5(new SimpleHashDelegate("MD5")),
MD2(new SimpleHashDelegate("MD2"));
private final HashDelegate delegate;
HashType(HashDelegate delegate) {
this.delegate = delegate;
}
public byte[] hash(byte[] data) {
return delegate.hash(data);
}
public byte[] hash(String string) {
return hash(string.getBytes());
}
public byte[] hash(ByteBuffer buffer) {
final byte[] data = new byte[buffer.remaining()];
buffer.get(data);
return hash(data);
}
public String hashAsString(byte[] data) {
return toString(hash(data));
}
public String hashAsString(String string) {
return hashAsString(string.getBytes());
}
public String hashAsString(ByteBuffer buffer) {
final byte[] data = new byte[buffer.remaining()];
buffer.get(data);
return hashAsString(data);
}
public String toString(byte[] hash) {
return delegate.toString(hash);
}
public byte[] fromString(String hash) {
return delegate.fromString(hash);
}
private interface HashDelegate {
byte[] hash(byte[] data);
String toString(byte[] hash);
byte[] fromString(String hash);
}
private static class SimpleHashDelegate implements HashDelegate {
private final String name;
protected SimpleHashDelegate(String name) {
this.name = name;
}
@Override
public byte[] hash(byte[] data) {
try {
final MessageDigest hasher = MessageDigest.getInstance(name);
hasher.update(data);
return hasher.digest();
} catch (NoSuchAlgorithmException e) {
return null;
}
}
@Override
public String toString(byte[] hash) {
return new String(Hex.encodeHex(hash));
}
@Override
public byte[] fromString(String hash) {
try {
return Hex.decodeHex(hash.toCharArray());
} catch (DecoderException e) {
return null;
}
}
}
}

View File

@@ -0,0 +1,11 @@
package com.torrent4j.util;
public class PeerIDGenerator {
public static String generateRandomPeerID() {
byte[] id = new byte[20];
for (int i = 0; i < id.length; i++) {
id[i] = (byte) (Math.random() * Byte.MAX_VALUE);
}
return new String(id);
}
}

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 com.torrent4j.util;
import java.io.Serializable;
/**
* This class represents an interval.
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
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,325 @@
package com.torrent4j.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 BDecoder extends InputStream {
private static final int MAX_STRING_LENGTH = 1024 * 1024;
private final PushbackInputStream in;
public BDecoder(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 {
BDecoder in = new BDecoder(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());
}
}
public static final Object decode(String encoded) throws IOException {
try(final ByteArrayInputStream in = new ByteArrayInputStream(
encoded.getBytes())) {
return new BDecoder(in).readElement();
}
}
}

View File

@@ -0,0 +1,21 @@
package com.torrent4j.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,180 @@
package com.torrent4j.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 BEncoder 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 BEncoder(OutputStream out) {
if (out == null)
throw new InvalidParameterException("output stream 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!");
try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) {
try (final BEncoder bout = new BEncoder(out)) {
bout.writeElement(obj);
}
return out.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 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);
}
public static final String encode(Object object) throws IOException {
final ByteArrayOutputStream out = new ByteArrayOutputStream(2048);
new BEncoder(out).writeElement(object);
return new String(out.toByteArray());
}
public static final byte[] encodeToByteArray(Object object)
throws IOException {
try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) {
new BEncoder(out).writeElement(object);
return out.toByteArray();
}
}
}

View File

@@ -0,0 +1,21 @@
package com.torrent4j.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 com.torrent4j.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 com.torrent4j.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 com.torrent4j.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);
}
}