├── .gitignore ├── .travis.yml ├── COPYING ├── INSTALL ├── README.md ├── TODO ├── build.gradle ├── codequality ├── HEADER └── checkstyle.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle ├── src └── main │ └── ghpages │ └── index.html ├── ttorrent-client ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── turn │ │ └── ttorrent │ │ └── client │ │ ├── Client.java │ │ ├── ClientEnvironment.java │ │ ├── ClientListener.java │ │ ├── ClientListenerAdapter.java │ │ ├── ErrorListener.java │ │ ├── PeerPieceProvider.java │ │ ├── PieceValidator.java │ │ ├── SwarmHandler.java │ │ ├── TorrentHandler.java │ │ ├── TorrentRegistry.java │ │ ├── TrackerHandler.java │ │ ├── io │ │ ├── PeerClient.java │ │ ├── PeerClientHandshakeHandler.java │ │ ├── PeerEndpoint.java │ │ ├── PeerExtendedMessage.java │ │ ├── PeerFrameDecoder.java │ │ ├── PeerFrameEncoder.java │ │ ├── PeerHandshakeHandler.java │ │ ├── PeerHandshakeMessage.java │ │ ├── PeerMessage.java │ │ ├── PeerMessageCodec.java │ │ ├── PeerMessageHandler.java │ │ ├── PeerMessageTrafficShapingHandler.java │ │ ├── PeerServer.java │ │ └── PeerServerHandshakeHandler.java │ │ ├── main │ │ ├── ClientMain.java │ │ └── TorrentMain.java │ │ ├── peer │ │ ├── Instrumentation.java │ │ ├── PeerActivityListener.java │ │ ├── PeerConnectionListener.java │ │ ├── PeerExistenceListener.java │ │ ├── PeerHandler.java │ │ ├── PeerMessageListener.java │ │ ├── PieceHandler.java │ │ ├── Rate.java │ │ └── RateComparator.java │ │ └── storage │ │ ├── ByteRangeStorage.java │ │ ├── ByteStorage.java │ │ ├── FileCollectionStorage.java │ │ ├── FileStorage.java │ │ └── RawFileStorage.java │ └── test │ └── java │ └── com │ └── turn │ └── ttorrent │ ├── client │ ├── AbstractReplicationTest.java │ ├── PeerExchangeTest.java │ ├── ReplicationCompletionListener.java │ ├── ReplicationHugeSwarmTest.java │ ├── ReplicationMultipleEarlyTest.java │ ├── ReplicationMultipleLateTest.java │ ├── ReplicationSingleEarlyTest.java │ ├── ReplicationSingleLateTest.java │ ├── ReplicationTimeoutTest.java │ ├── TorrentHandlerTest.java │ ├── TrackerHandlerTest.java │ ├── UbuntuImageDownloadTest.java │ ├── io │ │ ├── PeerFrameDecoderTest.java │ │ └── PeerMessageTest.java │ ├── peer │ │ ├── PeerHandlerTest.java │ │ └── PieceHandlerTest.java │ └── storage │ │ ├── FileCollectionStorageTest.java │ │ └── FileStorageTest.java │ └── test │ ├── LoggingInvocationHandler.java │ ├── TestPeerExistenceListener.java │ ├── TestPeerPieceProvider.java │ └── TorrentClientTestUtils.java ├── ttorrent-protocol ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── turn │ │ └── ttorrent │ │ └── protocol │ │ ├── PeerIdentityProvider.java │ │ ├── TorrentUtils.java │ │ ├── bcodec │ │ ├── AbstractBDecoder.java │ │ ├── AbstractBEncoder.java │ │ ├── BEUtils.java │ │ ├── BEValue.java │ │ ├── BytesBDecoder.java │ │ ├── BytesBEncoder.java │ │ ├── InvalidBEncodingException.java │ │ ├── NettyBDecoder.java │ │ ├── NettyBEncoder.java │ │ ├── StreamBDecoder.java │ │ └── StreamBEncoder.java │ │ ├── torrent │ │ ├── Torrent.java │ │ └── TorrentCreator.java │ │ └── tracker │ │ ├── InetAddressComparator.java │ │ ├── InetSocketAddressComparator.java │ │ ├── Peer.java │ │ ├── TrackerMessage.java │ │ ├── http │ │ ├── HTTPAnnounceRequestMessage.java │ │ ├── HTTPAnnounceResponseMessage.java │ │ ├── HTTPTrackerErrorMessage.java │ │ └── HTTPTrackerMessage.java │ │ └── udp │ │ ├── UDPAnnounceRequestMessage.java │ │ ├── UDPAnnounceResponseMessage.java │ │ ├── UDPConnectRequestMessage.java │ │ ├── UDPConnectResponseMessage.java │ │ ├── UDPTrackerErrorMessage.java │ │ └── UDPTrackerMessage.java │ └── test │ └── java │ └── com │ └── turn │ └── ttorrent │ └── protocol │ ├── test │ ├── PatternByteSource.java │ ├── PatternInputStream.java │ ├── RandomInputStream.java │ ├── TestPeerIdentityProvider.java │ └── TorrentTestUtils.java │ ├── torrent │ └── TorrentCreatorTest.java │ └── tracker │ ├── InetAddressComparatorTest.java │ └── http │ ├── HTTPAnnounceRequestMessageTest.java │ └── HTTPAnnounceResponseMessageTest.java ├── ttorrent-tracker-client └── src │ ├── main │ └── java │ │ └── com │ │ └── turn │ │ └── ttorrent │ │ └── tracker │ │ └── client │ │ ├── AnnounceException.java │ │ ├── AnnounceResponseListener.java │ │ ├── HTTPTrackerClient.java │ │ ├── PeerAddressProvider.java │ │ ├── TorrentMetadataProvider.java │ │ ├── TrackerClient.java │ │ └── UDPTrackerClient.removed │ └── test │ └── java │ └── com │ └── turn │ └── ttorrent │ └── tracker │ └── client │ ├── HTTPTrackerClientTest.java │ └── test │ ├── TestPeerAddressProvider.java │ └── TestTorrentMetadataProvider.java ├── ttorrent-tracker-servlet └── src │ └── main │ └── java │ └── com │ └── turn │ └── ttorrent │ └── tracker │ └── servlet │ ├── ServletTrackerService.java │ └── TrackerServlet.java ├── ttorrent-tracker-simple ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── turn │ │ └── ttorrent │ │ └── tracker │ │ └── simple │ │ ├── SimpleTracker.java │ │ ├── SimpleTrackerMain.java │ │ └── SimpleTrackerService.java │ └── test │ └── java │ └── com │ └── turn │ └── ttorrent │ └── tracker │ └── simple │ └── SimpleTrackerTest.java ├── ttorrent-tracker-spring ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── turn │ └── ttorrent │ └── tracker │ └── spring │ └── TrackerController.java └── ttorrent-tracker ├── build.gradle └── src └── main └── java └── com └── turn └── ttorrent └── tracker ├── TrackedPeer.java ├── TrackedPeerState.java ├── TrackedTorrent.java ├── TrackedTorrentRegistry.java ├── TrackerMetrics.java ├── TrackerService.java └── TrackerUtils.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Git ignore patterns 2 | # Author: Maxime Petazzoni 3 | # Author: Shevek 4 | 5 | # Ignore build output 6 | build 7 | 8 | # Ignore any eventual Eclipse project files, these don't belong in the 9 | # repository. 10 | /.classpath 11 | /.project 12 | /.settings 13 | 14 | # Ignore common editor swap files 15 | *.swp 16 | *.bak 17 | *~ 18 | *~ 19 | 20 | /.gradle 21 | /.nb-gradle 22 | 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | Howto build and use the BitTorrent library 2 | ========================================== 3 | 4 | Dependencies 5 | ------------ 6 | 7 | This Java implementation of the BitTorrent protocol implements a BitTorrent 8 | tracker (an HTTP service), and a BitTorrent client. The only dependencies of 9 | the BitTorrent library are: 10 | 11 | * the log4j library 12 | * the slf4j logging library 13 | * the SimpleHTTPFramework 14 | 15 | These libraries are provided in the lib/ directory, and are automatically 16 | included in the JAR file created by the build process. 17 | 18 | 19 | Building the distribution JAR 20 | ----------------------------- 21 | 22 | Simply execute the following command: 23 | 24 | $ mvn package 25 | 26 | To build the library's JAR file (in the target/ directory). You can then import 27 | this JAR file into your Java project and start using the Java BitTorrent 28 | library. 29 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Code required: 2 | 3 | 1) Protocol extension to specify maximum block size - we want 1Mb. 4 | 2) Keepalives. 5 | 6 | Tests required: 7 | 8 | Connect to peers which never offer anything. Make sure rarestPiece is null. 9 | 10 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | // Executed in context of buildscript 3 | repositories { 4 | // mavenLocal() 5 | mavenCentral() 6 | // jcenter() 7 | // maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } 8 | gradlePluginPortal() 9 | } 10 | 11 | dependencies { 12 | classpath 'org.anarres.gradle:gradle-stdproject-plugin:1.0.10' 13 | } 14 | } 15 | 16 | apply plugin: 'org.anarres.stdproject' 17 | stdproject { 18 | javadocLink "http://netty.io/4.0/api/" 19 | 20 | javadocGroup "BitTorrent Protocols", "com.turn.ttorrent.protocol*" 21 | javadocGroup "BitTorrent Client Implementation", "com.turn.ttorrent.client*" 22 | javadocGroup "BitTorrent Tracker Implementations", "com.turn.ttorrent.tracker*" 23 | } 24 | 25 | subprojects { 26 | group = "org.anarres.ttorrent" 27 | 28 | apply plugin: 'org.anarres.stdmodule' 29 | stdmodule { 30 | description "An embeddable high performance bittorrent library, client and tracker." 31 | author id: 'shevek', name: 'Shevek', email: 'github@anarres.org' 32 | license 'Apache-2.0' 33 | } 34 | 35 | apply plugin: 'eclipse' 36 | eclipse { 37 | classpath { 38 | downloadSources = true 39 | } 40 | jdt { 41 | sourceCompatibility = 1.7 42 | targetCompatibility = 1.7 43 | } 44 | } 45 | 46 | configurations { 47 | compile { 48 | exclude group: 'commons-logging', module: 'commons-logging' 49 | // exclude group: 'log4j', module: 'log4j' 50 | // exclude group: 'org.slf4j', module: 'slf4j-log4j12' 51 | } 52 | } 53 | 54 | dependencies { 55 | testCompile 'org.easymock:easymock:3.2' 56 | 57 | testRuntime 'org.slf4j:jcl-over-slf4j:1.7.12' 58 | } 59 | 60 | sourceCompatibility = 1.7 61 | } 62 | 63 | project(':ttorrent-protocol') { 64 | dependencies { 65 | compile 'com.google.code.findbugs:annotations:2.0.3' 66 | compile 'org.slf4j:slf4j-api:1.7.7' 67 | compile 'com.google.guava:guava:18.0' 68 | compile 'io.dropwizard.metrics:metrics-core:3.1.0' 69 | compile 'io.netty:netty-all:4.0.23.Final' 70 | 71 | testCompile project(':ttorrent-tracker-simple') 72 | testCompile 'commons-io:commons-io:2.4' 73 | } 74 | } 75 | 76 | project(':ttorrent-tracker-client') { 77 | dependencies { 78 | compile project(':ttorrent-protocol') 79 | compile 'org.apache.httpcomponents:httpasyncclient:4.0.2' 80 | 81 | testCompile project(':ttorrent-tracker-simple') 82 | } 83 | } 84 | 85 | project(':ttorrent-tracker') { 86 | dependencies { 87 | compile project(':ttorrent-protocol') 88 | 89 | testCompile project(':ttorrent-tracker-simple') 90 | } 91 | } 92 | 93 | project(':ttorrent-tracker-simple') { 94 | apply plugin: 'application' 95 | 96 | mainClassName = 'com.turn.ttorrent.tracker.simple.SimpleTrackerMain' 97 | 98 | dependencies { 99 | compile project(':ttorrent-tracker') 100 | 101 | compile 'org.simpleframework:simple:5.1.6' 102 | compile 'net.sf.jopt-simple:jopt-simple:4.7' 103 | 104 | testCompile project(':ttorrent-tracker-client') 105 | } 106 | } 107 | 108 | project(':ttorrent-tracker-servlet') { 109 | dependencies { 110 | compile project(':ttorrent-tracker') 111 | 112 | compile 'javax.servlet:javax.servlet-api:3.1.0' 113 | } 114 | } 115 | 116 | project(':ttorrent-tracker-spring') { 117 | dependencies { 118 | compile project(':ttorrent-tracker-servlet') 119 | 120 | def springVersion = "4.1.1.RELEASE" 121 | compile "org.springframework:spring-webmvc:$springVersion" 122 | 123 | // compile 'javax.servlet:servlet-api:2.5' 124 | compile 'javax.servlet:javax.servlet-api:3.1.0' 125 | } 126 | } 127 | 128 | project(':ttorrent-client') { 129 | apply plugin: 'application' 130 | 131 | mainClassName = 'com.turn.ttorrent.client.main.ClientMain' 132 | 133 | dependencies { 134 | compile project(':ttorrent-tracker-client') 135 | 136 | compile 'commons-io:commons-io:2.4' 137 | compile 'net.sf.jopt-simple:jopt-simple:4.7' 138 | 139 | testCompile project(':ttorrent-tracker-simple') 140 | testCompile project(':ttorrent-protocol').sourceSets.test.output 141 | testCompile project(':ttorrent-tracker-client').sourceSets.test.output 142 | } 143 | 144 | test { 145 | maxHeapSize = "4096m" 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /codequality/HEADER: -------------------------------------------------------------------------------- 1 | Copyright 2011-2012 Turn, Inc 2 | Copyright ${year} Shevek 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version=1.8.0-SNAPSHOT 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shevek/ttorrent/f8d80c6df4a3bc4532a7b1d363636b6dd4137f03/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name='ttorrent' 2 | include \ 3 | 'ttorrent-protocol', 4 | 'ttorrent-tracker', 5 | 'ttorrent-tracker-simple', 6 | 'ttorrent-tracker-servlet', 7 | 'ttorrent-tracker-spring', 8 | 'ttorrent-tracker-client', 9 | 'ttorrent-client' 10 | -------------------------------------------------------------------------------- /src/main/ghpages/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Javadoc 4 | Coverage 5 | 6 | 7 | -------------------------------------------------------------------------------- /ttorrent-client/build.gradle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shevek/ttorrent/f8d80c6df4a3bc4532a7b1d363636b6dd4137f03/ttorrent-client/build.gradle -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/ClientListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client; 6 | 7 | import javax.annotation.Nonnull; 8 | 9 | /** 10 | * 11 | * @author shevek 12 | */ 13 | public interface ClientListener { 14 | 15 | public void clientStateChanged(@Nonnull Client client, @Nonnull Client.State state); 16 | 17 | public void torrentStateChanged(@Nonnull Client client, @Nonnull TorrentHandler torrent, @Nonnull TorrentHandler.State state); 18 | } 19 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/ClientListenerAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client; 6 | 7 | /** 8 | * 9 | * @author shevek 10 | */ 11 | public class ClientListenerAdapter implements ClientListener { 12 | 13 | @Override 14 | public void clientStateChanged(Client client, Client.State state) { 15 | } 16 | 17 | @Override 18 | public void torrentStateChanged(Client client, TorrentHandler torrent, TorrentHandler.State state) { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/ErrorListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client; 6 | 7 | import javax.annotation.Nonnull; 8 | 9 | /** 10 | * 11 | * @author shevek 12 | */ 13 | public interface ErrorListener { 14 | 15 | // TODO: Use this for all protocol violations, stats, etc. 16 | public void error(@Nonnull String message); 17 | } 18 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/PeerPieceProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client; 6 | 7 | import com.turn.ttorrent.client.peer.PieceHandler; 8 | import com.turn.ttorrent.client.peer.PeerHandler; 9 | import com.turn.ttorrent.client.peer.Instrumentation; 10 | import com.turn.ttorrent.protocol.PeerIdentityProvider; 11 | import java.io.IOException; 12 | import java.nio.ByteBuffer; 13 | import java.util.BitSet; 14 | import javax.annotation.CheckForNull; 15 | import javax.annotation.Nonnegative; 16 | import javax.annotation.Nonnull; 17 | 18 | /** 19 | * 20 | * @author shevek 21 | */ 22 | public interface PeerPieceProvider extends PeerIdentityProvider { 23 | 24 | @Nonnull 25 | public Instrumentation getInstrumentation(); 26 | 27 | @Nonnegative 28 | public int getPieceCount(); 29 | 30 | @Nonnull 31 | public int getPieceLength(@Nonnegative int index); 32 | 33 | @Nonnegative 34 | public int getBlockLength(); 35 | 36 | @Nonnull 37 | public BitSet getCompletedPieces(); 38 | 39 | public boolean isCompletedPiece(@Nonnegative int index); 40 | 41 | /** Zero-copy. */ 42 | public void andNotCompletedPieces(@Nonnull BitSet out); 43 | 44 | @CheckForNull 45 | public Iterable getNextPieceHandler(@Nonnull PeerHandler peer, @Nonnull BitSet interesting); 46 | 47 | public int addRequestTimeout(@Nonnull Iterable requests); 48 | 49 | /** 50 | * Read a piece block from the underlying byte storage. 51 | * 52 | *

53 | * This is the public method for reading this piece's data, and it will 54 | * only succeed if the piece is complete and valid on disk, thus ensuring 55 | * any data that comes out of this function is valid piece data we can send 56 | * to other peers. 57 | *

58 | * 59 | * @param offset Offset inside this piece where to start reading. 60 | * @throws IllegalArgumentException If offset + length goes over 61 | * the piece boundary. 62 | * @throws IllegalStateException If the piece is not valid when attempting 63 | * to read it. 64 | * @throws IOException If the read can't be completed (I/O error, or EOF 65 | * reached, which can happen if the piece is not complete). 66 | */ 67 | public void readBlock(@Nonnull ByteBuffer block, @Nonnegative int piece, @Nonnegative int offset) throws IOException; 68 | 69 | /** 70 | * Consumes the block. 71 | */ 72 | public void writeBlock(@Nonnull ByteBuffer block, @Nonnegative int piece, @Nonnegative int offset) throws IOException; 73 | 74 | public boolean validateBlock(@Nonnegative ByteBuffer block, @Nonnegative int piece) throws IOException; 75 | } 76 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/PieceValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client; 6 | 7 | import com.turn.ttorrent.protocol.torrent.Torrent; 8 | import java.nio.ByteBuffer; 9 | import java.util.BitSet; 10 | import java.util.concurrent.CountDownLatch; 11 | import javax.annotation.Nonnegative; 12 | import javax.annotation.Nonnull; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | /** 17 | * A {@link Runnable} to call the piece validation function. 18 | * 19 | *

20 | * This {@link Runnable} implementation allows for the calling of the piece 21 | * validation function in a controlled context like a thread or an 22 | * executor. 23 | *

24 | * 25 | * @author mpetazzoni 26 | */ 27 | /* pp */ class PieceValidator implements Runnable { 28 | 29 | private static final Logger LOG = LoggerFactory.getLogger(PieceValidator.class); 30 | private final Torrent torrent; 31 | private final int piece; 32 | private final ByteBuffer data; 33 | private final BitSet valid; 34 | private final CountDownLatch latch; 35 | 36 | public PieceValidator(@Nonnull Torrent torrent, @Nonnegative int piece, @Nonnull ByteBuffer data, @Nonnull BitSet valid, @Nonnull CountDownLatch latch) { 37 | this.torrent = torrent; 38 | this.piece = piece; 39 | this.data = data; 40 | this.valid = valid; 41 | this.latch = latch; 42 | } 43 | 44 | @Override 45 | public void run() { 46 | try { 47 | if (torrent.isPieceValid(piece, data)) { 48 | // TODO: Synchronization on this lock may slow this down a lot. 49 | synchronized (valid) { 50 | valid.set(piece); 51 | } 52 | } 53 | } catch (Exception e) { 54 | LOG.error("Failed validation of " + this, e); 55 | } finally { 56 | latch.countDown(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/TorrentRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client; 6 | 7 | import com.turn.ttorrent.protocol.PeerIdentityProvider; 8 | import javax.annotation.CheckForNull; 9 | import javax.annotation.Nonnull; 10 | 11 | /** 12 | * 13 | * @author shevek 14 | */ 15 | public interface TorrentRegistry extends PeerIdentityProvider { 16 | 17 | @CheckForNull 18 | public TorrentHandler getTorrent(@Nonnull byte[] infoHash); 19 | } 20 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/io/PeerClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 shevek. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.client.io; 17 | 18 | import com.turn.ttorrent.client.ClientEnvironment; 19 | import com.turn.ttorrent.client.peer.PeerConnectionListener; 20 | import io.netty.bootstrap.Bootstrap; 21 | import io.netty.buffer.PooledByteBufAllocator; 22 | import io.netty.channel.Channel; 23 | import io.netty.channel.ChannelFuture; 24 | import io.netty.channel.ChannelFutureListener; 25 | import io.netty.channel.ChannelOption; 26 | import java.net.SocketAddress; 27 | import java.util.concurrent.TimeUnit; 28 | import javax.annotation.Nonnull; 29 | import org.slf4j.Logger; 30 | import org.slf4j.LoggerFactory; 31 | 32 | /** 33 | * Client for making outgoing connections to other peers. 34 | * 35 | *

36 | * A PeerClient is a singleton per {@link Client}, and can be shared across 37 | * torrents and swarms. 38 | *

39 | * 40 | * @author shevek 41 | */ 42 | public class PeerClient extends PeerEndpoint { 43 | 44 | private static final Logger LOG = LoggerFactory.getLogger(PeerClient.class); 45 | public static final int CLIENT_KEEP_ALIVE_MINUTES = PeerServer.CLIENT_KEEP_ALIVE_MINUTES; 46 | private final ClientEnvironment environment; 47 | private Bootstrap bootstrap; 48 | private final Object lock = new Object(); 49 | 50 | public PeerClient(ClientEnvironment environment) { 51 | this.environment = environment; 52 | } 53 | 54 | public void start() throws Exception { 55 | bootstrap = new Bootstrap(); 56 | bootstrap.group(environment.getEventService()); 57 | bootstrap.channel(environment.getEventLoopType().getClientChannelType()); 58 | // SocketAddress address = environment.getLocalPeerListenAddress(); 59 | // if (address != null) bootstrap.localAddress(address); 60 | bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); 61 | bootstrap.option(ChannelOption.SO_KEEPALIVE, true); 62 | bootstrap.option(ChannelOption.SO_REUSEADDR, true); 63 | bootstrap.option(ChannelOption.TCP_NODELAY, true); 64 | // bootstrap.option(ChannelOption.SO_TIMEOUT, (int) TimeUnit.MINUTES.toMillis(CLIENT_KEEP_ALIVE_MINUTES)); 65 | bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) TimeUnit.SECONDS.toMillis(10)); 66 | // TODO: Derive from PieceHandler.DEFAULT_BLOCK_SIZE 67 | // bootstrap.option(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, 1024 * 1024); 68 | // bootstrap.option(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, 8 * 1024); 69 | } 70 | 71 | public void stop() throws InterruptedException { 72 | bootstrap = null; 73 | } 74 | 75 | @Nonnull 76 | public ChannelFuture connect( 77 | @Nonnull final PeerConnectionListener listener, 78 | @Nonnull final byte[] infoHash, 79 | @Nonnull final SocketAddress remoteAddress) { 80 | final ChannelFuture future; 81 | synchronized (lock) { 82 | // connect -> initAndRegister grabs this, so we can safely synchronize here. 83 | bootstrap.handler(new PeerClientHandshakeHandler(listener, infoHash, listener.getLocalPeerId())); 84 | future = bootstrap.connect(remoteAddress); 85 | } 86 | future.addListener(new ChannelFutureListener() { 87 | @Override 88 | public void operationComplete(ChannelFuture future) throws Exception { 89 | try { 90 | LOG.trace("Succeeded: {}", future.get()); 91 | } catch (Exception e) { 92 | // LOG.error("Connection to " + remoteAddress + " failed.", e); 93 | listener.handlePeerConnectionFailed(remoteAddress, e); 94 | Channel channel = future.channel(); 95 | if (channel.isOpen()) 96 | channel.close(); // .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); 97 | } 98 | } 99 | }); 100 | return future; 101 | } 102 | } -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/io/PeerClientHandshakeHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 shevek. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.client.io; 17 | 18 | import com.turn.ttorrent.client.peer.PeerConnectionListener; 19 | import com.turn.ttorrent.protocol.TorrentUtils; 20 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 21 | import io.netty.channel.ChannelFutureListener; 22 | import io.netty.channel.ChannelHandlerContext; 23 | import io.netty.handler.logging.LoggingHandler; 24 | import java.util.Arrays; 25 | import javax.annotation.Nonnull; 26 | import org.slf4j.Logger; 27 | import org.slf4j.LoggerFactory; 28 | 29 | /** 30 | * 31 | * @author shevek 32 | */ 33 | public class PeerClientHandshakeHandler extends PeerHandshakeHandler { 34 | 35 | private static final Logger logger = LoggerFactory.getLogger(PeerClientHandshakeHandler.class); 36 | private static final LoggingHandler wireLogger = new LoggingHandler("client-wire"); 37 | private static final LoggingHandler frameLogger = new LoggingHandler("client-frame"); 38 | private static final LoggingHandler messageLogger = new LoggingHandler("client-message"); 39 | @Nonnull 40 | private final byte[] infoHash; 41 | @Nonnull 42 | private final byte[] peerId; 43 | @Nonnull 44 | private final PeerConnectionListener listener; 45 | 46 | @SuppressFBWarnings("EI_EXPOSE_REP2") 47 | public PeerClientHandshakeHandler( 48 | @Nonnull PeerConnectionListener listener, 49 | @Nonnull byte[] infoHash, 50 | @Nonnull byte[] peerId) { 51 | this.listener = listener; 52 | this.infoHash = infoHash; 53 | this.peerId = peerId; 54 | } 55 | 56 | @Override 57 | public LoggingHandler getWireLogger() { 58 | return wireLogger; 59 | } 60 | 61 | @Override 62 | protected LoggingHandler getFrameLogger() { 63 | return frameLogger; 64 | } 65 | 66 | @Override 67 | public LoggingHandler getMessageLogger() { 68 | return messageLogger; 69 | } 70 | 71 | @Override 72 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 73 | PeerHandshakeMessage response = new PeerHandshakeMessage(infoHash, peerId); 74 | ctx.writeAndFlush(toByteBuf(ctx, response), ctx.voidPromise()); 75 | super.channelActive(ctx); 76 | } 77 | 78 | @Override 79 | protected void process(ChannelHandlerContext ctx, PeerHandshakeMessage message) { 80 | // We were the connecting client. 81 | if (!Arrays.equals(infoHash, message.getInfoHash())) { 82 | logger.warn("InfoHash mismatch: requested " + TorrentUtils.toHex(infoHash) + " but received " + message); 83 | ctx.close().addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); 84 | return; 85 | } 86 | 87 | addPeer(ctx, message, listener); 88 | } 89 | } -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/io/PeerEndpoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client.io; 6 | 7 | /** 8 | * 9 | * @author shevek 10 | */ 11 | public class PeerEndpoint { 12 | } 13 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/io/PeerFrameDecoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 shevek. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.client.io; 17 | 18 | import com.google.common.annotations.VisibleForTesting; 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.channel.ChannelHandlerContext; 21 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 22 | 23 | /** 24 | * 25 | * @author shevek 26 | */ 27 | public class PeerFrameDecoder extends LengthFieldBasedFrameDecoder { 28 | 29 | public PeerFrameDecoder() { 30 | super(1048576, 0, PeerMessage.MESSAGE_LENGTH_FIELD_SIZE, 0, 4); 31 | } 32 | 33 | @VisibleForTesting 34 | /* pp */ ByteBuf _decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { 35 | return (ByteBuf) super.decode(ctx, in); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/io/PeerFrameEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 shevek. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.client.io; 17 | 18 | import io.netty.channel.ChannelHandler; 19 | import io.netty.handler.codec.LengthFieldPrepender; 20 | 21 | /** 22 | * 23 | * @author shevek 24 | */ 25 | @ChannelHandler.Sharable 26 | public class PeerFrameEncoder extends LengthFieldPrepender { 27 | 28 | public PeerFrameEncoder() { 29 | super(PeerMessage.MESSAGE_LENGTH_FIELD_SIZE); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/io/PeerHandshakeHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client.io; 6 | 7 | import com.turn.ttorrent.client.peer.PeerConnectionListener; 8 | import com.turn.ttorrent.client.peer.PeerHandler; 9 | import com.turn.ttorrent.client.peer.PeerMessageListener; 10 | import io.netty.buffer.ByteBuf; 11 | import io.netty.channel.Channel; 12 | import io.netty.channel.ChannelFutureListener; 13 | import io.netty.channel.ChannelHandlerContext; 14 | import io.netty.channel.ChannelPipeline; 15 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 16 | import io.netty.handler.logging.LoggingHandler; 17 | import io.netty.util.ReferenceCountUtil; 18 | import javax.annotation.Nonnull; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | /** 23 | * 24 | * @author shevek 25 | * @see BitTorrent handshake specification 26 | * @see PeerServerHandshakeHandler 27 | * @see PeerClientHandshakeHandler 28 | */ 29 | public abstract class PeerHandshakeHandler extends LengthFieldBasedFrameDecoder { 30 | 31 | private static final Logger LOG = LoggerFactory.getLogger(PeerHandshakeHandler.class); 32 | 33 | // protected static final PeerFrameEncoder frameEncoder = new PeerFrameEncoder(); 34 | public PeerHandshakeHandler() { 35 | super(1024, 0, 1, PeerHandshakeMessage.BASE_HANDSHAKE_LENGTH - 1, 0); 36 | } 37 | 38 | @Nonnull 39 | protected abstract LoggingHandler getWireLogger(); 40 | 41 | @Nonnull 42 | protected abstract LoggingHandler getFrameLogger(); 43 | 44 | @Nonnull 45 | protected abstract LoggingHandler getMessageLogger(); 46 | 47 | @Nonnull 48 | protected ByteBuf toByteBuf(@Nonnull ChannelHandlerContext ctx, @Nonnull PeerHandshakeMessage message) { 49 | ByteBuf buf = ctx.alloc().buffer(PeerHandshakeMessage.BASE_HANDSHAKE_LENGTH + 64); 50 | message.toWire(buf); 51 | return buf; 52 | } 53 | 54 | private void addMessageHandlers(@Nonnull ChannelPipeline pipeline, @Nonnull PeerMessageListener listener) { 55 | // TODO: Merge LengthFieldPrepender into PeerMessageCodec and use only a PeerFrameDecoder here. 56 | pipeline.addLast(new PeerFrameDecoder()); 57 | // pipeline.addLast(frameEncoder); 58 | // pipeline.addLast(getFrameLogger()); 59 | pipeline.addLast(new PeerMessageCodec(listener)); 60 | // pipeline.addLast(getMessageLogger()); 61 | // pipeline.addLast(new PeerMessageTrafficShapingHandler()); 62 | pipeline.addLast(new PeerMessageHandler(listener)); 63 | } 64 | 65 | @Override 66 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 67 | // ctx.pipeline().addFirst(getWireLogger()); 68 | super.channelRegistered(ctx); 69 | } 70 | 71 | protected abstract void process(@Nonnull ChannelHandlerContext ctx, @Nonnull PeerHandshakeMessage message); 72 | 73 | protected void addPeer(@Nonnull ChannelHandlerContext ctx, @Nonnull PeerHandshakeMessage message, 74 | @Nonnull PeerConnectionListener listener) { 75 | Channel channel = ctx.channel(); 76 | PeerHandler peer = listener.handlePeerConnectionCreated(channel, message.getPeerId(), message.getReserved()); 77 | if (peer == null) { 78 | ctx.close().addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); 79 | return; 80 | } 81 | 82 | addMessageHandlers(ctx.pipeline(), peer); 83 | ctx.pipeline().remove(this); 84 | 85 | listener.handlePeerConnectionReady(peer); 86 | } 87 | 88 | @Override 89 | protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { 90 | Object message = super.decode(ctx, in); 91 | if (message instanceof ByteBuf) { 92 | PeerHandshakeMessage request = new PeerHandshakeMessage(); 93 | request.fromWire((ByteBuf) message); 94 | process(ctx, request); 95 | ReferenceCountUtil.release(message); 96 | return null; 97 | } else { 98 | // Including null. 99 | return message; 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/io/PeerHandshakeMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 shevek. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.client.io; 17 | 18 | import com.turn.ttorrent.client.io.PeerExtendedMessage.ExtendedType; 19 | import com.turn.ttorrent.protocol.TorrentUtils; 20 | import com.turn.ttorrent.protocol.bcodec.BEUtils; 21 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 22 | import io.netty.buffer.ByteBuf; 23 | import java.util.Arrays; 24 | import java.util.Map; 25 | import javax.annotation.Nonnull; 26 | import org.apache.commons.io.Charsets; 27 | 28 | /** 29 | * 30 | * @author shevek 31 | */ 32 | public class PeerHandshakeMessage extends PeerMessage { 33 | 34 | public static enum Feature { 35 | 36 | BEP10_EXTENSION_PROTOCOL { 37 | @Override 38 | public boolean get(byte[] reserved) { 39 | return (reserved[5] & 0x10) != 0; 40 | } 41 | 42 | @Override 43 | public void set(byte[] reserved) { 44 | reserved[5] |= 0x10; 45 | } 46 | }; 47 | 48 | public abstract boolean get(@Nonnull byte[] reserved); 49 | 50 | public abstract void set(@Nonnull byte[] reserved); 51 | } 52 | public static final int BASE_HANDSHAKE_LENGTH = 49; 53 | private static final byte[] BITTORRENT_PROTOCOL_IDENTIFIER = "BitTorrent protocol".getBytes(BEUtils.BYTE_ENCODING); 54 | private byte[] protocolName; 55 | private final byte[] reserved = new byte[8]; 56 | private byte[] infoHash; // 20 57 | private byte[] peerId; // 20 58 | 59 | public PeerHandshakeMessage() { 60 | Feature.BEP10_EXTENSION_PROTOCOL.set(reserved); // Overwritten by fromWire() 61 | } 62 | 63 | @SuppressFBWarnings("EI_EXPOSE_REP2") 64 | public PeerHandshakeMessage(@Nonnull byte[] infoHash, @Nonnull byte[] peerId) { 65 | this(); 66 | if (infoHash.length != 20) 67 | throw new IllegalArgumentException("InfoHash length should be 20, not " + infoHash.length); 68 | if (peerId.length != 20) 69 | throw new IllegalArgumentException("PeerId length should be 20, not " + peerId.length); 70 | this.protocolName = BITTORRENT_PROTOCOL_IDENTIFIER; 71 | this.infoHash = infoHash; 72 | this.peerId = peerId; 73 | } 74 | 75 | @Override 76 | public Type getType() { 77 | return Type.HANDSHAKE; 78 | } 79 | 80 | @SuppressFBWarnings("EI_EXPOSE_REP2") 81 | public byte[] getInfoHash() { 82 | return infoHash; 83 | } 84 | 85 | @SuppressFBWarnings("EI_EXPOSE_REP2") 86 | public byte[] getPeerId() { 87 | return peerId; 88 | } 89 | 90 | @Nonnull 91 | @SuppressFBWarnings("EI_EXPOSE_REP2") 92 | public byte[] getReserved() { 93 | return reserved; 94 | } 95 | 96 | @Override 97 | public void fromWire(ByteBuf in) { 98 | int pstrlen = in.readUnsignedByte(); 99 | if (pstrlen < 0 || in.readableBytes() < BASE_HANDSHAKE_LENGTH + pstrlen - 1) 100 | throw new IllegalArgumentException("Incorrect handshake message length (pstrlen=" + pstrlen + ") !"); 101 | 102 | // Check the protocol identification string 103 | protocolName = new byte[pstrlen]; 104 | in.readBytes(protocolName); 105 | if (!Arrays.equals(protocolName, BITTORRENT_PROTOCOL_IDENTIFIER)) 106 | throw new IllegalArgumentException("Unknown protocol " + new String(protocolName, Charsets.ISO_8859_1)); 107 | 108 | // Ignore reserved bytes 109 | in.readBytes(reserved); 110 | 111 | infoHash = new byte[20]; 112 | in.readBytes(infoHash); 113 | peerId = new byte[20]; 114 | in.readBytes(peerId); 115 | } 116 | 117 | public void toWire(@Nonnull ByteBuf out) { 118 | out.writeByte(protocolName.length); 119 | out.writeBytes(protocolName); 120 | out.writeBytes(reserved); 121 | out.writeBytes(infoHash); 122 | out.writeBytes(peerId); 123 | } 124 | 125 | @Override 126 | public void toWire(ByteBuf out, Map extendedTypes) { 127 | throw new UnsupportedOperationException("Message should not appear in normal protocol."); 128 | } 129 | 130 | @Override 131 | public String toString() { 132 | return super.toString() + " P=" + TorrentUtils.toTextOrNull(getPeerId()) + " T=" + TorrentUtils.toHexOrNull(getInfoHash()); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/io/PeerMessageCodec.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 shevek. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.client.io; 17 | 18 | import com.turn.ttorrent.client.peer.PeerMessageListener; 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.channel.ChannelHandlerContext; 21 | import io.netty.handler.codec.ByteToMessageCodec; 22 | import java.io.IOException; 23 | import java.util.List; 24 | import javax.annotation.Nonnull; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | 28 | /** 29 | * 30 | * @author shevek 31 | */ 32 | // Not shareable 33 | public class PeerMessageCodec extends ByteToMessageCodec { 34 | 35 | private static final Logger LOG = LoggerFactory.getLogger(PeerMessageCodec.class); 36 | private final PeerMessageListener listener; 37 | 38 | public PeerMessageCodec(@Nonnull PeerMessageListener listener) { 39 | this.listener = listener; 40 | } 41 | 42 | @Override 43 | protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List out) throws Exception { 44 | if (buf.readableBytes() == 0) { 45 | out.add(new PeerMessage.KeepAliveMessage()); 46 | return; 47 | } 48 | 49 | byte type = buf.readByte(); 50 | 51 | PeerMessage message; 52 | switch (type) { 53 | case 0: 54 | message = new PeerMessage.ChokeMessage(); 55 | break; 56 | case 1: 57 | message = new PeerMessage.UnchokeMessage(); 58 | break; 59 | case 2: 60 | message = new PeerMessage.InterestedMessage(); 61 | break; 62 | case 3: 63 | message = new PeerMessage.NotInterestedMessage(); 64 | break; 65 | case 4: 66 | message = new PeerMessage.HaveMessage(); 67 | break; 68 | case 5: 69 | message = new PeerMessage.BitfieldMessage(); 70 | break; 71 | case 6: 72 | message = new PeerMessage.RequestMessage(); 73 | break; 74 | case 7: 75 | message = new PeerMessage.PieceMessage(); 76 | break; 77 | case 8: 78 | message = new PeerMessage.CancelMessage(); 79 | break; 80 | case 20: 81 | byte extendedType = buf.readByte(); // Throws on short packet. This is normal. 82 | switch (extendedType) { 83 | case 0: 84 | message = new PeerExtendedMessage.HandshakeMessage(); 85 | break; 86 | case 1: 87 | message = new PeerExtendedMessage.UtPexMessage(); 88 | break; 89 | default: 90 | throw new IOException("Unknown extended message type " + extendedType); 91 | } 92 | break; 93 | default: 94 | throw new IOException("Unknown message type " + type); 95 | } 96 | message.fromWire(buf); 97 | out.add(message); 98 | 99 | // if (buf.readableBytes() > 0) throw new IOException("Badly framed message " + message + "; remaining=" + buf.readableBytes()); 100 | } 101 | 102 | @Override 103 | protected void encode(ChannelHandlerContext ctx, PeerMessage value, ByteBuf out) throws Exception { 104 | // LOG.info("encode: " + value); 105 | int lengthIndex = out.writerIndex(); 106 | out.writeInt(0); 107 | int startIndex = out.writerIndex(); 108 | value.toWire(out, listener.getExtendedMessageTypes()); 109 | out.setInt(lengthIndex, out.writerIndex() - startIndex); 110 | } 111 | } -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/io/PeerMessageHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 shevek. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.client.io; 17 | 18 | import com.turn.ttorrent.client.peer.PeerMessageListener; 19 | import io.netty.channel.ChannelHandlerContext; 20 | import io.netty.channel.ChannelInboundHandlerAdapter; 21 | import javax.annotation.Nonnull; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | /** 26 | * 27 | * @author shevek 28 | */ 29 | public class PeerMessageHandler extends ChannelInboundHandlerAdapter { 30 | 31 | private static final Logger LOG = LoggerFactory.getLogger(PeerMessageHandler.class); 32 | private final PeerMessageListener listener; 33 | 34 | public PeerMessageHandler(@Nonnull PeerMessageListener listener) { 35 | this.listener = listener; 36 | } 37 | 38 | @Override 39 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 40 | // LOG.info("Received " + msg + " for " + listener); 41 | PeerMessage message = (PeerMessage) msg; 42 | listener.handleMessage(message); 43 | } 44 | 45 | @Override 46 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 47 | if (ctx.channel().isOpen()) 48 | listener.handleReadComplete(); 49 | } 50 | 51 | @Override 52 | public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { 53 | if (ctx.channel().isWritable()) 54 | listener.handleWritable(); 55 | } 56 | 57 | @Override 58 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 59 | listener.handleDisconnect(); 60 | } 61 | 62 | @Override 63 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 64 | // TODO: Pass this to the application: An incoming message threw an exception. 65 | listener.handleException(cause); 66 | } 67 | } -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/io/PeerMessageTrafficShapingHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client.io; 6 | 7 | import io.netty.handler.traffic.ChannelTrafficShapingHandler; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | /** 11 | * 12 | * @author shevek 13 | */ 14 | public class PeerMessageTrafficShapingHandler extends ChannelTrafficShapingHandler { 15 | 16 | public PeerMessageTrafficShapingHandler() { 17 | super(TimeUnit.MINUTES.toMillis(1)); 18 | } 19 | 20 | @Override 21 | protected long calculateSize(Object msg) { 22 | if (msg instanceof PeerMessage.PieceMessage) 23 | return ((PeerMessage.PieceMessage) msg).getLength(); 24 | return super.calculateSize(msg); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/io/PeerServerHandshakeHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 shevek. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.client.io; 17 | 18 | import com.turn.ttorrent.client.TorrentRegistry; 19 | import com.turn.ttorrent.client.peer.PeerConnectionListener; 20 | import com.turn.ttorrent.client.TorrentHandler; 21 | import io.netty.channel.ChannelFutureListener; 22 | import io.netty.channel.ChannelHandlerContext; 23 | import io.netty.handler.logging.LoggingHandler; 24 | import java.util.Arrays; 25 | import javax.annotation.Nonnull; 26 | import org.slf4j.Logger; 27 | import org.slf4j.LoggerFactory; 28 | 29 | /** 30 | * 31 | * @author shevek 32 | */ 33 | public class PeerServerHandshakeHandler extends PeerHandshakeHandler { 34 | 35 | private static final Logger LOG = LoggerFactory.getLogger(PeerServerHandshakeHandler.class); 36 | private static final LoggingHandler wireLogger = new LoggingHandler("server-wire"); 37 | private static final LoggingHandler frameLogger = new LoggingHandler("server-frame"); 38 | private static final LoggingHandler messageLogger = new LoggingHandler("server-message"); 39 | private final TorrentRegistry torrentProvider; 40 | 41 | public PeerServerHandshakeHandler(@Nonnull TorrentRegistry torrentProvider) { 42 | this.torrentProvider = torrentProvider; 43 | } 44 | 45 | @Override 46 | public LoggingHandler getWireLogger() { 47 | return wireLogger; 48 | } 49 | 50 | @Override 51 | protected LoggingHandler getFrameLogger() { 52 | return frameLogger; 53 | } 54 | 55 | @Override 56 | public LoggingHandler getMessageLogger() { 57 | return messageLogger; 58 | } 59 | 60 | @Override 61 | protected void process(ChannelHandlerContext ctx, PeerHandshakeMessage message) { 62 | if (LOG.isDebugEnabled()) 63 | LOG.debug("Processing {}", message); 64 | if (Arrays.equals(message.getPeerId(), torrentProvider.getLocalPeerId())) { 65 | LOG.warn("Connected to self. Closing."); 66 | ctx.close().addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); 67 | return; 68 | } 69 | 70 | // We are a server. 71 | TorrentHandler torrent = torrentProvider.getTorrent(message.getInfoHash()); 72 | if (torrent == null) { 73 | LOG.warn("Unknown torrent {}", message); 74 | ctx.close().addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); 75 | return; 76 | } 77 | PeerConnectionListener listener = torrent.getSwarmHandler(); 78 | if (LOG.isTraceEnabled()) 79 | LOG.trace("Found torrent {}", torrent); 80 | 81 | PeerHandshakeMessage response = new PeerHandshakeMessage(torrent.getInfoHash(), torrentProvider.getLocalPeerId()); 82 | ctx.writeAndFlush(toByteBuf(ctx, response), ctx.voidPromise()); 83 | 84 | addPeer(ctx, message, listener); 85 | } 86 | } -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/main/TorrentMain.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2013 Turn, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.client.main; 17 | 18 | import com.turn.ttorrent.protocol.torrent.Torrent; 19 | import com.turn.ttorrent.protocol.torrent.TorrentCreator; 20 | import java.io.File; 21 | import java.io.OutputStream; 22 | import java.net.URI; 23 | import java.util.ArrayList; 24 | 25 | import java.util.Collections; 26 | import java.util.List; 27 | import joptsimple.OptionParser; 28 | import joptsimple.OptionSet; 29 | import joptsimple.OptionSpec; 30 | import org.apache.commons.io.FileUtils; 31 | import org.apache.commons.io.IOUtils; 32 | import org.slf4j.Logger; 33 | import org.slf4j.LoggerFactory; 34 | 35 | /** 36 | * Command-line entry-point for reading and writing {@link Torrent} files. 37 | */ 38 | public class TorrentMain { 39 | 40 | private static final Logger logger = LoggerFactory.getLogger(TorrentMain.class); 41 | 42 | /** 43 | * Torrent creator. 44 | * 45 | *

46 | * You can use the {@code main()} function of this class to create 47 | * torrent files. See usage for details. 48 | *

49 | */ 50 | public static void main(String[] args) throws Exception { 51 | // BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%-5p: %m%n"))); 52 | 53 | OptionParser parser = new OptionParser(); 54 | OptionSpec helpOption = parser.accepts("help") 55 | .forHelp(); 56 | OptionSpec inputOption = parser.accepts("input") 57 | .withRequiredArg().ofType(File.class) 58 | .required() 59 | .describedAs("The input file or directory for the torrent."); 60 | OptionSpec outputOption = parser.accepts("output") 61 | .withRequiredArg().ofType(File.class) 62 | .required() 63 | .describedAs("The output torrent file."); 64 | OptionSpec announceOption = parser.accepts("announce") 65 | .withRequiredArg().ofType(URI.class) 66 | .required() 67 | .describedAs("The announce URL for the torrent."); 68 | parser.nonOptions().ofType(File.class) 69 | .describedAs("Files to include in the torrent."); 70 | 71 | OptionSet options = parser.parse(args); 72 | List otherArgs = options.nonOptionArguments(); 73 | 74 | // Display help and exit if requested 75 | if (options.has(helpOption)) { 76 | System.out.println("Usage: Torrent [] "); 77 | parser.printHelpOn(System.err); 78 | System.exit(0); 79 | } 80 | 81 | List files = new ArrayList(); 82 | for (Object o : otherArgs) 83 | files.add((File) o); 84 | Collections.sort(files); 85 | 86 | TorrentCreator creator = new TorrentCreator(options.valueOf(inputOption)); 87 | if (!files.isEmpty()) 88 | creator.setFiles(files); 89 | creator.setAnnounceList(options.valuesOf(announceOption)); 90 | Torrent torrent = creator.create(); 91 | 92 | File file = options.valueOf(outputOption); 93 | OutputStream fos = FileUtils.openOutputStream(file); 94 | try { 95 | torrent.save(fos); 96 | } finally { 97 | IOUtils.closeQuietly(fos); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/peer/Instrumentation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client.peer; 6 | 7 | import com.turn.ttorrent.client.PeerPieceProvider; 8 | import com.turn.ttorrent.client.io.PeerMessage; 9 | import javax.annotation.CheckForNull; 10 | import javax.annotation.Nonnull; 11 | 12 | /** 13 | * This is a hook class to interject cheeky behaviour into the client(s). 14 | * 15 | * @author shevek 16 | */ 17 | public class Instrumentation { 18 | 19 | public void instrumentThrowable(@Nonnull Object source, @Nonnull Throwable t) { 20 | } 21 | 22 | @CheckForNull 23 | public PeerMessage.RequestMessage instrumentBlockRequest(@Nonnull PeerHandler peer, @Nonnull PeerPieceProvider provider, @CheckForNull PeerMessage.RequestMessage request) { 24 | return request; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/peer/PeerActivityListener.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Turn, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.client.peer; 17 | 18 | import java.io.IOException; 19 | 20 | import java.util.BitSet; 21 | import java.util.EventListener; 22 | import javax.annotation.Nonnegative; 23 | import javax.annotation.Nonnull; 24 | 25 | /** 26 | * EventListener interface for objects that want to handle peer activity 27 | * events like piece availability, or piece completion events, and more. 28 | * 29 | * @author mpetazzoni 30 | */ 31 | public interface PeerActivityListener extends EventListener { 32 | 33 | /** 34 | * Peer choked handler. 35 | * 36 | *

37 | * This handler is fired when a peer choked and now refuses to send data to 38 | * us. This means we should not try to request or expect anything from it 39 | * until it becomes ready again. 40 | *

41 | * 42 | * @param peer The peer that choked. 43 | */ 44 | public void handlePeerChoking(PeerHandler peer); 45 | 46 | /** 47 | * Peer ready handler. 48 | * 49 | *

50 | * This handler is fired when a peer notified that it is no longer choked. 51 | * This means we can send piece block requests to it and start downloading. 52 | *

53 | * 54 | * @param peer The peer that became ready. 55 | */ 56 | public void handlePeerUnchoking(PeerHandler peer); 57 | 58 | /** 59 | * Piece availability handler. 60 | * 61 | *

62 | * This handler is fired when an update in piece availability is received 63 | * from a peer's HAVE message. 64 | *

65 | * 66 | * @param peer The peer we got the update from. 67 | * @param piece The piece that became available from this peer. 68 | */ 69 | public void handlePieceAvailability(@Nonnull PeerHandler peer, 70 | @Nonnegative int piece); 71 | 72 | /** 73 | * Bit field availability handler. 74 | * 75 | *

76 | * This handler is fired when an update in piece availability is received 77 | * from a peer's BITFIELD message. 78 | *

79 | * 80 | * @param peer The peer we got the update from. 81 | * @param availablePieces The pieces availability bit field of the peer. 82 | */ 83 | public void handleBitfieldAvailability(@Nonnull PeerHandler peer, 84 | @Nonnull BitSet prevAvailablePieces, 85 | @Nonnull BitSet availablePieces); 86 | 87 | /** 88 | * Piece upload completion handler. 89 | * 90 | * @param peer The peer the piece was sent to. 91 | * @param piece The piece in question. 92 | */ 93 | public void handleBlockSent(@Nonnull PeerHandler peer, 94 | @Nonnegative int piece, 95 | @Nonnegative int offset, @Nonnegative int length); 96 | 97 | public void handleBlockReceived(@Nonnull PeerHandler peer, 98 | @Nonnegative int piece, 99 | @Nonnegative int offset, @Nonnegative int length); 100 | 101 | /** 102 | * Piece download completion handler. 103 | * 104 | *

105 | * This handler is fired when a piece has been downloaded entirely and the 106 | * piece data has been revalidated. 107 | *

108 | * 109 | *

110 | * Note: the piece may not be valid after it has been 111 | * downloaded, in which case appropriate action should be taken to 112 | * redownload the piece. 113 | *

114 | * 115 | * @param peer The peer we got this piece from. 116 | * @param piece The piece in question. 117 | */ 118 | public void handlePieceCompleted(@Nonnull PeerHandler peer, 119 | @Nonnegative int piece, 120 | @Nonnull PieceHandler.Reception reception) 121 | throws IOException; 122 | } 123 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/peer/PeerConnectionListener.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Turn, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.client.peer; 17 | 18 | import com.turn.ttorrent.protocol.PeerIdentityProvider; 19 | import io.netty.channel.Channel; 20 | import java.io.IOException; 21 | import java.net.SocketAddress; 22 | import java.util.EventListener; 23 | import javax.annotation.CheckForNull; 24 | import javax.annotation.Nonnull; 25 | 26 | /** 27 | * EventListener interface for objects that want to handle incoming peer 28 | * connections. 29 | * 30 | * @author mpetazzoni 31 | */ 32 | public interface PeerConnectionListener extends PeerIdentityProvider, EventListener { 33 | 34 | public void handlePeerConnectionFailed(@Nonnull SocketAddress address, @CheckForNull Throwable cause); 35 | 36 | @CheckForNull 37 | public PeerHandler handlePeerConnectionCreated(@Nonnull Channel channel, @Nonnull byte[] peerId, @Nonnull byte[] remoteReserved); 38 | 39 | public void handlePeerConnectionReady(@Nonnull PeerHandler peer); 40 | 41 | /** 42 | * Peer disconnection handler. 43 | * 44 | *

45 | * This handler is fired when a peer disconnects, or is disconnected due to 46 | * protocol violation. 47 | *

48 | * 49 | * @param peer The peer we got this piece from. 50 | */ 51 | public void handlePeerDisconnected(@Nonnull PeerHandler peer); 52 | 53 | /** 54 | * Handler for IOException during peer operation. 55 | * 56 | * @param peer The peer whose activity trigger the exception. 57 | * @param ioe The IOException object, for reporting. 58 | */ 59 | public void handleIOException(@Nonnull PeerHandler peer, @CheckForNull IOException ioe); 60 | } 61 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/peer/PeerExistenceListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client.peer; 6 | 7 | import java.net.SocketAddress; 8 | import java.util.Map; 9 | import javax.annotation.Nonnull; 10 | 11 | /** 12 | * 13 | * @author shevek 14 | */ 15 | public interface PeerExistenceListener { 16 | 17 | /** Returns all known peers, possibly including this peer's known addresses. */ 18 | @Nonnull 19 | public Map getPeers(); 20 | 21 | /** Adds SocketAddress -> PeerIds. The PeerId may be null if not known. */ 22 | public void addPeers(@Nonnull Map peers, @Nonnull String source); 23 | } 24 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/peer/PeerMessageListener.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Turn, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.client.peer; 17 | 18 | import com.turn.ttorrent.client.io.PeerExtendedMessage; 19 | import com.turn.ttorrent.client.io.PeerMessage; 20 | import java.io.IOException; 21 | import java.util.EventListener; 22 | import java.util.Map; 23 | import javax.annotation.Nonnull; 24 | 25 | /** 26 | * EventListener interface for objects that want to receive incoming messages 27 | * from peers. 28 | * 29 | * @author mpetazzoni 30 | */ 31 | public interface PeerMessageListener extends EventListener { 32 | 33 | @Nonnull 34 | public Map getExtendedMessageTypes(); 35 | 36 | public void handleMessage(@Nonnull PeerMessage msg) throws IOException; 37 | 38 | public void handleReadComplete() throws IOException; 39 | 40 | public void handleWritable() throws IOException; 41 | 42 | public void handleDisconnect() throws IOException; 43 | 44 | public void handleException(@Nonnull Throwable exception); 45 | } 46 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/peer/Rate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 shevek. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.client.peer; 17 | 18 | import com.codahale.metrics.EWMA; 19 | import java.util.concurrent.TimeUnit; 20 | import static java.lang.Math.exp; 21 | 22 | /** 23 | * 24 | * @author shevek 25 | */ 26 | public class Rate extends EWMA { 27 | 28 | public static final int INTERVAL = 5; 29 | public static final long INTERVAL_MS = TimeUnit.SECONDS.toMillis(INTERVAL); 30 | 31 | public Rate(double seconds) { 32 | this(1 - exp(-INTERVAL / seconds), INTERVAL, TimeUnit.SECONDS); 33 | } 34 | 35 | public Rate(double alpha, long interval, TimeUnit intervalUnit) { 36 | super(alpha, interval, intervalUnit); 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return getRate(TimeUnit.SECONDS) + "/s"; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/peer/RateComparator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 shevek. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.client.peer; 17 | 18 | import java.io.Serializable; 19 | import java.util.Comparator; 20 | import java.util.concurrent.TimeUnit; 21 | import javax.annotation.Nonnull; 22 | 23 | /** 24 | * 25 | * @author shevek 26 | */ 27 | public abstract class RateComparator implements Comparator, Serializable { 28 | 29 | private static final long serialVersionUID = 1L; 30 | 31 | private static int compare(@Nonnull Rate a, @Nonnull Rate b) { 32 | return Double.compare(a.getRate(TimeUnit.SECONDS), b.getRate(TimeUnit.SECONDS)); 33 | } 34 | 35 | /** 36 | * Download rate comparator. 37 | * 38 | *

39 | * Compares sharing peers based on their current download rate. 40 | *

41 | * 42 | * @author mpetazzoni 43 | */ 44 | public static class DLRateComparator extends RateComparator { 45 | 46 | private static double getRate(PeerHandler peer) { 47 | double rate = peer.getDLRate().getRate(TimeUnit.SECONDS); 48 | rate += peer.getRequestsSentCount(); 49 | if (!peer.isChoked(0)) 50 | rate = (rate + 100) * 1.5; 51 | return rate; 52 | } 53 | 54 | @Override 55 | public int compare(PeerHandler a, PeerHandler b) { 56 | double ra = getRate(a); 57 | double rb = getRate(b); 58 | return Double.compare(rb, ra); 59 | } 60 | } 61 | 62 | /** 63 | * Upload rate comparator. 64 | * 65 | *

66 | * Compares sharing peers based on their current upload rate. 67 | *

68 | * 69 | * @author mpetazzoni 70 | */ 71 | public static class ULRateComparator extends RateComparator { 72 | 73 | private static double getRate(PeerHandler peer) { 74 | double rate = peer.getDLRate().getRate(TimeUnit.SECONDS); 75 | if (!peer.isChoked(0)) 76 | rate = (rate + 100) * 1.5; 77 | return rate; 78 | } 79 | 80 | @Override 81 | public int compare(PeerHandler a, PeerHandler b) { 82 | double ra = getRate(a); 83 | double rb = getRate(b); 84 | return Double.compare(rb, ra); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/storage/ByteRangeStorage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client.storage; 6 | 7 | import javax.annotation.Nonnegative; 8 | 9 | /** 10 | * 11 | * @author shevek 12 | */ 13 | public interface ByteRangeStorage extends ByteStorage { 14 | 15 | @Nonnegative 16 | public long offset(); 17 | 18 | @Nonnegative 19 | public long size(); 20 | } 21 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/storage/ByteStorage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Turn, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.client.storage; 17 | 18 | import java.io.Closeable; 19 | import java.io.Flushable; 20 | import java.io.IOException; 21 | import java.nio.ByteBuffer; 22 | import javax.annotation.Nonnegative; 23 | import javax.annotation.Nonnull; 24 | 25 | /** 26 | * Abstract torrent byte storage. 27 | * 28 | *

29 | * This interface defines the methods for accessing an abstracted torrent byte 30 | * storage. A torrent, especially when it contains multiple files, needs to be 31 | * seen as one single continuous stream of bytes. Torrent pieces will most 32 | * likely span across file boundaries. This abstracted byte storage aims at 33 | * providing a simple interface for read/write access to the torrent data, 34 | * regardless of how it is composed underneath the piece structure. 35 | *

36 | * 37 | * @author mpetazzoni 38 | * @author dgiffin 39 | */ 40 | public interface ByteStorage extends Flushable, Closeable { 41 | 42 | /** 43 | * Read from the byte storage. 44 | * 45 | *

46 | * Read {@code length} bytes at offset {@code offset} from the underlying 47 | * byte storage and return them in a {@link ByteBuffer}. 48 | * This method does NOT call {@link ByteBuffer#flip()}. 49 | *

50 | * 51 | * @param buffer The buffer to read the bytes into. The buffer's limit will 52 | * control how many bytes are read from the storage. 53 | * @param offset The offset, in bytes, to read from. This must be within 54 | * the storage boundary. 55 | * @return The number of bytes read from the storage. 56 | * @throws IOException If an I/O error occurs while reading from the 57 | * byte storage. 58 | */ 59 | public int read(@Nonnull ByteBuffer buffer, @Nonnegative long offset) throws IOException; 60 | 61 | /** 62 | * Write bytes to the byte storage. 63 | * 64 | *

65 | *

66 | * 67 | * @param block A {@link ByteBuffer} containing the bytes to write to the 68 | * storage. The buffer limit is expected to be set correctly: all bytes 69 | * from the buffer will be used. 70 | * @param offset Offset in the underlying byte storage to write the block 71 | * at. 72 | * @return The number of bytes written to the storage. 73 | * @throws IOException If an I/O error occurs while writing to the byte 74 | * storage. 75 | */ 76 | public int write(@Nonnull ByteBuffer block, @Nonnegative long offset) throws IOException; 77 | 78 | /** 79 | * Close this byte storage. 80 | * 81 | * @throws IOException If closing the underlying storage (file(s) ?) 82 | * failed. 83 | */ 84 | @Override 85 | public void close() throws IOException; 86 | 87 | /** 88 | * Finalize the byte storage when the download is complete. 89 | * 90 | *

91 | * This gives the byte storage the opportunity to perform finalization 92 | * operations when the download completes, like moving the files from a 93 | * temporary location to their destination. 94 | *

95 | * 96 | * @throws IOException If the finalization failed. 97 | */ 98 | public void finish() throws IOException; 99 | 100 | /** 101 | * Tells whether this byte storage has been finalized. 102 | */ 103 | public boolean isFinished(); 104 | } 105 | -------------------------------------------------------------------------------- /ttorrent-client/src/main/java/com/turn/ttorrent/client/storage/RawFileStorage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client.storage; 6 | 7 | import com.google.common.base.Objects; 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.nio.ByteBuffer; 11 | import java.nio.channels.FileChannel; 12 | import java.nio.file.StandardOpenOption; 13 | import java.util.EnumSet; 14 | import javax.annotation.Nonnull; 15 | 16 | /** 17 | * 18 | * @author shevek 19 | */ 20 | public class RawFileStorage implements ByteStorage { 21 | 22 | private final File file; 23 | private final FileChannel channel; 24 | private boolean finished = false; 25 | 26 | public RawFileStorage(@Nonnull File file) throws IOException { 27 | this.file = file; 28 | this.channel = FileChannel.open(file.toPath(), EnumSet.of(StandardOpenOption.READ, StandardOpenOption.WRITE)); 29 | } 30 | 31 | @Override 32 | public int read(ByteBuffer buffer, long offset) throws IOException { 33 | int bytes = channel.read(buffer, offset); 34 | buffer.position(buffer.limit()); 35 | return bytes; 36 | } 37 | 38 | @Override 39 | public int write(ByteBuffer buffer, long offset) throws IOException { 40 | return channel.write(buffer, offset); 41 | } 42 | 43 | public void flush() throws IOException { 44 | if (channel.isOpen()) 45 | channel.force(true); 46 | } 47 | 48 | @Override 49 | public void close() throws IOException { 50 | flush(); 51 | if (channel.isOpen()) 52 | channel.close(); 53 | } 54 | 55 | @Override 56 | public void finish() throws IOException { 57 | flush(); 58 | finished = true; 59 | } 60 | 61 | @Override 62 | public boolean isFinished() { 63 | return finished; 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return Objects.toStringHelper(this) 69 | .add("file", file) 70 | .toString(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/client/AbstractReplicationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client; 6 | 7 | import com.turn.ttorrent.tracker.simple.SimpleTracker; 8 | import com.turn.ttorrent.protocol.torrent.Torrent; 9 | import com.turn.ttorrent.protocol.torrent.TorrentCreator; 10 | import com.turn.ttorrent.protocol.test.TorrentTestUtils; 11 | import com.turn.ttorrent.test.TorrentClientTestUtils; 12 | import com.turn.ttorrent.tracker.TrackedTorrent; 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.net.InetSocketAddress; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.concurrent.CountDownLatch; 19 | import java.util.concurrent.TimeUnit; 20 | import javax.annotation.Nonnull; 21 | import org.junit.After; 22 | import org.junit.Before; 23 | import org.slf4j.Logger; 24 | import org.slf4j.LoggerFactory; 25 | 26 | /** 27 | * 28 | * @author shevek 29 | */ 30 | public class AbstractReplicationTest { 31 | 32 | private static final Logger LOG = LoggerFactory.getLogger(AbstractReplicationTest.class); 33 | protected SimpleTracker tracker; 34 | protected Torrent torrent; 35 | protected TrackedTorrent trackedTorrent; 36 | protected Client seed; 37 | protected final List leechers = new ArrayList(); 38 | 39 | @Before 40 | public void setUp() throws Exception { 41 | tracker = new SimpleTracker(new InetSocketAddress("localhost", 0)); 42 | tracker.start(); 43 | 44 | File dir = TorrentTestUtils.newTorrentDir(getClass().getSimpleName() + ".seed"); 45 | 46 | TorrentCreator creator = TorrentTestUtils.newTorrentCreator(dir, 126071); 47 | // TorrentCreator creator = TorrentTestUtils.newTorrentCreator(dir, 126); 48 | creator.setAnnounceList(tracker.getAnnounceUris()); 49 | creator.setPieceLength(512); 50 | torrent = creator.create(); 51 | 52 | trackedTorrent = tracker.addTorrent(torrent); 53 | trackedTorrent.setAnnounceInterval(60, TimeUnit.SECONDS); 54 | 55 | seed = new Client("S-"); 56 | TorrentHandler sharedTorrent = new TorrentHandler(seed, torrent, dir); 57 | sharedTorrent.setBlockLength(64); 58 | seed.addTorrent(sharedTorrent); 59 | } 60 | 61 | @After 62 | public void tearDown() throws Exception { 63 | for (Client leecher : leechers) 64 | leecher.stop(); 65 | seed.stop(); 66 | tracker.stop(); 67 | Thread.sleep(1000); // Wait for socket release. 68 | } 69 | 70 | @Nonnull 71 | protected Client leech(@Nonnull CountDownLatch latch, int i) throws IOException, InterruptedException { 72 | File d = TorrentTestUtils.newTorrentDir(getClass().getSimpleName() + ".client" + i); 73 | Client c = new Client("L-" + i + "-"); 74 | TorrentHandler sharedTorrent = new TorrentHandler(c, torrent, d); 75 | sharedTorrent.setBlockLength(64); 76 | c.addTorrent(sharedTorrent); 77 | c.addClientListener(new ReplicationCompletionListener(latch, TorrentHandler.State.SEEDING)); 78 | leechers.add(c); 79 | return c; 80 | } 81 | 82 | protected void await(@Nonnull CountDownLatch latch) throws InterruptedException { 83 | for (;;) { 84 | if (latch.await(30, TimeUnit.SECONDS)) 85 | break; 86 | seed.info(true); 87 | for (Client c : leechers) 88 | c.info(true); 89 | } 90 | } 91 | 92 | protected void testReplication(int seed_delay, int nclients) throws Exception { 93 | if (seed_delay <= 0) { 94 | seed.start(); 95 | Thread.sleep(-seed_delay); 96 | } 97 | 98 | CountDownLatch latch = new CountDownLatch(nclients); 99 | 100 | List clients = new ArrayList(); 101 | for (int i = 0; i < nclients; i++) { 102 | Client c = leech(latch, i); 103 | c.start(); 104 | clients.add(c); 105 | } 106 | 107 | if (seed_delay > 0) { 108 | Thread.sleep(seed_delay); 109 | seed.start(); 110 | } 111 | 112 | await(latch); 113 | 114 | for (Client peer : clients) 115 | TorrentClientTestUtils.assertTorrentData(seed, peer, torrent.getInfoHash()); 116 | } 117 | } -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/client/PeerExchangeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client; 6 | 7 | import com.turn.ttorrent.client.io.PeerServer; 8 | import com.turn.ttorrent.protocol.torrent.Torrent; 9 | import com.turn.ttorrent.protocol.torrent.TorrentCreator; 10 | import com.turn.ttorrent.protocol.test.TorrentTestUtils; 11 | import com.turn.ttorrent.test.TorrentClientTestUtils; 12 | import java.io.File; 13 | import java.net.InetSocketAddress; 14 | import java.net.SocketAddress; 15 | import java.util.Collections; 16 | import java.util.Map; 17 | import java.util.concurrent.CountDownLatch; 18 | import java.util.concurrent.TimeUnit; 19 | import javax.annotation.Nonnull; 20 | import org.junit.After; 21 | import org.junit.Before; 22 | import org.junit.Test; 23 | import org.slf4j.Logger; 24 | import org.slf4j.LoggerFactory; 25 | 26 | /** 27 | * 28 | * @author shevek 29 | */ 30 | public class PeerExchangeTest { 31 | 32 | private static final Logger LOG = LoggerFactory.getLogger(PeerExchangeTest.class); 33 | protected Torrent torrent; 34 | protected Client seed; 35 | protected Client[] peers; 36 | protected CountDownLatch latch; 37 | 38 | @Before 39 | public void setUp() throws Exception { 40 | SEED: 41 | { 42 | File dir = TorrentTestUtils.newTorrentDir(getClass().getSimpleName() + ".seed"); 43 | TorrentCreator creator = TorrentTestUtils.newTorrentCreator(dir, 126071); 44 | creator.setPieceLength(512); 45 | torrent = creator.create(); 46 | 47 | seed = new Client("S-"); 48 | seed.addTorrent(torrent, dir); 49 | } 50 | 51 | peers = new Client[8]; 52 | latch = new CountDownLatch(peers.length); 53 | 54 | for (int i = 0; i < peers.length; i++) { 55 | File dir = TorrentTestUtils.newTorrentDir(getClass().getSimpleName() + ".peer" + i); 56 | Client peer = new Client("C" + i + "-"); 57 | // This lets PeerServer.getPeerAddresses() return an explicit localhost 58 | // which would otherwise be ignored as "local" while walking NetworkInterfaces. 59 | // peer.getEnvironment().setLocalPeerListenAddress(new InetSocketAddress("localhost", 6882 + i)); 60 | TorrentHandler handler = peer.addTorrent(torrent, dir); 61 | peer.addClientListener(new ReplicationCompletionListener(latch, TorrentHandler.State.SEEDING)); 62 | peers[i] = peer; 63 | } 64 | } 65 | 66 | @After 67 | public void tearDown() throws Exception { 68 | seed.stop(); 69 | for (Client peer : peers) 70 | peer.stop(); 71 | Thread.sleep(1000); // Wait for socket release. 72 | } 73 | 74 | protected void await() throws InterruptedException { 75 | for (;;) { 76 | if (latch.await(5, TimeUnit.SECONDS)) 77 | break; 78 | seed.info(true); 79 | for (Client peer : peers) 80 | peer.info(true); 81 | } 82 | } 83 | 84 | @Nonnull 85 | private static InetSocketAddress toLocalhostAddress(@Nonnull PeerServer server) { 86 | InetSocketAddress in = server.getLocalAddress(); 87 | return new InetSocketAddress("localhost", in.getPort()); 88 | } 89 | 90 | private static void tellLeftAboutRight(Client left, Client right, byte[] torrentId) { 91 | SocketAddress peerAddress = toLocalhostAddress(right.getPeerServer()); 92 | Map peers = Collections.singletonMap(peerAddress, null); 93 | left.getTorrent(torrentId).getSwarmHandler().addPeers(Collections.singletonMap(peerAddress, (byte[]) null), "test"); 94 | } 95 | 96 | @Test 97 | public void testPeerExchange() throws Exception { 98 | for (Client peer : peers) 99 | peer.start(); 100 | 101 | byte[] torrentId = torrent.getInfoHash(); 102 | for (int i = 1; i < peers.length; i++) { 103 | // Tell each peer about the previous one. 104 | tellLeftAboutRight(peers[i], peers[i - 1], torrentId); 105 | } 106 | 107 | PEX: 108 | { 109 | SwarmHandler handler = peers[0].getTorrent(torrentId).getSwarmHandler(); 110 | for (;;) { 111 | LOG.info("Handler {} has {} peers, waiting for {}", handler, handler.getPeerCount(), peers.length - 1); 112 | if (handler.getPeerCount() >= peers.length - 1) // It should know everyone but itself. 113 | break; 114 | for (Client peer : peers) { 115 | peer.info(true); 116 | LOG.info("Known: " + peer.getTorrent(torrentId).getSwarmHandler().getPeers().keySet()); 117 | } 118 | Thread.sleep(5000); 119 | } 120 | LOG.info("All peers exchanged!"); 121 | } 122 | 123 | seed.start(); 124 | tellLeftAboutRight(peers[0], seed, torrentId); 125 | await(); 126 | 127 | for (Client peer : peers) 128 | TorrentClientTestUtils.assertTorrentData(seed, peer, torrentId); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/client/ReplicationCompletionListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client; 6 | 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | import java.util.concurrent.CountDownLatch; 10 | import javax.annotation.Nonnull; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | /** 15 | * 16 | * @author shevek 17 | */ 18 | public class ReplicationCompletionListener extends ClientListenerAdapter { 19 | 20 | private final Logger LOG = LoggerFactory.getLogger(ReplicationCompletionListener.class); 21 | private final CountDownLatch latch; 22 | private final TorrentHandler.State state; 23 | private final Set torrents = new HashSet(); 24 | private final Object lock = new Object(); 25 | 26 | public ReplicationCompletionListener(@Nonnull CountDownLatch latch, @Nonnull TorrentHandler.State state) { 27 | this.latch = latch; 28 | this.state = state; 29 | } 30 | 31 | @Override 32 | public void clientStateChanged(Client client, Client.State state) { 33 | LOG.info("ClientState: client=" + client + ", state=" + state); 34 | } 35 | 36 | @Override 37 | public void torrentStateChanged(Client client, TorrentHandler torrent, TorrentHandler.State state) { 38 | LOG.info("TorrentState: client=" + client + ", torrent=" + torrent + ", state=" + state + ", latch=" + latch.toString()); 39 | if (this.state.equals(state)) { 40 | LOG.info("Counting down for " + client.getLocalPeerName()); 41 | synchronized (lock) { 42 | if (!torrents.add(torrent)) 43 | throw new IllegalArgumentException("Duplicate count for " + client.getLocalPeerName() + " / " + torrent); 44 | } 45 | latch.countDown(); 46 | } 47 | /* 48 | switch (state) { 49 | case DONE: 50 | case SEEDING: 51 | try { 52 | client.stop(); 53 | } catch (Exception e) { 54 | throw Throwables.propagate(e); 55 | } 56 | break; 57 | } 58 | */ 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/client/ReplicationHugeSwarmTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client; 6 | 7 | import org.junit.Test; 8 | 9 | /** 10 | * 11 | * @author shevek 12 | */ 13 | public class ReplicationHugeSwarmTest extends AbstractReplicationTest { 14 | 15 | @Test 16 | public void testHugeSwarm() throws Exception { 17 | testReplication(-500, 16); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/client/ReplicationMultipleEarlyTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | import org.junit.Test; 9 | 10 | /** 11 | * 12 | * @author shevek 13 | */ 14 | public class ReplicationMultipleEarlyTest extends AbstractReplicationTest { 15 | 16 | @Test 17 | public void testReplicationMultipleEarly() throws Exception { 18 | trackedTorrent.setAnnounceInterval(1, TimeUnit.MINUTES); 19 | testReplication(-500, 3); 20 | } 21 | } -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/client/ReplicationMultipleLateTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client; 6 | 7 | import org.junit.Test; 8 | 9 | /** 10 | * 11 | * @author shevek 12 | */ 13 | public class ReplicationMultipleLateTest extends AbstractReplicationTest { 14 | 15 | @Test 16 | public void testReplicationMultipleLate() throws Exception { 17 | testReplication(500, 3); 18 | } 19 | } -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/client/ReplicationSingleEarlyTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | import org.junit.Test; 9 | 10 | /** 11 | * 12 | * @author shevek 13 | */ 14 | public class ReplicationSingleEarlyTest extends AbstractReplicationTest { 15 | 16 | @Test 17 | public void testReplicationSingleEarly() throws Exception { 18 | trackedTorrent.setAnnounceInterval(1, TimeUnit.MINUTES); 19 | testReplication(-500, 1); 20 | } 21 | } -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/client/ReplicationSingleLateTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client; 6 | 7 | import org.junit.Test; 8 | 9 | /** 10 | * 11 | * @author shevek 12 | */ 13 | public class ReplicationSingleLateTest extends AbstractReplicationTest { 14 | 15 | @Test 16 | public void testReplicationSingleLate() throws Exception { 17 | testReplication(500, 1); 18 | } 19 | } -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/client/ReplicationTimeoutTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client; 6 | 7 | import com.turn.ttorrent.client.io.PeerMessage; 8 | import com.turn.ttorrent.client.peer.Instrumentation; 9 | import com.turn.ttorrent.client.peer.PeerHandler; 10 | import java.util.Random; 11 | import java.util.concurrent.CountDownLatch; 12 | import java.util.concurrent.TimeUnit; 13 | import org.junit.Test; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | /** 18 | * 19 | * @author shevek 20 | */ 21 | public class ReplicationTimeoutTest extends AbstractReplicationTest { 22 | 23 | private static final Logger LOG = LoggerFactory.getLogger(ReplicationTimeoutTest.class); 24 | 25 | @Test 26 | public void testReplicationTimeout() throws Exception { 27 | trackedTorrent.setAnnounceInterval(10, TimeUnit.MINUTES); 28 | 29 | final Random r = seed.getEnvironment().getRandom(); 30 | seed.getEnvironment().setInstrumentation(new Instrumentation() { 31 | boolean dropped = false; 32 | 33 | @Override 34 | public synchronized PeerMessage.RequestMessage instrumentBlockRequest(PeerHandler peer, PeerPieceProvider provider, PeerMessage.RequestMessage request) { 35 | if (request == null) 36 | return null; 37 | if (!dropped) { 38 | // Drop at least one 39 | LOG.info("Drop " + request); 40 | dropped = true; 41 | return null; 42 | } 43 | if (r.nextFloat() < 0.02) { 44 | // And bin 1% of remaining block requests 45 | LOG.info("Drop " + request); 46 | return null; 47 | } 48 | return super.instrumentBlockRequest(peer, provider, request); 49 | } 50 | }); 51 | seed.start(); 52 | 53 | // Let the seed contact the tracker first. 54 | Thread.sleep(100); 55 | 56 | CountDownLatch latch = new CountDownLatch(1); 57 | Client c = leech(latch, 0); 58 | c.start(); 59 | await(latch); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/client/TorrentHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client; 6 | 7 | import com.turn.ttorrent.protocol.torrent.Torrent; 8 | import com.turn.ttorrent.protocol.torrent.TorrentCreator; 9 | import com.turn.ttorrent.protocol.test.TorrentTestUtils; 10 | import java.io.File; 11 | import org.junit.Test; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import static org.junit.Assert.*; 15 | 16 | /** 17 | * 18 | * @author shevek 19 | */ 20 | public class TorrentHandlerTest { 21 | 22 | private static final Logger LOG = LoggerFactory.getLogger(TorrentHandlerTest.class); 23 | 24 | private TorrentHandler test(Torrent torrent, File parent) throws Exception { 25 | Client client = new Client(getClass().getSimpleName()); 26 | TorrentHandler torrentHandler = client.addTorrent(torrent, parent); 27 | client.getEnvironment().start(); 28 | try { 29 | torrentHandler.init(); 30 | LOG.info("Available is " + torrentHandler.getCompletedPieces()); 31 | assertTrue("We have pieces.", torrentHandler.getPieceCount() > 0); 32 | return torrentHandler; 33 | } finally { 34 | client.getEnvironment().stop(); 35 | } 36 | } 37 | 38 | @Test 39 | public void testMultiFileSeed() throws Exception { 40 | File d_seed = TorrentTestUtils.newTorrentDir("TorrentHandlerTest"); 41 | TorrentCreator creator = TorrentTestUtils.newTorrentCreator(d_seed, 12345678); 42 | Torrent torrent = creator.create(); 43 | TorrentHandler torrentHandler = test(torrent, d_seed); 44 | assertTrue("We are complete, i.e. a seed.", torrentHandler.isComplete()); 45 | } 46 | 47 | @Test 48 | public void testSingleFileSeed() throws Exception { 49 | File d_seed = TorrentTestUtils.newTorrentDir("TorrentHandlerTest"); 50 | TorrentCreator creator = TorrentTestUtils.newTorrentCreator(d_seed, 12345678); 51 | Torrent torrent = creator.create(); 52 | File f_seed = new File(d_seed, TorrentTestUtils.FILENAME); 53 | TorrentHandler torrentHandler = test(torrent, f_seed); 54 | assertTrue("We are complete, i.e. a seed.", torrentHandler.isComplete()); 55 | } 56 | 57 | @Test 58 | public void testMultiFileLeech() throws Exception { 59 | File d_seed = TorrentTestUtils.newTorrentDir("TorrentHandlerTest"); 60 | TorrentCreator creator = TorrentTestUtils.newTorrentCreator(d_seed, 12345678); 61 | Torrent torrent = creator.create(); 62 | File d_leech = TorrentTestUtils.newTorrentDir("TorrentHandlerTest"); 63 | TorrentHandler torrentHandler = test(torrent, d_leech); 64 | assertEquals("We have no pieces.", 0, torrentHandler.getCompletedPieceCount()); 65 | assertFalse("We are not complete, i.e. a seed.", torrentHandler.isComplete()); 66 | } 67 | 68 | @Test 69 | public void testSingleFileLeech() throws Exception { 70 | File d_seed = TorrentTestUtils.newTorrentDir("TorrentHandlerTest"); 71 | TorrentCreator creator = TorrentTestUtils.newTorrentCreator(d_seed, 12345678); 72 | Torrent torrent = creator.create(); 73 | File d_leech = TorrentTestUtils.newTorrentDir("TorrentHandlerTest"); 74 | File f_leech = new File(d_leech, TorrentTestUtils.FILENAME); 75 | TorrentHandler torrentHandler = test(torrent, f_leech); 76 | assertEquals("We have no pieces.", 0, torrentHandler.getCompletedPieceCount()); 77 | assertFalse("We are not complete, i.e. a seed.", torrentHandler.isComplete()); 78 | } 79 | } -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/client/UbuntuImageDownloadTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client; 6 | 7 | import com.google.common.io.Files; 8 | import com.google.common.io.Resources; 9 | import com.turn.ttorrent.protocol.torrent.Torrent; 10 | import com.turn.ttorrent.tracker.client.TorrentMetadataProvider; 11 | import java.io.File; 12 | import java.net.URL; 13 | import java.util.concurrent.CountDownLatch; 14 | import java.util.concurrent.TimeUnit; 15 | import org.junit.Ignore; 16 | import org.junit.Test; 17 | 18 | /** 19 | * 20 | * @author joel 21 | */ 22 | public class UbuntuImageDownloadTest { 23 | 24 | @Ignore 25 | @Test 26 | public void testDownloadImage() throws Exception { 27 | File torrentFile = File.createTempFile("ttorrent-ubuntu", ".torrent"); 28 | File image = File.createTempFile("ttorrent-ubuntu", ".img"); 29 | //TODO(jfriedly): Remove when this works. 30 | torrentFile.deleteOnExit(); 31 | image.deleteOnExit(); 32 | URL url = new URL("http://releases.ubuntu.com/14.10/ubuntu-14.10-desktop-amd64.iso.torrent"); 33 | Resources.asByteSource(url).copyTo(Files.asByteSink(torrentFile)); 34 | 35 | Client c = new Client(null); 36 | Torrent torrent = new Torrent(torrentFile); 37 | c.addTorrent(torrent, image); 38 | 39 | CountDownLatch latch = new CountDownLatch(1); 40 | c.addClientListener(new ReplicationCompletionListener(latch, TorrentMetadataProvider.State.SEEDING)); 41 | 42 | try { 43 | c.start(); 44 | for (;;) { 45 | if (latch.await(10, TimeUnit.SECONDS)) 46 | break; 47 | c.info(true); 48 | } 49 | } finally { 50 | c.stop(); 51 | } 52 | 53 | torrentFile.delete(); 54 | image.delete(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/client/io/PeerFrameDecoderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client.io; 6 | 7 | import io.netty.buffer.ByteBuf; 8 | import io.netty.buffer.Unpooled; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import org.junit.Test; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | /** 15 | * 16 | * @author shevek 17 | */ 18 | public class PeerFrameDecoderTest { 19 | 20 | private static final Logger LOG = LoggerFactory.getLogger(PeerFrameDecoderTest.class); 21 | 22 | @Test 23 | public void testDecoder() throws Exception { 24 | ByteBuf in = Unpooled.buffer(27); 25 | in.writeInt(6); 26 | in.writeBytes(new byte[]{1, 2, 3, 4, 5, 6}); 27 | 28 | PeerFrameDecoder decoder = new PeerFrameDecoder() { 29 | @Override 30 | protected ByteBuf extractFrame(ChannelHandlerContext ctx, ByteBuf buffer, int index, int length) { 31 | ByteBuf frame = Unpooled.buffer(length); 32 | frame.writeBytes(buffer, index, length); 33 | return frame; 34 | } 35 | }; 36 | ByteBuf out = decoder._decode(null, in); 37 | 38 | PeerMessageTest.Formatter formatter = new PeerMessageTest.Formatter(); 39 | LOG.info(formatter.format("decoded", out)); 40 | } 41 | } -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/client/io/PeerMessageTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client.io; 6 | 7 | import com.google.common.primitives.UnsignedBytes; 8 | import io.netty.buffer.ByteBuf; 9 | import io.netty.buffer.Unpooled; 10 | import io.netty.handler.logging.LoggingHandler; 11 | import java.util.BitSet; 12 | import javax.annotation.Nonnull; 13 | import org.junit.Test; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import static org.junit.Assert.*; 17 | 18 | /** 19 | * 20 | * @author shevek 21 | */ 22 | public class PeerMessageTest { 23 | 24 | private static final Logger LOG = LoggerFactory.getLogger(PeerMessageTest.class); 25 | 26 | public static class Formatter extends LoggingHandler { 27 | 28 | public String format(String name, ByteBuf buf) { 29 | return super.formatByteBuf(name, buf); 30 | } 31 | } 32 | 33 | @Nonnull 34 | private T testMessage(@Nonnull T in) throws Exception { 35 | Formatter formatter = new Formatter(); 36 | 37 | ByteBuf buf = Unpooled.buffer(1234); 38 | 39 | in.toWire(buf, null); 40 | LOG.info(in + " -> " + formatter.format(in.getClass().getSimpleName(), buf)); 41 | 42 | T out = (T) in.getClass().newInstance(); 43 | buf.readByte(); 44 | out.fromWire(buf); 45 | assertEquals(0, buf.readableBytes()); 46 | 47 | return out; 48 | } 49 | 50 | @Test 51 | public void testChokeMessage() throws Exception { 52 | testMessage(new PeerMessage.ChokeMessage()); 53 | } 54 | 55 | @Test 56 | public void testBitfieldMessage() throws Exception { 57 | testMessage(new PeerMessage.BitfieldMessage(new BitSet())); 58 | BitSet set = new BitSet(); 59 | set.set(0); 60 | testMessage(new PeerMessage.BitfieldMessage(set)); 61 | set.set(31); 62 | testMessage(new PeerMessage.BitfieldMessage(set)); 63 | } 64 | 65 | // I can't believe I have to write this. 66 | private byte[] toBytes(int[] in) { 67 | byte[] out = new byte[in.length]; 68 | for (int i = 0; i < in.length; i++) 69 | out[i] = UnsignedBytes.checkedCast(in[i]); 70 | return out; 71 | } 72 | 73 | @Test 74 | public void testBitfieldMessageDecode() throws Exception { 75 | int[] packet = new int[]{ 76 | 0x00, 0x00, 0x00, 0xe6, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 77 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 78 | 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x08, 0x04, 0x00, 0x00, 0x00, 79 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 80 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 81 | 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 82 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 83 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 84 | 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 85 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 86 | 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 87 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 88 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 89 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 90 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80 91 | }; 92 | PeerMessage.BitfieldMessage message = new PeerMessage.BitfieldMessage(); 93 | ByteBuf buf = Unpooled.wrappedBuffer(toBytes(packet)); 94 | buf.readInt(); 95 | message.fromWire(buf); 96 | assertEquals(0, buf.readableBytes()); 97 | 98 | testMessage(message); 99 | } 100 | 101 | @Test 102 | public void testHaveMessage() throws Exception { 103 | testMessage(new PeerMessage.HaveMessage(0)); 104 | testMessage(new PeerMessage.HaveMessage(0x1234)); 105 | testMessage(new PeerMessage.HaveMessage(0x12345678)); 106 | } 107 | } -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/client/peer/PeerHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client.peer; 6 | 7 | import com.turn.ttorrent.client.Client; 8 | import com.turn.ttorrent.client.io.PeerClientHandshakeHandler; 9 | import com.turn.ttorrent.client.io.PeerServerHandshakeHandler; 10 | import com.turn.ttorrent.protocol.test.TestPeerIdentityProvider; 11 | import com.turn.ttorrent.test.TestPeerPieceProvider; 12 | import com.turn.ttorrent.protocol.torrent.Torrent; 13 | import com.turn.ttorrent.protocol.test.TorrentTestUtils; 14 | import com.turn.ttorrent.tracker.client.test.TestPeerAddressProvider; 15 | import io.netty.bootstrap.Bootstrap; 16 | import io.netty.bootstrap.ServerBootstrap; 17 | import io.netty.channel.Channel; 18 | import io.netty.channel.local.LocalAddress; 19 | import io.netty.channel.local.LocalChannel; 20 | import io.netty.channel.local.LocalEventLoopGroup; 21 | import io.netty.channel.local.LocalServerChannel; 22 | import java.io.File; 23 | import java.util.Arrays; 24 | import org.easymock.EasyMock; 25 | import org.junit.Test; 26 | import org.slf4j.Logger; 27 | import org.slf4j.LoggerFactory; 28 | 29 | /** 30 | * 31 | * @author shevek 32 | */ 33 | public class PeerHandlerTest { 34 | 35 | private static final Logger LOG = LoggerFactory.getLogger(PeerHandlerTest.class); 36 | 37 | @Test 38 | public void testPeerHandler() throws Exception { 39 | byte[] peerId = Arrays.copyOf(new byte[]{1, 2, 3, 4, 5, 6}, 20); 40 | File dir = TorrentTestUtils.newTorrentDir("PeerHandlerTest-server"); 41 | Torrent torrent = TorrentTestUtils.newTorrent(dir, 12345); 42 | 43 | LocalAddress address = new LocalAddress("test"); 44 | LocalEventLoopGroup group = new LocalEventLoopGroup(1); 45 | SERVER: 46 | { 47 | Client client = new Client(); 48 | client.addTorrent(torrent, dir); 49 | ServerBootstrap b = new ServerBootstrap() 50 | .group(group) 51 | .channel(LocalServerChannel.class) 52 | .childHandler(new PeerServerHandshakeHandler(client)); 53 | b.bind(address).sync(); 54 | } 55 | 56 | PeerConnectionListener connectionListener = EasyMock.createMock(PeerConnectionListener.class); 57 | 58 | Channel channel; 59 | CLIENT: 60 | { 61 | Bootstrap b = new Bootstrap() 62 | .group(group) 63 | .channel(LocalChannel.class) 64 | .handler(new PeerClientHandshakeHandler(connectionListener, torrent.getInfoHash(), peerId)); 65 | channel = b.connect(address).sync().channel(); 66 | } 67 | 68 | TestPeerAddressProvider addressProvider = new TestPeerAddressProvider(); 69 | TestPeerPieceProvider pieceProvider = new TestPeerPieceProvider(torrent); 70 | PeerExistenceListener existenceListener = EasyMock.createMock(PeerExistenceListener.class); 71 | PeerActivityListener activityListener = EasyMock.createMock(PeerActivityListener.class); 72 | PeerHandler peerHandler = new PeerHandler(channel, peerId, new byte[8], addressProvider, pieceProvider, existenceListener, connectionListener, activityListener); 73 | 74 | EasyMock.reset(activityListener, connectionListener); 75 | EasyMock.replay(activityListener, connectionListener); 76 | peerHandler.run("test 0"); 77 | EasyMock.verify(activityListener, connectionListener); 78 | 79 | if (true) 80 | return; 81 | 82 | EasyMock.reset(activityListener, connectionListener); 83 | EasyMock.replay(activityListener, connectionListener); 84 | pieceProvider.setPieceHandler(0); 85 | peerHandler.run("test 1"); 86 | EasyMock.verify(activityListener, connectionListener); 87 | 88 | EasyMock.reset(activityListener, connectionListener); 89 | EasyMock.replay(activityListener, connectionListener); 90 | peerHandler.run("test 2"); 91 | EasyMock.verify(activityListener, connectionListener); 92 | 93 | } 94 | } -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/client/peer/PieceHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client.peer; 6 | 7 | import com.turn.ttorrent.test.TestPeerPieceProvider; 8 | import com.google.common.math.IntMath; 9 | import com.turn.ttorrent.client.PeerPieceProvider; 10 | import com.turn.ttorrent.protocol.torrent.Torrent; 11 | import com.turn.ttorrent.protocol.test.TorrentTestUtils; 12 | import java.io.File; 13 | import java.math.RoundingMode; 14 | import java.util.Iterator; 15 | import org.junit.Test; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | import static org.junit.Assert.*; 19 | 20 | /** 21 | * 22 | * @author shevek 23 | */ 24 | public class PieceHandlerTest { 25 | 26 | private static final Logger LOG = LoggerFactory.getLogger(PieceHandlerTest.class); 27 | 28 | @Test 29 | public void testPiece() throws Exception { 30 | File dir = TorrentTestUtils.newTorrentDir("PieceHandlerTest"); 31 | Torrent torrent = TorrentTestUtils.newTorrent(dir, 465432); 32 | 33 | PeerPieceProvider provider = new TestPeerPieceProvider(torrent); 34 | PieceHandler pieceHandler = new PieceHandler(provider, 0); 35 | int blockCount = IntMath.divide(torrent.getPieceLength(0), PieceHandler.DEFAULT_BLOCK_SIZE, RoundingMode.UP); 36 | Iterator it = pieceHandler.iterator(); 37 | 38 | for (int i = 0; i < blockCount; i++) { 39 | assertTrue(it.hasNext()); 40 | PieceHandler.AnswerableRequestMessage request = it.next(); 41 | LOG.info("Request is " + request); 42 | assertNotNull(request); 43 | request.validate(provider); 44 | } 45 | 46 | for (int i = 0; i < 2; i++) { 47 | assertFalse(it.hasNext()); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/client/storage/FileCollectionStorageTest.java: -------------------------------------------------------------------------------- 1 | package com.turn.ttorrent.client.storage; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.IOException; 6 | import java.nio.ByteBuffer; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import org.junit.Test; 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * User: loyd 15 | * Date: 11/24/13 16 | */ 17 | public class FileCollectionStorageTest { 18 | 19 | @Test 20 | public void testSelect() throws Exception { 21 | final File file1 = File.createTempFile(getClass().getSimpleName(), ".0.tmp"); 22 | file1.deleteOnExit(); 23 | final File file2 = File.createTempFile(getClass().getSimpleName(), ".1.tmp"); 24 | file2.deleteOnExit(); 25 | 26 | final List files = new ArrayList(); 27 | files.add(new FileStorage(file1, 0, 2)); 28 | files.add(new FileStorage(file2, 2, 2)); 29 | final FileCollectionStorage storage = new FileCollectionStorage(files); 30 | // since all of these files already exist, we are considered finished 31 | assertFalse(storage.isFinished()); 32 | 33 | // write to first file works 34 | write(new byte[]{1, 2}, 0, storage); 35 | check(new byte[]{1, 2}, file1); 36 | 37 | // write to second file works 38 | write(new byte[]{5, 6}, 2, storage); 39 | check(new byte[]{5, 6}, file2); 40 | 41 | // write to two files works 42 | write(new byte[]{8, 9, 10, 11}, 0, storage); 43 | check(new byte[]{8, 9}, file1); 44 | check(new byte[]{10, 11}, file2); 45 | 46 | // make sure partial write into next file works 47 | write(new byte[]{100, 101, 102}, 0, storage); 48 | check(new byte[]{102, 11}, file2); 49 | } 50 | 51 | private void write(byte[] bytes, int offset, FileCollectionStorage storage) throws IOException { 52 | storage.write(ByteBuffer.wrap(bytes), offset); 53 | storage.flush(); 54 | } 55 | 56 | private void check(byte[] bytes, File f) throws IOException { 57 | final byte[] temp = new byte[bytes.length]; 58 | assertEquals(new FileInputStream(f).read(temp), temp.length); 59 | assertArrayEquals(temp, bytes); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/client/storage/FileStorageTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.client.storage; 6 | 7 | import com.google.common.base.Throwables; 8 | import com.google.common.io.Files; 9 | import com.turn.ttorrent.protocol.test.TorrentTestUtils; 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.nio.ByteBuffer; 13 | import java.util.ArrayList; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | import java.util.Random; 17 | import java.util.concurrent.CountDownLatch; 18 | import java.util.concurrent.ExecutorService; 19 | import java.util.concurrent.Executors; 20 | import java.util.concurrent.TimeUnit; 21 | import org.junit.Test; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | import static org.junit.Assert.*; 25 | 26 | /** 27 | * 28 | * @author shevek 29 | */ 30 | public class FileStorageTest { 31 | 32 | private static final Logger LOG = LoggerFactory.getLogger(FileStorageTest.class); 33 | 34 | @Test 35 | public void testStorage() throws Exception { 36 | File root = TorrentTestUtils.newTorrentDir("filestorage"); 37 | 38 | final List storage = new ArrayList(); 39 | for (int i = 0; i < 5; i++) { 40 | FileStorage s = new FileStorage(new File(root, "file" + i), 64); 41 | storage.add(s); 42 | } 43 | 44 | ExecutorService executor = Executors.newCachedThreadPool(); 45 | final Random r = new Random(); 46 | 47 | int ntasks = 100; 48 | final CountDownLatch latch = new CountDownLatch(ntasks); 49 | for (int i = 0; i < ntasks; i++) { 50 | executor.execute(new Runnable() { 51 | @Override 52 | public void run() { 53 | try { 54 | FileStorage s = storage.get(r.nextInt(storage.size())); 55 | ByteBuffer buf = ByteBuffer.wrap(new byte[]{1, 2, 3, 4, 5, 6, 7, 8}); 56 | s.write(buf, r.nextInt(48)); 57 | } catch (IOException e) { 58 | throw Throwables.propagate(e); 59 | } finally { 60 | latch.countDown(); 61 | } 62 | } 63 | }); 64 | } 65 | latch.await(); 66 | executor.shutdown(); 67 | executor.awaitTermination(5, TimeUnit.SECONDS); 68 | 69 | for (FileStorage s : storage) { 70 | ByteBuffer buf = ByteBuffer.wrap(new byte[]{1, 2, 3, 4, 5, 6, 7, 8}); 71 | s.write(buf, 0); 72 | 73 | s.finish(); 74 | 75 | byte[] data = Files.toByteArray(s.getFile()); 76 | LOG.info("File contains " + Arrays.toString(data)); 77 | for (int i = 0; i < 8; i++) 78 | assertEquals(i + 1, data[i]); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/test/LoggingInvocationHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.test; 6 | 7 | import com.google.common.reflect.AbstractInvocationHandler; 8 | import java.lang.reflect.Method; 9 | import java.lang.reflect.Proxy; 10 | import java.util.Arrays; 11 | import javax.annotation.Nonnull; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | /** 16 | * 17 | * @author shevek 18 | */ 19 | public class LoggingInvocationHandler extends AbstractInvocationHandler { 20 | 21 | private static final Logger LOG = LoggerFactory.getLogger(LoggingInvocationHandler.class); 22 | 23 | @Nonnull 24 | public static T create(@Nonnull Class type) { 25 | Object proxy = Proxy.newProxyInstance( 26 | type.getClassLoader(), 27 | new Class[]{type}, 28 | new LoggingInvocationHandler()); 29 | return type.cast(proxy); 30 | } 31 | 32 | @Override 33 | protected Object handleInvocation(Object proxy, Method method, Object[] args) throws Throwable { 34 | LOG.info(method + "(" + Arrays.toString(args) + ")"); 35 | return null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/test/TestPeerExistenceListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.test; 6 | 7 | import com.google.common.base.Functions; 8 | import com.google.common.collect.Iterables; 9 | import com.google.common.collect.Maps; 10 | import com.turn.ttorrent.client.peer.PeerExistenceListener; 11 | import java.net.SocketAddress; 12 | import java.util.HashSet; 13 | import java.util.Map; 14 | import java.util.Set; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | /** 19 | * 20 | * @author shevek 21 | */ 22 | public class TestPeerExistenceListener implements PeerExistenceListener { 23 | 24 | private static final Logger LOG = LoggerFactory.getLogger(TestPeerExistenceListener.class); 25 | private final Set addresses = new HashSet(); 26 | 27 | @Override 28 | public Map getPeers() { 29 | synchronized (addresses) { 30 | return Maps.asMap(addresses, Functions.constant(null)); 31 | } 32 | } 33 | 34 | @Override 35 | public void addPeers(Map peers, String reason) { 36 | LOG.info("Added " + reason + ": " + peers); 37 | synchronized (addresses) { 38 | Iterables.addAll(addresses, peers.keySet()); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/test/TestPeerPieceProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.test; 6 | 7 | import com.turn.ttorrent.client.PeerPieceProvider; 8 | import com.turn.ttorrent.client.peer.PieceHandler; 9 | import com.turn.ttorrent.client.peer.PeerHandler; 10 | import com.turn.ttorrent.client.peer.Instrumentation; 11 | import com.turn.ttorrent.protocol.torrent.Torrent; 12 | import com.turn.ttorrent.tracker.client.test.TestPeerAddressProvider; 13 | import java.io.IOException; 14 | import java.nio.ByteBuffer; 15 | import java.util.BitSet; 16 | 17 | /** 18 | * 19 | * @author shevek 20 | */ 21 | public class TestPeerPieceProvider extends TestPeerAddressProvider implements PeerPieceProvider { 22 | 23 | private final Instrumentation instrumentation = new Instrumentation(); 24 | private final Torrent torrent; 25 | private final BitSet completedPieces; 26 | private PieceHandler pieceHandler; 27 | private final Object lock = new Object(); 28 | 29 | public TestPeerPieceProvider(Torrent torrent) { 30 | this.torrent = torrent; 31 | this.completedPieces = new BitSet(torrent.getPieceCount()); 32 | } 33 | 34 | @Override 35 | public Instrumentation getInstrumentation() { 36 | return instrumentation; 37 | } 38 | 39 | @Override 40 | public int getPieceCount() { 41 | return torrent.getPieceCount(); 42 | } 43 | 44 | @Override 45 | public int getPieceLength(int index) { 46 | return torrent.getPieceLength(index); 47 | } 48 | 49 | @Override 50 | public int getBlockLength() { 51 | return PieceHandler.DEFAULT_BLOCK_SIZE; 52 | } 53 | 54 | @Override 55 | public BitSet getCompletedPieces() { 56 | synchronized (lock) { 57 | return (BitSet) completedPieces.clone(); 58 | } 59 | } 60 | 61 | @Override 62 | public boolean isCompletedPiece(int index) { 63 | synchronized (lock) { 64 | return completedPieces.get(index); 65 | } 66 | } 67 | 68 | @Override 69 | public void andNotCompletedPieces(BitSet out) { 70 | synchronized (lock) { 71 | out.andNot(completedPieces); 72 | } 73 | } 74 | 75 | @Override 76 | public Iterable getNextPieceHandler(PeerHandler peer, BitSet interesting) { 77 | synchronized (lock) { 78 | PieceHandler out = pieceHandler; 79 | pieceHandler = null; 80 | return out; 81 | } 82 | } 83 | 84 | @Override 85 | public int addRequestTimeout(Iterable requests) { 86 | throw new UnsupportedOperationException("Not supported yet."); 87 | } 88 | 89 | public void setPieceHandler(PieceHandler pieceHandler) { 90 | synchronized (lock) { 91 | this.pieceHandler = pieceHandler; 92 | } 93 | } 94 | 95 | public void setPieceHandler(int piece) { 96 | setPieceHandler(new PieceHandler(this, piece)); 97 | } 98 | 99 | @Override 100 | public void readBlock(ByteBuffer block, int piece, int offset) throws IOException { 101 | // Apparently fill the block. 102 | block.position(block.limit()); 103 | } 104 | 105 | @Override 106 | public void writeBlock(ByteBuffer block, int piece, int offset) throws IOException { 107 | // Apparently consume the block. 108 | block.position(block.limit()); 109 | } 110 | 111 | @Override 112 | public boolean validateBlock(ByteBuffer block, int piece) throws IOException { 113 | return true; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /ttorrent-client/src/test/java/com/turn/ttorrent/test/TorrentClientTestUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.test; 6 | 7 | import com.turn.ttorrent.client.Client; 8 | import com.turn.ttorrent.client.TorrentHandler; 9 | import com.turn.ttorrent.client.storage.ByteStorage; 10 | import io.netty.util.ResourceLeakDetector; 11 | import java.io.IOException; 12 | import java.nio.ByteBuffer; 13 | import javax.annotation.Nonnull; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import static org.junit.Assert.*; 17 | 18 | /** 19 | * 20 | * @author shevek 21 | */ 22 | public class TorrentClientTestUtils { 23 | 24 | private static final Logger LOG = LoggerFactory.getLogger(TorrentClientTestUtils.class); 25 | 26 | static { 27 | ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID); 28 | } 29 | 30 | public static void assertTorrentData(@Nonnull Client c0, @Nonnull Client c1, @Nonnull byte[] torrentId) throws IOException { 31 | TorrentHandler h0 = c0.getTorrent(torrentId); 32 | ByteStorage s0 = h0.getBucket(); 33 | ByteBuffer b0 = ByteBuffer.allocate(64 * 1024); 34 | 35 | TorrentHandler h1 = c1.getTorrent(torrentId); 36 | ByteStorage s1 = h1.getBucket(); 37 | ByteBuffer b1 = ByteBuffer.allocate(b0.capacity()); 38 | 39 | for (long i = 0; i < h0.getSize(); i += b0.capacity()) { 40 | long len = Math.min(h0.getSize() - i, b0.capacity()); 41 | LOG.info("Compare " + len + " bytes at " + i); 42 | 43 | b0.clear(); 44 | b0.limit((int) len); 45 | s0.read(b0, i); 46 | 47 | b1.clear(); 48 | b1.limit((int) len); 49 | s1.read(b1, i); 50 | 51 | assertArrayEquals(s0 + " != " + s1 + " @" + i, b0.array(), b1.array()); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ttorrent-protocol/build.gradle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shevek/ttorrent/f8d80c6df4a3bc4532a7b1d363636b6dd4137f03/ttorrent-protocol/build.gradle -------------------------------------------------------------------------------- /ttorrent-protocol/src/main/java/com/turn/ttorrent/protocol/PeerIdentityProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.protocol; 6 | 7 | import javax.annotation.Nonnull; 8 | 9 | /** 10 | * 11 | * @author shevek 12 | */ 13 | public interface PeerIdentityProvider { 14 | 15 | @Nonnull 16 | public byte[] getLocalPeerId(); 17 | 18 | @Nonnull 19 | public String getLocalPeerName(); 20 | } 21 | -------------------------------------------------------------------------------- /ttorrent-protocol/src/main/java/com/turn/ttorrent/protocol/bcodec/AbstractBEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 shevek. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.protocol.bcodec; 17 | 18 | import com.google.common.base.Charsets; 19 | import java.io.IOException; 20 | import java.util.ArrayList; 21 | import java.util.Collections; 22 | import java.util.List; 23 | import java.util.Map; 24 | import javax.annotation.Nonnull; 25 | 26 | /** 27 | * B-encoding encoder. 28 | * 29 | *

30 | * This class provides utility methods to encode objects and 31 | * {@link BEValue}s to B-encoding into a provided output stream. 32 | *

33 | * 34 | * @see B-encoding specification 35 | * @author shevek 36 | */ 37 | public abstract class AbstractBEncoder { 38 | 39 | protected abstract void writeByte(int b) 40 | throws IOException; 41 | 42 | protected abstract void writeBytes(@Nonnull byte[] b) 43 | throws IOException; 44 | 45 | @SuppressWarnings("unchecked") 46 | public void bencode(@Nonnull Object o) 47 | throws IOException { 48 | if (o instanceof BEValue) 49 | o = ((BEValue) o).getValue(); 50 | 51 | if (o instanceof String) { 52 | bencode((String) o); 53 | } else if (o instanceof byte[]) { 54 | bencode((byte[]) o); 55 | } else if (o instanceof Number) { 56 | bencode((Number) o); 57 | } else if (o instanceof List) { 58 | bencode((List) o); 59 | } else if (o instanceof Map) { 60 | bencode((Map) o); 61 | } else { 62 | throw new IllegalArgumentException("Cannot bencode: " + o.getClass()); 63 | } 64 | } 65 | 66 | public void bencode(@Nonnull String s) 67 | throws IOException { 68 | byte[] b = s.getBytes(Charsets.UTF_8); 69 | bencode(b); 70 | } 71 | 72 | public void bencode(@Nonnull Number n) 73 | throws IOException { 74 | writeByte('i'); 75 | String s = n.toString(); 76 | writeBytes(s.getBytes(Charsets.UTF_8)); 77 | writeByte('e'); 78 | } 79 | 80 | public void bencode(@Nonnull List l) 81 | throws IOException { 82 | writeByte('l'); 83 | for (BEValue value : l) 84 | bencode(value); 85 | writeByte('e'); 86 | } 87 | 88 | public void bencode(@Nonnull byte[] b) 89 | throws IOException { 90 | String l = Integer.toString(b.length); 91 | writeBytes(l.getBytes(Charsets.UTF_8)); 92 | writeByte(':'); 93 | writeBytes(b); 94 | } 95 | 96 | public void bencode(@Nonnull Map m) 97 | throws IOException { 98 | writeByte('d'); 99 | // Keys must be sorted. 100 | List keys = new ArrayList(m.keySet()); 101 | Collections.sort(keys); 102 | for (String key : keys) { 103 | bencode(key); 104 | bencode(m.get(key)); 105 | } 106 | writeByte('e'); 107 | } 108 | } -------------------------------------------------------------------------------- /ttorrent-protocol/src/main/java/com/turn/ttorrent/protocol/bcodec/BEUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.protocol.bcodec; 6 | 7 | import com.google.common.base.Charsets; 8 | import java.nio.charset.Charset; 9 | import javax.annotation.CheckForNull; 10 | 11 | /** 12 | * 13 | * @author shevek 14 | */ 15 | public class BEUtils { 16 | 17 | /** The query parameters encoding when parsing byte strings. */ 18 | public static final Charset BYTE_ENCODING = Charsets.ISO_8859_1; 19 | public static final String BYTE_ENCODING_NAME = BYTE_ENCODING.name(); 20 | 21 | @CheckForNull 22 | public static String getString(@CheckForNull BEValue value) 23 | throws InvalidBEncodingException { 24 | if (value == null) 25 | return null; 26 | return value.getString(BYTE_ENCODING); 27 | } 28 | 29 | @CheckForNull 30 | public static byte[] getBytes(@CheckForNull BEValue value) 31 | throws InvalidBEncodingException { 32 | if (value == null) 33 | return null; 34 | return value.getBytes(); 35 | } 36 | 37 | public static int getInt(@CheckForNull BEValue value, int dflt) 38 | throws InvalidBEncodingException { 39 | if (value == null) 40 | return dflt; 41 | return value.getInt(); 42 | } 43 | 44 | private BEUtils() { 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ttorrent-protocol/src/main/java/com/turn/ttorrent/protocol/bcodec/BEValue.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Turn, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.protocol.bcodec; 17 | 18 | import com.google.common.base.Charsets; 19 | import com.google.common.base.Preconditions; 20 | import java.nio.charset.Charset; 21 | import java.util.List; 22 | import java.util.Map; 23 | import javax.annotation.Nonnull; 24 | 25 | /** 26 | * A type-agnostic container for B-encoded values. 27 | * 28 | * @author mpetazzoni 29 | */ 30 | public class BEValue { 31 | 32 | /** 33 | * The B-encoded value can be a byte array, a Number, a List or a Map. 34 | * Lists and Maps contains BEValues too. 35 | */ 36 | @Nonnull 37 | private final Object value; 38 | 39 | public BEValue(@Nonnull byte[] value) { 40 | this.value = Preconditions.checkNotNull(value); 41 | } 42 | 43 | public BEValue(@Nonnull String value) { 44 | this(value, Charsets.UTF_8); 45 | } 46 | 47 | public BEValue(@Nonnull String value, @Nonnull Charset enc) { 48 | this.value = value.getBytes(enc); 49 | } 50 | 51 | public BEValue(int value) { 52 | this.value = Integer.valueOf(value); 53 | } 54 | 55 | public BEValue(long value) { 56 | this.value = Long.valueOf(value); 57 | } 58 | 59 | public BEValue(@Nonnull Number value) { 60 | this.value = Preconditions.checkNotNull(value); 61 | } 62 | 63 | public BEValue(@Nonnull List value) { 64 | this.value = Preconditions.checkNotNull(value); 65 | } 66 | 67 | public BEValue(@Nonnull Map value) { 68 | this.value = Preconditions.checkNotNull(value); 69 | } 70 | 71 | @Nonnull 72 | public Object getValue() { 73 | return value; 74 | } 75 | 76 | /** 77 | * Returns this BEValue as a String, interpreted as UTF-8. 78 | * @throws InvalidBEncodingException If the value is not a byte[]. 79 | */ 80 | @Nonnull 81 | public String getString() throws InvalidBEncodingException { 82 | return getString(Charsets.UTF_8); 83 | } 84 | 85 | /** 86 | * Returns this BEValue as a String, interpreted with the specified 87 | * encoding. 88 | * 89 | * @param encoding The encoding to interpret the bytes as when converting 90 | * them into a {@link String}. 91 | * @throws InvalidBEncodingException If the value is not a byte[]. 92 | */ 93 | @Nonnull 94 | public String getString(Charset encoding) throws InvalidBEncodingException { 95 | return new String(getBytes(), encoding); 96 | } 97 | 98 | /** 99 | * Returns this BEValue as a byte[]. 100 | * 101 | * @throws InvalidBEncodingException If the value is not a byte[]. 102 | */ 103 | public byte[] getBytes() throws InvalidBEncodingException { 104 | try { 105 | return (byte[]) value; 106 | } catch (ClassCastException cce) { 107 | throw new InvalidBEncodingException(cce); 108 | } 109 | } 110 | 111 | /** 112 | * Returns this BEValue as a Number. 113 | * 114 | * @throws InvalidBEncodingException If the value is not a {@link Number}. 115 | */ 116 | @Nonnull 117 | public Number getNumber() throws InvalidBEncodingException { 118 | try { 119 | return (Number) value; 120 | } catch (ClassCastException cce) { 121 | throw new InvalidBEncodingException(cce); 122 | } 123 | } 124 | 125 | /** 126 | * Returns this BEValue as short. 127 | * 128 | * @throws InvalidBEncodingException If the value is not a {@link Number}. 129 | */ 130 | public short getShort() throws InvalidBEncodingException { 131 | return getNumber().shortValue(); 132 | } 133 | 134 | /** 135 | * Returns this BEValue as int. 136 | * 137 | * @throws InvalidBEncodingException If the value is not a {@link Number}. 138 | */ 139 | public int getInt() throws InvalidBEncodingException { 140 | return getNumber().intValue(); 141 | } 142 | 143 | /** 144 | * Returns this BEValue as long. 145 | * 146 | * @throws InvalidBEncodingException If the value is not a {@link Number}. 147 | */ 148 | public long getLong() throws InvalidBEncodingException { 149 | return getNumber().longValue(); 150 | } 151 | 152 | /** 153 | * Returns this BEValue as a List of BEValues. 154 | * 155 | * @throws InvalidBEncodingException If the value is not a {@link List}. 156 | */ 157 | @Nonnull 158 | @SuppressWarnings("unchecked") 159 | public List getList() throws InvalidBEncodingException { 160 | try { 161 | return (List) value; 162 | } catch (ClassCastException cce) { 163 | throw new InvalidBEncodingException(cce); 164 | } 165 | } 166 | 167 | /** 168 | * Returns this BEValue as a Map of String keys and BEValue values. 169 | * 170 | * @throws InvalidBEncodingException If the value is not a {@link Map}. 171 | */ 172 | @Nonnull 173 | @SuppressWarnings("unchecked") 174 | public Map getMap() throws InvalidBEncodingException { 175 | try { 176 | return (Map) value; 177 | } catch (ClassCastException cce) { 178 | throw new InvalidBEncodingException(cce); 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /ttorrent-protocol/src/main/java/com/turn/ttorrent/protocol/bcodec/BytesBDecoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 shevek. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.protocol.bcodec; 17 | 18 | import java.io.ByteArrayInputStream; 19 | 20 | /** 21 | * 22 | * @author shevek 23 | */ 24 | public class BytesBDecoder extends StreamBDecoder { 25 | 26 | public BytesBDecoder(byte[] in) { 27 | super(new ByteArrayInputStream(in)); 28 | } 29 | } -------------------------------------------------------------------------------- /ttorrent-protocol/src/main/java/com/turn/ttorrent/protocol/bcodec/BytesBEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 shevek. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.protocol.bcodec; 17 | 18 | import java.io.ByteArrayOutputStream; 19 | import java.io.IOException; 20 | import javax.annotation.Nonnull; 21 | 22 | /** 23 | * 24 | * @author shevek 25 | */ 26 | public class BytesBEncoder extends AbstractBEncoder { 27 | 28 | private final ByteArrayOutputStream out = new ByteArrayOutputStream(); 29 | 30 | @Override 31 | protected void writeByte(int b) { 32 | out.write(b); 33 | } 34 | 35 | @Override 36 | protected void writeBytes(byte[] b) throws IOException { 37 | out.write(b); 38 | } 39 | 40 | @Nonnull 41 | public byte[] toByteArray() { 42 | return out.toByteArray(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ttorrent-protocol/src/main/java/com/turn/ttorrent/protocol/bcodec/InvalidBEncodingException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Turn, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.protocol.bcodec; 17 | 18 | import java.io.IOException; 19 | 20 | /** 21 | * Exception thrown when a B-encoded stream cannot be decoded. 22 | * 23 | * @author mpetazzoni 24 | */ 25 | public class InvalidBEncodingException extends IOException { 26 | 27 | public static final long serialVersionUID = -1; 28 | 29 | public InvalidBEncodingException(String message) { 30 | super(message); 31 | } 32 | 33 | public InvalidBEncodingException(Throwable cause) { 34 | super(cause); 35 | } 36 | 37 | public InvalidBEncodingException(String message, Throwable cause) { 38 | super(message, cause); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ttorrent-protocol/src/main/java/com/turn/ttorrent/protocol/bcodec/NettyBDecoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 shevek. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.protocol.bcodec; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import javax.annotation.Nonnull; 20 | 21 | /** 22 | * 23 | * @author shevek 24 | */ 25 | public class NettyBDecoder extends AbstractBDecoder { 26 | 27 | private final ByteBuf in; 28 | 29 | public NettyBDecoder(@Nonnull ByteBuf in) { 30 | this.in = in; 31 | } 32 | 33 | @Override 34 | protected byte readByte() { 35 | return in.readByte(); 36 | } 37 | 38 | @Override 39 | protected byte[] readBytes(int length) { 40 | byte[] out = new byte[length]; 41 | in.readBytes(out); 42 | return out; 43 | } 44 | } -------------------------------------------------------------------------------- /ttorrent-protocol/src/main/java/com/turn/ttorrent/protocol/bcodec/NettyBEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 shevek. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.protocol.bcodec; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | 20 | /** 21 | * 22 | * @author shevek 23 | */ 24 | public class NettyBEncoder extends AbstractBEncoder { 25 | 26 | private final ByteBuf out; 27 | 28 | public NettyBEncoder(ByteBuf out) { 29 | this.out = out; 30 | } 31 | 32 | @Override 33 | protected void writeByte(int b) { 34 | out.writeByte(b); 35 | } 36 | 37 | @Override 38 | protected void writeBytes(byte[] b) { 39 | out.writeBytes(b); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ttorrent-protocol/src/main/java/com/turn/ttorrent/protocol/bcodec/StreamBDecoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 shevek. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.protocol.bcodec; 17 | 18 | import com.google.common.io.ByteStreams; 19 | import java.io.EOFException; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | 23 | /** 24 | * 25 | * @author shevek 26 | */ 27 | public class StreamBDecoder extends AbstractBDecoder { 28 | 29 | private final InputStream in; 30 | 31 | public StreamBDecoder(InputStream in) { 32 | this.in = in; 33 | } 34 | 35 | @Override 36 | protected byte readByte() throws IOException { 37 | int value = in.read(); 38 | if (value == -1) 39 | throw new EOFException(); 40 | return (byte) value; 41 | } 42 | 43 | @Override 44 | protected byte[] readBytes(int length) throws IOException { 45 | byte[] bytes = new byte[length]; 46 | ByteStreams.readFully(in, bytes); 47 | return bytes; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ttorrent-protocol/src/main/java/com/turn/ttorrent/protocol/bcodec/StreamBEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 shevek. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.protocol.bcodec; 17 | 18 | import java.io.IOException; 19 | import java.io.OutputStream; 20 | 21 | /** 22 | * 23 | * @author shevek 24 | */ 25 | public class StreamBEncoder extends AbstractBEncoder { 26 | 27 | private final OutputStream out; 28 | 29 | public StreamBEncoder(OutputStream out) { 30 | this.out = out; 31 | } 32 | 33 | @Override 34 | protected void writeByte(int b) throws IOException { 35 | out.write(b); 36 | } 37 | 38 | @Override 39 | protected void writeBytes(byte[] b) throws IOException { 40 | out.write(b); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ttorrent-protocol/src/main/java/com/turn/ttorrent/protocol/tracker/InetAddressComparator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.protocol.tracker; 6 | 7 | import java.net.Inet4Address; 8 | import java.net.InetAddress; 9 | import java.util.Comparator; 10 | import javax.annotation.Nonnull; 11 | 12 | /** 13 | * 14 | * @author shevek 15 | */ 16 | public class InetAddressComparator implements Comparator { 17 | 18 | public static final InetAddressComparator INSTANCE = new InetAddressComparator(); 19 | 20 | private static int score(@Nonnull InetAddress a) { 21 | // Least likely to be a valid target address. 22 | if (a.isAnyLocalAddress()) 23 | return 10; 24 | if (a.isMulticastAddress()) 25 | return 6; 26 | if (a.isLoopbackAddress()) 27 | return 4; 28 | if (a.isLinkLocalAddress()) 29 | return 2; 30 | return 0; 31 | // Most likely to be a valid target address. 32 | } 33 | 34 | @Override 35 | public int compare(InetAddress o1, InetAddress o2) { 36 | int cmp; 37 | // Inet4Address is better than Inet6Address 38 | cmp = -Boolean.compare(o1 instanceof Inet4Address, o2 instanceof Inet4Address); 39 | if (cmp != 0) 40 | return cmp; 41 | // Avoid loopbacks. 42 | return Integer.compare(score(o1), score(o2)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ttorrent-protocol/src/main/java/com/turn/ttorrent/protocol/tracker/InetSocketAddressComparator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.protocol.tracker; 6 | 7 | import java.net.InetSocketAddress; 8 | import java.util.Comparator; 9 | 10 | /** 11 | * 12 | * @author shevek 13 | */ 14 | public class InetSocketAddressComparator implements Comparator { 15 | 16 | public static final InetSocketAddressComparator INSTANCE = new InetSocketAddressComparator(); 17 | 18 | @Override 19 | public int compare(InetSocketAddress o1, InetSocketAddress o2) { 20 | return InetAddressComparator.INSTANCE.compare(o1.getAddress(), o2.getAddress()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ttorrent-protocol/src/main/java/com/turn/ttorrent/protocol/tracker/http/HTTPTrackerErrorMessage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012 Turn, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.protocol.tracker.http; 17 | 18 | import com.turn.ttorrent.protocol.bcodec.BEUtils; 19 | import com.turn.ttorrent.protocol.bcodec.BEValue; 20 | import com.turn.ttorrent.protocol.bcodec.InvalidBEncodingException; 21 | import com.turn.ttorrent.protocol.tracker.TrackerMessage.ErrorMessage; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | import javax.annotation.Nonnull; 25 | 26 | /** 27 | * An error message from an HTTP tracker. 28 | * 29 | * @author mpetazzoni 30 | */ 31 | public class HTTPTrackerErrorMessage extends HTTPTrackerMessage implements ErrorMessage { 32 | 33 | private final String reason; 34 | 35 | public HTTPTrackerErrorMessage(String reason) { 36 | this.reason = reason; 37 | } 38 | 39 | public HTTPTrackerErrorMessage(ErrorMessage.FailureReason reason) { 40 | this(reason.getMessage()); 41 | } 42 | 43 | @Override 44 | public String getReason() { 45 | return this.reason; 46 | } 47 | 48 | @Nonnull 49 | public static HTTPTrackerErrorMessage fromBEValue(@Nonnull Map params) 50 | throws MessageValidationException { 51 | 52 | try { 53 | String reason = params.get("failure reason").getString(BEUtils.BYTE_ENCODING); 54 | return new HTTPTrackerErrorMessage(reason); 55 | } catch (InvalidBEncodingException ibee) { 56 | throw new MessageValidationException("Invalid tracker error " 57 | + "message!", ibee); 58 | } 59 | } 60 | 61 | @Nonnull 62 | public Map toBEValue() { 63 | Map params = new HashMap(); 64 | params.put("failure reason", new BEValue(getReason(), BEUtils.BYTE_ENCODING)); 65 | return params; 66 | } 67 | } -------------------------------------------------------------------------------- /ttorrent-protocol/src/main/java/com/turn/ttorrent/protocol/tracker/http/HTTPTrackerMessage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012 Turn, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.protocol.tracker.http; 17 | 18 | import com.google.common.collect.Multimap; 19 | import com.turn.ttorrent.protocol.bcodec.BEUtils; 20 | import com.turn.ttorrent.protocol.tracker.TrackerMessage; 21 | import java.util.Collection; 22 | import javax.annotation.CheckForNull; 23 | import javax.annotation.Nonnull; 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | 27 | /** 28 | * Base class for HTTP tracker messages. 29 | * 30 | * @author mpetazzoni 31 | */ 32 | public abstract class HTTPTrackerMessage extends TrackerMessage { 33 | 34 | private static final Logger LOG = LoggerFactory.getLogger(HTTPTrackerMessage.class); 35 | 36 | @CheckForNull 37 | protected static String toString(@Nonnull Multimap params, @Nonnull String key, @CheckForNull ErrorMessage.FailureReason error) throws MessageValidationException { 38 | LOOKUP: 39 | { 40 | Collection texts = params.get(key); 41 | if (texts == null) 42 | break LOOKUP; 43 | if (texts.isEmpty()) 44 | break LOOKUP; 45 | String text = texts.iterator().next(); 46 | if (text == null) 47 | break LOOKUP; 48 | return text; 49 | } 50 | if (error != null) 51 | throw new MessageValidationException("Invalid parameters " + params + ": " + error.getMessage()); 52 | return null; 53 | } 54 | 55 | @CheckForNull 56 | protected static byte[] toBytes(@Nonnull Multimap params, @Nonnull String key, @CheckForNull ErrorMessage.FailureReason error) throws MessageValidationException { 57 | String text = toString(params, key, error); 58 | if (text == null) 59 | return null; 60 | return text.getBytes(BEUtils.BYTE_ENCODING); 61 | } 62 | 63 | protected static int toInt(@Nonnull Multimap params, @Nonnull String key, int unknown, @CheckForNull ErrorMessage.FailureReason error) throws MessageValidationException { 64 | try { 65 | String text = toString(params, key, error); 66 | if (text == null) 67 | return unknown; 68 | return Integer.parseInt(text); 69 | } catch (NumberFormatException e) { 70 | throw new MessageValidationException(e.getMessage(), e); 71 | } 72 | } 73 | 74 | protected static long toLong(@Nonnull Multimap params, @Nonnull String key, long unknown, @CheckForNull ErrorMessage.FailureReason error) throws MessageValidationException { 75 | try { 76 | String text = toString(params, key, error); 77 | if (text == null) 78 | return unknown; 79 | return Long.parseLong(text); 80 | } catch (NumberFormatException e) { 81 | throw new MessageValidationException(e.getMessage(), e); 82 | } 83 | } 84 | 85 | protected static boolean toBoolean(@Nonnull Multimap params, @Nonnull String key) throws MessageValidationException { 86 | return toInt(params, key, 0, null) != 0; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /ttorrent-protocol/src/main/java/com/turn/ttorrent/protocol/tracker/udp/UDPAnnounceResponseMessage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012 Turn, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.protocol.tracker.udp; 17 | 18 | import com.turn.ttorrent.protocol.tracker.Peer; 19 | import com.turn.ttorrent.protocol.tracker.TrackerMessage; 20 | import io.netty.buffer.ByteBuf; 21 | import java.net.InetAddress; 22 | import java.net.InetSocketAddress; 23 | import java.net.UnknownHostException; 24 | import java.util.ArrayList; 25 | import java.util.Collection; 26 | import java.util.List; 27 | 28 | /** 29 | * The announce response message for the UDP tracker protocol. 30 | * 31 | * @author mpetazzoni 32 | */ 33 | public class UDPAnnounceResponseMessage 34 | extends UDPTrackerMessage.UDPTrackerResponseMessage 35 | implements TrackerMessage.AnnounceResponseMessage { 36 | 37 | private static final int UDP_ANNOUNCE_RESPONSE_MIN_MESSAGE_SIZE = 20; 38 | private int interval; 39 | private int complete; 40 | private int incomplete; 41 | private final List peers = new ArrayList(); 42 | 43 | private UDPAnnounceResponseMessage() { 44 | super(Type.ANNOUNCE_REQUEST); 45 | } 46 | 47 | @Override 48 | public int getInterval() { 49 | return this.interval; 50 | } 51 | 52 | @Override 53 | public int getComplete() { 54 | return this.complete; 55 | } 56 | 57 | @Override 58 | public int getIncomplete() { 59 | return this.incomplete; 60 | } 61 | 62 | @Override 63 | public Collection getPeers() { 64 | return this.peers; 65 | } 66 | 67 | @Override 68 | public void fromWire(ByteBuf in) throws MessageValidationException { 69 | if (in.readableBytes() < UDP_ANNOUNCE_RESPONSE_MIN_MESSAGE_SIZE 70 | || (in.readableBytes() - UDP_ANNOUNCE_RESPONSE_MIN_MESSAGE_SIZE) % 6 != 0) { 71 | throw new MessageValidationException("Invalid announce response message size " + in.readableBytes()); 72 | } 73 | _fromWire(in, -1); 74 | 75 | interval = in.readInt(); 76 | incomplete = in.readInt(); 77 | complete = in.readInt(); 78 | 79 | peers.clear(); 80 | while (in.readableBytes() > 0) { 81 | try { 82 | byte[] ipBytes = new byte[4]; 83 | in.readBytes(ipBytes); 84 | InetAddress ip = InetAddress.getByAddress(ipBytes); 85 | int port = in.readShort() & 0xFFFF; 86 | peers.add(new Peer(new InetSocketAddress(ip, port), null)); 87 | } catch (UnknownHostException uhe) { 88 | throw new MessageValidationException( 89 | "Invalid IP address in announce request!"); 90 | } 91 | } 92 | } 93 | 94 | @Override 95 | public void toWire(ByteBuf out) { 96 | _toWire(out); 97 | out.writeInt(interval); 98 | 99 | /** 100 | * Leechers (incomplete) are first, before seeders (complete) in the packet. 101 | */ 102 | out.writeInt(incomplete); 103 | out.writeInt(complete); 104 | 105 | for (Peer peer : peers) { 106 | byte[] ip = peer.getIpBytes(); 107 | if (ip == null || ip.length != 4) 108 | continue; 109 | out.writeBytes(ip); 110 | out.writeShort((short) peer.getPort()); 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /ttorrent-protocol/src/main/java/com/turn/ttorrent/protocol/tracker/udp/UDPConnectRequestMessage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012 Turn, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.protocol.tracker.udp; 17 | 18 | 19 | import io.netty.buffer.ByteBuf; 20 | 21 | /** 22 | * The connection request message for the UDP tracker protocol. 23 | * 24 | * @author mpetazzoni 25 | */ 26 | public class UDPConnectRequestMessage 27 | extends UDPTrackerMessage.UDPTrackerRequestMessage { 28 | 29 | private static final int UDP_CONNECT_REQUEST_MESSAGE_SIZE = 16; 30 | private static final long UDP_CONNECT_REQUEST_MAGIC = 0x41727101980L; 31 | 32 | public UDPConnectRequestMessage() { 33 | super(Type.CONNECT_REQUEST); 34 | setConnectionId(UDP_CONNECT_REQUEST_MAGIC); 35 | } 36 | 37 | public UDPConnectRequestMessage(int transactionId) { 38 | this(); 39 | setTransactionId(transactionId); 40 | } 41 | 42 | @Override 43 | public void fromWire(ByteBuf in) throws MessageValidationException { 44 | _fromWire(in, UDP_CONNECT_REQUEST_MESSAGE_SIZE); 45 | if (getConnectionId() != UDP_CONNECT_REQUEST_MAGIC) 46 | throw new MessageValidationException("Packet contained bad ConnectionId: " + this); 47 | } 48 | 49 | @Override 50 | public void toWire(ByteBuf out) { 51 | _toWire(out); 52 | } 53 | } -------------------------------------------------------------------------------- /ttorrent-protocol/src/main/java/com/turn/ttorrent/protocol/tracker/udp/UDPConnectResponseMessage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012 Turn, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.protocol.tracker.udp; 17 | 18 | 19 | import io.netty.buffer.ByteBuf; 20 | 21 | /** 22 | * The connection response message for the UDP tracker protocol. 23 | * 24 | * @author mpetazzoni 25 | */ 26 | public class UDPConnectResponseMessage 27 | extends UDPTrackerMessage.UDPTrackerResponseMessage { 28 | 29 | private static final int UDP_CONNECT_RESPONSE_MESSAGE_SIZE = 16; 30 | private long connectionId; 31 | 32 | public UDPConnectResponseMessage() { 33 | super(Type.CONNECT_RESPONSE); 34 | } 35 | 36 | public long getConnectionId() { 37 | return this.connectionId; 38 | } 39 | 40 | public void setConnectionId(long connectionId) { 41 | this.connectionId = connectionId; 42 | } 43 | 44 | @Override 45 | public void fromWire(ByteBuf in) throws MessageValidationException { 46 | _fromWire(in, UDP_CONNECT_RESPONSE_MESSAGE_SIZE); 47 | setConnectionId(in.readLong()); 48 | } 49 | 50 | @Override 51 | public void toWire(ByteBuf out) { 52 | _toWire(out); 53 | out.writeLong(connectionId); 54 | } 55 | } -------------------------------------------------------------------------------- /ttorrent-protocol/src/main/java/com/turn/ttorrent/protocol/tracker/udp/UDPTrackerErrorMessage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2012 Turn, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.protocol.tracker.udp; 17 | 18 | import com.turn.ttorrent.protocol.bcodec.BEUtils; 19 | import com.turn.ttorrent.protocol.tracker.TrackerMessage; 20 | import io.netty.buffer.ByteBuf; 21 | 22 | /** 23 | * The error message for the UDP tracker protocol. 24 | * 25 | * @author mpetazzoni 26 | */ 27 | public class UDPTrackerErrorMessage 28 | extends UDPTrackerMessage.UDPTrackerResponseMessage 29 | implements TrackerMessage.ErrorMessage { 30 | 31 | private static final int UDP_TRACKER_ERROR_MIN_MESSAGE_SIZE = 8; 32 | private String reason; 33 | 34 | private UDPTrackerErrorMessage() { 35 | super(Type.ERROR); 36 | } 37 | 38 | @Override 39 | public String getReason() { 40 | return this.reason; 41 | } 42 | 43 | @Override 44 | public void fromWire(ByteBuf in) throws MessageValidationException { 45 | if (in.readableBytes() < UDP_TRACKER_ERROR_MIN_MESSAGE_SIZE) 46 | throw new MessageValidationException("Invalid tracker error message size " + in.readableBytes()); 47 | _fromWire(in, -1); 48 | 49 | byte[] reasonBytes = new byte[in.readableBytes()]; 50 | in.readBytes(reasonBytes); 51 | reason = new String(reasonBytes, BEUtils.BYTE_ENCODING); 52 | } 53 | 54 | @Override 55 | public void toWire(ByteBuf out) { 56 | _toWire(out); 57 | out.writeBytes(reason.getBytes(BEUtils.BYTE_ENCODING)); 58 | } 59 | } -------------------------------------------------------------------------------- /ttorrent-protocol/src/test/java/com/turn/ttorrent/protocol/test/PatternByteSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.protocol.test; 6 | 7 | import com.google.common.io.ByteSource; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | 11 | /** 12 | * 13 | * @author shevek 14 | */ 15 | public class PatternByteSource extends ByteSource { 16 | 17 | private final long size; 18 | 19 | public PatternByteSource(long size) { 20 | this.size = size; 21 | } 22 | 23 | @Override 24 | public long size() throws IOException { 25 | return size; 26 | } 27 | 28 | @Override 29 | public InputStream openStream() throws IOException { 30 | return new PatternInputStream(size); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ttorrent-protocol/src/test/java/com/turn/ttorrent/protocol/test/PatternInputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.protocol.test; 6 | 7 | import org.apache.commons.io.input.NullInputStream; 8 | 9 | /** 10 | * 11 | * @author shevek 12 | */ 13 | public class PatternInputStream extends NullInputStream { 14 | 15 | private long offset; 16 | 17 | public PatternInputStream(long size) { 18 | super(size); 19 | } 20 | 21 | @Override 22 | protected int processByte() { 23 | try { 24 | long word = offset >> 3; 25 | int index = (int) (offset & 0x7); 26 | return (int) (word >>> (Long.SIZE - (Byte.SIZE * index))); 27 | } finally { 28 | offset++; 29 | } 30 | } 31 | 32 | @Override 33 | protected void processBytes(byte[] bytes, int offset, int length) { 34 | for (int i = 0; i < length; i++) 35 | bytes[offset + i] = (byte) processByte(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ttorrent-protocol/src/test/java/com/turn/ttorrent/protocol/test/RandomInputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.protocol.test; 6 | 7 | import java.util.Random; 8 | import org.apache.commons.io.input.NullInputStream; 9 | 10 | /** 11 | * 12 | * @author shevek 13 | */ 14 | public class RandomInputStream extends NullInputStream { 15 | 16 | private final Random r = new Random(1234); 17 | 18 | public RandomInputStream(long size) { 19 | super(size); 20 | } 21 | 22 | @Override 23 | protected int processByte() { 24 | return r.nextInt() & 0xFF; 25 | } 26 | 27 | @Override 28 | protected void processBytes(byte[] bytes, int offset, int length) { 29 | if (offset == 0 && length == bytes.length) { 30 | r.nextBytes(bytes); 31 | } else { 32 | byte[] tmp = new byte[length]; 33 | r.nextBytes(tmp); 34 | System.arraycopy(tmp, 0, bytes, offset, length); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ttorrent-protocol/src/test/java/com/turn/ttorrent/protocol/test/TestPeerIdentityProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.protocol.test; 6 | 7 | import com.google.common.base.Charsets; 8 | import com.turn.ttorrent.protocol.PeerIdentityProvider; 9 | import com.turn.ttorrent.protocol.TorrentUtils; 10 | 11 | /** 12 | * 13 | * @author shevek 14 | */ 15 | public class TestPeerIdentityProvider implements PeerIdentityProvider { 16 | 17 | @Override 18 | public byte[] getLocalPeerId() { 19 | return getClass().getSimpleName().getBytes(Charsets.ISO_8859_1); 20 | } 21 | 22 | @Override 23 | public String getLocalPeerName() { 24 | return TorrentUtils.toText(getLocalPeerId()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ttorrent-protocol/src/test/java/com/turn/ttorrent/protocol/test/TorrentTestUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.protocol.test; 6 | 7 | import com.google.common.io.Files; 8 | import com.turn.ttorrent.protocol.torrent.Torrent; 9 | import com.turn.ttorrent.protocol.torrent.TorrentCreator; 10 | import io.netty.util.ResourceLeakDetector; 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.net.URISyntaxException; 14 | import javax.annotation.Nonnegative; 15 | import javax.annotation.Nonnull; 16 | import org.apache.commons.io.FileUtils; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | /** 21 | * 22 | * @author shevek 23 | */ 24 | public class TorrentTestUtils { 25 | 26 | private static final Logger LOG = LoggerFactory.getLogger(TorrentTestUtils.class); 27 | 28 | static { 29 | ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID); 30 | } 31 | public static final String FILENAME = "torrent-data-file"; 32 | private static File ROOT; 33 | 34 | @Nonnull 35 | public static synchronized File newTorrentRoot() 36 | throws IOException { 37 | if (ROOT != null) 38 | return ROOT; 39 | File buildDir = new File("build/tmp"); 40 | FileUtils.forceMkdir(buildDir); 41 | File rootDir = File.createTempFile("ttorrent", ".tmp", buildDir); 42 | FileUtils.forceDeleteOnExit(rootDir); 43 | FileUtils.forceDelete(rootDir); 44 | FileUtils.forceMkdir(rootDir); 45 | ROOT = rootDir; 46 | return rootDir; 47 | } 48 | 49 | @Nonnull 50 | public static File newTorrentDir(@Nonnull String name) 51 | throws IOException { 52 | File torrentDir = new File(newTorrentRoot(), name); 53 | if (torrentDir.exists()) 54 | FileUtils.forceDelete(torrentDir); 55 | FileUtils.forceMkdir(torrentDir); 56 | return torrentDir; 57 | } 58 | 59 | @Nonnull 60 | public static TorrentCreator newTorrentCreator(@Nonnull File dir, @Nonnegative final long size) 61 | throws IOException, InterruptedException { 62 | File file = new File(dir, FILENAME); 63 | Files.asByteSink(file).writeFrom(new PatternInputStream(size)); 64 | return new TorrentCreator(file); 65 | } 66 | 67 | @Nonnull 68 | public static Torrent newTorrent(@Nonnull File dir, @Nonnegative long size) 69 | throws IOException, InterruptedException, URISyntaxException { 70 | return newTorrentCreator(dir, size).create(); 71 | } 72 | } -------------------------------------------------------------------------------- /ttorrent-protocol/src/test/java/com/turn/ttorrent/protocol/torrent/TorrentCreatorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.protocol.torrent; 6 | 7 | import com.google.common.io.Files; 8 | import com.google.common.math.LongMath; 9 | import com.turn.ttorrent.protocol.test.PatternInputStream; 10 | import com.turn.ttorrent.protocol.test.TorrentTestUtils; 11 | import java.io.File; 12 | import java.math.RoundingMode; 13 | import java.util.Random; 14 | import org.junit.After; 15 | import org.junit.Test; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | import static org.junit.Assert.*; 19 | 20 | /** 21 | * 22 | * @author shevek 23 | */ 24 | public class TorrentCreatorTest { 25 | 26 | private static final Logger LOG = LoggerFactory.getLogger(TorrentCreatorTest.class); 27 | 28 | @After 29 | public void tearDown() throws Exception { 30 | for (int i = 0; i < 4; i++) { 31 | Thread.sleep(50); 32 | System.gc(); 33 | } 34 | } 35 | 36 | @Test 37 | public void testCreatorSingle() throws Exception { 38 | File dir = TorrentTestUtils.newTorrentDir("single"); 39 | TorrentCreator creator = TorrentTestUtils.newTorrentCreator(dir, 193723193); 40 | creator.create(); 41 | } 42 | 43 | @Test 44 | public void testCreatorMultiple() throws Exception { 45 | final long length = 109372319; 46 | File dir = TorrentTestUtils.newTorrentDir("multiple"); 47 | for (int i = 0; i < 4; i++) { 48 | File file = new File(dir, "file-" + i); 49 | Files.asByteSink(file).writeFrom(new PatternInputStream(length)); 50 | } 51 | TorrentCreator creator = new TorrentCreator(dir); 52 | Torrent torrent = creator.create(); 53 | 54 | long pieceCount = LongMath.divide(length * 4, creator.getPieceLength(), RoundingMode.CEILING); 55 | assertEquals(pieceCount, torrent.getPieceCount()); 56 | 57 | // TODO: Assert that the hash came out right. 58 | } 59 | 60 | @Test 61 | public void testFuzz() throws Exception { 62 | Random r = new Random(); 63 | File dir = TorrentTestUtils.newTorrentDir("fuzz"); 64 | long total = 0L; 65 | for (int i = 0; i < 10; i++) { 66 | File file = new File(dir, "file-" + i); 67 | Files.asByteSink(file).writeFrom(new PatternInputStream(r.nextInt(17 + i * 187))); 68 | total += file.length(); 69 | 70 | TorrentCreator creator = new TorrentCreator(dir); 71 | creator.setPieceLength(64); 72 | Torrent torrent = creator.create(); 73 | assertEquals(LongMath.divide(total, creator.getPieceLength(), RoundingMode.CEILING), 74 | torrent.getPieceCount()); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /ttorrent-protocol/src/test/java/com/turn/ttorrent/protocol/tracker/InetAddressComparatorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.protocol.tracker; 6 | 7 | import com.turn.ttorrent.protocol.tracker.InetAddressComparator; 8 | import com.google.common.net.InetAddresses; 9 | import java.net.InetAddress; 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.List; 13 | import org.junit.Test; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | // import static org.junit.Assert.*; 17 | 18 | /** 19 | * 20 | * @author shevek 21 | */ 22 | public class InetAddressComparatorTest { 23 | 24 | private static final Logger LOG = LoggerFactory.getLogger(InetAddressComparatorTest.class); 25 | 26 | @Test 27 | public void testComparator() { 28 | List addresses = new ArrayList(); 29 | addresses.add(InetAddresses.forString("1.2.3.4")); 30 | addresses.add(InetAddresses.forString("0.0.0.0")); 31 | addresses.add(InetAddresses.forString("::1")); 32 | addresses.add(InetAddresses.forString("fe80::a6")); 33 | Collections.shuffle(addresses); 34 | LOG.info("In: " + addresses); 35 | Collections.sort(addresses, new InetAddressComparator()); 36 | LOG.info("Out: " + addresses); 37 | } 38 | } -------------------------------------------------------------------------------- /ttorrent-protocol/src/test/java/com/turn/ttorrent/protocol/tracker/http/HTTPAnnounceRequestMessageTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.protocol.tracker.http; 6 | 7 | import com.google.common.collect.Multimap; 8 | import com.turn.ttorrent.protocol.tracker.Peer; 9 | import com.turn.ttorrent.protocol.tracker.TrackerMessage; 10 | import com.turn.ttorrent.tracker.TrackerUtils; 11 | import java.net.InetSocketAddress; 12 | import java.net.URI; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | import java.util.Random; 16 | import javax.annotation.Nonnull; 17 | import org.junit.Test; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | import static org.junit.Assert.*; 21 | 22 | /** 23 | * 24 | * @author shevek 25 | */ 26 | public class HTTPAnnounceRequestMessageTest { 27 | 28 | private static final Logger LOG = LoggerFactory.getLogger(HTTPAnnounceRequestMessageTest.class); 29 | 30 | private HTTPAnnounceRequestMessage test(@Nonnull HTTPAnnounceRequestMessage in) throws Exception { 31 | LOG.info("In: " + in); 32 | URI tracker = URI.create("http://localhost:3128/announce"); 33 | URI request = in.toURI(tracker); 34 | LOG.info("Request: " + request); 35 | Multimap params = TrackerUtils.parseQuery(request.getRawQuery()); 36 | // for (Map.Entry e : parser.entrySet()) LOG.info(e.getKey() + " -> " + e.getValue()); 37 | HTTPAnnounceRequestMessage out = HTTPAnnounceRequestMessage.fromParams(params); 38 | LOG.info("Out: " + out); 39 | return out; 40 | } 41 | private Random random = new Random(); 42 | 43 | @Nonnull 44 | private HTTPAnnounceRequestMessage newRequest(List addresses) { 45 | byte[] infoHash = new byte[20]; 46 | random.nextBytes(infoHash); 47 | byte[] peerId = new byte[Peer.PEER_ID_LENGTH]; 48 | random.nextBytes(peerId); 49 | return new HTTPAnnounceRequestMessage(infoHash, peerId, addresses, 50 | 1, 2, 3, true, true, TrackerMessage.AnnounceEvent.NONE, 50); 51 | } 52 | 53 | private void test(@Nonnull InetSocketAddress... addresses) throws Exception { 54 | HTTPAnnounceRequestMessage message = test(newRequest(Arrays.asList(addresses))); 55 | assertEquals(Arrays.asList(addresses), message.getPeerAddresses()); 56 | } 57 | 58 | @Test 59 | public void testRequest() throws Exception { 60 | test(new InetSocketAddress(123)); 61 | test(new InetSocketAddress("1.2.3.4", 123)); 62 | test(new InetSocketAddress("1.2.3.4", 123), new InetSocketAddress("2.3.4.5", 123)); 63 | test( 64 | new InetSocketAddress("fe80::3e97:eff:fe67:5809", 6882), 65 | new InetSocketAddress("fe80::3e97:eff:fe67:5808", 6882), 66 | new InetSocketAddress("fe80::3e97:eff:fe67:5807", 6882) 67 | ); 68 | } 69 | } -------------------------------------------------------------------------------- /ttorrent-protocol/src/test/java/com/turn/ttorrent/protocol/tracker/http/HTTPAnnounceResponseMessageTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.protocol.tracker.http; 6 | 7 | import com.turn.ttorrent.protocol.tracker.http.HTTPAnnounceResponseMessage; 8 | import com.turn.ttorrent.protocol.bcodec.BEValue; 9 | import com.turn.ttorrent.protocol.tracker.Peer; 10 | import java.net.InetAddress; 11 | import java.net.InetSocketAddress; 12 | import java.net.UnknownHostException; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.Random; 17 | import javax.annotation.Nonnull; 18 | import org.junit.Test; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | /** 23 | * 24 | * @author shevek 25 | */ 26 | public class HTTPAnnounceResponseMessageTest { 27 | 28 | private static final Logger LOG = LoggerFactory.getLogger(HTTPAnnounceResponseMessageTest.class); 29 | 30 | private void test(HTTPAnnounceResponseMessage in, boolean compact, boolean noPeerIds) throws Exception { 31 | LOG.info("in=" + in + ", compact=" + compact + ", noPeerIds=" + noPeerIds); 32 | Map value = in.toBEValue(compact, noPeerIds); 33 | HTTPAnnounceResponseMessage out = HTTPAnnounceResponseMessage.fromBEValue(value); 34 | LOG.info("out=" + out); 35 | } 36 | 37 | private void test(HTTPAnnounceResponseMessage message) throws Exception { 38 | test(message, true, true); 39 | test(message, true, false); 40 | test(message, false, true); 41 | test(message, false, false); 42 | } 43 | private Random random = new Random(); 44 | 45 | @Nonnull 46 | private Peer newPeer(int len, boolean hasPeerId) throws UnknownHostException { 47 | byte[] address = new byte[len]; 48 | random.nextBytes(address); 49 | int port = random.nextInt() & 0xFFFF; 50 | byte[] peerId = null; 51 | if (hasPeerId) { 52 | peerId = new byte[Peer.PEER_ID_LENGTH]; 53 | random.nextBytes(peerId); 54 | } 55 | InetAddress inaddr = InetAddress.getByAddress(address); 56 | return new Peer(new InetSocketAddress(inaddr, port), peerId); 57 | } 58 | 59 | @Test 60 | public void testSerialization() throws Exception { 61 | InetAddress clientAddress = InetAddress.getLoopbackAddress(); 62 | 63 | EMPTY: 64 | { 65 | List peers = new ArrayList(); 66 | test(new HTTPAnnounceResponseMessage( 67 | clientAddress, 68 | 1, 2, 3, 69 | peers)); 70 | } 71 | 72 | IP4_WITHOUT: 73 | { 74 | List peers = new ArrayList(); 75 | peers.add(newPeer(4, false)); 76 | test(new HTTPAnnounceResponseMessage( 77 | clientAddress, 78 | 1, 2, 3, 79 | peers)); 80 | } 81 | 82 | IP4_WITH: 83 | { 84 | List peers = new ArrayList(); 85 | peers.add(newPeer(4, true)); 86 | test(new HTTPAnnounceResponseMessage( 87 | clientAddress, 88 | 1, 2, 3, 89 | peers)); 90 | } 91 | 92 | IP6_WITHOUT: 93 | { 94 | List peers = new ArrayList(); 95 | peers.add(newPeer(16, false)); 96 | test(new HTTPAnnounceResponseMessage( 97 | clientAddress, 98 | 1, 2, 3, 99 | peers)); 100 | } 101 | 102 | IP6_WITH: 103 | { 104 | List peers = new ArrayList(); 105 | peers.add(newPeer(16, true)); 106 | test(new HTTPAnnounceResponseMessage( 107 | clientAddress, 108 | 1, 2, 3, 109 | peers)); 110 | } 111 | 112 | ALL: 113 | { 114 | List peers = new ArrayList(); 115 | for (int i = 0; i < 3; i++) { 116 | peers.add(newPeer(4, false)); 117 | peers.add(newPeer(4, true)); 118 | peers.add(newPeer(16, false)); 119 | peers.add(newPeer(16, true)); 120 | } 121 | test(new HTTPAnnounceResponseMessage( 122 | clientAddress, 123 | 1, 2, 3, 124 | peers)); 125 | } 126 | 127 | } 128 | } -------------------------------------------------------------------------------- /ttorrent-tracker-client/src/main/java/com/turn/ttorrent/tracker/client/AnnounceException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Turn, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.tracker.client; 17 | 18 | 19 | /** 20 | * Exception thrown when an announce request failed. 21 | * 22 | * @author mpetazzoni 23 | */ 24 | public class AnnounceException extends Exception { 25 | 26 | private static final long serialVersionUID = -1; 27 | 28 | public AnnounceException(String message) { 29 | super(message); 30 | } 31 | 32 | public AnnounceException(Throwable cause) { 33 | super(cause); 34 | } 35 | 36 | public AnnounceException(String message, Throwable cause) { 37 | super(message, cause); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ttorrent-tracker-client/src/main/java/com/turn/ttorrent/tracker/client/AnnounceResponseListener.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Turn, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.tracker.client; 17 | 18 | import com.turn.ttorrent.protocol.tracker.TrackerMessage; 19 | 20 | import java.net.URI; 21 | import java.util.EventListener; 22 | import javax.annotation.Nonnull; 23 | 24 | /** 25 | * EventListener interface for objects that want to receive tracker responses. 26 | * 27 | * @author mpetazzoni 28 | */ 29 | public interface AnnounceResponseListener extends EventListener { 30 | 31 | /** 32 | * Handle an announce response event. 33 | */ 34 | public void handleAnnounceResponse(@Nonnull URI tracker, @Nonnull TrackerMessage.AnnounceEvent event, @Nonnull TrackerMessage.AnnounceResponseMessage response); 35 | 36 | public void handleAnnounceFailed(@Nonnull URI tracker, @Nonnull TrackerMessage.AnnounceEvent event, @Nonnull String reason); 37 | } -------------------------------------------------------------------------------- /ttorrent-tracker-client/src/main/java/com/turn/ttorrent/tracker/client/PeerAddressProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.tracker.client; 6 | 7 | import com.turn.ttorrent.protocol.PeerIdentityProvider; 8 | import java.net.SocketAddress; 9 | import java.util.Set; 10 | import javax.annotation.Nonnull; 11 | 12 | /** 13 | * 14 | * @author shevek 15 | */ 16 | public interface PeerAddressProvider extends PeerIdentityProvider { 17 | 18 | @Nonnull 19 | public Set getLocalAddresses(); 20 | } 21 | -------------------------------------------------------------------------------- /ttorrent-tracker-client/src/main/java/com/turn/ttorrent/tracker/client/TorrentMetadataProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.tracker.client; 6 | 7 | import java.net.URI; 8 | import java.util.List; 9 | import javax.annotation.Nonnegative; 10 | import javax.annotation.Nonnull; 11 | 12 | /** 13 | * 14 | * @author shevek 15 | */ 16 | public interface TorrentMetadataProvider { 17 | 18 | public enum State { 19 | 20 | WAITING, 21 | VALIDATING, 22 | SHARING, 23 | SEEDING, 24 | ERROR, 25 | DONE; 26 | }; 27 | 28 | @Nonnull 29 | public byte[] getInfoHash(); 30 | 31 | @Nonnull 32 | public State getState(); 33 | 34 | @Nonnull 35 | public List> getAnnounceList(); 36 | 37 | @Nonnegative 38 | public long getUploaded(); 39 | 40 | @Nonnegative 41 | public long getDownloaded(); 42 | 43 | @Nonnegative 44 | public long getLeft(); 45 | } 46 | -------------------------------------------------------------------------------- /ttorrent-tracker-client/src/main/java/com/turn/ttorrent/tracker/client/TrackerClient.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Turn, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.tracker.client; 17 | 18 | import com.turn.ttorrent.protocol.tracker.InetSocketAddressComparator; 19 | import com.turn.ttorrent.protocol.tracker.TrackerMessage; 20 | import com.turn.ttorrent.protocol.tracker.TrackerMessage.*; 21 | 22 | import java.net.InetSocketAddress; 23 | import java.net.SocketAddress; 24 | import java.net.URI; 25 | import java.util.ArrayList; 26 | import java.util.Collections; 27 | import java.util.List; 28 | import javax.annotation.Nonnull; 29 | 30 | public abstract class TrackerClient { 31 | 32 | private final PeerAddressProvider peerAddressProvider; 33 | 34 | public TrackerClient(@Nonnull PeerAddressProvider peerAddressProvider) { 35 | this.peerAddressProvider = peerAddressProvider; 36 | } 37 | 38 | @Nonnull 39 | protected byte[] getLocalPeerId() { 40 | return peerAddressProvider.getLocalPeerId(); 41 | } 42 | 43 | /** Returns a fresh, concrete list. */ 44 | @Nonnull 45 | protected List getLocalPeerAddresses() { 46 | List out = new ArrayList(); 47 | for (SocketAddress address : peerAddressProvider.getLocalAddresses()) 48 | if (address instanceof InetSocketAddress) 49 | out.add((InetSocketAddress) address); 50 | Collections.sort(out, InetSocketAddressComparator.INSTANCE); 51 | return out; 52 | } 53 | 54 | /** 55 | * Build, send and process a tracker announce request. 56 | * 57 | *

58 | * This function first builds an announce request for the specified event 59 | * with all the required parameters. Then, the request is made to the 60 | * tracker and the response analyzed. 61 | *

62 | * 63 | *

64 | * All registered {@link AnnounceResponseListener} objects are then fired 65 | * with the decoded payload. 66 | *

67 | * 68 | * @param event The announce event type (can be AnnounceEvent.NONE for 69 | * periodic updates). 70 | * @param inhibitEvent Prevent event listeners from being notified. 71 | */ 72 | public abstract void announce( 73 | AnnounceResponseListener listener, 74 | TorrentMetadataProvider torrent, 75 | URI tracker, 76 | TrackerMessage.AnnounceEvent event, 77 | boolean inhibitEvents) throws AnnounceException; 78 | 79 | public void start() throws Exception { 80 | } 81 | 82 | /** 83 | * Close any opened announce connection. 84 | * 85 | *

86 | * This method is called by Client.stop() to make sure all connections 87 | * are correctly closed when the announce thread is asked to stop. 88 | *

89 | */ 90 | public void stop() throws Exception { 91 | // Do nothing by default, but can be overloaded. 92 | } 93 | 94 | /** 95 | * Formats an announce event into a usable string. 96 | */ 97 | public static String formatAnnounceEvent(TrackerMessage.AnnounceEvent event) { 98 | return TrackerMessage.AnnounceEvent.NONE.equals(event) 99 | ? "" 100 | : String.format(" %s", event.name()); 101 | } 102 | 103 | /** 104 | * Handle the announce response from the tracker. 105 | * 106 | *

107 | * Analyzes the response from the tracker and acts on it. If the response 108 | * is an error, it is logged. Otherwise, the announce response is used 109 | * to fire the corresponding announce and peer events to all announce 110 | * listeners. 111 | *

112 | * 113 | * @param message The incoming {@link TrackerMessage}. 114 | * @param inhibitEvents Whether or not to prevent events from being fired. 115 | */ 116 | protected void handleTrackerAnnounceResponse( 117 | @Nonnull AnnounceResponseListener listener, 118 | @Nonnull URI tracker, 119 | @Nonnull TrackerMessage.AnnounceEvent event, 120 | @Nonnull TrackerMessage message, // AnnounceResponse or Error 121 | boolean inhibitEvents) throws AnnounceException { 122 | if (message instanceof ErrorMessage) { 123 | ErrorMessage error = (ErrorMessage) message; 124 | throw new AnnounceException(tracker + "(" + event + "): " + error.getReason()); 125 | } 126 | 127 | if (!(message instanceof AnnounceResponseMessage)) { 128 | throw new AnnounceException("Unexpected tracker message " + message); 129 | } 130 | 131 | if (inhibitEvents) { 132 | return; 133 | } 134 | 135 | AnnounceResponseMessage response = (AnnounceResponseMessage) message; 136 | listener.handleAnnounceResponse(tracker, event, response); 137 | } 138 | } -------------------------------------------------------------------------------- /ttorrent-tracker-client/src/test/java/com/turn/ttorrent/tracker/client/HTTPTrackerClientTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.tracker.client; 6 | 7 | import com.turn.ttorrent.protocol.tracker.TrackerMessage; 8 | import com.turn.ttorrent.tracker.client.test.TestPeerAddressProvider; 9 | import com.turn.ttorrent.tracker.client.test.TestTorrentMetadataProvider; 10 | import java.net.URI; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.concurrent.CountDownLatch; 14 | import java.util.concurrent.atomic.AtomicInteger; 15 | import org.junit.Test; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | import static org.junit.Assert.*; 19 | 20 | /** 21 | * 22 | * @author shevek 23 | */ 24 | public class HTTPTrackerClientTest { 25 | 26 | private static final Logger LOG = LoggerFactory.getLogger(HTTPTrackerClientTest.class); 27 | private final byte[] infoHash = new byte[]{1, 2, 3, 4, 5, 6, 7, 8}; 28 | private final List> uris = new ArrayList>(); 29 | private final TorrentMetadataProvider metadataProvider = new TestTorrentMetadataProvider(infoHash, uris); 30 | 31 | private static class ResponseListener implements AnnounceResponseListener { 32 | 33 | private final CountDownLatch latch = new CountDownLatch(1); 34 | private final AtomicInteger failed = new AtomicInteger(0); 35 | 36 | @Override 37 | public void handleAnnounceResponse(URI tracker, TrackerMessage.AnnounceEvent event, TrackerMessage.AnnounceResponseMessage response) { 38 | LOG.info("Response: " + tracker + ": " + event + " -> " + response); 39 | latch.countDown(); 40 | } 41 | 42 | @Override 43 | public void handleAnnounceFailed(URI tracker, TrackerMessage.AnnounceEvent event, String reason) { 44 | LOG.info("Failed: " + tracker + ": " + event + " -> " + reason); 45 | failed.getAndIncrement(); 46 | latch.countDown(); 47 | } 48 | } 49 | 50 | @Test 51 | public void testConnectionRefused() throws Exception { 52 | HTTPTrackerClient client = new HTTPTrackerClient(new TestPeerAddressProvider()); 53 | client.start(); 54 | try { 55 | ResponseListener listener = new ResponseListener(); 56 | URI uri = new URI("http://localhost:12/announce"); // Connection refused. 57 | client.announce(listener, metadataProvider, uri, TrackerMessage.AnnounceEvent.STARTED, true); 58 | listener.latch.await(); 59 | assertEquals(1, listener.failed.get()); 60 | } finally { 61 | client.stop(); 62 | } 63 | } 64 | 65 | @Test 66 | public void testConnectionTimeout() throws Exception { 67 | HTTPTrackerClient client = new HTTPTrackerClient(new TestPeerAddressProvider()); 68 | client.start(); 69 | try { 70 | ResponseListener listener = new ResponseListener(); 71 | URI uri = new URI("http://1.1.1.1:80/announce"); // Connection timeout, I hope. 72 | client.announce(listener, metadataProvider, uri, TrackerMessage.AnnounceEvent.STARTED, true); 73 | listener.latch.await(); 74 | assertEquals(1, listener.failed.get()); 75 | } finally { 76 | client.stop(); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /ttorrent-tracker-client/src/test/java/com/turn/ttorrent/tracker/client/test/TestPeerAddressProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.tracker.client.test; 6 | 7 | import com.google.common.base.Charsets; 8 | import com.turn.ttorrent.protocol.TorrentUtils; 9 | import com.turn.ttorrent.tracker.client.PeerAddressProvider; 10 | import java.net.InetSocketAddress; 11 | import java.net.SocketAddress; 12 | import java.util.Collections; 13 | import java.util.Set; 14 | 15 | /** 16 | * 17 | * @author shevek 18 | */ 19 | public class TestPeerAddressProvider /* extends TestPeerIdentityProvider */ implements PeerAddressProvider { 20 | 21 | @Override 22 | public byte[] getLocalPeerId() { 23 | return getClass().getSimpleName().getBytes(Charsets.ISO_8859_1); 24 | } 25 | 26 | @Override 27 | public String getLocalPeerName() { 28 | return TorrentUtils.toText(getLocalPeerId()); 29 | } 30 | 31 | @Override 32 | public Set getLocalAddresses() { 33 | return Collections.singleton(new InetSocketAddress(17)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ttorrent-tracker-client/src/test/java/com/turn/ttorrent/tracker/client/test/TestTorrentMetadataProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.tracker.client.test; 6 | 7 | import com.turn.ttorrent.tracker.client.TorrentMetadataProvider; 8 | import com.turn.ttorrent.protocol.TorrentUtils; 9 | import java.net.URI; 10 | import java.util.List; 11 | import javax.annotation.Nonnull; 12 | 13 | /** 14 | * 15 | * @author shevek 16 | */ 17 | public class TestTorrentMetadataProvider implements TorrentMetadataProvider { 18 | 19 | private final byte[] infoHash; 20 | private final List> uris; 21 | 22 | public TestTorrentMetadataProvider(@Nonnull byte[] infoHash, @Nonnull List> uris) { 23 | this.infoHash = infoHash; 24 | this.uris = uris; 25 | } 26 | 27 | @Override 28 | public byte[] getInfoHash() { 29 | return infoHash; 30 | } 31 | 32 | @Override 33 | public State getState() { 34 | return State.SHARING; 35 | } 36 | 37 | @Override 38 | public List> getAnnounceList() { 39 | return uris; 40 | } 41 | 42 | @Override 43 | public long getUploaded() { 44 | return 0L; 45 | } 46 | 47 | @Override 48 | public long getDownloaded() { 49 | return 0L; 50 | } 51 | 52 | @Override 53 | public long getLeft() { 54 | return 0L; 55 | } 56 | 57 | @Override 58 | public String toString() { 59 | return "TestTorrentMetadataProvider(" + TorrentUtils.toHex(getInfoHash()) + ")"; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ttorrent-tracker-servlet/src/main/java/com/turn/ttorrent/tracker/servlet/ServletTrackerService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.tracker.servlet; 6 | 7 | import com.google.common.collect.Multimap; 8 | import com.google.common.net.InetAddresses; 9 | import com.turn.ttorrent.protocol.tracker.TrackerMessage; 10 | import com.turn.ttorrent.protocol.tracker.http.HTTPAnnounceRequestMessage; 11 | import com.turn.ttorrent.protocol.tracker.http.HTTPTrackerMessage; 12 | import com.turn.ttorrent.tracker.TrackerService; 13 | import com.turn.ttorrent.tracker.TrackerUtils; 14 | import java.io.IOException; 15 | import java.net.InetAddress; 16 | import java.net.InetSocketAddress; 17 | import javax.annotation.Nonnull; 18 | import javax.servlet.ServletException; 19 | import javax.servlet.http.HttpServletRequest; 20 | import javax.servlet.http.HttpServletResponse; 21 | 22 | /** 23 | * 24 | * @author shevek 25 | */ 26 | public class ServletTrackerService extends TrackerService { 27 | 28 | public void process(@Nonnull HttpServletRequest request, @Nonnull HttpServletResponse response) throws IOException, TrackerMessage.MessageValidationException { 29 | Multimap params = TrackerUtils.parseQuery(request.getParameterMap()); 30 | HTTPAnnounceRequestMessage announceRequest = HTTPAnnounceRequestMessage.fromParams(params); 31 | InetAddress clientAddress = InetAddresses.forString(request.getRemoteAddr()); 32 | HTTPTrackerMessage announceResponse = super.process(new InetSocketAddress(clientAddress, request.getRemotePort()), announceRequest); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ttorrent-tracker-servlet/src/main/java/com/turn/ttorrent/tracker/servlet/TrackerServlet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.tracker.servlet; 6 | 7 | import com.turn.ttorrent.protocol.tracker.TrackerMessage; 8 | import java.io.IOException; 9 | import javax.annotation.Nonnull; 10 | import javax.servlet.ServletException; 11 | import javax.servlet.http.HttpServlet; 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | 15 | /** 16 | * 17 | * @author shevek 18 | */ 19 | public class TrackerServlet extends HttpServlet { 20 | 21 | private final ServletTrackerService service; 22 | 23 | public TrackerServlet(@Nonnull ServletTrackerService service) { 24 | this.service = service; 25 | } 26 | 27 | @Override 28 | protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 29 | try { 30 | service.process(request, response); 31 | } catch (TrackerMessage.MessageValidationException e) { 32 | throw new ServletException(e); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ttorrent-tracker-simple/build.gradle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shevek/ttorrent/f8d80c6df4a3bc4532a7b1d363636b6dd4137f03/ttorrent-tracker-simple/build.gradle -------------------------------------------------------------------------------- /ttorrent-tracker-simple/src/main/java/com/turn/ttorrent/tracker/simple/SimpleTrackerMain.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2013 Turn, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.tracker.simple; 17 | 18 | import com.codahale.metrics.JmxReporter; 19 | import com.turn.ttorrent.protocol.torrent.Torrent; 20 | import com.turn.ttorrent.tracker.TrackerUtils; 21 | import java.io.File; 22 | import java.io.IOException; 23 | import java.net.InetSocketAddress; 24 | 25 | import java.net.URISyntaxException; 26 | import java.util.Arrays; 27 | import joptsimple.OptionParser; 28 | import joptsimple.OptionSet; 29 | import joptsimple.OptionSpec; 30 | import org.slf4j.Logger; 31 | import org.slf4j.LoggerFactory; 32 | 33 | /** 34 | * Command-line entry-point for starting a {@link SimpleTracker} 35 | */ 36 | public class SimpleTrackerMain { 37 | 38 | private static final Logger logger = LoggerFactory.getLogger(SimpleTrackerMain.class); 39 | 40 | private static void addAnnounce(SimpleTracker tracker, File file, int depth) throws IOException, URISyntaxException { 41 | if (file.isFile()) { 42 | logger.info("Loading torrent from " + file.getName()); 43 | if (file.getName().endsWith(".torrent")) 44 | tracker.addTorrent(new Torrent(file)); 45 | return; 46 | } 47 | if (depth > 3) 48 | return; 49 | for (File child : file.listFiles()) 50 | addAnnounce(tracker, child, depth + 1); 51 | } 52 | 53 | /** 54 | * Main function to start a tracker. 55 | */ 56 | public static void main(String[] args) throws Exception { 57 | // BasicConfigurator.configure(new ConsoleAppender(new PatternLayout("%d [%-25t] %-5p: %m%n"))); 58 | 59 | OptionParser parser = new OptionParser(); 60 | OptionSpec helpOption = parser.accepts("help") 61 | .forHelp(); 62 | OptionSpec fileOption = parser.acceptsAll(Arrays.asList("file", "directory")) 63 | .withRequiredArg().ofType(File.class) 64 | .required() 65 | .describedAs("The list of torrent directories or files to announce."); 66 | OptionSpec portOption = parser.accepts("port") 67 | .withRequiredArg().ofType(Integer.class) 68 | .defaultsTo(TrackerUtils.DEFAULT_TRACKER_PORT) 69 | .required() 70 | .describedAs("The port to listen on."); 71 | parser.nonOptions().ofType(File.class); 72 | 73 | OptionSet options = parser.parse(args); 74 | // List otherArgs = options.nonOptionArguments(); 75 | 76 | // Display help and exit if requested 77 | if (options.has(helpOption)) { 78 | System.out.println("Usage: " + SimpleTrackerMain.class.getSimpleName() + " []"); 79 | parser.printHelpOn(System.err); 80 | System.exit(0); 81 | } 82 | 83 | InetSocketAddress address = new InetSocketAddress(options.valueOf(portOption)); 84 | SimpleTracker t = new SimpleTracker(address); 85 | JmxReporter reporter = JmxReporter.forRegistry(t.getMetricRegistry()).build(); 86 | 87 | try { 88 | for (File file : options.valuesOf(fileOption)) 89 | addAnnounce(t, file, 0); 90 | logger.info("Starting tracker with {} announced torrents...", t.getTorrents().size()); 91 | t.start(); 92 | reporter.start(); 93 | } catch (Exception e) { 94 | logger.error("{}", e.getMessage(), e); 95 | System.exit(2); 96 | } finally { 97 | t.stop(); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /ttorrent-tracker-simple/src/test/java/com/turn/ttorrent/tracker/simple/SimpleTrackerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.tracker.simple; 6 | 7 | import com.turn.ttorrent.tracker.TrackerUtils; 8 | import java.net.InetAddress; 9 | import java.net.InetSocketAddress; 10 | import java.net.URI; 11 | import javax.annotation.Nonnull; 12 | import org.apache.http.client.methods.CloseableHttpResponse; 13 | import org.apache.http.client.methods.HttpGet; 14 | import org.apache.http.impl.client.CloseableHttpClient; 15 | import org.apache.http.impl.client.HttpClientBuilder; 16 | import org.junit.Test; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | /** 21 | * 22 | * @author shevek 23 | */ 24 | public class SimpleTrackerTest { 25 | 26 | private static final Logger LOG = LoggerFactory.getLogger(SimpleTrackerTest.class); 27 | private static final String[] PATHS = { 28 | "/", 29 | "/foo", 30 | "/announce", 31 | "/announce?foo" 32 | }; 33 | 34 | private void test(@Nonnull SimpleTracker tracker) throws Exception { 35 | LOG.info("Before start: " + tracker.getAnnounceUris()); 36 | tracker.start(); 37 | try { 38 | LOG.info("Running: " + tracker.getAnnounceUris()); 39 | CloseableHttpClient client = HttpClientBuilder.create().build(); 40 | for (URI uri : tracker.getAnnounceUris()) { 41 | for (String path : PATHS) { 42 | HttpGet request = new HttpGet(uri.resolve(path)); 43 | CloseableHttpResponse response = client.execute(request); 44 | LOG.info(request + " -> " + response); 45 | response.close(); 46 | } 47 | } 48 | } finally { 49 | tracker.stop(); 50 | } 51 | LOG.info("Done."); 52 | } 53 | 54 | private void testTracker(@Nonnull InetSocketAddress address) throws Exception { 55 | SimpleTracker tracker = new SimpleTracker(); 56 | tracker.addListenAddress(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); 57 | test(tracker); 58 | } 59 | 60 | @Test 61 | public void testLoopback() throws Exception { 62 | testTracker(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); 63 | } 64 | 65 | @Test 66 | public void testPort() throws Exception { 67 | testTracker(new InetSocketAddress(TrackerUtils.DEFAULT_TRACKER_PORT)); 68 | } 69 | 70 | @Test 71 | public void testInaddrLoopback() throws Exception { 72 | testTracker(new InetSocketAddress(InetAddress.getLoopbackAddress(), TrackerUtils.DEFAULT_TRACKER_PORT)); 73 | } 74 | 75 | @Test 76 | public void testInaddrAny() throws Exception { 77 | testTracker(new InetSocketAddress("0.0.0.0", TrackerUtils.DEFAULT_TRACKER_PORT)); 78 | } 79 | } -------------------------------------------------------------------------------- /ttorrent-tracker-spring/build.gradle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shevek/ttorrent/f8d80c6df4a3bc4532a7b1d363636b6dd4137f03/ttorrent-tracker-spring/build.gradle -------------------------------------------------------------------------------- /ttorrent-tracker-spring/src/main/java/com/turn/ttorrent/tracker/spring/TrackerController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.tracker.spring; 6 | 7 | import com.turn.ttorrent.protocol.tracker.TrackerMessage; 8 | import com.turn.ttorrent.tracker.servlet.ServletTrackerService; 9 | import java.io.IOException; 10 | import javax.annotation.Nonnull; 11 | import javax.servlet.ServletException; 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | 17 | /** 18 | * 19 | * @author shevek 20 | */ 21 | @RequestMapping("/announce") 22 | public class TrackerController { 23 | 24 | private final ServletTrackerService service; 25 | 26 | @Autowired 27 | public TrackerController(@Nonnull ServletTrackerService service) { 28 | this.service = service; 29 | } 30 | 31 | @RequestMapping(value = "/") 32 | public void request( 33 | @Nonnull HttpServletRequest request, 34 | @Nonnull HttpServletResponse response) throws ServletException, IOException, TrackerMessage.MessageValidationException { 35 | service.process(request, response); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ttorrent-tracker/build.gradle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shevek/ttorrent/f8d80c6df4a3bc4532a7b1d363636b6dd4137f03/ttorrent-tracker/build.gradle -------------------------------------------------------------------------------- /ttorrent-tracker/src/main/java/com/turn/ttorrent/tracker/TrackedPeerState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 shevek. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.turn.ttorrent.tracker; 17 | 18 | /** 19 | * Represents the state of a peer exchanging on this torrent. 20 | * 21 | *

22 | * Peers can be in the STARTED state, meaning they have announced 23 | * themselves to us and are eventually exchanging data with other peers. 24 | * Note that a peer starting with a completed file will also be in the 25 | * started state and will never notify as being in the completed state. 26 | * This information can be inferred from the fact that the peer reports 0 27 | * bytes left to download. 28 | *

29 | * 30 | *

31 | * Peers enter the COMPLETED state when they announce they have entirely 32 | * downloaded the file. As stated above, we may also elect them for this 33 | * state if they report 0 bytes left to download. 34 | *

35 | * 36 | *

37 | * Peers enter the STOPPED state very briefly before being removed. We 38 | * still pass them to the STOPPED state in case someone else kept a 39 | * reference on them. 40 | *

41 | */ 42 | public enum TrackedPeerState { 43 | 44 | UNKNOWN, STARTED, COMPLETED, STOPPED 45 | } 46 | -------------------------------------------------------------------------------- /ttorrent-tracker/src/main/java/com/turn/ttorrent/tracker/TrackerMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.tracker; 6 | 7 | import com.codahale.metrics.Counter; 8 | import com.codahale.metrics.Gauge; 9 | import com.codahale.metrics.Meter; 10 | import com.codahale.metrics.MetricRegistry; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.concurrent.TimeUnit; 14 | import javax.annotation.Nonnull; 15 | 16 | /** 17 | * 18 | * @author shevek 19 | */ 20 | public class TrackerMetrics { 21 | 22 | private final MetricRegistry metricRegistry; 23 | private final Object trackerId; 24 | private final List names = new ArrayList(); 25 | 26 | public TrackerMetrics(@Nonnull MetricRegistry metricRegistry, Object trackerId) { 27 | this.metricRegistry = metricRegistry; 28 | this.trackerId = trackerId; 29 | } 30 | 31 | @Nonnull 32 | private String newMetricName(@Nonnull String name) { 33 | String n = MetricRegistry.name(getClass().getName(), "Tracker-" + trackerId, name); 34 | synchronized (names) { 35 | names.add(n); 36 | } 37 | return n; 38 | } 39 | 40 | @Nonnull 41 | public Counter addCounter(@Nonnull String name) { 42 | return metricRegistry.counter(newMetricName(name)); 43 | } 44 | 45 | @Nonnull 46 | public Gauge addGauge(@Nonnull String name, @Nonnull Gauge gauge) { 47 | return metricRegistry.register(newMetricName(name), gauge); 48 | } 49 | 50 | @Nonnull 51 | public Meter addMeter(@Nonnull String name, @Nonnull String item, @Nonnull TimeUnit unit) { 52 | return metricRegistry.meter(newMetricName(name)); 53 | } 54 | 55 | public void shutdown() { 56 | for (String n : names) { 57 | metricRegistry.remove(n); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ttorrent-tracker/src/main/java/com/turn/ttorrent/tracker/TrackerUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.turn.ttorrent.tracker; 6 | 7 | import com.google.common.base.Splitter; 8 | import com.google.common.collect.ArrayListMultimap; 9 | import com.google.common.collect.HashMultimap; 10 | import com.google.common.collect.Multimap; 11 | import com.turn.ttorrent.protocol.bcodec.BEUtils; 12 | import java.io.UnsupportedEncodingException; 13 | import java.net.URLDecoder; 14 | import java.util.Map; 15 | import javax.annotation.CheckForNull; 16 | import javax.annotation.Nonnull; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | /** 21 | * 22 | * @author shevek 23 | */ 24 | public class TrackerUtils { 25 | 26 | private static final Logger LOG = LoggerFactory.getLogger(TrackerUtils.class); 27 | /** Default server name and version announced by the tracker. */ 28 | public static final String DEFAULT_VERSION_STRING = "BitTorrent Tracker (ttorrent)"; 29 | /** Request path handled by the tracker announce request handler. */ 30 | public static final String DEFAULT_ANNOUNCE_URL = "/announce"; 31 | /** Default tracker listening port (BitTorrent's default is 6969). */ 32 | public static final int DEFAULT_TRACKER_PORT = 6969; 33 | 34 | @Nonnull 35 | public static Multimap parseQuery(String query) { 36 | Multimap params = ArrayListMultimap.create(); 37 | Splitter ampersand = Splitter.on('&').omitEmptyStrings(); 38 | // Splitter equals = Splitter.on('=').limit(2); 39 | 40 | try { 41 | for (String pair : ampersand.split(query)) { 42 | String[] keyval = pair.split("[=]", 2); 43 | if (keyval.length == 1) { 44 | parseParam(params, keyval[0], null); 45 | } else { 46 | parseParam(params, keyval[0], keyval[1]); 47 | } 48 | } 49 | } catch (ArrayIndexOutOfBoundsException e) { 50 | params.clear(); 51 | } 52 | return params; 53 | } 54 | 55 | private static void parseParam(@Nonnull Multimap params, @Nonnull String key, @CheckForNull String value) { 56 | try { 57 | if (value != null) 58 | value = URLDecoder.decode(value, BEUtils.BYTE_ENCODING_NAME); 59 | else 60 | value = ""; 61 | params.put(key, value); 62 | } catch (UnsupportedEncodingException uee) { 63 | // Ignore, act like parameter was not there 64 | if (LOG.isDebugEnabled()) 65 | LOG.debug("Could not decode {}", value); 66 | } 67 | } 68 | 69 | @Nonnull 70 | public static Multimap parseQuery(Map in) { 71 | Multimap out = HashMultimap.create(); 72 | for (Map.Entry e : in.entrySet()) { 73 | for (String value : e.getValue()) 74 | out.put(e.getKey(), value); 75 | } 76 | return out; 77 | } 78 | } 79 | --------------------------------------------------------------------------------