mirror of
https://github.com/Rogiel/torrent4j
synced 2025-12-07 08:02:49 +00:00
Change-Id: I0f4f7ffe65dcfbaaaa792ebb43566c7d07c7569d
This commit is contained in:
173
src/main/java/net/torrent/BitTorrentClient.java
Normal file
173
src/main/java/net/torrent/BitTorrentClient.java
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import net.torrent.protocol.algorithm.TorrentAlgorithm;
|
||||
import net.torrent.protocol.peerwire.PeerWireManager;
|
||||
import net.torrent.protocol.peerwire.manager.TorrentManager;
|
||||
import net.torrent.torrent.context.TorrentContext;
|
||||
import net.torrent.torrent.context.TorrentPeer;
|
||||
|
||||
/**
|
||||
* This is the main class used to controll your torrent transfer. It is not
|
||||
* recommended to directly instantiate this class, instead use
|
||||
* {@link BitTorrentClientFactory} to create new instances.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class BitTorrentClient implements Runnable {
|
||||
/**
|
||||
* Configuration of an BitTorrentClient.
|
||||
*/
|
||||
private final BitTorrentConfiguration config;
|
||||
|
||||
/**
|
||||
* The torrent context
|
||||
*/
|
||||
private final TorrentContext context;
|
||||
/**
|
||||
* The torrent manager
|
||||
*/
|
||||
private final TorrentManager manager;
|
||||
/**
|
||||
* The peer wire protocol manager
|
||||
*/
|
||||
private final PeerWireManager peerWire;
|
||||
/**
|
||||
* The torrent algorithm
|
||||
*/
|
||||
private final TorrentAlgorithm algorithm;
|
||||
|
||||
/**
|
||||
* Timer used to create new connections
|
||||
*/
|
||||
private final Timer connectorTimer = new Timer();
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param config
|
||||
* the configuration
|
||||
* @param manager
|
||||
* the torrent manager
|
||||
* @param peerWire
|
||||
* the peer wire protocol manager
|
||||
* @param algorithm
|
||||
* the torrent algorithm
|
||||
*/
|
||||
public BitTorrentClient(final BitTorrentConfiguration config,
|
||||
TorrentManager manager, PeerWireManager peerWire,
|
||||
TorrentAlgorithm algorithm) {
|
||||
this.config = config;
|
||||
this.context = manager.getContext();
|
||||
this.manager = manager;
|
||||
this.peerWire = peerWire;
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start this torrent
|
||||
*/
|
||||
public void start() {
|
||||
start((InetSocketAddress[]) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start this torrent. Once network is up, tries to connect to all the peers
|
||||
* in <tt>addrs</tt>.
|
||||
*
|
||||
* @param addrs
|
||||
* addresses
|
||||
*/
|
||||
public void start(InetSocketAddress... addrs) {
|
||||
if (config.getListenPort() > 0)
|
||||
peerWire.listen(config.getListenPort());
|
||||
|
||||
// run every 10 seconds - only 1 connection per turn
|
||||
connectorTimer.schedule(new ConnectorTimerTask(), 0, 10 * 1000);
|
||||
|
||||
if (addrs != null)
|
||||
for (final InetSocketAddress addr : addrs) {
|
||||
peerWire.connect(addr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Task that creates new connections at a certain repeat rate. Only one
|
||||
* connection per turn.
|
||||
*
|
||||
* @author Rogiel Josias Sulzbach (<a
|
||||
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
|
||||
*/
|
||||
public class ConnectorTimerTask extends TimerTask {
|
||||
@Override
|
||||
public void run() {
|
||||
TorrentPeer peer = null;
|
||||
while ((peer = algorithm.getPeerAlgorithm().connect()) != null) {
|
||||
peerWire.connect(peer.getSocketAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the torrent context
|
||||
*
|
||||
* @return the torrent context
|
||||
*/
|
||||
public TorrentContext getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the torrent manager
|
||||
*
|
||||
* @return the torrent manager
|
||||
*/
|
||||
public TorrentManager getManager() {
|
||||
return manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* The peerwire manager
|
||||
*
|
||||
* @return the peerwire manager
|
||||
*/
|
||||
public PeerWireManager getPeerWire() {
|
||||
return peerWire;
|
||||
}
|
||||
|
||||
/**
|
||||
* The torret
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public BitTorrentConfiguration getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public TorrentAlgorithm getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
}
|
||||
231
src/main/java/net/torrent/BitTorrentClientFactory.java
Normal file
231
src/main/java/net/torrent/BitTorrentClientFactory.java
Normal file
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import net.torrent.protocol.algorithm.TorrentAlgorithm;
|
||||
import net.torrent.protocol.algorithm.impl.TorrentStdAlgorithm;
|
||||
import net.torrent.protocol.datastore.TorrentDatastore;
|
||||
import net.torrent.protocol.datastore.impl.PlainTorrentDatastore;
|
||||
import net.torrent.protocol.peerwire.PeerWireManager;
|
||||
import net.torrent.protocol.peerwire.manager.TorrentManager;
|
||||
import net.torrent.torrent.Torrent;
|
||||
import net.torrent.torrent.context.TorrentContext;
|
||||
|
||||
/**
|
||||
* Factory class for {@link BitTorrentClient}.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class BitTorrentClientFactory {
|
||||
/**
|
||||
* The client's configuration
|
||||
*/
|
||||
private BitTorrentConfiguration config = new BitTorrentConfiguration();
|
||||
|
||||
/**
|
||||
* The torrent context
|
||||
*/
|
||||
private final TorrentContext context;
|
||||
/**
|
||||
* The torrent datastore
|
||||
*/
|
||||
private TorrentDatastore datastore;
|
||||
/**
|
||||
* The torrent manager
|
||||
*/
|
||||
private TorrentManager manager;
|
||||
/**
|
||||
* The torrent algorithm
|
||||
*/
|
||||
private TorrentAlgorithm algorithm;
|
||||
|
||||
/**
|
||||
* Creates a new standard {@link BitTorrentClient BitTorrent client}
|
||||
*
|
||||
* @param file
|
||||
* the torrent file
|
||||
* @return a new client
|
||||
* @throws IOException
|
||||
* @throws URISyntaxException
|
||||
*/
|
||||
public static BitTorrentClient newStandardBitTorrentClient(File file)
|
||||
throws IOException, URISyntaxException {
|
||||
return new BitTorrentClientFactory(Torrent.load(file))
|
||||
.newBitTorrentClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new factory instance
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
*/
|
||||
public BitTorrentClientFactory(final Torrent torrent) {
|
||||
context = new TorrentContext(torrent);
|
||||
datastore = new PlainTorrentDatastore(new File("store.bin"));
|
||||
manager = new TorrentManager(context, datastore);
|
||||
algorithm = new TorrentStdAlgorithm(manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new factory instance
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param algorithm
|
||||
* the torrent algorithm
|
||||
*/
|
||||
public BitTorrentClientFactory(final Torrent torrent,
|
||||
final TorrentAlgorithm algorithm) {
|
||||
context = new TorrentContext(torrent);
|
||||
datastore = new PlainTorrentDatastore(null);
|
||||
manager = new TorrentManager(context, datastore);
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new factory instance
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param datastore
|
||||
* the torrent datastore
|
||||
*/
|
||||
public BitTorrentClientFactory(final Torrent torrent,
|
||||
TorrentDatastore datastore) {
|
||||
context = new TorrentContext(torrent);
|
||||
this.datastore = datastore;
|
||||
manager = new TorrentManager(context, datastore);
|
||||
algorithm = new TorrentStdAlgorithm(manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new factory instance
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param datastore
|
||||
* the torrent datastore
|
||||
* @param algorithm
|
||||
* the torrent algorithm
|
||||
*/
|
||||
public BitTorrentClientFactory(final Torrent torrent,
|
||||
TorrentDatastore datastore, final TorrentAlgorithm algorithm) {
|
||||
context = new TorrentContext(torrent);
|
||||
this.datastore = datastore;
|
||||
manager = new TorrentManager(context, datastore);
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the {@link BitTorrentClient} object
|
||||
*
|
||||
* @return the created {@link BitTorrentClient}
|
||||
*/
|
||||
public BitTorrentClient newBitTorrentClient() {
|
||||
final PeerWireManager peerWire = new PeerWireManager(manager, algorithm);
|
||||
return new BitTorrentClient(config, manager, peerWire, algorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the client configuration
|
||||
*
|
||||
* @return the client configuration
|
||||
*/
|
||||
public BitTorrentConfiguration getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the client configuration
|
||||
*
|
||||
* @param config
|
||||
* the client configuration
|
||||
*/
|
||||
public void setConfig(BitTorrentConfiguration config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the datastore
|
||||
*
|
||||
* @return the datastore
|
||||
*/
|
||||
public TorrentDatastore getDatastore() {
|
||||
return datastore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the datastore
|
||||
*
|
||||
* @param datastore
|
||||
* the datastore
|
||||
*/
|
||||
public void setDatastore(TorrentDatastore datastore) {
|
||||
this.datastore = datastore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the torrent manager
|
||||
*
|
||||
* @return the torrent manager
|
||||
*/
|
||||
public TorrentManager getManager() {
|
||||
return manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the torrent manager
|
||||
*
|
||||
* @param manager
|
||||
* the torrent manager
|
||||
*/
|
||||
public void setManager(TorrentManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the torrent algorithm
|
||||
*
|
||||
* @return the torrent algorithm
|
||||
*/
|
||||
public TorrentAlgorithm getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the torrent algorithm
|
||||
*
|
||||
* @param algorithm
|
||||
* the torrent algorithm
|
||||
*/
|
||||
public void setAlgorithm(TorrentAlgorithm algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the torrent context
|
||||
*
|
||||
* @return the torrent context
|
||||
*/
|
||||
public TorrentContext getContext() {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
71
src/main/java/net/torrent/BitTorrentConfiguration.java
Normal file
71
src/main/java/net/torrent/BitTorrentConfiguration.java
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent;
|
||||
|
||||
/**
|
||||
* Configurations for an {@link BitTorrentClient BitTorrent client} instance.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class BitTorrentConfiguration {
|
||||
/**
|
||||
* Default peerwire listen port
|
||||
*/
|
||||
private int listenPort = 58462;
|
||||
/**
|
||||
* Default peer id
|
||||
*/
|
||||
private byte[] peerID = null;
|
||||
|
||||
/**
|
||||
* Get the port
|
||||
*
|
||||
* @return the port
|
||||
*/
|
||||
public int getListenPort() {
|
||||
return listenPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the listening port for the server.<br />
|
||||
* 0 will disable server.
|
||||
*
|
||||
* @param listenPort
|
||||
* the port
|
||||
*/
|
||||
public void setListenPort(int listenPort) {
|
||||
this.listenPort = listenPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the peer id.
|
||||
*
|
||||
* @return the peer id
|
||||
*/
|
||||
public byte[] getPeerID() {
|
||||
return peerID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the peer id. If null a random peerid will be generated.
|
||||
*
|
||||
* @param peerID
|
||||
* the peerid. Can be null.
|
||||
*/
|
||||
public void setPeerID(byte[] peerID) {
|
||||
this.peerID = peerID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.algorithm;
|
||||
|
||||
import net.torrent.protocol.peerwire.handler.PeerWireAlgorithmHandler;
|
||||
|
||||
/**
|
||||
* An {@link TorrentAlgorithm} defines the rules for download, upload and
|
||||
* connection management. These algorithms provide limited control, in the
|
||||
* boundaries of standard torrent behavior. If you wish more control, at the
|
||||
* protocol layer, try implementing a new {@link PeerWireAlgorithmHandler}.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
* @see TorrentPeerAlgorithm
|
||||
* @see TorrentInterestAlgorithm
|
||||
* @see TorrentPieceDownloadAlgorithm
|
||||
* @see TorrentPieceUploadAlgorithm
|
||||
*/
|
||||
public interface TorrentAlgorithm {
|
||||
/**
|
||||
* Creates a new instance of {@link TorrentPeerAlgorithm}.
|
||||
*
|
||||
* @return the new {@link TorrentPeerAlgorithm} instance
|
||||
*/
|
||||
TorrentPeerAlgorithm getPeerAlgorithm();
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link TorrentInterestAlgorithm}.
|
||||
*
|
||||
* @return the new {@link TorrentInterestAlgorithm} instance
|
||||
*/
|
||||
TorrentInterestAlgorithm getInterestAlgorithm();
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link TorrentPieceDownloadAlgorithm}.
|
||||
*
|
||||
* @return the new {@link TorrentPieceDownloadAlgorithm} instance
|
||||
*/
|
||||
TorrentPieceDownloadAlgorithm getDownloadAlgorithm();
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link TorrentPieceUploadAlgorithm}.
|
||||
*
|
||||
* @return the new {@link TorrentPieceUploadAlgorithm} instance
|
||||
*/
|
||||
TorrentPieceUploadAlgorithm getUploadAlgorithm();
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.algorithm;
|
||||
|
||||
import net.torrent.torrent.context.TorrentPeer;
|
||||
import net.torrent.torrent.context.TorrentPeer.ChokingState;
|
||||
import net.torrent.torrent.context.TorrentPeer.InterestState;
|
||||
|
||||
/**
|
||||
* Algorithm used to determine the interest and choking in peers.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public interface TorrentInterestAlgorithm {
|
||||
/**
|
||||
* Test if we are interested in this peer pieces. Interest is for download
|
||||
* only.
|
||||
*
|
||||
* @param peer
|
||||
* the peer
|
||||
* @return our interest
|
||||
* @see InterestState
|
||||
*/
|
||||
InterestState interested(TorrentPeer peer);
|
||||
|
||||
/**
|
||||
* Test if we want to choke this peer. This is normally invoked when we have
|
||||
* no more interest in the peer pieces.
|
||||
*
|
||||
* @param peer
|
||||
* the peer
|
||||
* @return the choking state
|
||||
* @see ChokingState
|
||||
*/
|
||||
ChokingState choke(TorrentPeer peer);
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.algorithm;
|
||||
|
||||
import net.torrent.torrent.context.TorrentPeer;
|
||||
import net.torrent.torrent.context.TorrentPeer.ChokingState;
|
||||
import net.torrent.torrent.context.TorrentPeer.InterestState;
|
||||
|
||||
/**
|
||||
* Algorithm that processes peer interest, choking state and connections.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public interface TorrentPeerAlgorithm {
|
||||
/**
|
||||
* Get an peer to be connected. This method is invoked in many situations.
|
||||
*
|
||||
* @return the new peer to be connected
|
||||
* @see TorrentPeer
|
||||
*/
|
||||
TorrentPeer connect();
|
||||
|
||||
/**
|
||||
* Once a new peer is discovered, this method is called to test if we want
|
||||
* to connect to it or not.
|
||||
*
|
||||
* @param peer
|
||||
* the new discovered peer
|
||||
* @return the desired action
|
||||
*/
|
||||
PeerDiscoveredAction discovered(TorrentPeer peer);
|
||||
|
||||
/**
|
||||
* Action to be performed when an peer is idle.
|
||||
*
|
||||
* @author Rogiel Josias Sulzbach (<a
|
||||
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
|
||||
*/
|
||||
public enum PeerDiscoveredAction {
|
||||
/**
|
||||
* Try to establish an connection to this new peer
|
||||
*/
|
||||
CONNECT,
|
||||
|
||||
/**
|
||||
* Remove this peer from list
|
||||
*/
|
||||
REMOVE,
|
||||
|
||||
/**
|
||||
* Nothing is done.
|
||||
*/
|
||||
NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to keep this connection alive. The value sent represents the action
|
||||
* which the handler will do with the idle peer.
|
||||
*
|
||||
* @param peer
|
||||
* the peer
|
||||
* @return the action to be done
|
||||
* @see KeepAliveAction KeepAliveAction for a list of actions
|
||||
*/
|
||||
KeepAliveAction keepAlive(TorrentPeer peer);
|
||||
|
||||
/**
|
||||
* Action to be performed when an peer is idle.
|
||||
*
|
||||
* @author Rogiel Josias Sulzbach (<a
|
||||
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
|
||||
*/
|
||||
public enum KeepAliveAction {
|
||||
/**
|
||||
* Keep this connection alive
|
||||
*/
|
||||
KEEP_ALIVE,
|
||||
|
||||
/**
|
||||
* Disconnect the peer. No new connection is made.
|
||||
*/
|
||||
DISCONNECT,
|
||||
|
||||
/**
|
||||
* Disconnect the peer AND connects another peer.
|
||||
*/
|
||||
CONNECT_NEW_PEER,
|
||||
|
||||
/**
|
||||
* Nothing is done.
|
||||
*/
|
||||
NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the peer interested in us changes. Return the desired choke
|
||||
* or unchoked action.
|
||||
*
|
||||
* @param peer
|
||||
* the peer
|
||||
* @param interest
|
||||
* the new interest (it is also already set in the peer object)
|
||||
* @return the desired choke/unchoke action
|
||||
* @see ChokingState
|
||||
*/
|
||||
ChokingState interested(TorrentPeer peer, InterestState interest);
|
||||
|
||||
/**
|
||||
* Called when the peer choke state change with us (that is, <b>when the
|
||||
* peer choke or unchoke</b>!).
|
||||
*
|
||||
* @param peer
|
||||
* the peer
|
||||
* @param state
|
||||
* the new choking state
|
||||
* @return the desired action to be taken
|
||||
* @see PeerChokedAction
|
||||
*/
|
||||
PeerChokedAction choked(TorrentPeer peer, ChokingState state);
|
||||
|
||||
/**
|
||||
* Action to be performed when the peer changes it's choke state
|
||||
*
|
||||
* @author Rogiel Josias Sulzbach (<a
|
||||
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
|
||||
*/
|
||||
public enum PeerChokedAction {
|
||||
/**
|
||||
* Disconnects the current peer and connects a new one
|
||||
*/
|
||||
CONNECT_NEW_PEER,
|
||||
|
||||
/**
|
||||
* Only disconnects the peer, does not initiate a new connection with
|
||||
* anyone.
|
||||
*/
|
||||
DISCONNECT,
|
||||
|
||||
/**
|
||||
* Download a new piece (this is only valid if unchoked)
|
||||
*/
|
||||
DOWNLOAD,
|
||||
|
||||
/**
|
||||
* Do nothing, ignore.
|
||||
*/
|
||||
NONE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.algorithm;
|
||||
|
||||
import net.torrent.protocol.peerwire.handler.PeerWireAlgorithmHandler;
|
||||
import net.torrent.torrent.TorrentPart;
|
||||
import net.torrent.torrent.TorrentPiece;
|
||||
import net.torrent.torrent.context.TorrentPeer;
|
||||
|
||||
/**
|
||||
* This algorithm is used to return the {@link TorrentPart part} for download
|
||||
* and validates if an certain {@link TorrentPiece piece} is complete. Please
|
||||
* note that this algorithm DOES NOT do the checksum in the piece! The checksum
|
||||
* is done at the {@link PeerWireAlgorithmHandler handler} level.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public interface TorrentPieceDownloadAlgorithm {
|
||||
/**
|
||||
* Return the next part desired for download
|
||||
*
|
||||
* @param peer
|
||||
* the peer
|
||||
* @param part
|
||||
* the part which has completed. Might be null!
|
||||
* @return the new part
|
||||
* @see TorrentPart
|
||||
*/
|
||||
TorrentPart getNextPart(TorrentPeer peer, TorrentPart part);
|
||||
|
||||
/**
|
||||
* Issued when an suggestion is received. If wished to accept it, return the
|
||||
* first part of it, otherwise return null.
|
||||
*
|
||||
* @param peer
|
||||
* the suggesting peer
|
||||
* @param piece
|
||||
* the suggested piece
|
||||
*/
|
||||
TorrentPart sugested(TorrentPeer peer, TorrentPiece piece);
|
||||
|
||||
/**
|
||||
* Issued when allowed to fast download an given piece, even while choked.
|
||||
* If multiple pieces are allowed, one call per piece will be done. If
|
||||
* willing to download pieces, return the first part of the piece.
|
||||
*
|
||||
* @param peer
|
||||
* the allowing peer
|
||||
* @param piece
|
||||
* the allowed piece
|
||||
*/
|
||||
TorrentPart allowedFast(TorrentPeer peer, TorrentPiece piece);
|
||||
|
||||
/**
|
||||
* Test if an certain piece has all its parts already download. If true, a
|
||||
* checksum will be performed and a message informing we have this piece
|
||||
* will be broadcasted. This call is only valid once the next part has
|
||||
* already been taken.
|
||||
*
|
||||
* @param peer
|
||||
* the peer
|
||||
* @param piece
|
||||
* the piece to test
|
||||
* @return true if complete, false otherwise.
|
||||
*/
|
||||
boolean isComplete(TorrentPeer peer, TorrentPiece piece);
|
||||
|
||||
/**
|
||||
* Called when an piece is complete but found to be corrupted
|
||||
*
|
||||
* @param peer
|
||||
* the peer who send the piece (more precisely the completing
|
||||
* part)
|
||||
* @param piece
|
||||
* the piece
|
||||
* @return the action to be performed
|
||||
* @see CorruptedAction
|
||||
*/
|
||||
CorruptedAction corrupted(TorrentPeer peer, TorrentPiece piece);
|
||||
|
||||
/**
|
||||
* Actions to be taken when a corrupted piece is downloaded.
|
||||
*
|
||||
* @author Rogiel Josias Sulzbach (<a
|
||||
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
|
||||
*/
|
||||
public enum CorruptedAction {
|
||||
/**
|
||||
* Only disconnects the peer, does not initiate a new connection with
|
||||
* anyone.
|
||||
*/
|
||||
DISCONNECT,
|
||||
|
||||
/**
|
||||
* Disconnects the current peer and connects a new one
|
||||
*/
|
||||
CONNECT_NEW_PEER,
|
||||
|
||||
/**
|
||||
* Choke this peer
|
||||
*/
|
||||
CHOKE,
|
||||
|
||||
/**
|
||||
* Retry to download the piece
|
||||
*/
|
||||
CONTINUE,
|
||||
|
||||
/**
|
||||
* Do nothing, ignore. Might cause peer to become idle.
|
||||
*/
|
||||
CANCEL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.algorithm;
|
||||
|
||||
import net.torrent.torrent.TorrentPart;
|
||||
import net.torrent.torrent.context.TorrentPeer;
|
||||
|
||||
/**
|
||||
* Algorithm used for upload management. It verifies if we have interest in
|
||||
* uploading an piece and handles cancel requests. TODO how to handle cancels?
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public interface TorrentPieceUploadAlgorithm {
|
||||
/**
|
||||
* Called when an peer has requested a piece to be uploaded.
|
||||
*
|
||||
* @param peer
|
||||
* the peer requesting this piece
|
||||
* @param part
|
||||
* the part requested
|
||||
* @return true if allowed to upload, false otherwise.
|
||||
*/
|
||||
RequestAction request(TorrentPeer peer, TorrentPart part);
|
||||
|
||||
public enum RequestAction {
|
||||
/**
|
||||
* Only disconnects the peer, does not initiate a new connection with
|
||||
* anyone.
|
||||
*/
|
||||
DISCONNECT,
|
||||
|
||||
/**
|
||||
* Disconnects the current peer and connects a new one
|
||||
*/
|
||||
CONNECT_NEW_PEER,
|
||||
|
||||
/**
|
||||
* Reject this request (only if supports Fast Extension). If not
|
||||
* supported will fall back to {@link RequestAction#NONE}
|
||||
*/
|
||||
REJECT,
|
||||
|
||||
/**
|
||||
* Choke this peer
|
||||
*/
|
||||
CHOKE,
|
||||
|
||||
/**
|
||||
* Upload a new piece (this is only valid if unchoked)
|
||||
*/
|
||||
UPLOAD,
|
||||
|
||||
/**
|
||||
* Do nothing, ignore.
|
||||
*/
|
||||
NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a part request.
|
||||
*
|
||||
* @param peer
|
||||
* the peer
|
||||
* @param part
|
||||
* the part
|
||||
* @return TODO
|
||||
*/
|
||||
boolean cancel(TorrentPeer peer, TorrentPart part);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.algorithm.impl;
|
||||
|
||||
import net.torrent.protocol.algorithm.TorrentAlgorithm;
|
||||
import net.torrent.protocol.algorithm.TorrentInterestAlgorithm;
|
||||
import net.torrent.protocol.algorithm.TorrentPeerAlgorithm;
|
||||
import net.torrent.protocol.algorithm.TorrentPieceDownloadAlgorithm;
|
||||
import net.torrent.protocol.algorithm.TorrentPieceUploadAlgorithm;
|
||||
import net.torrent.protocol.peerwire.manager.TorrentManager;
|
||||
|
||||
/**
|
||||
* Standard torrent algorithm
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class TorrentStdAlgorithm implements TorrentAlgorithm {
|
||||
private final TorrentPeerAlgorithm peerAlgorithm;
|
||||
private final TorrentInterestAlgorithm interestAlgorithm;
|
||||
private final TorrentPieceDownloadAlgorithm downloadAlgorithm;
|
||||
private final TorrentPieceUploadAlgorithm uploadAlgorithm;
|
||||
|
||||
public TorrentStdAlgorithm(final TorrentManager manager) {
|
||||
peerAlgorithm = new TorrentStdPeerAlgorithm(manager);
|
||||
interestAlgorithm = new TorrentStdInterestAlgorithm(manager);
|
||||
downloadAlgorithm = new TorrentStdPieceDownloadAlgorithm(manager);
|
||||
uploadAlgorithm = new TorrentStdPieceUploadAlgorithm(manager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TorrentPeerAlgorithm getPeerAlgorithm() {
|
||||
return peerAlgorithm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TorrentInterestAlgorithm getInterestAlgorithm() {
|
||||
return interestAlgorithm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TorrentPieceDownloadAlgorithm getDownloadAlgorithm() {
|
||||
return downloadAlgorithm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TorrentPieceUploadAlgorithm getUploadAlgorithm() {
|
||||
return uploadAlgorithm;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.algorithm.impl;
|
||||
|
||||
import net.torrent.protocol.algorithm.TorrentInterestAlgorithm;
|
||||
import net.torrent.protocol.peerwire.manager.TorrentManager;
|
||||
import net.torrent.torrent.context.TorrentPeer;
|
||||
import net.torrent.torrent.context.TorrentPeer.ChokingState;
|
||||
import net.torrent.torrent.context.TorrentPeer.InterestState;
|
||||
|
||||
/**
|
||||
* Standard torrent interest algorithm
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class TorrentStdInterestAlgorithm implements TorrentInterestAlgorithm {
|
||||
@SuppressWarnings("unused")
|
||||
private final TorrentManager manager;
|
||||
|
||||
public TorrentStdInterestAlgorithm(TorrentManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterestState interested(TorrentPeer peer) {
|
||||
// if(peer.getPort() == 25944)
|
||||
// return InterestState.UNINTERESTED;
|
||||
return InterestState.INTERESTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChokingState choke(TorrentPeer peer) {
|
||||
return ChokingState.UNCHOKED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.algorithm.impl;
|
||||
|
||||
import net.torrent.protocol.algorithm.TorrentPeerAlgorithm;
|
||||
import net.torrent.protocol.peerwire.manager.TorrentManager;
|
||||
import net.torrent.torrent.context.TorrentPeer;
|
||||
import net.torrent.torrent.context.TorrentPeer.ChokingState;
|
||||
import net.torrent.torrent.context.TorrentPeer.InterestState;
|
||||
|
||||
/**
|
||||
* Standard torrent peer algorithm
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class TorrentStdPeerAlgorithm implements TorrentPeerAlgorithm {
|
||||
@SuppressWarnings("unused")
|
||||
private final TorrentManager manager;
|
||||
|
||||
public TorrentStdPeerAlgorithm(TorrentManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TorrentPeer connect() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PeerDiscoveredAction discovered(TorrentPeer peer) {
|
||||
return PeerDiscoveredAction.CONNECT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeepAliveAction keepAlive(TorrentPeer peer) {
|
||||
return KeepAliveAction.KEEP_ALIVE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChokingState interested(TorrentPeer peer, InterestState interest) {
|
||||
switch (interest) {
|
||||
case INTERESTED:
|
||||
return ChokingState.UNCHOKED;
|
||||
case UNINTERESTED:
|
||||
return ChokingState.CHOKED;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PeerChokedAction choked(TorrentPeer peer, ChokingState state) {
|
||||
switch (state) {
|
||||
case CHOKED:
|
||||
return PeerChokedAction.NONE;
|
||||
case UNCHOKED:
|
||||
return PeerChokedAction.DOWNLOAD;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.algorithm.impl;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import net.torrent.protocol.algorithm.TorrentPieceDownloadAlgorithm;
|
||||
import net.torrent.protocol.peerwire.manager.TorrentManager;
|
||||
import net.torrent.torrent.TorrentPart;
|
||||
import net.torrent.torrent.TorrentPiece;
|
||||
import net.torrent.torrent.context.TorrentPeer;
|
||||
import net.torrent.torrent.piece.PieceSelector;
|
||||
import net.torrent.torrent.piece.RandomPieceSelector;
|
||||
|
||||
/**
|
||||
* This standard implementation of {@link TorrentPieceDownloadAlgorithm} chooses
|
||||
* a random missing piece and tries to download all the parts from the same
|
||||
* peer, following the standard behavior of most of torrent clients.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class TorrentStdPieceDownloadAlgorithm implements
|
||||
TorrentPieceDownloadAlgorithm {
|
||||
/**
|
||||
* The torrent manager
|
||||
*/
|
||||
private final TorrentManager manager;
|
||||
// private final TorrentContext context;
|
||||
// private final Torrent torrent;
|
||||
|
||||
/**
|
||||
* This selector is used to find the next piece to be downloaded. Parts are
|
||||
* managed inside this algorithm.
|
||||
*/
|
||||
private final PieceSelector selector;
|
||||
/**
|
||||
* Maps all unchecked completed pieces. The piece is removed from the list
|
||||
* once
|
||||
* {@link TorrentPieceDownloadAlgorithm#isComplete(TorrentPeer, TorrentPiece)}
|
||||
* is called.
|
||||
*/
|
||||
private Set<TorrentPiece> completedPieces = new HashSet<TorrentPiece>();
|
||||
|
||||
/**
|
||||
* Creates a new instance of this algorithm.
|
||||
*
|
||||
* @param manager
|
||||
* the torrent manager instance. With this object is possible to
|
||||
* retrieve current downloads/uploads and connections.
|
||||
*/
|
||||
public TorrentStdPieceDownloadAlgorithm(TorrentManager manager) {
|
||||
this.manager = manager;
|
||||
// this.context = this.manager.getContext();
|
||||
// this.torrent = this.manager.getTorrent();
|
||||
selector = new RandomPieceSelector(manager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TorrentPart getNextPart(TorrentPeer peer, TorrentPart part) {
|
||||
if (part != null) {
|
||||
if (part.isLast()) {
|
||||
completedPieces.add(part.getPiece());
|
||||
} else {
|
||||
return part.getNextPart();
|
||||
}
|
||||
}
|
||||
TorrentPiece piece = selector.select(peer);
|
||||
if (piece == null)
|
||||
// no piece, return null. The default handler will check, again, the
|
||||
// interest on this peer.
|
||||
return null;
|
||||
return piece.getFirstPart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TorrentPart sugested(TorrentPeer peer, TorrentPiece piece) {
|
||||
return piece.getFirstPart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TorrentPart allowedFast(TorrentPeer peer, TorrentPiece piece) {
|
||||
return piece.getFirstPart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isComplete(TorrentPeer peer, TorrentPiece piece) {
|
||||
if (manager.getContext().getBitfield().hasPiece(piece))
|
||||
return true;
|
||||
// minimum overhead possible, will return true if was on list
|
||||
return completedPieces.remove(piece);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CorruptedAction corrupted(TorrentPeer peer, TorrentPiece piece) {
|
||||
// TODO ban peer sending many corrupted pieces
|
||||
return CorruptedAction.CANCEL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.algorithm.impl;
|
||||
|
||||
import net.torrent.protocol.algorithm.TorrentPieceUploadAlgorithm;
|
||||
import net.torrent.protocol.peerwire.manager.TorrentManager;
|
||||
import net.torrent.torrent.TorrentPart;
|
||||
import net.torrent.torrent.context.TorrentPeer;
|
||||
|
||||
/**
|
||||
* Standard torrent upload algorithm
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class TorrentStdPieceUploadAlgorithm implements
|
||||
TorrentPieceUploadAlgorithm {
|
||||
@SuppressWarnings("unused")
|
||||
private final TorrentManager manager;
|
||||
|
||||
public TorrentStdPieceUploadAlgorithm(TorrentManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestAction request(TorrentPeer peer, TorrentPart part) {
|
||||
return RequestAction.NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel(TorrentPeer peer, TorrentPart part) {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.datastore;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
import net.torrent.protocol.datastore.impl.FileAwareTorrentDatastore;
|
||||
import net.torrent.torrent.TorrentPiece;
|
||||
|
||||
/**
|
||||
* Abstract implementation of {@link TorrentDatastore}. The only implemented
|
||||
* method is {@link TorrentDatastore#checksum(TorrentPiece)} since it always the
|
||||
* same.
|
||||
* <p>
|
||||
* Since it is noticeable that this interface does not use the concept of files,
|
||||
* it might be much more useful implementing {@link FileAwareTorrentDatastore}
|
||||
* class instead of this one.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public abstract class AbstractTorrentDatastore implements TorrentDatastore {
|
||||
@Override
|
||||
public boolean checksum(TorrentPiece piece) throws IOException {
|
||||
final MessageDigest digest = piece.getHash().getType()
|
||||
.getMessageDigest();
|
||||
if (digest == null)
|
||||
throw new NullPointerException("Digest is null");
|
||||
byte[] hash = digest.digest(this.read(piece.asSinglePart()).array());
|
||||
return piece.getHash().compare(hash);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.datastore;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import net.torrent.protocol.datastore.impl.FileAwareTorrentDatastore;
|
||||
import net.torrent.torrent.TorrentPart;
|
||||
import net.torrent.torrent.TorrentPiece;
|
||||
|
||||
/**
|
||||
* The datastore is responsible for storing data downloaded from peers and read
|
||||
* them for further upload. The storage should be done as fast as possible,
|
||||
* since slow operations might and will slowdown download and upload rates.
|
||||
* <p>
|
||||
* Since it is noticeable that this interface does not use the concept of files,
|
||||
* it might be much more useful implementing {@link FileAwareTorrentDatastore}
|
||||
* class instead of this interface.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public interface TorrentDatastore {
|
||||
/**
|
||||
* Read an segment of data relative to {@link TorrentPart part}.
|
||||
* <p>
|
||||
* <b>Warning</b>: Synchronization <b>MUST</b> be dealt by implementations.
|
||||
*
|
||||
* @param part
|
||||
* the part readed
|
||||
* @return {@link ByteBuffer} with the contents
|
||||
* @throws IOException
|
||||
* if exception occur at IO level
|
||||
*/
|
||||
ByteBuffer read(TorrentPart part) throws IOException;
|
||||
|
||||
/**
|
||||
* Stores an segment of data relative to {@link TorrentPart part}.
|
||||
* <p>
|
||||
* <b>Warning</b>: Synchronization <b>MUST</b> be dealt by implementations.
|
||||
*
|
||||
* @param part
|
||||
* the part to written
|
||||
* @param buffer
|
||||
* the buffer containing the data
|
||||
* @return {@link ByteBuffer} with the contents
|
||||
* @throws IOException
|
||||
* if exception occur at IO level
|
||||
*/
|
||||
boolean write(TorrentPart part, ByteBuffer buffer) throws IOException;
|
||||
|
||||
/**
|
||||
* Calculates the checksum of an {@link TorrentPiece piece}.
|
||||
*
|
||||
* @param piece
|
||||
* the piece to be tested
|
||||
* @return true if checksum is correct, false otherwise.
|
||||
* @throws IOException
|
||||
* if exception occur at IO level
|
||||
*/
|
||||
boolean checksum(TorrentPiece piece) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.datastore.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import net.torrent.protocol.datastore.AbstractTorrentDatastore;
|
||||
import net.torrent.protocol.datastore.TorrentDatastore;
|
||||
import net.torrent.torrent.Torrent;
|
||||
import net.torrent.torrent.TorrentFile;
|
||||
import net.torrent.torrent.TorrentPart;
|
||||
import net.torrent.util.Range;
|
||||
|
||||
/**
|
||||
* Since {@link TorrentDatastore} does not uses the concept of files when
|
||||
* storing and/or retrieving data, this abstract class provides abstraction for
|
||||
* that issue and allows, easily to store and/or retrieve data in separated
|
||||
* files.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public abstract class FileAwareTorrentDatastore extends
|
||||
AbstractTorrentDatastore {
|
||||
/**
|
||||
* Read data contained withing <tt>file</tt> in the given <tt>range</tt>
|
||||
*
|
||||
* @param buffer
|
||||
* the buffer in which data should be readed.
|
||||
* @param file
|
||||
* the file to be read
|
||||
* @param range
|
||||
* range of data being readed.
|
||||
* @throws IOException
|
||||
* if any exception occur at IO level
|
||||
*/
|
||||
protected abstract void read(ByteBuffer buffer, TorrentFile file,
|
||||
Range range) throws IOException;
|
||||
|
||||
/**
|
||||
* Write data contained in <tt>buffer</tt> to <tt>file</tt> in the given
|
||||
* <tt>range</tt>
|
||||
*
|
||||
* @param buffer
|
||||
* the buffer in which data should be written.
|
||||
* @param file
|
||||
* the file to be written
|
||||
* @param range
|
||||
* range of data being written.
|
||||
* @throws IOException
|
||||
* if any exception occur at IO level
|
||||
*/
|
||||
protected abstract boolean write(ByteBuffer buffer, TorrentFile file,
|
||||
Range range) throws IOException;
|
||||
|
||||
@Override
|
||||
public ByteBuffer read(TorrentPart part) throws IOException {
|
||||
final ByteBuffer buffer = allocate(part);
|
||||
|
||||
final Torrent torrent = part.getTorrent();
|
||||
Range partRange = part.asTorrentRange();
|
||||
|
||||
for (final TorrentFile file : torrent.getFiles()) {
|
||||
final Range fileRange = file.asTorrentRange();
|
||||
if (fileRange.intersects(partRange)) {
|
||||
final Range range = part.asFileRange(file).intersection(
|
||||
fileRange);
|
||||
buffer.limit((int) (buffer.position() + range.getLength()));
|
||||
this.read(buffer, file, range);
|
||||
}
|
||||
}
|
||||
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean write(TorrentPart part, ByteBuffer buffer)
|
||||
throws IOException {
|
||||
final Torrent torrent = part.getTorrent();
|
||||
Range partRange = part.asTorrentRange();
|
||||
|
||||
for (final TorrentFile file : torrent.getFiles()) {
|
||||
final Range fileRange = file.asFileRange();
|
||||
if (fileRange.intersects(partRange)) {
|
||||
final Range range = part.asFileRange(file);
|
||||
buffer.limit((int) (buffer.position() + range.getLength()));
|
||||
if (!this.write(buffer, file, range))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates a new {@link ByteBuffer}.
|
||||
*
|
||||
* @param part
|
||||
* the {@link TorrentPart}s
|
||||
* @return a newly allocated ByteBuffer
|
||||
*/
|
||||
private ByteBuffer allocate(TorrentPart part) {
|
||||
return ByteBuffer.allocate(part.getLength());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.datastore.impl;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
|
||||
import net.torrent.protocol.datastore.AbstractTorrentDatastore;
|
||||
import net.torrent.torrent.TorrentPart;
|
||||
|
||||
/**
|
||||
* Stores data into a single plain file, ignoring any file metadata into the
|
||||
* original .torrent file.
|
||||
* <p>
|
||||
* Unless for a single torrent file, data might (and possibly will) not be
|
||||
* readable outside this library.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class PlainTorrentDatastore extends AbstractTorrentDatastore {
|
||||
/**
|
||||
* The target file
|
||||
*/
|
||||
private final File file;
|
||||
/**
|
||||
* The file channel
|
||||
*/
|
||||
private FileChannel channel;
|
||||
|
||||
/**
|
||||
* Creates a new datastore instance
|
||||
*
|
||||
* @param file
|
||||
* the single file to be used
|
||||
*/
|
||||
public PlainTorrentDatastore(File file) {
|
||||
this.file = file;
|
||||
try {
|
||||
this.channel = new RandomAccessFile(this.file, "rw").getChannel();
|
||||
} catch (FileNotFoundException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized ByteBuffer read(TorrentPart part) throws IOException {
|
||||
final ByteBuffer buffer = ByteBuffer.allocate(part.getLength());
|
||||
synchronized (channel) {
|
||||
channel.position(part.getOffset());
|
||||
while (buffer.hasRemaining()) {
|
||||
if (channel.read(buffer) == -1) // EOF
|
||||
break;
|
||||
}
|
||||
}
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean write(TorrentPart part, ByteBuffer buffer)
|
||||
throws IOException {
|
||||
synchronized (channel) {
|
||||
channel.position(part.getOffset());
|
||||
while (buffer.hasRemaining()) {
|
||||
if (channel.write(buffer) == -1)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.datastore.impl;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.torrent.torrent.TorrentFile;
|
||||
import net.torrent.util.Range;
|
||||
|
||||
/**
|
||||
* An simple implementation for torrent. Stores files in local file system.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class SimpleTorrentDatastore extends FileAwareTorrentDatastore {
|
||||
private final Map<TorrentFile, FileChannel> cache = new HashMap<TorrentFile, FileChannel>();
|
||||
private final File root;
|
||||
|
||||
public SimpleTorrentDatastore(File root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
public SimpleTorrentDatastore() {
|
||||
this(new File("."));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void read(ByteBuffer buffer, TorrentFile file, Range range)
|
||||
throws IOException {
|
||||
final FileChannel channel = cache(file);
|
||||
synchronized (channel) {
|
||||
channel.position(file.getOffset() + range.getStart());
|
||||
while (buffer.hasRemaining()) {
|
||||
if (channel.read(buffer) == -1) // EOF
|
||||
break;
|
||||
}
|
||||
}
|
||||
// don't flip the buffer!
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean write(ByteBuffer buffer, TorrentFile file, Range range)
|
||||
throws IOException {
|
||||
final FileChannel channel = cache(file);
|
||||
synchronized (channel) {
|
||||
channel.position(file.getOffset() + range.getStart());
|
||||
while (buffer.hasRemaining()) {
|
||||
if (channel.write(buffer) == -1)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an cached {@link FileChannel channel}. If not cached, a new will
|
||||
* be open in read/write mode and cached.
|
||||
*
|
||||
* @param file
|
||||
* the {@link TorrentFile file}
|
||||
* @return the cached {@link FileChannel channel}
|
||||
* @throws FileNotFoundException
|
||||
* if parent folder does not exists.
|
||||
*/
|
||||
private FileChannel cache(TorrentFile file) throws FileNotFoundException {
|
||||
FileChannel channel = cache.get(file);
|
||||
if (channel == null) {
|
||||
channel = new RandomAccessFile(new File(root,
|
||||
file.getRelativePath()), "rw").getChannel();
|
||||
cache.put(file, channel);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.datastore.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import net.torrent.protocol.datastore.AbstractTorrentDatastore;
|
||||
import net.torrent.torrent.TorrentPart;
|
||||
import net.torrent.torrent.TorrentPiece;
|
||||
|
||||
/**
|
||||
* This is much like the <tt>/dev/null</tt> and <tt>/dev/zero</tt> pseudo-file
|
||||
* in UNIX systems. All data written is discarded and all readed data is
|
||||
* composed of <tt>NULL</tt> (<tt>0x00</tt>). Checksums always succeed.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class VoidTorrentDatastore extends AbstractTorrentDatastore {
|
||||
@Override
|
||||
public ByteBuffer read(TorrentPart part) throws IOException {
|
||||
return ByteBuffer.allocate(part.getLength());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean write(TorrentPart part, ByteBuffer buffer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checksum(TorrentPiece piece) throws IOException {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
23
src/main/java/net/torrent/protocol/dht/DHTClient.java
Normal file
23
src/main/java/net/torrent/protocol/dht/DHTClient.java
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.dht;
|
||||
|
||||
/**
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class DHTClient {
|
||||
|
||||
}
|
||||
197
src/main/java/net/torrent/protocol/peerwire/PeerWireManager.java
Normal file
197
src/main/java/net/torrent/protocol/peerwire/PeerWireManager.java
Normal file
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import net.torrent.protocol.algorithm.TorrentAlgorithm;
|
||||
import net.torrent.protocol.peerwire.handler.PeerWireAlgorithmHandler;
|
||||
import net.torrent.protocol.peerwire.manager.TorrentManager;
|
||||
|
||||
import org.jboss.netty.bootstrap.ClientBootstrap;
|
||||
import org.jboss.netty.bootstrap.ServerBootstrap;
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelFuture;
|
||||
import org.jboss.netty.channel.ChannelHandler;
|
||||
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
|
||||
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
|
||||
|
||||
/**
|
||||
* Manager for the peerwire protocol. Can be used to start or stop the server
|
||||
* and initiate new connections.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class PeerWireManager {
|
||||
/**
|
||||
* The Netty client's {@link ClientBootstrap bootstrap}
|
||||
*/
|
||||
private final ClientBootstrap client = new ClientBootstrap(
|
||||
new NioClientSocketChannelFactory(Executors.newCachedThreadPool(),
|
||||
Executors.newCachedThreadPool()));
|
||||
/**
|
||||
* The Netty server's {@link ServerBootstrap bootstrap}
|
||||
*/
|
||||
private final ServerBootstrap server = new ServerBootstrap(
|
||||
new NioServerSocketChannelFactory(Executors.newCachedThreadPool(),
|
||||
Executors.newCachedThreadPool()));
|
||||
|
||||
/**
|
||||
* The server's listen address
|
||||
*/
|
||||
private InetSocketAddress listenAddress;
|
||||
/**
|
||||
* The server's channel
|
||||
*/
|
||||
private Channel serverChannel;
|
||||
|
||||
/**
|
||||
* The pipeline channel factory
|
||||
*/
|
||||
private final PeerWirePipelineFactory pipelineFactory;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param manager
|
||||
* the torrent manager
|
||||
* @param algorithm
|
||||
* the torrent algorithm
|
||||
* @param listenAddress
|
||||
* the server's listen address
|
||||
*/
|
||||
public PeerWireManager(TorrentManager manager, ChannelHandler algorithm,
|
||||
InetSocketAddress listenAddress) {
|
||||
pipelineFactory = new PeerWirePipelineFactory(manager, algorithm);
|
||||
|
||||
client.setPipelineFactory(pipelineFactory);
|
||||
server.setPipelineFactory(pipelineFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param manager
|
||||
* the torrent manager
|
||||
* @param algorithm
|
||||
* the torrent algorithm
|
||||
*/
|
||||
public PeerWireManager(TorrentManager manager, TorrentAlgorithm algorithm) {
|
||||
this(manager, new PeerWireAlgorithmHandler(manager, algorithm), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen the server to the given port
|
||||
*
|
||||
* @param port
|
||||
* the port
|
||||
* @return the server {@link Channel}.
|
||||
*/
|
||||
public Channel listen(int port) {
|
||||
if (listenAddress == null)
|
||||
listenAddress = new InetSocketAddress(port);
|
||||
return (serverChannel = server.bind(listenAddress));
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a new client
|
||||
*
|
||||
* @param address
|
||||
* the address
|
||||
* @return the {@link ChannelFuture} for monitoring the connection progress
|
||||
*/
|
||||
public ChannelFuture connect(InetSocketAddress address) {
|
||||
return client.connect(address);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a new client and wait until is complete.
|
||||
*
|
||||
* @param address
|
||||
* the peer address
|
||||
* @param wait
|
||||
* wait time
|
||||
* @param unit
|
||||
* unit of <tt>wait</tt>
|
||||
* @return the newly created {@link Channel}
|
||||
*/
|
||||
public Channel connectWait(InetSocketAddress address, long wait,
|
||||
TimeUnit unit) {
|
||||
final ChannelFuture future = connect(address);
|
||||
if (future.awaitUninterruptibly(wait, unit)) {
|
||||
if (future.isSuccess())
|
||||
return future.getChannel();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the server and the client, and then release the external resources.
|
||||
*/
|
||||
public void close() {
|
||||
if (serverChannel != null)
|
||||
serverChannel.close().awaitUninterruptibly();
|
||||
client.releaseExternalResources();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the server's listening channel
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Channel getServerChannel() {
|
||||
return serverChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Netty client's {@link ClientBootstrap bootstrap}
|
||||
*
|
||||
* @return the {@link ClientBootstrap}
|
||||
*/
|
||||
protected ClientBootstrap getClientBootstrap() {
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Netty server's {@link ServerBootstrap bootstrap}
|
||||
*
|
||||
* @return the {@link ServerBootstrap}
|
||||
*/
|
||||
protected ServerBootstrap getServerBootstrap() {
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current server's listening address
|
||||
*
|
||||
* @return the listen address
|
||||
*/
|
||||
public InetSocketAddress getListenAddress() {
|
||||
return listenAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current server's listening address
|
||||
*
|
||||
* @param listenAddress
|
||||
* the listen address
|
||||
*/
|
||||
public void setListenAddress(InetSocketAddress listenAddress) {
|
||||
this.listenAddress = listenAddress;
|
||||
}
|
||||
}
|
||||
401
src/main/java/net/torrent/protocol/peerwire/PeerWirePeer.java
Normal file
401
src/main/java/net/torrent/protocol/peerwire/PeerWirePeer.java
Normal file
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.BitSet;
|
||||
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
|
||||
import net.torrent.protocol.peerwire.message.BitfieldMessage;
|
||||
import net.torrent.protocol.peerwire.message.CancelMessage;
|
||||
import net.torrent.protocol.peerwire.message.ChokeMessage;
|
||||
import net.torrent.protocol.peerwire.message.HandshakeMessage;
|
||||
import net.torrent.protocol.peerwire.message.HaveMessage;
|
||||
import net.torrent.protocol.peerwire.message.InterestedMessage;
|
||||
import net.torrent.protocol.peerwire.message.KeepAliveMessage;
|
||||
import net.torrent.protocol.peerwire.message.NotInterestedMessage;
|
||||
import net.torrent.protocol.peerwire.message.PieceMessage;
|
||||
import net.torrent.protocol.peerwire.message.PortMessage;
|
||||
import net.torrent.protocol.peerwire.message.RequestMessage;
|
||||
import net.torrent.protocol.peerwire.message.UnchokeMessage;
|
||||
import net.torrent.protocol.peerwire.message.fast.AllowedFastMessage;
|
||||
import net.torrent.protocol.peerwire.message.fast.HaveAllMessage;
|
||||
import net.torrent.protocol.peerwire.message.fast.HaveNoneMessage;
|
||||
import net.torrent.protocol.peerwire.message.fast.RejectMessage;
|
||||
import net.torrent.protocol.peerwire.message.fast.SuggestPieceMessage;
|
||||
import net.torrent.torrent.context.TorrentPeer;
|
||||
import net.torrent.torrent.context.TorrentPeer.ChokingState;
|
||||
import net.torrent.torrent.context.TorrentPeer.InterestState;
|
||||
import net.torrent.torrent.context.TorrentPeerCapabilities.TorrentPeerCapability;
|
||||
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelFuture;
|
||||
import org.jboss.netty.channel.ChannelFutureListener;
|
||||
|
||||
/**
|
||||
* An PeerWire Peer manages the {@link Channel channel} (the current peer
|
||||
* connection) and the {@link TorrentPeer torrent peer} object.
|
||||
* <p>
|
||||
* This object contains handy methods to send messages to the peer.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class PeerWirePeer {
|
||||
/**
|
||||
* The active {@link Channel}
|
||||
*/
|
||||
private final Channel channel;
|
||||
/**
|
||||
* The active {@link TorrentPeer}. It might no be available until handshake.
|
||||
*/
|
||||
private TorrentPeer peer;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param channel
|
||||
* the channel
|
||||
* @param peer
|
||||
* the peer. Can be null.
|
||||
*/
|
||||
public PeerWirePeer(Channel channel, TorrentPeer peer) {
|
||||
this.channel = channel;
|
||||
this.peer = peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an handshake message
|
||||
*
|
||||
* @param infohash
|
||||
* the info hash
|
||||
* @param peerId
|
||||
* the peer id
|
||||
* @param reserved
|
||||
* the capabilities {@link BitSet}
|
||||
* @return the {@link ChannelFuture} for the message
|
||||
*/
|
||||
public ChannelFuture handshake(byte[] infohash, byte[] peerId,
|
||||
BitSet reserved) {
|
||||
return write(new HandshakeMessage(infohash, peerId, reserved));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an bitfield message
|
||||
*
|
||||
* @param bitset
|
||||
* the bitfield bits
|
||||
* @return the {@link ChannelFuture} for the message
|
||||
*/
|
||||
public ChannelFuture bitfield(BitSet bitset) {
|
||||
return write(new BitfieldMessage(bitset));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an choke message
|
||||
*/
|
||||
public void choke() {
|
||||
if (peer.getChokingState() == ChokingState.CHOKED)
|
||||
return;
|
||||
write(new ChokeMessage()).addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future)
|
||||
throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
peer.setChokingState(ChokingState.CHOKED);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an unchoke message
|
||||
*/
|
||||
public void unchoke() {
|
||||
if (peer.getChokingState() == ChokingState.UNCHOKED)
|
||||
return;
|
||||
write(new UnchokeMessage()).addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future)
|
||||
throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
peer.setChokingState(ChokingState.UNCHOKED);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an interest message
|
||||
*/
|
||||
public void interested() {
|
||||
if (peer.getInterestState() == InterestState.INTERESTED)
|
||||
return;
|
||||
write(new InterestedMessage()).addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future)
|
||||
throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
peer.setInterestState(InterestState.INTERESTED);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an not interest message
|
||||
*/
|
||||
public void uninterested() {
|
||||
if (peer.getInterestState() == InterestState.UNINTERESTED)
|
||||
return;
|
||||
write(new NotInterestedMessage()).addListener(
|
||||
new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future)
|
||||
throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
peer.setInterestState(InterestState.UNINTERESTED);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an request message
|
||||
*
|
||||
* @param index
|
||||
* the piece index
|
||||
* @param start
|
||||
* the part start
|
||||
* @param length
|
||||
* the part length
|
||||
* @return the {@link ChannelFuture} for this message
|
||||
*/
|
||||
public ChannelFuture request(int index, int start, int length) {
|
||||
return write(new RequestMessage(index, start, length));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an upload message
|
||||
*
|
||||
* @param index
|
||||
* the index
|
||||
* @param start
|
||||
* the start
|
||||
* @param length
|
||||
* the length
|
||||
* @param data
|
||||
* the upload {@link ByteBuffer content}
|
||||
*/
|
||||
public void upload(int index, int start, int length, ByteBuffer data) {
|
||||
this.unchoke();
|
||||
write(new PieceMessage(index, start, length, data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an have message
|
||||
*
|
||||
* @param index
|
||||
* the piece index
|
||||
*/
|
||||
public void have(int index) {
|
||||
write(new HaveMessage(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an cancel message
|
||||
*
|
||||
* @param index
|
||||
* the index
|
||||
* @param start
|
||||
* the start
|
||||
* @param length
|
||||
* the length
|
||||
*/
|
||||
public void cancel(int index, int start, int length) {
|
||||
write(new CancelMessage(index, start, length));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an keep alive message
|
||||
*/
|
||||
public void keepAlive() {
|
||||
write(new KeepAliveMessage());
|
||||
}
|
||||
|
||||
// //////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* *** REQUIRES {@link TorrentPeerCapability#DHT DHT Protocol} ***
|
||||
* <p>
|
||||
* Send and port message. The port is used for UDP.
|
||||
*
|
||||
* @param port
|
||||
* the port number
|
||||
*/
|
||||
public void port(short port) {
|
||||
write(new PortMessage(port));
|
||||
}
|
||||
|
||||
/**
|
||||
* *** REQUIRES {@link TorrentPeerCapability#EXTENSION_PROTOCOL Extension
|
||||
* Protocol} ***
|
||||
* <p>
|
||||
* Send an have none message.
|
||||
*/
|
||||
public void haveNone() {
|
||||
write(new HaveNoneMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* *** REQUIRES {@link TorrentPeerCapability#EXTENSION_PROTOCOL Extension
|
||||
* Protocol} ***
|
||||
* <p>
|
||||
* Send an have all message.
|
||||
*/
|
||||
public void haveAll() {
|
||||
write(new HaveAllMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* *** REQUIRES {@link TorrentPeerCapability#EXTENSION_PROTOCOL Extension
|
||||
* Protocol} ***
|
||||
* <p>
|
||||
* Send an reject message.
|
||||
*
|
||||
* @param index
|
||||
* the piece index
|
||||
* @param start
|
||||
* the part start
|
||||
* @param length
|
||||
* the part length
|
||||
*/
|
||||
public void reject(int index, int start, int length) {
|
||||
this.choke();
|
||||
write(new RejectMessage(index, start, length));
|
||||
}
|
||||
|
||||
/**
|
||||
* *** REQUIRES {@link TorrentPeerCapability#EXTENSION_PROTOCOL Extension
|
||||
* Protocol} ***
|
||||
* <p>
|
||||
* Send an suggest message
|
||||
*
|
||||
* @param index
|
||||
* the piece index
|
||||
*/
|
||||
public void suggest(int index) {
|
||||
write(new SuggestPieceMessage(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* *** REQUIRES {@link TorrentPeerCapability#EXTENSION_PROTOCOL Extension
|
||||
* Protocol} ***
|
||||
* <p>
|
||||
* Send an list of pieces allowed to download fast (download even when
|
||||
* choked)
|
||||
*
|
||||
* @param indexes
|
||||
* the piece indexes
|
||||
*/
|
||||
public void allowedFast(int... indexes) {
|
||||
write(new AllowedFastMessage(indexes));
|
||||
}
|
||||
|
||||
// //////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Write an message to the peer
|
||||
*
|
||||
* @return an {@link ChannelFuture} to monitor write progress
|
||||
* @see Channel#write(Object)
|
||||
*/
|
||||
public ChannelFuture write(PeerWireWritableMessage message) {
|
||||
return channel.write(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect the peer
|
||||
*
|
||||
* @return an {@link ChannelFuture} to monitor disconnect progress
|
||||
* @see Channel#diconnect()
|
||||
*/
|
||||
public ChannelFuture disconnect() {
|
||||
return channel.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the peer {@link Channel channel}
|
||||
*
|
||||
* @return an {@link ChannelFuture} to monitor close progress
|
||||
* @see Channel#close()
|
||||
*/
|
||||
public ChannelFuture close() {
|
||||
return channel.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the peer {@link Channel channel}
|
||||
*
|
||||
* @return the peer active {@link Channel}
|
||||
*/
|
||||
public Channel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the active {@link TorrentPeer}
|
||||
*
|
||||
* @return the active peer
|
||||
*/
|
||||
public TorrentPeer getTorrentPeer() {
|
||||
return peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the active peer
|
||||
*
|
||||
* @param peer
|
||||
* the active peer
|
||||
*/
|
||||
public void setTorrentPeer(TorrentPeer peer) {
|
||||
this.peer = peer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((channel == null) ? 0 : channel.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
PeerWirePeer other = (PeerWirePeer) obj;
|
||||
if (channel == null) {
|
||||
if (other.channel != null)
|
||||
return false;
|
||||
} else if (!channel.equals(other.channel))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire;
|
||||
|
||||
import static org.jboss.netty.channel.Channels.pipeline;
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireDecoder;
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireEncoder;
|
||||
import net.torrent.protocol.peerwire.handler.PeerWireManagerHeadHandler;
|
||||
import net.torrent.protocol.peerwire.handler.PeerWireManagerTailHandler;
|
||||
import net.torrent.protocol.peerwire.manager.TorrentManager;
|
||||
|
||||
import org.jboss.netty.channel.ChannelHandler;
|
||||
import org.jboss.netty.channel.ChannelPipeline;
|
||||
import org.jboss.netty.channel.ChannelPipelineFactory;
|
||||
import org.jboss.netty.handler.logging.LoggingHandler;
|
||||
import org.jboss.netty.logging.InternalLogLevel;
|
||||
|
||||
/**
|
||||
* The {@link ChannelPipeline} factory for all PeerWire connections.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class PeerWirePipelineFactory implements ChannelPipelineFactory {
|
||||
/**
|
||||
* The logging handler
|
||||
*/
|
||||
private final LoggingHandler loggingHandler = new LoggingHandler(
|
||||
InternalLogLevel.WARN);
|
||||
/**
|
||||
* The algorithm handler
|
||||
*/
|
||||
private final ChannelHandler algorithmHandler;
|
||||
/**
|
||||
* The head manager handler
|
||||
*/
|
||||
private final PeerWireManagerHeadHandler headManagerHandler;
|
||||
/**
|
||||
* The tail manager handler
|
||||
*/
|
||||
private final PeerWireManagerTailHandler tailManagerHandler;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param manager
|
||||
* the torrent manager
|
||||
* @param algorithmHandler
|
||||
* the algorithm handler
|
||||
*/
|
||||
public PeerWirePipelineFactory(TorrentManager manager,
|
||||
ChannelHandler algorithmHandler) {
|
||||
this.algorithmHandler = algorithmHandler;
|
||||
this.headManagerHandler = new PeerWireManagerHeadHandler(manager);
|
||||
this.tailManagerHandler = new PeerWireManagerTailHandler(manager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelPipeline getPipeline() throws Exception {
|
||||
final ChannelPipeline pipeline = pipeline();
|
||||
|
||||
// TODO create traffic shape handler
|
||||
// TODO create firewall handler - block connections from unwanted peers
|
||||
|
||||
pipeline.addLast("decoder", new PeerWireDecoder());
|
||||
pipeline.addLast("encoder", new PeerWireEncoder());
|
||||
|
||||
pipeline.addLast("logging", loggingHandler);
|
||||
|
||||
pipeline.addLast("head-handler", headManagerHandler);
|
||||
pipeline.addLast("algorithm", algorithmHandler);
|
||||
pipeline.addLast("tail-handler", tailManagerHandler);
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.codec;
|
||||
|
||||
import net.torrent.protocol.peerwire.message.BitfieldMessage;
|
||||
import net.torrent.protocol.peerwire.message.CancelMessage;
|
||||
import net.torrent.protocol.peerwire.message.ChokeMessage;
|
||||
import net.torrent.protocol.peerwire.message.HandshakeMessage;
|
||||
import net.torrent.protocol.peerwire.message.HaveMessage;
|
||||
import net.torrent.protocol.peerwire.message.InterestedMessage;
|
||||
import net.torrent.protocol.peerwire.message.KeepAliveMessage;
|
||||
import net.torrent.protocol.peerwire.message.NotInterestedMessage;
|
||||
import net.torrent.protocol.peerwire.message.PieceMessage;
|
||||
import net.torrent.protocol.peerwire.message.PortMessage;
|
||||
import net.torrent.protocol.peerwire.message.RequestMessage;
|
||||
import net.torrent.protocol.peerwire.message.UnchokeMessage;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.handler.codec.frame.CorruptedFrameException;
|
||||
import org.jboss.netty.handler.codec.frame.FrameDecoder;
|
||||
|
||||
/**
|
||||
* BitTorrent has two types of message headers:
|
||||
* <p>
|
||||
* <h1>Handshake message</h1> Handshake messages are composed of 1 byte, which
|
||||
* indicates the size of protocol string ("BitTorrent protocol").
|
||||
* <p>
|
||||
* <h1>Messages</h1> All other messages are 4-byte integer containing the
|
||||
* message length, followed by 1 byte opcode.
|
||||
* <p>
|
||||
* <p>
|
||||
* Instances of this class keep channel state content and must not be shared nor
|
||||
* cached.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class PeerWireDecoder extends FrameDecoder {
|
||||
private boolean handshaked = false;
|
||||
|
||||
@Override
|
||||
protected Object decode(ChannelHandlerContext ctx, Channel channel,
|
||||
ChannelBuffer buffer) throws Exception {
|
||||
buffer.markReaderIndex();
|
||||
|
||||
if (!handshaked) {
|
||||
if (buffer.readableBytes() <= 47) // at least 47 bytes
|
||||
return null;
|
||||
|
||||
final int pstrlen = buffer.readByte();
|
||||
if (buffer.readableBytes() < pstrlen + 47) {
|
||||
buffer.resetReaderIndex();
|
||||
return null;
|
||||
}
|
||||
buffer.readerIndex(buffer.readerIndex() - 1);
|
||||
|
||||
final HandshakeMessage message = new HandshakeMessage();
|
||||
message.read(buffer);
|
||||
handshaked = true;
|
||||
|
||||
return message;
|
||||
} else {
|
||||
if (buffer.readableBytes() <= 4) {
|
||||
buffer.resetReaderIndex();
|
||||
return null;
|
||||
}
|
||||
|
||||
int len = buffer.readInt();
|
||||
if (len == 0) {
|
||||
return new KeepAliveMessage();
|
||||
} else if (buffer.readableBytes() < len) {
|
||||
buffer.resetReaderIndex();
|
||||
return null;
|
||||
}
|
||||
|
||||
final byte id = buffer.readByte();
|
||||
final PeerWireReadableMessage message = getMessage(id);
|
||||
if (message == null)
|
||||
// force connection to be closed
|
||||
throw new CorruptedFrameException("unknown message " + id);
|
||||
message.read(buffer);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the message represented by <tt>id</tt>. Will return null if
|
||||
* message id is unknown.
|
||||
*
|
||||
* @param id
|
||||
* the id of the message
|
||||
* @return the message
|
||||
*/
|
||||
private PeerWireReadableMessage getMessage(byte id) {
|
||||
PeerWireReadableMessage message = null;
|
||||
switch (id) {
|
||||
case BitfieldMessage.MESSAGE_ID:
|
||||
message = new BitfieldMessage();
|
||||
break;
|
||||
case CancelMessage.MESSAGE_ID:
|
||||
message = new CancelMessage();
|
||||
break;
|
||||
case ChokeMessage.MESSAGE_ID:
|
||||
message = new ChokeMessage();
|
||||
break;
|
||||
case HaveMessage.MESSAGE_ID:
|
||||
message = new HaveMessage();
|
||||
break;
|
||||
case InterestedMessage.MESSAGE_ID:
|
||||
message = new InterestedMessage();
|
||||
break;
|
||||
case NotInterestedMessage.MESSAGE_ID:
|
||||
message = new NotInterestedMessage();
|
||||
break;
|
||||
case PieceMessage.MESSAGE_ID:
|
||||
message = new PieceMessage();
|
||||
break;
|
||||
case PortMessage.MESSAGE_ID:
|
||||
message = new PortMessage();
|
||||
break;
|
||||
case RequestMessage.MESSAGE_ID:
|
||||
message = new RequestMessage();
|
||||
break;
|
||||
case UnchokeMessage.MESSAGE_ID:
|
||||
message = new UnchokeMessage();
|
||||
break;
|
||||
}
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.codec;
|
||||
|
||||
import net.torrent.protocol.peerwire.message.HandshakeMessage;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
import org.jboss.netty.buffer.ChannelBuffers;
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
|
||||
|
||||
/**
|
||||
* Messages are encoded in {@link PeerWireWritableMessage#write(ChannelBuffer)}
|
||||
* method. Message length is measured automatically by the encoder.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class PeerWireEncoder extends OneToOneEncoder {
|
||||
@Override
|
||||
protected Object encode(ChannelHandlerContext ctx, Channel channel,
|
||||
Object msg) throws Exception {
|
||||
ChannelBuffer buffer = ChannelBuffers.dynamicBuffer();
|
||||
if (!(msg instanceof PeerWireWritableMessage))
|
||||
return msg;
|
||||
|
||||
if (msg instanceof HandshakeMessage) {
|
||||
final HandshakeMessage message = (HandshakeMessage) msg;
|
||||
message.write(buffer);
|
||||
} else {
|
||||
final PeerWireWritableMessage message = (PeerWireWritableMessage) msg;
|
||||
buffer.writeInt(0); // allocate 4 bytes for header
|
||||
message.write(buffer);
|
||||
int len = buffer.readableBytes();
|
||||
buffer.setInt(0, len - 4);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.codec;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* An readable message in the BitTorrent protocol
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public interface PeerWireReadableMessage {
|
||||
/**
|
||||
* Read the content of the message contained in this buffer.
|
||||
*
|
||||
* @param buffer
|
||||
* the buffer
|
||||
* @throws IOException
|
||||
*/
|
||||
void read(ChannelBuffer buffer) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.codec;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* An writable message in the BitTorrent protocol
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public interface PeerWireWritableMessage {
|
||||
/**
|
||||
* Write the content of the message to this buffer.
|
||||
*
|
||||
* @param buffer
|
||||
* the buffer
|
||||
* @throws IOException
|
||||
*/
|
||||
void write(ChannelBuffer buffer) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,481 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.BitSet;
|
||||
|
||||
import net.torrent.protocol.algorithm.TorrentAlgorithm;
|
||||
import net.torrent.protocol.algorithm.TorrentInterestAlgorithm;
|
||||
import net.torrent.protocol.algorithm.TorrentPeerAlgorithm;
|
||||
import net.torrent.protocol.algorithm.TorrentPieceDownloadAlgorithm;
|
||||
import net.torrent.protocol.algorithm.TorrentPieceUploadAlgorithm;
|
||||
import net.torrent.protocol.datastore.TorrentDatastore;
|
||||
import net.torrent.protocol.peerwire.PeerWirePeer;
|
||||
import net.torrent.protocol.peerwire.manager.TorrentManager;
|
||||
import net.torrent.protocol.peerwire.message.BitfieldMessage;
|
||||
import net.torrent.protocol.peerwire.message.ChokeMessage;
|
||||
import net.torrent.protocol.peerwire.message.HaveMessage;
|
||||
import net.torrent.protocol.peerwire.message.InterestedMessage;
|
||||
import net.torrent.protocol.peerwire.message.KeepAliveMessage;
|
||||
import net.torrent.protocol.peerwire.message.NotInterestedMessage;
|
||||
import net.torrent.protocol.peerwire.message.PieceMessage;
|
||||
import net.torrent.protocol.peerwire.message.RequestMessage;
|
||||
import net.torrent.protocol.peerwire.message.UnchokeMessage;
|
||||
import net.torrent.protocol.peerwire.message.fast.AllowedFastMessage;
|
||||
import net.torrent.protocol.peerwire.message.fast.SuggestPieceMessage;
|
||||
import net.torrent.torrent.Torrent;
|
||||
import net.torrent.torrent.TorrentPart;
|
||||
import net.torrent.torrent.TorrentPiece;
|
||||
import net.torrent.torrent.context.TorrentPeer;
|
||||
import net.torrent.torrent.context.TorrentPeer.ChokingState;
|
||||
import net.torrent.torrent.context.TorrentPeer.InterestState;
|
||||
import net.torrent.torrent.context.TorrentPeerCapabilities.TorrentPeerCapability;
|
||||
import net.torrent.util.PeerCallback;
|
||||
|
||||
import org.jboss.netty.channel.ChannelFuture;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.channel.ChannelStateEvent;
|
||||
import org.jboss.netty.channel.ExceptionEvent;
|
||||
import org.jboss.netty.channel.MessageEvent;
|
||||
import org.jboss.netty.handler.timeout.IdleStateAwareChannelHandler;
|
||||
import org.jboss.netty.handler.timeout.IdleStateEvent;
|
||||
|
||||
/**
|
||||
* Standard handler responsible for forwarding calls to {@link TorrentAlgorithm}
|
||||
* methods. This class handles low-level protocol specific behavior.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class PeerWireAlgorithmHandler extends IdleStateAwareChannelHandler {
|
||||
/**
|
||||
* The torrent manager
|
||||
*/
|
||||
private final TorrentManager manager;
|
||||
/**
|
||||
* The torrent datastore
|
||||
*/
|
||||
private final TorrentDatastore datastore;
|
||||
|
||||
/**
|
||||
* The peer algorithm
|
||||
*/
|
||||
private final TorrentPeerAlgorithm peerAlgorithm;
|
||||
/**
|
||||
* The interest algorithm
|
||||
*/
|
||||
private final TorrentInterestAlgorithm interestAlgorithm;
|
||||
/**
|
||||
* The download algorithm
|
||||
*/
|
||||
private final TorrentPieceDownloadAlgorithm downloadAlgorithm;
|
||||
/**
|
||||
* The upload algorithm
|
||||
*/
|
||||
private final TorrentPieceUploadAlgorithm uploadAlgorithm;
|
||||
|
||||
/**
|
||||
* Creates a new handler
|
||||
*
|
||||
* @param manager
|
||||
* the torrent manager
|
||||
* @param algorithm
|
||||
* the algorithm
|
||||
*/
|
||||
public PeerWireAlgorithmHandler(TorrentManager manager,
|
||||
final TorrentAlgorithm algorithm) {
|
||||
this.manager = manager;
|
||||
this.datastore = manager.getDatastore();
|
||||
this.peerAlgorithm = algorithm.getPeerAlgorithm();
|
||||
this.interestAlgorithm = algorithm.getInterestAlgorithm();
|
||||
this.downloadAlgorithm = algorithm.getDownloadAlgorithm();
|
||||
this.uploadAlgorithm = algorithm.getUploadAlgorithm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)
|
||||
throws Exception {
|
||||
final PeerWirePeer peer = manager.getPeerManager().getPeer(
|
||||
e.getChannel());
|
||||
// TODO handshake with random peer id
|
||||
peer.handshake(manager.getTorrent().getInfoHash().toByteArray(),
|
||||
"-TR2050-mcm14ye4h2mq".getBytes(), manager.getContext()
|
||||
.getCapabilites().toBitSet());
|
||||
peer.port((short) 1541);
|
||||
super.channelConnected(ctx, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
|
||||
throws Exception {
|
||||
final PeerWirePeer peer = manager.getPeerManager().getPeer(
|
||||
e.getChannel());
|
||||
final Object msg = e.getMessage();
|
||||
|
||||
if (msg instanceof BitfieldMessage) {
|
||||
final BitfieldMessage bitfield = (BitfieldMessage) msg;
|
||||
final BitSet bitset = bitfield.getBitfield();
|
||||
peer.getTorrentPeer().getBitfield().setBits(bitset);
|
||||
testInterest(peer);
|
||||
} else if (msg instanceof InterestedMessage) {
|
||||
peer.getTorrentPeer()
|
||||
.setPeerInterestState(InterestState.INTERESTED);
|
||||
peerIntrestUpdate(peer, peer.getTorrentPeer()
|
||||
.getPeerInterestState());
|
||||
} else if (msg instanceof NotInterestedMessage) {
|
||||
peer.getTorrentPeer().setPeerInterestState(
|
||||
InterestState.UNINTERESTED);
|
||||
peerIntrestUpdate(peer, peer.getTorrentPeer()
|
||||
.getPeerInterestState());
|
||||
} else if (msg instanceof UnchokeMessage) {
|
||||
peer.getTorrentPeer().setPeerChokingState(ChokingState.UNCHOKED);
|
||||
peerChokeUpdate(peer, peer.getTorrentPeer().getPeerChokingState());
|
||||
} else if (msg instanceof ChokeMessage) {
|
||||
peer.getTorrentPeer().setPeerChokingState(ChokingState.CHOKED);
|
||||
peerChokeUpdate(peer, peer.getTorrentPeer().getPeerChokingState());
|
||||
} else if (msg instanceof RequestMessage) {
|
||||
final RequestMessage request = (RequestMessage) msg;
|
||||
final Torrent torrent = manager.getTorrent();
|
||||
final TorrentPart part = torrent.getPart(request.getIndex(),
|
||||
request.getStart(), request.getLength());
|
||||
processRequest(peer, part);
|
||||
} else if (msg instanceof PieceMessage) {
|
||||
final PieceMessage pieceMsg = (PieceMessage) msg;
|
||||
final Torrent torrent = manager.getTorrent();
|
||||
final TorrentPart part = torrent.getPart(pieceMsg.getIndex(),
|
||||
pieceMsg.getStart(), pieceMsg.getLength());
|
||||
processDownload(peer, part, pieceMsg.getBlock());
|
||||
} else if (msg instanceof HaveMessage) {
|
||||
final HaveMessage have = (HaveMessage) msg;
|
||||
final Torrent torrent = manager.getTorrent();
|
||||
final TorrentPiece piece = torrent.getPiece(have.getPiece());
|
||||
peer.getTorrentPeer().getBitfield().setPiece(piece, true);
|
||||
testInterest(peer);
|
||||
} else if (msg instanceof KeepAliveMessage) {
|
||||
keepAlive(peer);
|
||||
} else if (msg instanceof AllowedFastMessage) {
|
||||
final AllowedFastMessage fast = (AllowedFastMessage) msg;
|
||||
final Torrent torrent = manager.getTorrent();
|
||||
for (final int index : fast.getPieces()) {
|
||||
final TorrentPiece piece = torrent.getPiece(index);
|
||||
allowedFast(peer, piece);
|
||||
}
|
||||
} else if (msg instanceof SuggestPieceMessage) {
|
||||
final SuggestPieceMessage suggest = (SuggestPieceMessage) msg;
|
||||
final Torrent torrent = manager.getTorrent();
|
||||
final TorrentPiece piece = torrent.getPiece(suggest.getPiece());
|
||||
suggested(peer, piece);
|
||||
}
|
||||
super.messageReceived(ctx, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
|
||||
throws Exception {
|
||||
// any exception thrown, the channel is disconnected.
|
||||
e.getCause().printStackTrace();
|
||||
e.getChannel().disconnect();
|
||||
super.exceptionCaught(ctx, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e)
|
||||
throws Exception {
|
||||
final PeerWirePeer peer = manager.getPeerManager().getPeer(
|
||||
e.getChannel());
|
||||
keepAlive(peer);
|
||||
super.channelIdle(ctx, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the interest in the peer
|
||||
*
|
||||
* @param peer
|
||||
* the peer
|
||||
*/
|
||||
private void testInterest(PeerWirePeer peer) {
|
||||
switch (interestAlgorithm.interested(peer.getTorrentPeer())) {
|
||||
case INTERESTED:
|
||||
peer.interested();
|
||||
return;
|
||||
case UNINTERESTED:
|
||||
testChoke(peer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test choke interest to this peer
|
||||
*
|
||||
* @param peer
|
||||
* the peer
|
||||
*/
|
||||
private void testChoke(PeerWirePeer peer) {
|
||||
switch (interestAlgorithm.choke(peer.getTorrentPeer())) {
|
||||
case CHOKED:
|
||||
peer.choke();
|
||||
return;
|
||||
case UNCHOKED:
|
||||
peer.unchoke();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the peer choke interest change.
|
||||
*
|
||||
* @param peer
|
||||
* the peer
|
||||
* @param state
|
||||
* the new interest state
|
||||
*/
|
||||
private void peerIntrestUpdate(PeerWirePeer peer, InterestState state) {
|
||||
switch (peerAlgorithm.interested(peer.getTorrentPeer(), state)) {
|
||||
case CHOKED:
|
||||
peer.choke();
|
||||
return;
|
||||
|
||||
case UNCHOKED:
|
||||
peer.unchoke();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the peer choke change.
|
||||
*
|
||||
* @param peer
|
||||
* the peer
|
||||
* @param state
|
||||
* the choke state
|
||||
*/
|
||||
private void peerChokeUpdate(PeerWirePeer peer, ChokingState state) {
|
||||
switch (peerAlgorithm.choked(peer.getTorrentPeer(), state)) {
|
||||
case DISCONNECT:
|
||||
peer.disconnect();
|
||||
break;
|
||||
|
||||
case CONNECT_NEW_PEER:
|
||||
peer.disconnect();
|
||||
connect(peerAlgorithm.connect());
|
||||
return;
|
||||
|
||||
case DOWNLOAD:
|
||||
download(peer,
|
||||
downloadAlgorithm.getNextPart(peer.getTorrentPeer(), null));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the upload request
|
||||
*
|
||||
* @param peer
|
||||
* the peer
|
||||
* @param part
|
||||
* the part
|
||||
* @throws IOException
|
||||
*/
|
||||
private void processRequest(PeerWirePeer peer, TorrentPart part)
|
||||
throws IOException {
|
||||
switch (uploadAlgorithm.request(peer.getTorrentPeer(), part)) {
|
||||
case DISCONNECT:
|
||||
peer.disconnect();
|
||||
break;
|
||||
case REJECT:
|
||||
if (!peer.getTorrentPeer().getCapabilities()
|
||||
.supports(TorrentPeerCapability.FAST_PEERS))
|
||||
return;
|
||||
peer.reject(part.getPiece().getIndex(), part.getStart(),
|
||||
part.getLength());
|
||||
break;
|
||||
case CONNECT_NEW_PEER:
|
||||
peer.disconnect();
|
||||
connect(peerAlgorithm.connect());
|
||||
break;
|
||||
case CHOKE:
|
||||
peer.choke();
|
||||
break;
|
||||
case UPLOAD:
|
||||
upload(peer, part);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an download
|
||||
*
|
||||
* @param peer
|
||||
* the uploader peer
|
||||
* @param part
|
||||
* the part
|
||||
* @param data
|
||||
* the downloaded content
|
||||
* @throws IOException
|
||||
*/
|
||||
private void processDownload(final PeerWirePeer peer,
|
||||
final TorrentPart part, ByteBuffer data) throws IOException {
|
||||
final TorrentPart nextPart = downloadAlgorithm.getNextPart(
|
||||
peer.getTorrentPeer(), part);
|
||||
boolean complete = downloadAlgorithm.isComplete(peer.getTorrentPeer(),
|
||||
part.getPiece());
|
||||
final ChannelFuture future = download(peer, nextPart);
|
||||
|
||||
// store piece
|
||||
if (!datastore.write(part, data))
|
||||
return;
|
||||
if (!complete)
|
||||
return;
|
||||
|
||||
if (datastore.checksum(part.getPiece())) {
|
||||
manager.getContext().getBitfield().setPiece(part.getPiece(), true);
|
||||
manager.getPeerManager().executeActive(new PeerCallback() {
|
||||
@Override
|
||||
public void callback(PeerWirePeer peer) {
|
||||
peer.have(part.getPiece().getIndex());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
System.exit(0);
|
||||
manager.getContext().getBitfield().setPiece(part.getPiece(), false);
|
||||
switch (downloadAlgorithm.corrupted(peer.getTorrentPeer(),
|
||||
part.getPiece())) {
|
||||
case CHOKE:
|
||||
if (future != null && !future.cancel())
|
||||
peer.cancel(nextPart.getPiece().getIndex(),
|
||||
nextPart.getStart(), nextPart.getLength());
|
||||
peer.choke();
|
||||
break;
|
||||
case DISCONNECT:
|
||||
peer.disconnect();
|
||||
break;
|
||||
case CONNECT_NEW_PEER:
|
||||
peer.disconnect();
|
||||
connect(peerAlgorithm.connect());
|
||||
break;
|
||||
case CONTINUE:
|
||||
break;
|
||||
case CANCEL:
|
||||
if (future != null && !future.cancel())
|
||||
peer.cancel(nextPart.getPiece().getIndex(),
|
||||
nextPart.getStart(), nextPart.getLength());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check keep alive interest in this peer.
|
||||
*
|
||||
* @param peer
|
||||
* the peer
|
||||
*/
|
||||
private void keepAlive(PeerWirePeer peer) {
|
||||
switch (peerAlgorithm.keepAlive(peer.getTorrentPeer())) {
|
||||
case DISCONNECT:
|
||||
peer.disconnect();
|
||||
break;
|
||||
case CONNECT_NEW_PEER:
|
||||
peer.disconnect();
|
||||
connect(peerAlgorithm.connect());
|
||||
break;
|
||||
case KEEP_ALIVE:
|
||||
peer.keepAlive();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the part request
|
||||
*
|
||||
* @param peer
|
||||
* the uploader peer
|
||||
* @param part
|
||||
* the part
|
||||
* @return an {@link ChannelFuture}. Can be used to cancel the message send.
|
||||
*/
|
||||
private ChannelFuture download(PeerWirePeer peer, TorrentPart part) {
|
||||
if (part == null) {
|
||||
testInterest(peer);
|
||||
return null;
|
||||
}
|
||||
return peer.request(part.getPiece().getIndex(), part.getStart(),
|
||||
part.getLength());
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the part upload
|
||||
*
|
||||
* @param peer
|
||||
* the downloader peer
|
||||
* @param part
|
||||
* the part
|
||||
*/
|
||||
private void upload(PeerWirePeer peer, TorrentPart part) throws IOException {
|
||||
if (part == null) {
|
||||
testChoke(peer);
|
||||
return;
|
||||
}
|
||||
final ByteBuffer data = datastore.read(part);
|
||||
peer.upload(part.getPiece().getIndex(), part.getStart(),
|
||||
part.getLength(), data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to establish an connection to <tt>peer</tt>
|
||||
*
|
||||
* @param peer
|
||||
* the peer to be connected.
|
||||
*/
|
||||
private void connect(TorrentPeer peer) {
|
||||
if (peer == null)
|
||||
return;
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the suggested piece
|
||||
*
|
||||
* @param peer
|
||||
* the suggesting peer
|
||||
* @param piece
|
||||
* the suggested piece
|
||||
*/
|
||||
private void suggested(PeerWirePeer peer, TorrentPiece piece) {
|
||||
final TorrentPart part = downloadAlgorithm.sugested(
|
||||
peer.getTorrentPeer(), piece);
|
||||
if (part == null)
|
||||
return;
|
||||
download(peer, part);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the allowed fast piece
|
||||
*
|
||||
* @param peer
|
||||
* the allowing peer
|
||||
* @param piece
|
||||
* the allowed piece
|
||||
*/
|
||||
private void allowedFast(PeerWirePeer peer, TorrentPiece piece) {
|
||||
final TorrentPart part = downloadAlgorithm.allowedFast(
|
||||
peer.getTorrentPeer(), piece);
|
||||
if (part == null)
|
||||
return;
|
||||
download(peer, part);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.handler;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import net.torrent.protocol.peerwire.PeerWirePeer;
|
||||
import net.torrent.protocol.peerwire.manager.TorrentManager;
|
||||
import net.torrent.protocol.peerwire.message.HandshakeMessage;
|
||||
import net.torrent.torrent.context.TorrentPeer;
|
||||
import net.torrent.torrent.context.TorrentPeerID;
|
||||
import net.torrent.torrent.context.TorrentPeerCapabilities.TorrentPeerCapability;
|
||||
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.channel.ChannelStateEvent;
|
||||
import org.jboss.netty.channel.MessageEvent;
|
||||
import org.jboss.netty.channel.SimpleChannelHandler;
|
||||
|
||||
/**
|
||||
* Handles pre-algoritihm handler stuff.
|
||||
* <p>
|
||||
* Updates {@link TorrentManager} state.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class PeerWireManagerHeadHandler extends SimpleChannelHandler {
|
||||
/**
|
||||
* The torrent manager
|
||||
*/
|
||||
private final TorrentManager manager;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param manager
|
||||
* the torrent manager
|
||||
*/
|
||||
public PeerWireManagerHeadHandler(TorrentManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e)
|
||||
throws Exception {
|
||||
manager.getConnectionManager().add(e.getChannel());
|
||||
manager.getPeerManager().add(e.getChannel(), null);
|
||||
super.channelOpen(ctx, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)
|
||||
throws Exception {
|
||||
if (!manager.getConnectionManager().update(e.getChannel())) {
|
||||
e.getChannel().close();
|
||||
}
|
||||
manager.getPeerManager().update(e.getChannel());
|
||||
super.channelConnected(ctx, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
|
||||
throws Exception {
|
||||
final Channel channel = e.getChannel();
|
||||
final PeerWirePeer pwpeer = manager.getPeerManager().getPeer(
|
||||
e.getChannel());
|
||||
final Object msg = e.getMessage();
|
||||
|
||||
if (msg instanceof HandshakeMessage) {
|
||||
final HandshakeMessage handshake = (HandshakeMessage) msg;
|
||||
final TorrentPeer peer = manager.getContext().getPeer(
|
||||
TorrentPeerID.create(handshake.getPeerId()),
|
||||
(InetSocketAddress) channel.getRemoteAddress());
|
||||
pwpeer.setTorrentPeer(peer);
|
||||
peer.setSocketAddress((InetSocketAddress) channel
|
||||
.getRemoteAddress());
|
||||
peer.getCapabilities().setCapabilities(handshake.getReserved());
|
||||
|
||||
// TODO send bitfield
|
||||
if (peer.getCapabilities().supports(
|
||||
TorrentPeerCapability.FAST_PEERS)) {
|
||||
pwpeer.haveAll();
|
||||
} else {
|
||||
// pwpeer.bitfield(manager.getContext().getBitfield().getBits());
|
||||
}
|
||||
}
|
||||
super.messageReceived(ctx, e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.handler;
|
||||
|
||||
import net.torrent.protocol.peerwire.PeerWirePeer;
|
||||
import net.torrent.protocol.peerwire.manager.TorrentManager;
|
||||
import net.torrent.protocol.peerwire.message.CancelMessage;
|
||||
import net.torrent.protocol.peerwire.message.PieceMessage;
|
||||
import net.torrent.protocol.peerwire.message.RequestMessage;
|
||||
import net.torrent.protocol.peerwire.message.fast.RejectMessage;
|
||||
import net.torrent.torrent.Torrent;
|
||||
import net.torrent.torrent.TorrentPart;
|
||||
|
||||
import org.jboss.netty.channel.ChannelFuture;
|
||||
import org.jboss.netty.channel.ChannelFutureListener;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.channel.ChannelStateEvent;
|
||||
import org.jboss.netty.channel.MessageEvent;
|
||||
import org.jboss.netty.channel.SimpleChannelHandler;
|
||||
|
||||
/**
|
||||
* Handles post-algorithm handler stuff.
|
||||
* <p>
|
||||
* Updates {@link TorrentManager} state.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class PeerWireManagerTailHandler extends SimpleChannelHandler {
|
||||
/**
|
||||
* The torrent manager
|
||||
*/
|
||||
private final TorrentManager manager;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param manager
|
||||
* the torrent manager
|
||||
*/
|
||||
public PeerWireManagerTailHandler(TorrentManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
|
||||
throws Exception {
|
||||
final Object msg = e.getMessage();
|
||||
|
||||
if (msg instanceof PieceMessage) {
|
||||
final PieceMessage pieceMsg = (PieceMessage) msg;
|
||||
final Torrent torrent = manager.getTorrent();
|
||||
final TorrentPart part = torrent.getPart(pieceMsg.getIndex(),
|
||||
pieceMsg.getStart(), pieceMsg.getLength());
|
||||
manager.getDownloadManager().remove(part);
|
||||
} else if (msg instanceof RejectMessage) {
|
||||
final RejectMessage reject = (RejectMessage) msg;
|
||||
final Torrent torrent = manager.getTorrent();
|
||||
final TorrentPart part = torrent.getPart(reject.getIndex(),
|
||||
reject.getStart(), reject.getLength());
|
||||
manager.getDownloadManager().remove(part);
|
||||
}
|
||||
super.messageReceived(ctx, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeRequested(ChannelHandlerContext ctx, MessageEvent e)
|
||||
throws Exception {
|
||||
final PeerWirePeer peer = manager.getPeerManager().getPeer(
|
||||
e.getChannel());
|
||||
final Object msg = e.getMessage();
|
||||
|
||||
if (msg instanceof PieceMessage) {
|
||||
final PieceMessage message = (PieceMessage) msg;
|
||||
final Torrent torrent = manager.getContext().getTorrent();
|
||||
final TorrentPart part = torrent.getPart(message.getIndex(),
|
||||
message.getStart(), message.getLength());
|
||||
manager.getUploadManager().add(part, peer.getTorrentPeer());
|
||||
e.getFuture().addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future)
|
||||
throws Exception {
|
||||
manager.getUploadManager().remove(part);
|
||||
}
|
||||
});
|
||||
} else if (msg instanceof RequestMessage) {
|
||||
final RequestMessage message = (RequestMessage) msg;
|
||||
final Torrent torrent = manager.getContext().getTorrent();
|
||||
final TorrentPart part = torrent.getPart(message.getIndex(),
|
||||
message.getStart(), message.getLength());
|
||||
manager.getDownloadManager().add(part, peer.getTorrentPeer());
|
||||
} else if (msg instanceof CancelMessage) {
|
||||
final CancelMessage message = (CancelMessage) msg;
|
||||
final Torrent torrent = manager.getContext().getTorrent();
|
||||
final TorrentPart part = torrent.getPart(message.getIndex(),
|
||||
message.getStart(), message.getLength());
|
||||
e.getFuture().addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future)
|
||||
throws Exception {
|
||||
manager.getDownloadManager().remove(part);
|
||||
}
|
||||
});
|
||||
}
|
||||
super.writeRequested(ctx, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelDisconnected(ChannelHandlerContext ctx,
|
||||
ChannelStateEvent e) throws Exception {
|
||||
manager.getConnectionManager().update(e.getChannel());
|
||||
final PeerWirePeer peer = manager.getPeerManager().update(
|
||||
e.getChannel());
|
||||
if (peer.getTorrentPeer() != null) {
|
||||
manager.getDownloadManager().remove(peer.getTorrentPeer());
|
||||
manager.getUploadManager().remove(peer.getTorrentPeer());
|
||||
}
|
||||
super.channelDisconnected(ctx, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
|
||||
throws Exception {
|
||||
manager.getConnectionManager().remove(e.getChannel());
|
||||
final PeerWirePeer peer = manager.getPeerManager().remove(
|
||||
e.getChannel());
|
||||
if (peer.getTorrentPeer() != null) {
|
||||
manager.getDownloadManager().remove(peer.getTorrentPeer());
|
||||
manager.getUploadManager().remove(peer.getTorrentPeer());
|
||||
}
|
||||
super.channelClosed(ctx, e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.manager;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import net.torrent.torrent.context.TorrentContext;
|
||||
|
||||
import org.jboss.netty.channel.Channel;
|
||||
|
||||
/**
|
||||
* Connection manager: manages active and inactive connections.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class ConnectionManager implements Iterable<Channel> {
|
||||
/**
|
||||
* The torrent context
|
||||
*/
|
||||
private final TorrentContext context;
|
||||
|
||||
/**
|
||||
* The list of active channels
|
||||
*/
|
||||
private final Set<Channel> activeChannels = new HashSet<Channel>();
|
||||
/**
|
||||
* The list of inactive channels
|
||||
*/
|
||||
private final Set<Channel> inactiveChannels = new HashSet<Channel>();
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param context
|
||||
* the torrent context
|
||||
*/
|
||||
public ConnectionManager(TorrentContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new channel
|
||||
*
|
||||
* @param channel
|
||||
* the channel
|
||||
* @return true if channel was added. False if was already in list.
|
||||
*/
|
||||
public boolean add(Channel channel) {
|
||||
if (channel.isConnected()) {
|
||||
return activeChannels.add(channel);
|
||||
} else {
|
||||
return inactiveChannels.add(channel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if contains the channel.
|
||||
*
|
||||
* @param channel
|
||||
* the channel
|
||||
* @return true if channel is registered
|
||||
*/
|
||||
public boolean contains(Channel channel) {
|
||||
if (activeChannels.contains(channel))
|
||||
return true;
|
||||
if (inactiveChannels.contains(channel))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the channel
|
||||
*
|
||||
* @param channel
|
||||
* the channel
|
||||
* @return true if channel was registered
|
||||
*/
|
||||
public boolean remove(Channel channel) {
|
||||
if (activeChannels.remove(channel))
|
||||
return true;
|
||||
if (inactiveChannels.remove(channel))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the state of the channel. In practice, remove and adds again the
|
||||
* channel.
|
||||
*
|
||||
* @param channel
|
||||
* the channel
|
||||
* @return true if channel has been added successfully.
|
||||
*/
|
||||
public boolean update(Channel channel) {
|
||||
if (!remove(channel))
|
||||
return false;
|
||||
return add(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get to count of active connections
|
||||
*
|
||||
* @return the amount of active connections
|
||||
*/
|
||||
public int getActiveConnections() {
|
||||
return activeChannels.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get to count of inactive connections
|
||||
*
|
||||
* @return the amount of inactive connections
|
||||
*/
|
||||
public int getInactiveConnections() {
|
||||
return inactiveChannels.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of active channels
|
||||
*
|
||||
* @return list of active channels
|
||||
*/
|
||||
public Set<Channel> getActiveChannels() {
|
||||
return Collections.unmodifiableSet(activeChannels);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of inactive channels
|
||||
*
|
||||
* @return list of inactive channels
|
||||
*/
|
||||
public Set<Channel> getInactiveChannels() {
|
||||
return Collections.unmodifiableSet(inactiveChannels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Channel> iterator() {
|
||||
return activeChannels.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the torent context
|
||||
*
|
||||
* @return the torrent context
|
||||
*/
|
||||
public TorrentContext getContext() {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.manager;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import net.torrent.torrent.TorrentPart;
|
||||
import net.torrent.torrent.TorrentPiece;
|
||||
import net.torrent.torrent.context.TorrentContext;
|
||||
import net.torrent.torrent.context.TorrentPeer;
|
||||
|
||||
public class DownloadManager implements Iterable<TorrentPart> {
|
||||
private final TorrentContext context;
|
||||
|
||||
private final Map<TorrentPart, TorrentPeer> activeParts = new HashMap<TorrentPart, TorrentPeer>();
|
||||
|
||||
public DownloadManager(TorrentContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public boolean isDownloading(TorrentPart torrentPart) {
|
||||
return activeParts.containsKey(torrentPart);
|
||||
}
|
||||
|
||||
public boolean isDownloading(TorrentPiece piece) {
|
||||
for (final TorrentPart part : activeParts.keySet()) {
|
||||
if (part.getPiece().equals(piece))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isDownloading(TorrentPeer peer) {
|
||||
return activeParts.containsValue(peer);
|
||||
}
|
||||
|
||||
public TorrentPeer getPeer(TorrentPart torrentPart) {
|
||||
return activeParts.get(torrentPart);
|
||||
}
|
||||
|
||||
public Set<TorrentPart> getTorrentParts(TorrentPeer peer) {
|
||||
final Set<TorrentPart> parts = new HashSet<TorrentPart>();
|
||||
for (final Entry<TorrentPart, TorrentPeer> entry : activeParts
|
||||
.entrySet()) {
|
||||
if (entry.getValue().equals(peer))
|
||||
parts.add(entry.getKey());
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
public boolean isInactive() {
|
||||
return activeParts.isEmpty();
|
||||
}
|
||||
|
||||
public TorrentPeer add(TorrentPart torrentPart, TorrentPeer peer) {
|
||||
return activeParts.put(torrentPart, peer);
|
||||
}
|
||||
|
||||
public TorrentPeer remove(TorrentPart torrentPart) {
|
||||
return activeParts.remove(torrentPart);
|
||||
}
|
||||
|
||||
public Set<TorrentPart> remove(TorrentPeer peer) {
|
||||
final Set<TorrentPart> parts = new HashSet<TorrentPart>();
|
||||
for (TorrentPart part : getTorrentParts(peer)) {
|
||||
if (activeParts.remove(part) != null)
|
||||
parts.add(part);
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
public int getActiveDownloadsCount() {
|
||||
return activeParts.size();
|
||||
}
|
||||
|
||||
public Map<TorrentPart, TorrentPeer> getActiveDownloads() {
|
||||
return Collections.unmodifiableMap(activeParts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<TorrentPart> iterator() {
|
||||
return activeParts.keySet().iterator();
|
||||
}
|
||||
|
||||
public TorrentContext getContext() {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.manager;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import net.torrent.protocol.peerwire.PeerWirePeer;
|
||||
import net.torrent.torrent.context.TorrentContext;
|
||||
import net.torrent.torrent.context.TorrentPeer;
|
||||
import net.torrent.util.PeerCallback;
|
||||
|
||||
import org.jboss.netty.channel.Channel;
|
||||
|
||||
public class PeerManager implements Iterable<PeerWirePeer> {
|
||||
private final TorrentContext context;
|
||||
private final ConnectionManager connectionManager;
|
||||
|
||||
private final Map<Channel, PeerWirePeer> activePeers = new HashMap<Channel, PeerWirePeer>();
|
||||
private final Map<Channel, PeerWirePeer> inactivePeers = new HashMap<Channel, PeerWirePeer>();
|
||||
|
||||
public PeerManager(TorrentContext context,
|
||||
ConnectionManager connectionManager) {
|
||||
this.context = context;
|
||||
this.connectionManager = connectionManager;
|
||||
}
|
||||
|
||||
public boolean contains(Channel channel) {
|
||||
if (activePeers.containsKey(channel))
|
||||
return true;
|
||||
if (inactivePeers.containsKey(channel))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean contains(PeerWirePeer peer) {
|
||||
if (activePeers.containsValue(peer))
|
||||
return true;
|
||||
if (inactivePeers.containsValue(peer))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public PeerWirePeer getPeer(Channel channel) {
|
||||
PeerWirePeer peer = activePeers.get(channel);
|
||||
if (peer == null)
|
||||
peer = inactivePeers.get(channel);
|
||||
return peer;
|
||||
}
|
||||
|
||||
public Channel getChannel(PeerWirePeer peer) {
|
||||
for (final Entry<Channel, PeerWirePeer> entry : activePeers.entrySet()) {
|
||||
if (entry.getValue().equals(peer))
|
||||
return entry.getKey();
|
||||
}
|
||||
for (final Entry<Channel, PeerWirePeer> entry : inactivePeers
|
||||
.entrySet()) {
|
||||
if (entry.getValue().equals(peer))
|
||||
return entry.getKey();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return activePeers.isEmpty();
|
||||
}
|
||||
|
||||
public PeerWirePeer add(Channel channel, TorrentPeer peer) {
|
||||
if (channel.isConnected()) {
|
||||
return activePeers.put(channel, new PeerWirePeer(channel, peer));
|
||||
} else {
|
||||
return inactivePeers.put(channel, new PeerWirePeer(channel, peer));
|
||||
}
|
||||
}
|
||||
|
||||
public PeerWirePeer remove(Channel channel) {
|
||||
PeerWirePeer peer;
|
||||
if ((peer = activePeers.remove(channel)) != null)
|
||||
return peer;
|
||||
if ((peer = inactivePeers.remove(channel)) != null)
|
||||
return peer;
|
||||
return null;
|
||||
}
|
||||
|
||||
public PeerWirePeer remove(PeerWirePeer peer) {
|
||||
final Channel channel = getChannel(peer);
|
||||
PeerWirePeer peerRemoved;
|
||||
if ((peerRemoved = activePeers.remove(channel)) != null)
|
||||
return peerRemoved;
|
||||
if ((peerRemoved = inactivePeers.remove(channel)) != null)
|
||||
return peerRemoved;
|
||||
return null;
|
||||
}
|
||||
|
||||
public PeerWirePeer update(Channel channel) {
|
||||
PeerWirePeer peer;
|
||||
if ((peer = remove(channel)) == null)
|
||||
return null;
|
||||
return add(channel, peer.getTorrentPeer());
|
||||
}
|
||||
|
||||
public int getActivePeersCount() {
|
||||
return activePeers.size();
|
||||
}
|
||||
|
||||
public int getImactivePeersCount() {
|
||||
return activePeers.size();
|
||||
}
|
||||
|
||||
public Map<Channel, PeerWirePeer> getActivePeers() {
|
||||
return Collections.unmodifiableMap(activePeers);
|
||||
}
|
||||
|
||||
public Map<Channel, PeerWirePeer> getInactivePeers() {
|
||||
return Collections.unmodifiableMap(inactivePeers);
|
||||
}
|
||||
|
||||
public Set<Channel> getActiveChannels() {
|
||||
return Collections.unmodifiableSet(activePeers.keySet());
|
||||
}
|
||||
|
||||
public Set<Channel> getInactiveChannels() {
|
||||
return Collections.unmodifiableSet(inactivePeers.keySet());
|
||||
}
|
||||
|
||||
public void executeActive(PeerCallback callback) {
|
||||
for (final Entry<Channel, PeerWirePeer> entry : this.activePeers
|
||||
.entrySet()) {
|
||||
callback.callback(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void executeInactive(PeerCallback callback) {
|
||||
for (final Entry<Channel, PeerWirePeer> entry : this.inactivePeers
|
||||
.entrySet()) {
|
||||
callback.callback(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void execute(PeerCallback callback) {
|
||||
executeActive(callback);
|
||||
executeInactive(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<PeerWirePeer> iterator() {
|
||||
return activePeers.values().iterator();
|
||||
}
|
||||
|
||||
public TorrentContext getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public ConnectionManager getConnectionManager() {
|
||||
return connectionManager;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.manager;
|
||||
|
||||
import net.torrent.protocol.datastore.TorrentDatastore;
|
||||
import net.torrent.torrent.Torrent;
|
||||
import net.torrent.torrent.context.TorrentContext;
|
||||
|
||||
public class TorrentManager {
|
||||
private final TorrentContext context;
|
||||
|
||||
private final ConnectionManager connectionManager;
|
||||
private final PeerManager peerManager;
|
||||
private final DownloadManager downloadManager;
|
||||
private final UploadManager uploadManager;
|
||||
|
||||
private final TorrentDatastore datastore;
|
||||
|
||||
public TorrentManager(TorrentContext context, TorrentDatastore datastore) {
|
||||
this.context = context;
|
||||
this.datastore = datastore;
|
||||
connectionManager = new ConnectionManager(context);
|
||||
peerManager = new PeerManager(context, connectionManager);
|
||||
downloadManager = new DownloadManager(context);
|
||||
uploadManager = new UploadManager(context);
|
||||
}
|
||||
|
||||
public TorrentContext getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public TorrentDatastore getDatastore() {
|
||||
return datastore;
|
||||
}
|
||||
|
||||
public ConnectionManager getConnectionManager() {
|
||||
return connectionManager;
|
||||
}
|
||||
|
||||
public PeerManager getPeerManager() {
|
||||
return peerManager;
|
||||
}
|
||||
|
||||
public DownloadManager getDownloadManager() {
|
||||
return downloadManager;
|
||||
}
|
||||
|
||||
public UploadManager getUploadManager() {
|
||||
return uploadManager;
|
||||
}
|
||||
|
||||
public Torrent getTorrent() {
|
||||
return context.getTorrent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.manager;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import net.torrent.torrent.TorrentPart;
|
||||
import net.torrent.torrent.context.TorrentContext;
|
||||
import net.torrent.torrent.context.TorrentPeer;
|
||||
|
||||
public class UploadManager implements Iterable<TorrentPart> {
|
||||
private final TorrentContext context;
|
||||
|
||||
private final Map<TorrentPart, TorrentPeer> activeParts = new HashMap<TorrentPart, TorrentPeer>();
|
||||
|
||||
public UploadManager(TorrentContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public boolean isUploading(TorrentPart torrentPart) {
|
||||
return activeParts.containsKey(torrentPart);
|
||||
}
|
||||
|
||||
public boolean isUploading(TorrentPeer peer) {
|
||||
return activeParts.containsValue(peer);
|
||||
}
|
||||
|
||||
public TorrentPeer getPeer(TorrentPart torrentPart) {
|
||||
return activeParts.get(torrentPart);
|
||||
}
|
||||
|
||||
public Set<TorrentPart> getTorrentParts(TorrentPeer peer) {
|
||||
final Set<TorrentPart> parts = new HashSet<TorrentPart>();
|
||||
for (final Entry<TorrentPart, TorrentPeer> entry : activeParts
|
||||
.entrySet()) {
|
||||
if (entry.getValue().equals(peer))
|
||||
parts.add(entry.getKey());
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
public boolean isInactive() {
|
||||
return activeParts.isEmpty();
|
||||
}
|
||||
|
||||
public TorrentPeer add(TorrentPart torrentPart, TorrentPeer peer) {
|
||||
return activeParts.put(torrentPart, peer);
|
||||
}
|
||||
|
||||
public TorrentPeer remove(TorrentPart torrentPart) {
|
||||
return activeParts.remove(torrentPart);
|
||||
}
|
||||
|
||||
public Set<TorrentPart> remove(TorrentPeer peer) {
|
||||
final Set<TorrentPart> parts = new HashSet<TorrentPart>();
|
||||
for (TorrentPart part : getTorrentParts(peer)) {
|
||||
if (activeParts.remove(part) != null)
|
||||
parts.add(part);
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
public int getActiveUploadsCount() {
|
||||
return activeParts.size();
|
||||
}
|
||||
|
||||
public Map<TorrentPart, TorrentPeer> getActiveUploads() {
|
||||
return Collections.unmodifiableMap(activeParts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<TorrentPart> iterator() {
|
||||
return activeParts.keySet().iterator();
|
||||
}
|
||||
|
||||
public TorrentContext getContext() {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.message;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.BitSet;
|
||||
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* bitfield: [len=0001+X][id=5][bitfield]
|
||||
* </pre>
|
||||
*
|
||||
* The bitfield message may only be sent immediately after the handshaking
|
||||
* sequence is completed, and before any other messages are sent. It is
|
||||
* optional, and need not be sent if a client has no pieces. The bitfield
|
||||
* message is variable length, where X is the length of the bitfield. The
|
||||
* payload is a bitfield representing the pieces that have been successfully
|
||||
* downloaded. The high bit in the first byte corresponds to piece index 0. Bits
|
||||
* that are cleared indicated a missing piece, and set bits indicate a valid and
|
||||
* available piece. Spare bits at the end are set to zero. A bitfield of the
|
||||
* wrong length is considered an error. Clients should drop the connection if
|
||||
* they receive bitfields that are not of the correct size, or if the bitfield
|
||||
* has any of the spare bits set.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
* @source BitTorrent documentation
|
||||
*/
|
||||
public class BitfieldMessage implements PeerWireWritableMessage,
|
||||
PeerWireReadableMessage {
|
||||
public static final byte MESSAGE_ID = 0x05;
|
||||
|
||||
/**
|
||||
* A set of bits with the following scheme:
|
||||
* <ul>
|
||||
* <li><b>true</b>: has the piece</li>
|
||||
* </li><b>false</b>: piece is missing</li>
|
||||
* </ul>
|
||||
*/
|
||||
private BitSet bitfield;
|
||||
|
||||
public BitfieldMessage(BitSet bitfield) {
|
||||
this.bitfield = bitfield;
|
||||
}
|
||||
|
||||
public BitfieldMessage() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ChannelBuffer buffer) throws IOException {
|
||||
buffer.readerIndex(buffer.readerIndex() - 5);
|
||||
int len = buffer.readInt() - 2;
|
||||
buffer.readByte(); // unk
|
||||
|
||||
bitfield = new BitSet(len * 8);
|
||||
int i = 0;
|
||||
int read = 0;
|
||||
while (read <= len) {
|
||||
byte b = buffer.readByte();
|
||||
for (int j = 128; j > 0; j >>= 1) {
|
||||
bitfield.set(i++, (b & j) != 0);
|
||||
}
|
||||
read++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelBuffer buffer) throws IOException {
|
||||
buffer.writeByte(MESSAGE_ID);
|
||||
// byte[] bytes = new byte[bitfield.size() / 8 - 4];
|
||||
// for (int i = 0; i < bitfield.size(); i++) {
|
||||
// if((bytes.length - i / 8 - 1) > bytes.length)
|
||||
// break;
|
||||
// if (bitfield.get(i)) {
|
||||
// bytes[bytes.length - i / 8 - 1] |= 1 << (i % 8);
|
||||
// }
|
||||
// }
|
||||
// buffer.put(bytes);
|
||||
for (int i = 0; i < bitfield.size();) {
|
||||
byte data = 0;
|
||||
for (int j = 128; i < bitfield.size() && j > 0; j >>= 1, i++) {
|
||||
if (bitfield.get(i)) {
|
||||
data |= j;
|
||||
}
|
||||
}
|
||||
buffer.writeByte(data);
|
||||
}
|
||||
}
|
||||
|
||||
public BitSet getBitfield() {
|
||||
return bitfield;
|
||||
}
|
||||
|
||||
public void setBitfield(BitSet bitfield) {
|
||||
this.bitfield = bitfield;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BitfieldMessage [bitfield=" + bitfield + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.message;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* cancel: [len=0013][id=8][index][begin][length]
|
||||
* </pre>
|
||||
*
|
||||
* The cancel message is fixed length, and is used to cancel block requests. The
|
||||
* payload is identical to that of the "request" message. It is typically used
|
||||
* during "End Game" (check for algorithms).
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
* @source BitTorrent documentation
|
||||
*/
|
||||
public class CancelMessage implements PeerWireWritableMessage,
|
||||
PeerWireReadableMessage {
|
||||
public static final byte MESSAGE_ID = 0x08;
|
||||
|
||||
/**
|
||||
* The piece index
|
||||
*/
|
||||
private int index;
|
||||
/**
|
||||
* The start offset
|
||||
*/
|
||||
private int start;
|
||||
/**
|
||||
* The length
|
||||
*/
|
||||
private int length;
|
||||
|
||||
public CancelMessage() {
|
||||
}
|
||||
|
||||
public CancelMessage(int index, int start, int length) {
|
||||
this.index = index;
|
||||
this.start = start;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ChannelBuffer buffer) throws IOException {
|
||||
this.index = buffer.readInt();
|
||||
this.start = buffer.readInt();
|
||||
this.length = buffer.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelBuffer buffer) throws IOException {
|
||||
buffer.writeByte(MESSAGE_ID);
|
||||
buffer.writeInt(index);
|
||||
buffer.writeInt(start);
|
||||
buffer.writeInt(length);
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public void setStart(int start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public void setLength(int length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CancelMessage [index=" + index + ", start=" + start
|
||||
+ ", length=" + length + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.message;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* choke: [len=0001][id=0]
|
||||
* </pre>
|
||||
*
|
||||
* The choke message is fixed-length and has no payload.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
* @source BitTorrent documentation
|
||||
*/
|
||||
public class ChokeMessage implements PeerWireWritableMessage,
|
||||
PeerWireReadableMessage {
|
||||
public static final byte MESSAGE_ID = 0x00;
|
||||
|
||||
@Override
|
||||
public void read(ChannelBuffer buffer) throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelBuffer buffer) throws IOException {
|
||||
buffer.writeByte(MESSAGE_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChokeMessage []";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.message;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* TODO documentation
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class HandshakeMessage implements PeerWireWritableMessage,
|
||||
PeerWireReadableMessage {
|
||||
/**
|
||||
* Length of "BitTorrent protocol" string.
|
||||
*/
|
||||
private int pstrlen = 19;
|
||||
/**
|
||||
* The protocol string, must match to validate the protocol connection
|
||||
*/
|
||||
private String pstr = "BitTorrent protocol";
|
||||
/**
|
||||
* Addons supported by the client (bitfield)
|
||||
*/
|
||||
private BitSet reserved = new BitSet(64);
|
||||
/**
|
||||
* The torrent's hash
|
||||
*/
|
||||
private byte[] infohash;
|
||||
/**
|
||||
* The peer id
|
||||
*/
|
||||
private byte[] peerId;
|
||||
|
||||
public HandshakeMessage(byte[] infohash, byte[] peerId, BitSet reserved) {
|
||||
this.infohash = infohash;
|
||||
this.peerId = peerId;
|
||||
this.reserved = reserved;
|
||||
}
|
||||
|
||||
public HandshakeMessage() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ChannelBuffer buffer) throws IOException {
|
||||
this.pstrlen = buffer.readByte();
|
||||
this.pstr = new String(buffer.readBytes(pstrlen).array());
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
byte b = buffer.readByte();
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if ((b & (1 << j)) > 0) {
|
||||
reserved.set(i * 8 + j);
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println(reserved);
|
||||
// int bit = 0;
|
||||
// for (int i = 0; i < 8; i++) {
|
||||
// byte b = buffer.readByte();
|
||||
// for (int j = 128; j > 0; j >>= 1) {
|
||||
// reserved.set(bit++, (b & j) != 0);
|
||||
// }
|
||||
// }
|
||||
|
||||
// this.reserved = buffer.readBytes(8).array();
|
||||
this.infohash = buffer.readBytes(20).array();
|
||||
this.peerId = buffer.readBytes(20).array();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelBuffer buffer) throws IOException {
|
||||
buffer.writeByte((byte) this.pstr.length());
|
||||
buffer.writeBytes(this.pstr.getBytes());
|
||||
|
||||
for (int i = 0; i < 64;) {
|
||||
byte data = 0;
|
||||
for (int j = 128; i < reserved.size() && j > 0; j >>= 1, i++) {
|
||||
if (reserved.get(i)) {
|
||||
data |= j;
|
||||
}
|
||||
}
|
||||
buffer.writeByte(data);
|
||||
}
|
||||
|
||||
// buffer.writeBytes(reserved);
|
||||
buffer.writeBytes(this.infohash);
|
||||
buffer.writeBytes(this.peerId);
|
||||
}
|
||||
|
||||
public int getPstrlen() {
|
||||
return pstrlen;
|
||||
}
|
||||
|
||||
public void setPstrlen(int pstrlen) {
|
||||
this.pstrlen = pstrlen;
|
||||
}
|
||||
|
||||
public String getPstr() {
|
||||
return pstr;
|
||||
}
|
||||
|
||||
public void setPstr(String pstr) {
|
||||
this.pstr = pstr;
|
||||
}
|
||||
|
||||
public BitSet getReserved() {
|
||||
return reserved;
|
||||
}
|
||||
|
||||
public void setReserved(BitSet reserved) {
|
||||
this.reserved = reserved;
|
||||
}
|
||||
|
||||
public byte[] getInfohash() {
|
||||
return infohash;
|
||||
}
|
||||
|
||||
public void setInfohash(byte[] infohash) {
|
||||
this.infohash = infohash;
|
||||
}
|
||||
|
||||
public byte[] getPeerId() {
|
||||
return peerId;
|
||||
}
|
||||
|
||||
public void setPeerId(byte[] peerId) {
|
||||
this.peerId = peerId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final int maxLen = 10;
|
||||
return "HandshakeMessage [pstrlen="
|
||||
+ pstrlen
|
||||
+ ", pstr="
|
||||
+ pstr
|
||||
+ ", reserved="
|
||||
+ reserved
|
||||
+ ", infohash="
|
||||
+ (infohash != null ? Arrays.toString(Arrays.copyOf(infohash,
|
||||
Math.min(infohash.length, maxLen))) : null)
|
||||
+ ", peerId="
|
||||
+ (peerId != null ? Arrays.toString(Arrays.copyOf(peerId,
|
||||
Math.min(peerId.length, maxLen))) : null) + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.message;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* have: [len=0005][id=4][piece index(int)]
|
||||
* </pre>
|
||||
*
|
||||
* The have message is fixed length. The payload is the zero-based index of a
|
||||
* that has just been successfully downloaded and verified via the hash.<br>
|
||||
* <br>
|
||||
* <b>Implementer's Note</b>: That is the strict definition, in reality some
|
||||
* games may be played. In particular because peers are extremely unlikely to
|
||||
* download pieces that they already have, a peer may choose not to advertise
|
||||
* having a piece to a peer that already has that piece. At a minimum
|
||||
* "HAVE supression" will result in a 50% reduction in the number of HAVE
|
||||
* messages, this translates to around a 25-35% reduction in protocol overhead.
|
||||
* At the same time, it may be worthwhile to send a HAVE message to a peer that
|
||||
* has that piece already since it will be useful in determining which piece is
|
||||
* rare. A malicious peer might also choose to advertise having pieces that it
|
||||
* knows the peer will never download. Due to this attempting to model peers
|
||||
* using this information is a bad idea.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
* @source BitTorrent documentation
|
||||
*/
|
||||
public class HaveMessage implements PeerWireWritableMessage,
|
||||
PeerWireReadableMessage {
|
||||
public static final byte MESSAGE_ID = 0x04;
|
||||
|
||||
/**
|
||||
* The new obtained piece index
|
||||
*/
|
||||
private int piece;
|
||||
|
||||
public HaveMessage(int piece) {
|
||||
this.piece = piece;
|
||||
}
|
||||
|
||||
public HaveMessage() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ChannelBuffer buffer) throws IOException {
|
||||
this.piece = buffer.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelBuffer buffer) throws IOException {
|
||||
buffer.writeByte(MESSAGE_ID);
|
||||
buffer.writeInt(piece);
|
||||
}
|
||||
|
||||
public int getPiece() {
|
||||
return piece;
|
||||
}
|
||||
|
||||
public void setPiece(int piece) {
|
||||
this.piece = piece;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HaveMessage [piece=" + piece + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.message;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* interested: [len=0001][id=2]
|
||||
* </pre>
|
||||
*
|
||||
* The interested message is fixed-length and has no payload.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
* @source BitTorrent documentation
|
||||
*/
|
||||
public class InterestedMessage implements PeerWireWritableMessage,
|
||||
PeerWireReadableMessage {
|
||||
public static final byte MESSAGE_ID = 0x02;
|
||||
|
||||
@Override
|
||||
public void read(ChannelBuffer buffer) throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelBuffer buffer) throws IOException {
|
||||
buffer.writeByte(MESSAGE_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InterestedMessage []";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.message;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* keep-alive: [len=0000]
|
||||
* </pre>
|
||||
*
|
||||
* The keep-alive message is a message with zero bytes, specified with the
|
||||
* length prefix set to zero. There is no message ID and no payload. Peers may
|
||||
* close a connection if they receive no messages (keep-alive or any other
|
||||
* message) for a certain period of time, so a keep-alive message must be sent
|
||||
* to maintain the connection alive if no command have been sent for a given
|
||||
* amount of time. This amount of time is generally two minutes.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
* @source BitTorrent documentation
|
||||
*/
|
||||
public class KeepAliveMessage implements PeerWireWritableMessage,
|
||||
PeerWireReadableMessage {
|
||||
@Override
|
||||
public void read(ChannelBuffer buffer) throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelBuffer buffer) throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KeepAliveMessage []";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.message;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* not interested: [len=0001][id=3]
|
||||
* </pre>
|
||||
*
|
||||
* The not interested message is fixed-length and has no payload.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
* @source BitTorrent documentation
|
||||
*/
|
||||
public class NotInterestedMessage implements PeerWireWritableMessage,
|
||||
PeerWireReadableMessage {
|
||||
public static final byte MESSAGE_ID = 0x03;
|
||||
|
||||
@Override
|
||||
public void read(ChannelBuffer buffer) throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelBuffer buffer) throws IOException {
|
||||
buffer.writeByte(MESSAGE_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NotInterestedMessage []";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.message;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* piece: [len=0009+X][id=7][index][begin][block]
|
||||
* </pre>
|
||||
*
|
||||
* The piece message is variable length, where X is the length of the block. The
|
||||
* payload contains the following information:<br>
|
||||
* index: integer specifying the zero-based piece index<br>
|
||||
* begin: integer specifying the zero-based byte offset within the piece<br>
|
||||
* block: block of data, which is a subset of the piece specified by index.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
* @source BitTorrent documentation
|
||||
*/
|
||||
public class PieceMessage implements PeerWireWritableMessage,
|
||||
PeerWireReadableMessage {
|
||||
public static final byte MESSAGE_ID = 0x07;
|
||||
|
||||
/**
|
||||
* The piece index
|
||||
*/
|
||||
private int index;
|
||||
/**
|
||||
* The part start offset
|
||||
*/
|
||||
private int start;
|
||||
/**
|
||||
* The part length
|
||||
*/
|
||||
private int length;
|
||||
/**
|
||||
* The downloaded/uploaded data
|
||||
*/
|
||||
private ByteBuffer block;
|
||||
|
||||
public PieceMessage(RequestMessage request, ByteBuffer block) {
|
||||
this.index = request.getIndex();
|
||||
this.start = request.getStart();
|
||||
this.length = request.getLength();
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
public PieceMessage(int index, int start, int length, ByteBuffer block) {
|
||||
this.index = index;
|
||||
this.start = start;
|
||||
this.length = length;
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
public PieceMessage() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ChannelBuffer buffer) throws IOException {
|
||||
this.index = buffer.readInt();
|
||||
this.start = buffer.readInt();
|
||||
this.length = buffer.readableBytes();
|
||||
this.block = buffer.readBytes(length).toByteBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelBuffer buffer) throws IOException {
|
||||
buffer.writeByte(MESSAGE_ID);
|
||||
buffer.writeInt(index);
|
||||
buffer.writeInt(start);
|
||||
buffer.writeBytes(block);
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public void setStart(int start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public void setLength(int length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public ByteBuffer getBlock() {
|
||||
return block;
|
||||
}
|
||||
|
||||
public void setBlock(ByteBuffer block) {
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PieceMessage [index=" + index + ", start=" + start
|
||||
+ ", length=" + length + ", block=" + block + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.message;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* port: [len=0003][id=9][listen-port]
|
||||
* </pre>
|
||||
*
|
||||
* The port message is sent by newer versions of the Mainline that implements a
|
||||
* DHT tracker. The listen port is the port this peer's DHT node is listening
|
||||
* on. This peer should be inserted in the local routing table (if DHT tracker
|
||||
* is supported).
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
* @source BitTorrent documentation
|
||||
*/
|
||||
public class PortMessage implements PeerWireWritableMessage,
|
||||
PeerWireReadableMessage {
|
||||
public static final byte MESSAGE_ID = 0x09;
|
||||
|
||||
/**
|
||||
* The DHT port
|
||||
*/
|
||||
private short port;
|
||||
|
||||
public PortMessage() {
|
||||
}
|
||||
|
||||
public PortMessage(short port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ChannelBuffer buffer) throws IOException {
|
||||
this.port = buffer.readShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelBuffer buffer) throws IOException {
|
||||
buffer.writeByte(MESSAGE_ID);
|
||||
buffer.writeShort(port);
|
||||
}
|
||||
|
||||
public short getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(short port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PortMessage [port=" + port + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.message;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* request: [len=0013][id=6][index][begin][length]
|
||||
* </pre>
|
||||
*
|
||||
* The request message is fixed length, and is used to request a block. The
|
||||
* payload contains the following information:<br>
|
||||
* index: integer specifying the zero-based piece index<br>
|
||||
* begin: integer specifying the zero-based byte offset within the piece<br>
|
||||
* length: integer specifying the requested length.<br>
|
||||
* View #1 According to the official specification, "All current implementations
|
||||
* use 2^15 (32KB), and close connections which request an amount greater than
|
||||
* 2^17 (128KB)." As early as version 3 or 2004, this behavior was changed to
|
||||
* use 2^14 (16KB) blocks. As of version 4.0 or mid-2005, the mainline
|
||||
* disconnected on requests larger than 2^14 (16KB); and some clients have
|
||||
* followed suit. Note that block requests are smaller than pieces (>=2^18
|
||||
* bytes), so multiple requests will be needed to download a whole piece.<br>
|
||||
* Strictly, the specification allows 2^15 (32KB) requests. The reality is near
|
||||
* all clients will now use 2^14 (16KB) requests. Due to clients that enforce
|
||||
* that size, it is recommended that implementations make requests of that size.
|
||||
* Due to smaller requests resulting in higher overhead due to tracking a
|
||||
* greater number of requests, implementers are advised against going below 2^14
|
||||
* (16KB). The choice of request block size limit enforcement is not nearly so
|
||||
* clear cut. With mainline version 4 enforcing 16KB requests, most clients will
|
||||
* use that size.<br>
|
||||
* At the same time 2^14 (16KB) is the semi-official (only semi because the
|
||||
* official protocol document has not been updated) limit now, so enforcing that
|
||||
* isn't wrong. At the same time, allowing larger requests enlarges the set of
|
||||
* possible peers, and except on very low bandwidth connections (<256kbps)
|
||||
* multiple blocks will be downloaded in one choke-timeperiod, thus merely
|
||||
* enforcing the old limit causes minimal performance degradation. Due to this
|
||||
* factor, it is recommended that only the older 2^17 (128KB) maximum size limit
|
||||
* be enforced. View #2 This section has contained falsehoods for a large
|
||||
* portion of the time this page has existed. This is the third time I (uau) am
|
||||
* correcting this same section for incorrect information being added, so I
|
||||
* won't rewrite it completely since it'll probably be broken again... Current
|
||||
* version has at least the following errors: Mainline started using 2^14
|
||||
* (16384) byte requests when it was still the only client in existence; only
|
||||
* the "official specification" still talked about the obsolete 32768 byte value
|
||||
* which was in reality neither the default size nor maximum allowed. In version
|
||||
* 4 the request behavior did not change, but the maximum allowed size did
|
||||
* change to equal the default size. In latest mainline versions the max has
|
||||
* changed to 32768 (note that this is the first appearance of 32768 for either
|
||||
* default or max size since the first ancient versions).<br>
|
||||
* "Most older clients use 32KB requests" is false. Discussion of larger
|
||||
* requests fails to take latency effects into account.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
* @source BitTorrent documentation
|
||||
*/
|
||||
public class RequestMessage implements PeerWireWritableMessage,
|
||||
PeerWireReadableMessage {
|
||||
public static final byte MESSAGE_ID = 0x06;
|
||||
|
||||
/**
|
||||
* The piece index
|
||||
*/
|
||||
private int index;
|
||||
/**
|
||||
* The piece start offset
|
||||
*/
|
||||
private int start;
|
||||
/**
|
||||
* The part length
|
||||
*/
|
||||
private int length;
|
||||
|
||||
public RequestMessage(int index, int start, int length) {
|
||||
this.index = index;
|
||||
this.start = start;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public RequestMessage() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ChannelBuffer buffer) throws IOException {
|
||||
this.index = buffer.readInt();
|
||||
this.start = buffer.readInt();
|
||||
this.length = buffer.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelBuffer buffer) throws IOException {
|
||||
buffer.writeByte(MESSAGE_ID);
|
||||
buffer.writeInt(index);
|
||||
buffer.writeInt(start);
|
||||
buffer.writeInt(length);
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public void setStart(int start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public void setLength(int length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RequestMessage [index=" + index + ", start=" + start
|
||||
+ ", length=" + length + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.message;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* unchoke: [len=0001][id=1]
|
||||
* </pre>
|
||||
*
|
||||
* The unchoke message is fixed-length and has no payload.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
* @source BitTorrent documentation
|
||||
*/
|
||||
public class UnchokeMessage implements PeerWireWritableMessage,
|
||||
PeerWireReadableMessage {
|
||||
public static final byte MESSAGE_ID = 0x01;
|
||||
|
||||
@Override
|
||||
public void read(ChannelBuffer buffer) throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelBuffer buffer) throws IOException {
|
||||
buffer.writeByte(MESSAGE_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UnchokeMessage []";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.message.fast;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* AllowedFast: [len=0x0004+pieces(4 bytes each)][id=0x11][piece indexes(int)]*
|
||||
* </pre>
|
||||
*
|
||||
* With the BitTorrent protocol specified in BEP 0003 [2], new peers take
|
||||
* several minutes to ramp up before they can effectively engage in BitTorrent's
|
||||
* tit-for-tat. The reason is simple: starting peers have few pieces to trade.
|
||||
* <p>
|
||||
* Allowed Fast is an advisory message which means
|
||||
* "if you ask for this piece, I'll give it to you even if you're choked."
|
||||
* Allowed Fast thus shortens the awkward stage during which the peer obtains
|
||||
* occasional optimistic unchokes but cannot sufficiently reciprocate to remain
|
||||
* unchoked.
|
||||
* <p>
|
||||
* The pieces that can be downloaded when choked constitute a peer's allowed
|
||||
* fast set. The set is generated using a canonical algorithm that produces
|
||||
* piece indices unique to the message receiver so that if two peers offer k
|
||||
* pieces fast it will be the same k, and if one offers k+1 it will be the same
|
||||
* k plus one more. k should be small enough to avoid abuse, but large enough to
|
||||
* ramp up tit-for-tat. We currently set k to 10, but peers are free to change
|
||||
* this number, e.g., to suit load.
|
||||
* <p>
|
||||
* The message sender MAY list pieces that the message sender does not have. The
|
||||
* receiver MUST NOT interpret an Allowed Fast message as meaning that the
|
||||
* message sender has the piece. This allows peers to generate and communicate
|
||||
* allowed fast sets at the beginning of a connection. However, a peer MAY send
|
||||
* Allowed Fast messages at any time.
|
||||
* <p>
|
||||
* A peer SHOULD send Allowed Fast messages to any starting peer unless the
|
||||
* local peer lacks sufficient resources. A peer MAY reject requests for already
|
||||
* Allowed Fast pieces if the local peer lacks sufficient resources, if the
|
||||
* requested piece has already been sent to the requesting peer, or if the
|
||||
* requesting peer is not a starting peer. Our current implementation rejects
|
||||
* requests for Allowed Fast messages whenever the requesting peer has more than
|
||||
* * k * pieces.
|
||||
* <p>
|
||||
* When the fast extension is disabled, if a peer recieves an Allowed Fast
|
||||
* message then the peer MUST close the connection.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
* @source http://www.bittorrent.org/beps/bep_0006.html
|
||||
*/
|
||||
public class AllowedFastMessage implements PeerWireWritableMessage,
|
||||
PeerWireReadableMessage {
|
||||
public static final byte MESSAGE_ID = 0x0D;
|
||||
|
||||
/**
|
||||
* The pieces indexes
|
||||
*/
|
||||
private int[] pieces;
|
||||
|
||||
public AllowedFastMessage(int... pieces) {
|
||||
this.pieces = pieces;
|
||||
}
|
||||
|
||||
public AllowedFastMessage() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ChannelBuffer buffer) throws IOException {
|
||||
this.pieces = new int[buffer.readableBytes() / 4];
|
||||
for (int i = 0; i < pieces.length; i++) {
|
||||
pieces[i] = buffer.readInt();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelBuffer buffer) throws IOException {
|
||||
buffer.writeByte(MESSAGE_ID);
|
||||
for (final int piece : pieces) {
|
||||
buffer.writeInt(piece);
|
||||
}
|
||||
}
|
||||
|
||||
public int[] getPieces() {
|
||||
return pieces;
|
||||
}
|
||||
|
||||
public void setPieces(int[] pieces) {
|
||||
this.pieces = pieces;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AllowedFastMessage [pieces="
|
||||
+ (pieces != null ? Arrays.toString(pieces) : null) + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.message.fast;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* HaveAll: [len=0x0001][id=0x0E]
|
||||
* </pre>
|
||||
*
|
||||
* Have All and Have None specify that the message sender has all or none of the
|
||||
* pieces respectively. When present, Have All or Have None replace the Have
|
||||
* Bitfield. Exactly one of Have All, Have None, or Have Bitfield MUST appear
|
||||
* and only immediately after the handshake. The reason for these messages is to
|
||||
* save bandwidth. Also slightly to remove the idiosyncrasy of sending no
|
||||
* message when a peer has no pieces.
|
||||
* <p>
|
||||
* When the fast extension is disabled, if a peer receives Have All or Have None
|
||||
* then the peer MUST close the connection.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
* @source http://www.bittorrent.org/beps/bep_0006.html
|
||||
*/
|
||||
public class HaveAllMessage implements PeerWireWritableMessage,
|
||||
PeerWireReadableMessage {
|
||||
public static final byte MESSAGE_ID = 0x0E;
|
||||
|
||||
@Override
|
||||
public void read(ChannelBuffer buffer) throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelBuffer buffer) throws IOException {
|
||||
buffer.writeByte(MESSAGE_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HaveAllMessage []";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.message.fast;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* HaveNone: [len=0x0001][id=0x0F]
|
||||
* </pre>
|
||||
*
|
||||
* Have All and Have None specify that the message sender has all or none of the
|
||||
* pieces respectively. When present, Have All or Have None replace the Have
|
||||
* Bitfield. Exactly one of Have All, Have None, or Have Bitfield MUST appear
|
||||
* and only immediately after the handshake. The reason for these messages is to
|
||||
* save bandwidth. Also slightly to remove the idiosyncrasy of sending no
|
||||
* message when a peer has no pieces.
|
||||
* <p>
|
||||
* When the fast extension is disabled, if a peer receives Have All or Have None
|
||||
* then the peer MUST close the connection.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
* @source http://www.bittorrent.org/beps/bep_0006.html
|
||||
*/
|
||||
public class HaveNoneMessage implements PeerWireWritableMessage,
|
||||
PeerWireReadableMessage {
|
||||
public static final byte MESSAGE_ID = 0x0F;
|
||||
|
||||
@Override
|
||||
public void read(ChannelBuffer buffer) throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelBuffer buffer) throws IOException {
|
||||
buffer.writeByte(MESSAGE_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HaveNoneMessage []";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.message.fast;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* Reject: [len=0013][id=0x10][index][begin][length]
|
||||
* </pre>
|
||||
*
|
||||
* Reject Request notifies a requesting peer that its request will not be
|
||||
* satisfied.
|
||||
*
|
||||
* If the fast extension is disabled and a peer receives a reject request then
|
||||
* the peer MUST close the connection.
|
||||
* <p>
|
||||
* When the fast extension is enabled:
|
||||
* <ul>
|
||||
* <li>If a peer receives a reject for a request that was never sent then the
|
||||
* peer SHOULD close the connection.</li>
|
||||
* <li>If a peer sends a choke, it MUST reject all requests from the peer to
|
||||
* whom the choke was sent except it SHOULD NOT reject requests for pieces that
|
||||
* are in the allowed fast set. A peer SHOULD choke first and then reject
|
||||
* requests so that the peer receiving the choke does not re-request the pieces.
|
||||
* </li>
|
||||
* <li>If a peer receives a request from a peer its choking, the peer receiving
|
||||
* the request SHOULD send a reject unless the piece is in the allowed fast set.
|
||||
* </li>
|
||||
* <li>If a peer receives an excessive number of requests from a peer it is
|
||||
* choking, the peer receiving the requests MAY close the connection rather than
|
||||
* reject the request. However, consider that it can take several seconds for
|
||||
* buffers to drain and messages to propagate once a peer is choked.
|
||||
* </ul>
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
* @source http://www.bittorrent.org/beps/bep_0006.html
|
||||
*/
|
||||
public class RejectMessage implements PeerWireWritableMessage,
|
||||
PeerWireReadableMessage {
|
||||
public static final byte MESSAGE_ID = 0x06;
|
||||
|
||||
/**
|
||||
* The piece index
|
||||
*/
|
||||
private int index;
|
||||
/**
|
||||
* The piece start offset
|
||||
*/
|
||||
private int start;
|
||||
/**
|
||||
* The part length
|
||||
*/
|
||||
private int length;
|
||||
|
||||
public RejectMessage(int index, int start, int length) {
|
||||
this.index = index;
|
||||
this.start = start;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public RejectMessage() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ChannelBuffer buffer) throws IOException {
|
||||
this.index = buffer.readInt();
|
||||
this.start = buffer.readInt();
|
||||
this.length = buffer.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelBuffer buffer) throws IOException {
|
||||
buffer.writeByte(MESSAGE_ID);
|
||||
buffer.writeInt(index);
|
||||
buffer.writeInt(start);
|
||||
buffer.writeInt(length);
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public void setStart(int start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public void setLength(int length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RejectMessage [index=" + index + ", start=" + start
|
||||
+ ", length=" + length + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire.message.fast;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
|
||||
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* SuggestPiece: [len=0x0005][id=0x0D][piece index(int)]
|
||||
* </pre>
|
||||
*
|
||||
* Suggest Piece is an advisory message meaning
|
||||
* "you might like to download this piece." The intended usage is for
|
||||
* 'super-seeding' without throughput reduction, to avoid redundant downloads,
|
||||
* and so that a seed which is disk I/O bound can upload continguous or
|
||||
* identical pieces to avoid excessive disk seeks. In all cases, the seed SHOULD
|
||||
* operate to maintain a roughly equal number of copies of each piece in the
|
||||
* network. A peer MAY send more than one suggest piece message at any given
|
||||
* time. A peer receiving multiple suggest piece messages MAY interpret this as
|
||||
* meaning that all of the suggested pieces are equally appropriate.
|
||||
* <p>
|
||||
* When the fast extension is disabled, if a peer receives a Suggest Piece
|
||||
* message, the peer MUST close the connection.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
* @source http://www.bittorrent.org/beps/bep_0006.html
|
||||
*/
|
||||
public class SuggestPieceMessage implements PeerWireWritableMessage,
|
||||
PeerWireReadableMessage {
|
||||
public static final byte MESSAGE_ID = 0x0D;
|
||||
|
||||
/**
|
||||
* The new obtained piece index
|
||||
*/
|
||||
private int piece;
|
||||
|
||||
public SuggestPieceMessage(int piece) {
|
||||
this.piece = piece;
|
||||
}
|
||||
|
||||
public SuggestPieceMessage() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ChannelBuffer buffer) throws IOException {
|
||||
this.piece = buffer.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelBuffer buffer) throws IOException {
|
||||
buffer.writeByte(MESSAGE_ID);
|
||||
buffer.writeInt(piece);
|
||||
}
|
||||
|
||||
public int getPiece() {
|
||||
return piece;
|
||||
}
|
||||
|
||||
public void setPiece(int piece) {
|
||||
this.piece = piece;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SuggestPieceMessage [piece=" + piece + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.tracker;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import net.torrent.protocol.tracker.message.AnnounceMessage;
|
||||
import net.torrent.protocol.tracker.message.AnnounceMessage.Event;
|
||||
import net.torrent.torrent.Torrent;
|
||||
import net.torrent.torrent.TorrentTracker;
|
||||
|
||||
import org.jboss.netty.bootstrap.ClientBootstrap;
|
||||
import org.jboss.netty.channel.ChannelFuture;
|
||||
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
|
||||
|
||||
public class HttpTorrentTrackerAnnouncer {
|
||||
private final ClientBootstrap client = new ClientBootstrap(
|
||||
new NioClientSocketChannelFactory(Executors.newCachedThreadPool(),
|
||||
Executors.newCachedThreadPool()));
|
||||
|
||||
public HttpTorrentTrackerAnnouncer() {
|
||||
client.setPipelineFactory(new HttpTorrentTrackerPipelineFactory());
|
||||
}
|
||||
|
||||
public boolean announce(Torrent torrent, TorrentTracker tracker)
|
||||
throws UnsupportedEncodingException, MalformedURLException {
|
||||
final AnnounceMessage announceMessage = new AnnounceMessage(tracker
|
||||
.getURL().toString(), torrent.getInfoHash().toByteArray(),
|
||||
torrent.getInfoHash().toByteArray(), 10, 0, 0, 0, true, false,
|
||||
Event.STARTED);
|
||||
int port = (tracker.getURL().getPort() > 0 ? tracker.getURL().getPort()
|
||||
: tracker.getURL().getDefaultPort());
|
||||
final ChannelFuture chFuture = client.connect(new InetSocketAddress(
|
||||
tracker.getURL().getHost(), port));
|
||||
chFuture.awaitUninterruptibly(60, TimeUnit.SECONDS);
|
||||
if (!chFuture.isSuccess())
|
||||
return false;
|
||||
return chFuture.getChannel().write(announceMessage.write())
|
||||
.awaitUninterruptibly(60, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.tracker;
|
||||
|
||||
import static org.jboss.netty.channel.Channels.pipeline;
|
||||
import net.torrent.protocol.tracker.codec.ISO8859HttpRequestEncoder;
|
||||
import net.torrent.protocol.tracker.codec.TorrentTrackerBDecoder;
|
||||
import net.torrent.protocol.tracker.codec.TorrentTrackerDecoder;
|
||||
import net.torrent.protocol.tracker.codec.TorrentTrackerEncoder;
|
||||
|
||||
import org.jboss.netty.channel.ChannelPipeline;
|
||||
import org.jboss.netty.channel.ChannelPipelineFactory;
|
||||
import org.jboss.netty.handler.codec.http.HttpResponseDecoder;
|
||||
import org.jboss.netty.handler.logging.LoggingHandler;
|
||||
import org.jboss.netty.logging.InternalLogLevel;
|
||||
|
||||
public class HttpTorrentTrackerPipelineFactory implements
|
||||
ChannelPipelineFactory {
|
||||
@Override
|
||||
public ChannelPipeline getPipeline() throws Exception {
|
||||
final ChannelPipeline pipeline = pipeline();
|
||||
|
||||
// log binary data input and object output
|
||||
// pipeline.addFirst("logging", new LoggingHandler());
|
||||
|
||||
pipeline.addLast("tracker.encoder", new TorrentTrackerEncoder());
|
||||
pipeline.addLast("encoder", new ISO8859HttpRequestEncoder());
|
||||
|
||||
pipeline.addLast("interceptor", new Interceptor());
|
||||
|
||||
pipeline.addLast("decoder", new HttpResponseDecoder());
|
||||
pipeline.addLast("bdecoder", new TorrentTrackerBDecoder());
|
||||
pipeline.addLast("tracker.decoder", new TorrentTrackerDecoder());
|
||||
|
||||
pipeline.addLast("handler", new TrackerHandler());
|
||||
|
||||
pipeline.addLast("logging", new LoggingHandler(InternalLogLevel.WARN));
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
}
|
||||
36
src/main/java/net/torrent/protocol/tracker/Interceptor.java
Normal file
36
src/main/java/net/torrent/protocol/tracker/Interceptor.java
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.tracker;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
|
||||
|
||||
public class Interceptor extends OneToOneEncoder {
|
||||
@Override
|
||||
protected Object encode(ChannelHandlerContext ctx, Channel channel,
|
||||
Object msg) throws Exception {
|
||||
System.out.println(">" + msg);
|
||||
if (!(msg instanceof ChannelBuffer))
|
||||
return msg;
|
||||
|
||||
final ChannelBuffer buffer = (ChannelBuffer) msg;
|
||||
System.out.println(new String(buffer.array()));
|
||||
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.tracker;
|
||||
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.channel.MessageEvent;
|
||||
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
|
||||
|
||||
public class TrackerHandler extends SimpleChannelUpstreamHandler {
|
||||
@Override
|
||||
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
|
||||
throws Exception {
|
||||
System.out.println(e.getMessage());
|
||||
super.messageReceived(ctx, e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.tracker.codec;
|
||||
|
||||
import org.jboss.netty.buffer.ChannelBuffer;
|
||||
import org.jboss.netty.handler.codec.http.HttpMessage;
|
||||
import org.jboss.netty.handler.codec.http.HttpRequest;
|
||||
import org.jboss.netty.handler.codec.http.HttpRequestEncoder;
|
||||
|
||||
public class ISO8859HttpRequestEncoder extends HttpRequestEncoder {
|
||||
static final byte SP = 32;
|
||||
static final byte CR = 13;
|
||||
static final byte LF = 10;
|
||||
|
||||
@Override
|
||||
protected void encodeInitialLine(ChannelBuffer buf, HttpMessage message)
|
||||
throws Exception {
|
||||
HttpRequest request = (HttpRequest) message;
|
||||
buf.writeBytes(request.getMethod().toString().getBytes());
|
||||
buf.writeByte(SP);
|
||||
buf.writeBytes(request.getUri().getBytes());
|
||||
buf.writeByte(SP);
|
||||
buf.writeBytes(request.getProtocolVersion().toString().getBytes());
|
||||
buf.writeByte(CR);
|
||||
buf.writeByte(LF);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.tracker.codec;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
import net.torrent.util.bencoding.BEncodedInputStream;
|
||||
import net.torrent.util.bencoding.BMap;
|
||||
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.handler.codec.http.HttpChunk;
|
||||
import org.jboss.netty.handler.codec.oneone.OneToOneDecoder;
|
||||
|
||||
public class TorrentTrackerBDecoder extends OneToOneDecoder {
|
||||
@Override
|
||||
protected Object decode(ChannelHandlerContext ctx, Channel channel,
|
||||
Object msg) throws Exception {
|
||||
if (!(msg instanceof HttpChunk))
|
||||
return msg;
|
||||
final HttpChunk message = (HttpChunk) msg;
|
||||
|
||||
System.out.println(new String(message.getContent().array()));
|
||||
if (message.getContent().readableBytes() <= 0)
|
||||
return null;
|
||||
|
||||
final BEncodedInputStream in = new BEncodedInputStream(
|
||||
new ByteArrayInputStream(message.getContent().array()));
|
||||
final BMap map = (BMap) in.readElement();
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.tracker.codec;
|
||||
|
||||
import net.torrent.protocol.tracker.message.PeerListMessage;
|
||||
import net.torrent.util.bencoding.BMap;
|
||||
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.handler.codec.oneone.OneToOneDecoder;
|
||||
|
||||
public class TorrentTrackerDecoder extends OneToOneDecoder {
|
||||
@Override
|
||||
protected Object decode(ChannelHandlerContext ctx, Channel channel,
|
||||
Object msg) throws Exception {
|
||||
if (!(msg instanceof BMap))
|
||||
return msg;
|
||||
|
||||
final BMap bencodedData = (BMap) msg;
|
||||
final TorrentTrackerResponseMessage message = new PeerListMessage();
|
||||
message.read(bencodedData);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.tracker.codec;
|
||||
|
||||
import org.jboss.netty.channel.Channel;
|
||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
|
||||
|
||||
public class TorrentTrackerEncoder extends OneToOneEncoder {
|
||||
@Override
|
||||
protected Object encode(ChannelHandlerContext ctx, Channel channel,
|
||||
Object msg) throws Exception {
|
||||
if (!(msg instanceof TorrentTrackerRequestMessage))
|
||||
return msg;
|
||||
|
||||
final TorrentTrackerRequestMessage message = (TorrentTrackerRequestMessage) msg;
|
||||
return message.write();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.tracker.codec;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
import org.jboss.netty.handler.codec.http.HttpRequest;
|
||||
|
||||
public interface TorrentTrackerRequestMessage {
|
||||
HttpRequest write() throws UnsupportedEncodingException,
|
||||
MalformedURLException;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.tracker.codec;
|
||||
|
||||
import net.torrent.util.bencoding.BMap;
|
||||
import net.torrent.util.bencoding.BTypeException;
|
||||
|
||||
public interface TorrentTrackerResponseMessage {
|
||||
void read(BMap map) throws BTypeException;
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.tracker.message;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
import net.torrent.protocol.tracker.codec.TorrentTrackerRequestMessage;
|
||||
|
||||
import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
|
||||
import org.jboss.netty.handler.codec.http.HttpMethod;
|
||||
import org.jboss.netty.handler.codec.http.HttpRequest;
|
||||
import org.jboss.netty.handler.codec.http.HttpVersion;
|
||||
|
||||
public class AnnounceMessage implements TorrentTrackerRequestMessage {
|
||||
private String url;
|
||||
private byte[] infoHash;
|
||||
private byte[] peerId;
|
||||
private int port;
|
||||
|
||||
private long uploaded;
|
||||
private long downloaded;
|
||||
private long left;
|
||||
|
||||
private boolean compact;
|
||||
private boolean noPeerId;
|
||||
|
||||
private Event event;
|
||||
|
||||
public enum Event {
|
||||
UPDATE(null), STARTED("started"), STOPPED("stopped"), COMPLETED(
|
||||
"completed");
|
||||
private String urlArg;
|
||||
|
||||
Event(String arg) {
|
||||
urlArg = arg;
|
||||
}
|
||||
|
||||
public String urlArg() {
|
||||
return urlArg;
|
||||
}
|
||||
}
|
||||
|
||||
private String ip;
|
||||
private Integer numWant = 10;
|
||||
private String key = "avtbyit8";
|
||||
private String trackerId;
|
||||
|
||||
public AnnounceMessage(String url, byte[] infoHash, byte[] peerId,
|
||||
int port, long uploaded, long downloaded, long left,
|
||||
boolean compact, boolean noPeerId, Event event, String ip,
|
||||
Integer numWant, String key, String trackerId) {
|
||||
this.url = url;
|
||||
this.infoHash = infoHash;
|
||||
this.peerId = peerId;
|
||||
this.port = port;
|
||||
this.uploaded = uploaded;
|
||||
this.downloaded = downloaded;
|
||||
this.left = left;
|
||||
this.compact = compact;
|
||||
this.noPeerId = noPeerId;
|
||||
this.event = event;
|
||||
this.ip = ip;
|
||||
this.numWant = numWant;
|
||||
this.key = key;
|
||||
this.trackerId = trackerId;
|
||||
}
|
||||
|
||||
public AnnounceMessage(String url, byte[] infoHash, byte[] peerId,
|
||||
int port, long uploaded, long downloaded, long left,
|
||||
boolean compact, boolean noPeerId, Event event) {
|
||||
this.url = url;
|
||||
this.infoHash = infoHash;
|
||||
this.peerId = peerId;
|
||||
this.port = port;
|
||||
this.uploaded = uploaded;
|
||||
this.downloaded = downloaded;
|
||||
this.left = left;
|
||||
this.compact = compact;
|
||||
this.noPeerId = noPeerId;
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpRequest write() throws UnsupportedEncodingException,
|
||||
MalformedURLException {
|
||||
StringBuilder builder = new StringBuilder(url);
|
||||
if (url.contains("?")) {
|
||||
builder.append("&");
|
||||
} else {
|
||||
builder.append("?");
|
||||
}
|
||||
|
||||
addLowerCase(builder, "info_hash", infoHash);
|
||||
add(builder, "peer_id", "-TR2130-g4mvcv2iyehf");
|
||||
add(builder, "port", port);
|
||||
|
||||
add(builder, "uploaded", Long.toString(uploaded));
|
||||
add(builder, "downloaded", Long.toString(downloaded));
|
||||
add(builder, "left", Long.toString(left));
|
||||
|
||||
add(builder, "compact", compact);
|
||||
add(builder, "no_peer_id", noPeerId);
|
||||
if (event != Event.UPDATE)
|
||||
add(builder, "event", event.urlArg());
|
||||
|
||||
add(builder, "ip", ip);
|
||||
add(builder, "numwant", numWant);
|
||||
add(builder, "key", key);
|
||||
add(builder, "trackerid", trackerId);
|
||||
|
||||
builder.setLength(builder.length() - 1);// trim last character it is an
|
||||
// unnecessary &.
|
||||
final URL url = new URL(builder.toString());
|
||||
|
||||
return new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
|
||||
url.getPath() + "?" + url.getQuery());
|
||||
}
|
||||
|
||||
private void add(StringBuilder builder, String key, String value)
|
||||
throws UnsupportedEncodingException {
|
||||
if (value == null)
|
||||
return;
|
||||
builder.append(key + "=" + value).append("&");
|
||||
}
|
||||
|
||||
private void add(StringBuilder builder, String key, byte[] value)
|
||||
throws UnsupportedEncodingException {
|
||||
if (value == null)
|
||||
return;
|
||||
add(builder, key, URLEncoder.encode(new String(value), "ISO-8859-1")
|
||||
.replaceAll("\\+", "%20"));
|
||||
}
|
||||
|
||||
private void addLowerCase(StringBuilder builder, String key, byte[] value)
|
||||
throws UnsupportedEncodingException {
|
||||
if (value == null)
|
||||
return;
|
||||
add(builder, key,
|
||||
URLEncoder
|
||||
.encode(new String(value, "ISO-8859-1"), "ISO-8859-1")
|
||||
.replaceAll("\\+", "%20").toLowerCase());
|
||||
}
|
||||
|
||||
private void add(StringBuilder builder, String key, Number value)
|
||||
throws UnsupportedEncodingException {
|
||||
if (value == null)
|
||||
return;
|
||||
add(builder, key, value.toString());
|
||||
}
|
||||
|
||||
private void add(StringBuilder builder, String key, Boolean value)
|
||||
throws UnsupportedEncodingException {
|
||||
if (value == null)
|
||||
return;
|
||||
add(builder, key, (value ? "1" : "0"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.tracker.message;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.torrent.protocol.tracker.codec.TorrentTrackerResponseMessage;
|
||||
import net.torrent.util.bencoding.BList;
|
||||
import net.torrent.util.bencoding.BMap;
|
||||
import net.torrent.util.bencoding.BTypeException;
|
||||
|
||||
public class PeerListMessage implements TorrentTrackerResponseMessage {
|
||||
private static final String FAILURE_REASON = "failure reason";
|
||||
private static final String WARNING_MESSAGE = "warning message";
|
||||
private static final String INTERVAL = "interval";
|
||||
private static final String MIN_INTERVAL = "min interval";
|
||||
private static final String TRACKER_ID = "tracker id";
|
||||
private static final String COMPLETE = "complete";
|
||||
private static final String INCOMPLETE = "incomplete";
|
||||
private static final String PEERS = "peers";
|
||||
|
||||
private String failureReason;
|
||||
private String warningMessage;
|
||||
|
||||
private Integer interval;
|
||||
private Integer minInterval;
|
||||
private byte[] trackerID;
|
||||
private Integer complete;
|
||||
private Integer incomplete;
|
||||
private List<PeerInfo> peerList;
|
||||
private boolean compact;
|
||||
|
||||
@Override
|
||||
public void read(BMap response) throws BTypeException {
|
||||
if (response == null)
|
||||
throw new InvalidParameterException("Tracker response is null");
|
||||
if (response.containsKey(FAILURE_REASON)) {
|
||||
failureReason = response.getString(FAILURE_REASON);
|
||||
return;
|
||||
}
|
||||
this.interval = response.getInteger(INTERVAL);
|
||||
|
||||
warningMessage = response.getString(WARNING_MESSAGE);
|
||||
interval = response.getInteger(INTERVAL);
|
||||
minInterval = response.getInteger(MIN_INTERVAL);
|
||||
trackerID = (byte[]) response.get(TRACKER_ID);
|
||||
|
||||
complete = response.getInteger(COMPLETE);
|
||||
incomplete = response.getInteger(INCOMPLETE);
|
||||
|
||||
Object peers = response.get(PEERS);
|
||||
if (peers instanceof BList) {
|
||||
BList list = (BList) peers;
|
||||
peerList = new ArrayList<PeerInfo>(list.size());
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
peerList.add(PeerInfo.fromBMap(list.getMap(i)));
|
||||
}
|
||||
} else {
|
||||
byte[] list = (byte[]) peers;
|
||||
if (list.length % 6 != 0)
|
||||
throw new IllegalStateException(
|
||||
"Peerlist not in format IPv4:port!");
|
||||
peerList = new ArrayList<PeerInfo>(list.length / 6);
|
||||
for (int i = 0; i < list.length; i += 6) {
|
||||
peerList.add(PeerInfo.fromRawIP(list, i, 6));
|
||||
}
|
||||
compact = true;
|
||||
}
|
||||
}
|
||||
|
||||
public String getFailureReason() {
|
||||
return failureReason;
|
||||
}
|
||||
|
||||
public String getWarningMessage() {
|
||||
return warningMessage;
|
||||
}
|
||||
|
||||
public Integer getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
public Integer getMinInterval() {
|
||||
return minInterval;
|
||||
}
|
||||
|
||||
public byte[] getTrackerID() {
|
||||
return trackerID;
|
||||
}
|
||||
|
||||
public Integer getComplete() {
|
||||
return complete;
|
||||
}
|
||||
|
||||
public Integer getIncomplete() {
|
||||
return incomplete;
|
||||
}
|
||||
|
||||
public List<PeerInfo> getPeerList() {
|
||||
return peerList;
|
||||
}
|
||||
|
||||
public boolean isCompact() {
|
||||
return compact;
|
||||
}
|
||||
|
||||
public static class PeerInfo {
|
||||
private byte[] peerId;
|
||||
private String ip;
|
||||
private int port;
|
||||
|
||||
public PeerInfo(byte[] peerId, String ip, int port) {
|
||||
this.peerId = peerId;
|
||||
this.ip = ip;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public byte[] getPeerId() {
|
||||
return peerId;
|
||||
}
|
||||
|
||||
public void setPeerId(byte[] peerId) {
|
||||
this.peerId = peerId;
|
||||
}
|
||||
|
||||
public String getIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
public void setIp(String ip) {
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public static PeerInfo fromBMap(BMap map) throws BTypeException {
|
||||
return new PeerInfo((byte[]) map.get("peer id"),
|
||||
map.getString("peer ip"), map.getInteger("port"));
|
||||
}
|
||||
|
||||
public static PeerInfo fromRawIP(byte[] list, int i, int j) {
|
||||
byte[] addr = new byte[4];
|
||||
System.arraycopy(list, i, addr, 0, 4);
|
||||
InetAddress address;
|
||||
try {
|
||||
address = InetAddress.getByAddress(addr);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
InetSocketAddress socketAddress = new InetSocketAddress(
|
||||
address.getHostAddress(), ((list[i + j - 2] & 0xFF) << 8)
|
||||
+ (list[i + j - 1] & 0xFF));
|
||||
return new PeerInfo(null, socketAddress.getHostName(),
|
||||
socketAddress.getPort());
|
||||
}
|
||||
}
|
||||
}
|
||||
362
src/main/java/net/torrent/torrent/Torrent.java
Normal file
362
src/main/java/net/torrent/torrent/Torrent.java
Normal file
@@ -0,0 +1,362 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.torrent;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import net.torrent.torrent.TorrentInfo.TorrentType;
|
||||
import net.torrent.util.bencoding.BEncodedInputStream;
|
||||
import net.torrent.util.bencoding.BList;
|
||||
import net.torrent.util.bencoding.BMap;
|
||||
import net.torrent.util.bencoding.BTypeException;
|
||||
|
||||
/**
|
||||
* An class representing an .torrent file.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class Torrent {
|
||||
/**
|
||||
* Dictionary key for <tt>announce</tt>
|
||||
*/
|
||||
private static final String ANNOUNCE = "announce";
|
||||
/**
|
||||
* Dictionary key for <tt>announce-list</tt>
|
||||
*/
|
||||
private static final String ANNOUNCE_LIST = "announce-list";
|
||||
/**
|
||||
* Dictionary key for <tt>info</tt>
|
||||
*/
|
||||
private static final String INFO = "info";
|
||||
/**
|
||||
* Dictionary key for <tt>comment</tt>
|
||||
*/
|
||||
private static final String COMMENT = "comment";
|
||||
/**
|
||||
* Dictionary key for <tt>creation date</tt>
|
||||
*/
|
||||
private static final String CREATION_DATE = "creation date";
|
||||
/**
|
||||
* Dictionary key for <tt>created by</tt>
|
||||
*/
|
||||
private static final String CREATED_BY = "created by";
|
||||
|
||||
/**
|
||||
* The torrent information
|
||||
*/
|
||||
private final TorrentInfo info;
|
||||
/**
|
||||
* The list of trackers
|
||||
*/
|
||||
private final Set<TorrentTracker> trackers = new HashSet<TorrentTracker>();
|
||||
|
||||
/**
|
||||
* The torrent comment
|
||||
*/
|
||||
private final String comment;
|
||||
/**
|
||||
* The creation date of the torrent
|
||||
*/
|
||||
private final Date creationDate;
|
||||
/**
|
||||
* The torrent creator
|
||||
*/
|
||||
private final String createdBy;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param info
|
||||
* the torrent information
|
||||
* @param comment
|
||||
* the comment
|
||||
* @param creationDate
|
||||
* the date of creation
|
||||
* @param createdBy
|
||||
* the creator
|
||||
* @param trackers
|
||||
* the list of trackers
|
||||
*/
|
||||
public Torrent(TorrentInfo info, String comment, Date creationDate,
|
||||
String createdBy, TorrentTracker... trackers) {
|
||||
if (info == null)
|
||||
throw new InvalidParameterException("Torrent info is null");
|
||||
if (trackers == null || trackers.length == 0)
|
||||
throw new InvalidParameterException(
|
||||
"Trackers null or empty: DHT is not yet implemented, a tracker is needed.");
|
||||
|
||||
Collections.addAll(this.trackers, trackers);
|
||||
|
||||
this.info = info;
|
||||
this.comment = comment;
|
||||
this.creationDate = creationDate;
|
||||
this.createdBy = createdBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param info
|
||||
* the torrent information
|
||||
* @param trackers
|
||||
* the list o trackers
|
||||
*/
|
||||
public Torrent(TorrentInfo info, TorrentTracker... trackers) {
|
||||
this(info, null, null, null, trackers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent bencoded map
|
||||
* @throws BTypeException
|
||||
* @throws URISyntaxException
|
||||
*/
|
||||
protected Torrent(BMap torrent) throws BTypeException, URISyntaxException {
|
||||
this.comment = torrent.getString(COMMENT);
|
||||
|
||||
final Long time = torrent.getLong(CREATION_DATE);
|
||||
this.creationDate = (time != null ? new Date(time * 1000) : null);
|
||||
|
||||
this.createdBy = torrent.getString(CREATED_BY);
|
||||
|
||||
BList aLists = torrent.getList(ANNOUNCE_LIST);
|
||||
if (aLists != null) {
|
||||
for (int i = 0; i < aLists.size(); i++) {
|
||||
BList list = aLists.getList(i);
|
||||
URI primary = null;
|
||||
URI[] backup = new URI[(list.size() > 1 ? list.size() - 1 : 0)];
|
||||
int backups = 0;
|
||||
for (int j = 0; j < list.size(); j++) {
|
||||
if (j == 0) {
|
||||
final String url = list.getString(j);
|
||||
if (!url.startsWith("http"))
|
||||
break;
|
||||
primary = new URI(url);
|
||||
} else {
|
||||
final String url = list.getString(j);
|
||||
if (url.startsWith("http"))
|
||||
backup[backups++] = new URI(url);
|
||||
}
|
||||
}
|
||||
this.trackers.add(new TorrentTracker(this, primary, Arrays
|
||||
.copyOf(backup, backups)));
|
||||
}
|
||||
} else {
|
||||
if (torrent.containsKey(ANNOUNCE)) {
|
||||
this.trackers.add(new TorrentTracker(this, new URI(torrent
|
||||
.getString(ANNOUNCE))));
|
||||
}
|
||||
}
|
||||
|
||||
info = new TorrentInfo(this, torrent.getMap(INFO));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get torrent information
|
||||
*
|
||||
* @return the torrent information
|
||||
*/
|
||||
public TorrentInfo getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of trackers
|
||||
*
|
||||
* @return the trackers
|
||||
*/
|
||||
public Set<TorrentTracker> getTrackers() {
|
||||
return Collections.unmodifiableSet(trackers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the torrent comment
|
||||
*
|
||||
* @return the torrent comment
|
||||
*/
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the creation adte
|
||||
*
|
||||
* @return the creation date
|
||||
*/
|
||||
public Date getCreationDate() {
|
||||
return creationDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the torrent creator
|
||||
*
|
||||
* @return the torrent creator
|
||||
*/
|
||||
public String getCreatedBy() {
|
||||
return createdBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the torrent info hash
|
||||
*
|
||||
* @return the torrent info hash
|
||||
*/
|
||||
public TorrentHash getInfoHash() {
|
||||
return info.getInfoHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of files
|
||||
*
|
||||
* @return the file list
|
||||
*/
|
||||
public List<TorrentFile> getFiles() {
|
||||
return info.getFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of pieces
|
||||
*
|
||||
* @return the list of pieces
|
||||
*/
|
||||
public TorrentPiece[] getPieces() {
|
||||
return info.getPieces();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the torrent type
|
||||
*
|
||||
* @return the torrent type
|
||||
*/
|
||||
public TorrentType getType() {
|
||||
return info.getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the piece by index
|
||||
*
|
||||
* @param index
|
||||
* the piece index
|
||||
* @return the found piece
|
||||
*/
|
||||
public TorrentPiece getPiece(int index) {
|
||||
return info.getPiece(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an certain part
|
||||
*
|
||||
* @param piece
|
||||
* the piece
|
||||
* @param start
|
||||
* the part start
|
||||
* @param len
|
||||
* the part length
|
||||
* @return the found part
|
||||
*/
|
||||
public TorrentPart getPart(TorrentPiece piece, int start, int len) {
|
||||
return info.getPart(piece, start, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an certain part
|
||||
*
|
||||
* @param piece
|
||||
* the piece index
|
||||
* @param start
|
||||
* the part start
|
||||
* @param len
|
||||
* the part length
|
||||
* @return the found part
|
||||
*/
|
||||
public TorrentPart getPart(int index, int start, int len) {
|
||||
return info.getPart(index, start, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result
|
||||
+ ((info == null) ? 0 : info.getInfoHash().hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Torrent other = (Torrent) obj;
|
||||
if (info == null) {
|
||||
if (other.info != null)
|
||||
return false;
|
||||
} else if (!info.equals(other.info))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an torrent from an {@link InputStream}
|
||||
*
|
||||
* @param in
|
||||
* the {@link InputStream}
|
||||
* @return the loaded {@link Torrent} instance
|
||||
* @throws IOException
|
||||
* @throws URISyntaxException
|
||||
*/
|
||||
public static Torrent load(InputStream in) throws IOException,
|
||||
URISyntaxException {
|
||||
if (in == null)
|
||||
throw new InvalidParameterException("InputStream cannot be null");
|
||||
|
||||
final BMap map = (BMap) new BEncodedInputStream(in).readElement();
|
||||
return new Torrent(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an torrent from an {@link File}
|
||||
*
|
||||
* @param file
|
||||
* the {@link File}
|
||||
* @return the loaded {@link Torrent} instance
|
||||
* @throws IOException
|
||||
* @throws URISyntaxException
|
||||
*/
|
||||
public static Torrent load(File file) throws IOException,
|
||||
URISyntaxException {
|
||||
if (file == null)
|
||||
throw new InvalidParameterException("File cannot be null");
|
||||
return load(new FileInputStream(file));
|
||||
}
|
||||
}
|
||||
172
src/main/java/net/torrent/torrent/TorrentFile.java
Normal file
172
src/main/java/net/torrent/torrent/TorrentFile.java
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.torrent;
|
||||
|
||||
import net.torrent.util.Range;
|
||||
|
||||
/**
|
||||
* An file inside an {@link Torrent torrent} file
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class TorrentFile {
|
||||
/**
|
||||
* The torrent information
|
||||
*/
|
||||
private final TorrentInfo info;
|
||||
/**
|
||||
* The filename
|
||||
*/
|
||||
private final String name;
|
||||
/**
|
||||
* The file length
|
||||
*/
|
||||
private final long length;
|
||||
/**
|
||||
* The file offset
|
||||
*/
|
||||
private final long offset;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param info
|
||||
* the torrent information
|
||||
* @param name
|
||||
* the filename
|
||||
* @param length
|
||||
* the file length
|
||||
* @param offset
|
||||
* the file offset
|
||||
*/
|
||||
public TorrentFile(TorrentInfo info, String name, long length, long offset) {
|
||||
this.info = info;
|
||||
this.name = name;
|
||||
this.length = length;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the torrent informatiom
|
||||
*
|
||||
* @return the torrent information
|
||||
*/
|
||||
public TorrentInfo getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filename
|
||||
*
|
||||
* @return the filename
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filesize
|
||||
*
|
||||
* @return the filesize
|
||||
*/
|
||||
public long getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file offset
|
||||
*
|
||||
* @return the offset
|
||||
*/
|
||||
public long getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base directory name
|
||||
*
|
||||
* @return the base directory name
|
||||
* @see TorrentInfo#getDirectoryName()
|
||||
*/
|
||||
public String getBaseDirectoryName() {
|
||||
return info.getDirectoryName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relative name. This is the same as:
|
||||
* <p>
|
||||
* <code>
|
||||
* String name = getBaseDirectoryName() + "/" + getName();
|
||||
* </code>
|
||||
*
|
||||
* @return the relative name
|
||||
*/
|
||||
public String getRelativePath() {
|
||||
if (getBaseDirectoryName() != null) {
|
||||
return getBaseDirectoryName() + "/" + name;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file as torrent range
|
||||
*
|
||||
* @return the torrent range representing the file
|
||||
*/
|
||||
public Range asTorrentRange() {
|
||||
return Range.getRangeByLength(offset, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file as file range
|
||||
*
|
||||
* @return the file range representing the file
|
||||
*/
|
||||
public Range asFileRange() {
|
||||
return Range.getRangeByLength(0, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + (int) (length ^ (length >>> 32));
|
||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
||||
result = prime * result + (int) (offset ^ (offset >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
TorrentFile other = (TorrentFile) obj;
|
||||
if (length != other.length)
|
||||
return false;
|
||||
if (name == null) {
|
||||
if (other.name != null)
|
||||
return false;
|
||||
} else if (!name.equals(other.name))
|
||||
return false;
|
||||
if (offset != other.offset)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
221
src/main/java/net/torrent/torrent/TorrentHash.java
Normal file
221
src/main/java/net/torrent/torrent/TorrentHash.java
Normal file
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.torrent;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Represents an {@link TorrentPiece} hash.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class TorrentHash {
|
||||
/**
|
||||
* An table containing hexadecimal characters
|
||||
*/
|
||||
private static final char[] HEX_TABLE = { '0', '1', '2', '3', '4', '5',
|
||||
'6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
|
||||
/**
|
||||
* The hash
|
||||
*/
|
||||
private final byte[] hash;
|
||||
/**
|
||||
* The hash type
|
||||
*/
|
||||
private final HashType type;
|
||||
|
||||
/**
|
||||
* The hashes type
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public enum HashType {
|
||||
/**
|
||||
* SHA1 -> 20 bytes
|
||||
*/
|
||||
SHA1(20, "SHA1"),
|
||||
|
||||
/**
|
||||
* MD5 -> 16 bytes
|
||||
*/
|
||||
MD5(16, "MD5");
|
||||
|
||||
/**
|
||||
* The hash length
|
||||
*/
|
||||
private final int length;
|
||||
/**
|
||||
* The digest algorithm
|
||||
*/
|
||||
private final String digestAlgorithm;
|
||||
/**
|
||||
* The {@link MessageDigest} instance
|
||||
*/
|
||||
private MessageDigest digest;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param length
|
||||
* the hash length
|
||||
* @param digestAlgorithm
|
||||
* the algorithm
|
||||
*/
|
||||
HashType(int length, String digestAlgorithm) {
|
||||
this.length = length;
|
||||
this.digestAlgorithm = digestAlgorithm;
|
||||
try {
|
||||
this.digest = MessageDigest.getInstance(digestAlgorithm);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
this.digest = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hash length
|
||||
*
|
||||
* @return the hash length
|
||||
*/
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hash algorithm
|
||||
*
|
||||
* @return the hash algorithm
|
||||
*/
|
||||
public String getDigestAlgorithm() {
|
||||
return digestAlgorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link MessageDigest}
|
||||
*
|
||||
* @return the {@link MessageDigest}
|
||||
*/
|
||||
public MessageDigest getMessageDigest() {
|
||||
return digest;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param hash
|
||||
* the hash
|
||||
* @param type
|
||||
* the hash type
|
||||
*/
|
||||
public TorrentHash(byte[] hash, HashType type) {
|
||||
if (hash == null)
|
||||
throw new IllegalArgumentException("hash is null");
|
||||
if (type == null)
|
||||
throw new IllegalArgumentException("type is null");
|
||||
if (hash.length != type.length)
|
||||
throw new IllegalArgumentException("hash does not have "
|
||||
+ type.length + " bytes");
|
||||
this.hash = Arrays.copyOf(hash, hash.length);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hash as an hexadecimal string
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String asHexString() {
|
||||
StringBuilder b = new StringBuilder(hash.length * 2);
|
||||
for (int i = 0; i < hash.length; i++) {
|
||||
b.append(HEX_TABLE[((hash[i] & 0xff) >> 4)]);
|
||||
b.append(HEX_TABLE[hash[i] & 15]);
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the two hashes
|
||||
*
|
||||
* @param hash
|
||||
* the hash
|
||||
* @return true if are equal
|
||||
*/
|
||||
public boolean compare(byte[] hash) {
|
||||
if (hash == null)
|
||||
throw new InvalidParameterException("Hash is null");
|
||||
return Arrays.equals(this.hash, hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hash byte array
|
||||
*
|
||||
* @return the hash byte array
|
||||
*/
|
||||
public byte[] toByteArray() {
|
||||
return Arrays.copyOf(hash, hash.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hash length
|
||||
*
|
||||
* @return the hash length
|
||||
*/
|
||||
public int getHashLength() {
|
||||
return hash.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hash type
|
||||
*
|
||||
* @return the hash type
|
||||
*/
|
||||
public HashType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.hashCode(hash);
|
||||
result = prime * result + type.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
TorrentHash other = (TorrentHash) obj;
|
||||
if (!Arrays.equals(hash, other.hash))
|
||||
return false;
|
||||
if (type != other.type)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return type + ":" + asHexString();
|
||||
}
|
||||
}
|
||||
198
src/main/java/net/torrent/torrent/TorrentInfo.java
Normal file
198
src/main/java/net/torrent/torrent/TorrentInfo.java
Normal file
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.torrent;
|
||||
|
||||
import java.io.File;
|
||||
import java.math.BigInteger;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.torrent.torrent.TorrentHash.HashType;
|
||||
import net.torrent.util.bencoding.BEncodedOutputStream;
|
||||
import net.torrent.util.bencoding.BList;
|
||||
import net.torrent.util.bencoding.BMap;
|
||||
import net.torrent.util.bencoding.BTypeException;
|
||||
|
||||
public class TorrentInfo {
|
||||
private static final String PIECE_LENGTH = "piece length";
|
||||
private static final String PIECES = "pieces";
|
||||
private static final String NAME = "name";
|
||||
private static final String LENGTH = "length";
|
||||
private static final String FILES = "files";
|
||||
private static final String PATH = "path";
|
||||
private static final String PRIVATE = "private";
|
||||
|
||||
private final Torrent torrent;
|
||||
private final TorrentHash infohash;
|
||||
|
||||
private final List<TorrentFile> files = new ArrayList<TorrentFile>();
|
||||
private final TorrentPiece[] pieces;
|
||||
|
||||
private long size;
|
||||
private final int pieceLength;
|
||||
private final boolean privTracker;
|
||||
private final TorrentType type;
|
||||
|
||||
public enum TorrentType {
|
||||
SINGLE_FILE, DIRECTORY;
|
||||
}
|
||||
|
||||
private final String directoryName;
|
||||
|
||||
public TorrentInfo(Torrent torrent, TorrentHash infohash,
|
||||
TorrentPiece[] pieces, int pieceLength, boolean privTracker,
|
||||
TorrentType type, String directoryName) {
|
||||
this.torrent = torrent;
|
||||
this.infohash = infohash;
|
||||
this.pieces = pieces;
|
||||
this.pieceLength = pieceLength;
|
||||
this.privTracker = privTracker;
|
||||
this.type = type;
|
||||
this.directoryName = directoryName;
|
||||
}
|
||||
|
||||
public TorrentInfo(Torrent torrent, BMap info) throws BTypeException {
|
||||
if (torrent == null)
|
||||
throw new InvalidParameterException("Torrent is null");
|
||||
if (info == null)
|
||||
throw new InvalidParameterException("Info BMap is null");
|
||||
this.torrent = torrent;
|
||||
|
||||
infohash = calculateInfoHash(info);
|
||||
privTracker = Integer.valueOf(1).equals(info.getInteger(PRIVATE));
|
||||
pieceLength = ((BigInteger) info.get(PIECE_LENGTH)).intValue();
|
||||
|
||||
String base = info.getString(NAME);
|
||||
if (base == null)
|
||||
throw new IllegalStateException("Missing a required key: " + NAME);
|
||||
if (info.containsKey(LENGTH)) { // single file mode
|
||||
type = TorrentType.SINGLE_FILE;
|
||||
directoryName = null;
|
||||
files.add(new TorrentFile(this, base, ((BigInteger) info
|
||||
.get(LENGTH)).longValue(), 0));
|
||||
} else {
|
||||
type = TorrentType.DIRECTORY;
|
||||
directoryName = base;
|
||||
|
||||
long offset = 0;
|
||||
BList fList = info.getList(FILES);
|
||||
for (int i = 0; i < fList.size(); i++) {
|
||||
BMap file = fList.getMap(i);
|
||||
StringBuilder path = new StringBuilder();
|
||||
BList elements = file.getList(PATH);
|
||||
for (int j = 0; j < elements.size(); j++) {
|
||||
String element = elements.getString(j);
|
||||
if (path.length() > 0) {
|
||||
path.append(File.separatorChar);
|
||||
}
|
||||
path.append(element);
|
||||
}
|
||||
if (files.add(new TorrentFile(this, path.toString(),
|
||||
((BigInteger) file.get(LENGTH)).longValue(), offset))) {
|
||||
offset += ((BigInteger) file.get(LENGTH)).longValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
calculateSize();
|
||||
|
||||
byte[] hashes = (byte[]) info.get(PIECES);
|
||||
if (hashes == null)
|
||||
throw new IllegalStateException("Pieces hash is null");
|
||||
if (hashes.length % 20 != 0)
|
||||
throw new IllegalStateException(
|
||||
"Pieces hash dictionary has an invalid size: "
|
||||
+ hashes.length);
|
||||
pieces = new TorrentPiece[(hashes.length / 20)];
|
||||
for (int index = 0; index < pieces.length; index++) {
|
||||
int len = pieceLength;
|
||||
if (pieces.length - 1 == index) // last piece
|
||||
len = (int) (size - (pieceLength * (pieces.length - 1)));
|
||||
|
||||
pieces[index] = new TorrentPiece(this, Arrays.copyOfRange(hashes,
|
||||
index * 20, index * 20 + 20), index, len);
|
||||
}
|
||||
// Arrays.sort(pieces, PieceIndexComparator.SHARED_INSTANCE);
|
||||
}
|
||||
|
||||
private void calculateSize() {
|
||||
for (TorrentFile file : files) {
|
||||
size += file.getLength();
|
||||
}
|
||||
}
|
||||
|
||||
private TorrentHash calculateInfoHash(Map<String, ?> info) {
|
||||
try {
|
||||
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
|
||||
sha1.update(BEncodedOutputStream.bencode(info));
|
||||
return new TorrentHash(sha1.digest(), HashType.SHA1);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Torrent getTorrent() {
|
||||
return torrent;
|
||||
}
|
||||
|
||||
public TorrentHash getInfoHash() {
|
||||
return infohash;
|
||||
}
|
||||
|
||||
public List<TorrentFile> getFiles() {
|
||||
return files;
|
||||
}
|
||||
|
||||
public TorrentPiece[] getPieces() {
|
||||
return pieces;
|
||||
}
|
||||
|
||||
public int getPieceLength() {
|
||||
return pieceLength;
|
||||
}
|
||||
|
||||
public boolean isPrivTracker() {
|
||||
return privTracker;
|
||||
}
|
||||
|
||||
public TorrentType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getDirectoryName() {
|
||||
return directoryName;
|
||||
}
|
||||
|
||||
public TorrentPiece getPiece(int index) {
|
||||
for (final TorrentPiece piece : pieces) {
|
||||
if (piece.getIndex() == index)
|
||||
return piece;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public TorrentPart getPart(TorrentPiece piece, int start, int len) {
|
||||
return piece.getPart(start, len);
|
||||
}
|
||||
|
||||
public TorrentPart getPart(int index, int start, int len) {
|
||||
return getPart(getPiece(index), start, len);
|
||||
}
|
||||
}
|
||||
239
src/main/java/net/torrent/torrent/TorrentPart.java
Normal file
239
src/main/java/net/torrent/torrent/TorrentPart.java
Normal file
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.torrent;
|
||||
|
||||
import net.torrent.protocol.peerwire.message.RequestMessage;
|
||||
import net.torrent.util.Range;
|
||||
|
||||
/**
|
||||
* BitTorrent files are divided in small chunks of hashes, each hash represent
|
||||
* an <b>piece</b> and each of those pieces are divided into smaller parts. Each
|
||||
* part can be requested to an peer and received afterwards.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
* @see RequestMessage RequestMessage for more about requests and parts
|
||||
*/
|
||||
public class TorrentPart {
|
||||
/**
|
||||
* The standard part length -- most clients respect this!
|
||||
*/
|
||||
public static final int PART_LENGTH = 16 * 1024;
|
||||
|
||||
/**
|
||||
* The piece index, only for internal management
|
||||
*/
|
||||
private int index; // internal use only
|
||||
/**
|
||||
* The piece of this part
|
||||
*/
|
||||
private final TorrentPiece piece;
|
||||
/**
|
||||
* The start offset of data
|
||||
*/
|
||||
private final int start;
|
||||
/**
|
||||
* The length of data
|
||||
*/
|
||||
private final int length;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param piece
|
||||
* the piece
|
||||
* @param start
|
||||
* the start offset
|
||||
* @param length
|
||||
* the length
|
||||
*/
|
||||
public TorrentPart(TorrentPiece piece, int start, int length) {
|
||||
this.index = start / length;
|
||||
this.piece = piece;
|
||||
this.start = start;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param index
|
||||
* the part index. <b>Note</b>: internal use only!
|
||||
* @param piece
|
||||
* the piece
|
||||
* @param start
|
||||
* the start offset
|
||||
* @param length
|
||||
* the length
|
||||
*/
|
||||
public TorrentPart(int index, TorrentPiece piece, int start, int length) {
|
||||
this.index = index;
|
||||
this.piece = piece;
|
||||
this.start = start;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the piece
|
||||
*
|
||||
* @return the piece
|
||||
*/
|
||||
public TorrentPiece getPiece() {
|
||||
return piece;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the torrent
|
||||
*
|
||||
* @return the torrent
|
||||
*/
|
||||
public Torrent getTorrent() {
|
||||
return piece.getTorrent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data start offset
|
||||
*
|
||||
* @return the start offset
|
||||
*/
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data length
|
||||
*
|
||||
* @return the data length
|
||||
*/
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the offset of part inside the torrent.
|
||||
*
|
||||
* @return the part's torrent offset
|
||||
*/
|
||||
public long getOffset() {
|
||||
return piece.getOffset() + start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is the last part in this piece.
|
||||
* <p>
|
||||
* <code>
|
||||
* TorrentPart part = ...;<br>
|
||||
* boolean first = (part.getStart() + part.getLength() == part.getPiece().getLength());
|
||||
* </code>
|
||||
*
|
||||
* @return true if last
|
||||
*/
|
||||
public boolean isLast() {
|
||||
return (start + length == piece.getLength());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is the first part in this piece.
|
||||
* <p>
|
||||
* <code>
|
||||
* TorrentPart part = ...;<br>
|
||||
* boolean first = (part.getStart() == 0);
|
||||
* </code>
|
||||
*
|
||||
* @return true if first
|
||||
*/
|
||||
public boolean isFirst() {
|
||||
return start == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next part. Null if last.
|
||||
*
|
||||
* @return the next part. Might be null.
|
||||
*/
|
||||
public TorrentPart getNextPart() {
|
||||
if (isLast())
|
||||
return null;
|
||||
return piece.getParts()[index + 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the range inside the piece.
|
||||
*
|
||||
* @return the range in piece
|
||||
*/
|
||||
public Range asPieceRange() {
|
||||
return Range.getRangeByLength(start, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the range inside the torrent
|
||||
*
|
||||
* @return the range in torrent
|
||||
*/
|
||||
public Range asTorrentRange() {
|
||||
return Range.getRangeByLength(piece.getOffset() + start, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the range inside the file
|
||||
*
|
||||
* @param file
|
||||
* the file
|
||||
* @return the range in file
|
||||
*/
|
||||
public Range asFileRange(TorrentFile file) {
|
||||
return Range.getRangeByLength(
|
||||
piece.getOffset() - file.getOffset() + start, length)
|
||||
.intersection(file.asFileRange());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TorrentPart [piece=" + piece + ", start=" + start + ", length="
|
||||
+ length + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + length;
|
||||
result = prime * result + ((piece == null) ? 0 : piece.hashCode());
|
||||
result = prime * result + start;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
TorrentPart other = (TorrentPart) obj;
|
||||
if (length != other.length)
|
||||
return false;
|
||||
if (piece == null) {
|
||||
if (other.piece != null)
|
||||
return false;
|
||||
} else if (!piece.equals(other.piece))
|
||||
return false;
|
||||
if (start != other.start)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
252
src/main/java/net/torrent/torrent/TorrentPiece.java
Normal file
252
src/main/java/net/torrent/torrent/TorrentPiece.java
Normal file
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.torrent;
|
||||
|
||||
import net.torrent.torrent.TorrentHash.HashType;
|
||||
|
||||
/**
|
||||
* Torrent files are divided into small chunks of data called <tt>pieces</tt>.
|
||||
* Each piece has the same size, except for the last which can be equal or
|
||||
* smaller (enough to fit all data).
|
||||
* <p>
|
||||
* This class handles this distribution and takes care of creating
|
||||
* {@link TorrentPart parts}.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class TorrentPiece {
|
||||
/**
|
||||
* The Torrent information
|
||||
*/
|
||||
private final TorrentInfo info;
|
||||
/**
|
||||
* The piece hash
|
||||
*/
|
||||
private final TorrentHash hash;
|
||||
/**
|
||||
* The piece index
|
||||
*/
|
||||
private final int index;
|
||||
/**
|
||||
* The piece length
|
||||
*/
|
||||
private final int length;
|
||||
/**
|
||||
* The torrent parts. All parts have the standard length (
|
||||
* {@link TorrentPart#PART_LENGTH})
|
||||
*/
|
||||
private final TorrentPart[] parts;
|
||||
|
||||
/**
|
||||
* Create a new instance
|
||||
*
|
||||
* @param info
|
||||
* the torrent information
|
||||
* @param hash
|
||||
* the torrent hash (as {@link TorrentHash})
|
||||
* @param index
|
||||
* the piece index
|
||||
* @param length
|
||||
* the piece length
|
||||
*/
|
||||
public TorrentPiece(TorrentInfo info, final TorrentHash hash, int index,
|
||||
int length) {
|
||||
this.info = info;
|
||||
this.hash = hash;
|
||||
this.index = index;
|
||||
this.length = length;
|
||||
|
||||
int len = TorrentPart.PART_LENGTH;
|
||||
if (len > length)
|
||||
len = length;
|
||||
int partCount = (length / len);
|
||||
if ((length % len) > 0)
|
||||
partCount += 1;
|
||||
|
||||
parts = new TorrentPart[partCount];
|
||||
int remain = length;
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
int plen = remain;
|
||||
if (remain > len)
|
||||
plen = len;
|
||||
parts[i] = new TorrentPart(i, this, length - remain, plen);
|
||||
remain -= plen;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param info
|
||||
* the torrent info
|
||||
* @param hash
|
||||
* the info hash (as byte array)
|
||||
* @param index
|
||||
* the piece index
|
||||
* @param length
|
||||
* the piece length
|
||||
*/
|
||||
public TorrentPiece(TorrentInfo info, final byte[] hash, int index,
|
||||
int length) {
|
||||
this(info, new TorrentHash(hash, HashType.SHA1), index, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the torrent information
|
||||
*
|
||||
* @return the torrent information
|
||||
*/
|
||||
public TorrentInfo getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the torrent
|
||||
*
|
||||
* @return the torrent
|
||||
*/
|
||||
public Torrent getTorrent() {
|
||||
return info.getTorrent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the piece hash
|
||||
*
|
||||
* @return the piece hash
|
||||
*/
|
||||
public TorrentHash getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the piece index
|
||||
*
|
||||
* @return the piece index
|
||||
*/
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the piece length
|
||||
*
|
||||
* @return the piece length
|
||||
*/
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the piece offset
|
||||
*
|
||||
* @return the piece offset (relative to torrent)
|
||||
*/
|
||||
public long getOffset() {
|
||||
return (long) index * (long) info.getPieceLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next sequential part
|
||||
*
|
||||
* @return the next part
|
||||
*/
|
||||
public TorrentPiece getNextPiece() {
|
||||
return info.getPiece(index + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all parts
|
||||
*
|
||||
* @return all the parts
|
||||
*/
|
||||
public TorrentPart[] getParts() {
|
||||
return parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first part
|
||||
*
|
||||
* @return the first part
|
||||
*/
|
||||
public TorrentPart getFirstPart() {
|
||||
return parts[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last part
|
||||
*
|
||||
* @return the last part
|
||||
*/
|
||||
public TorrentPart getLastPart() {
|
||||
return parts[parts.length - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* The the part starting at <tt>start</tt> with <tt>length</tt>
|
||||
*
|
||||
* @param start
|
||||
* @param length
|
||||
* @return the found or created part
|
||||
*/
|
||||
public TorrentPart getPart(int start, int length) {
|
||||
for (final TorrentPart part : parts) {
|
||||
if (part.getStart() == start && part.getLength() == length)
|
||||
return part;
|
||||
}
|
||||
return new TorrentPart(this, start, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this piece as an entire part
|
||||
*
|
||||
* @return the part
|
||||
*/
|
||||
public TorrentPart asSinglePart() {
|
||||
return getPart(0, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TorrentPiece [info=" + info + ", hash=" + hash + ", index="
|
||||
+ index + ", length=" + length + ", parts="
|
||||
+ (parts != null ? parts.length : null) + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((hash == null) ? 0 : hash.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
TorrentPiece other = (TorrentPiece) obj;
|
||||
if (hash == null) {
|
||||
if (other.hash != null)
|
||||
return false;
|
||||
} else if (!hash.equals(other.hash))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
146
src/main/java/net/torrent/torrent/TorrentTracker.java
Normal file
146
src/main/java/net/torrent/torrent/TorrentTracker.java
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.torrent;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An tracker has the responsibility to discover new peers. Each peer, once
|
||||
* begin downloading, send its own peer id and got registered into an peer table
|
||||
* in the tracker. Once the next peer starts, it receives an copy of that peer
|
||||
* table and connect to some or all peers in that table.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class TorrentTracker {
|
||||
/**
|
||||
* The torrent
|
||||
*/
|
||||
private final Torrent torrent;
|
||||
/**
|
||||
* The tracker URI. There are several types of trackers:
|
||||
* <ul>
|
||||
* <li>HTTP</li>
|
||||
* <li>HTTPS</li>
|
||||
* <li>UDP</li>
|
||||
* </ul>
|
||||
* Each type have its own protocol and methods to retrieve peers.
|
||||
*/
|
||||
private final URI uri;
|
||||
/**
|
||||
* The tracker backups uris
|
||||
*/
|
||||
private final Set<URI> backup = new HashSet<URI>();
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param uri
|
||||
* the tracker uri
|
||||
* @param backup
|
||||
* the tracker's backup uris
|
||||
*/
|
||||
public TorrentTracker(Torrent torrent, URI uri, URI... backup) {
|
||||
this.torrent = torrent;
|
||||
this.uri = uri;
|
||||
if (backup != null && backup.length > 0)
|
||||
Collections.addAll(this.backup, backup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent
|
||||
* @param uri
|
||||
* the tracker uri
|
||||
*/
|
||||
public TorrentTracker(Torrent torrent, URI uri) {
|
||||
this(torrent, uri, (URI[]) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the torrent
|
||||
*
|
||||
* @return the torrent
|
||||
*/
|
||||
public Torrent getTorrent() {
|
||||
return torrent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tracker {@link URL}
|
||||
*
|
||||
* @return the url
|
||||
*/
|
||||
public URL getURL() {
|
||||
try {
|
||||
return uri.toURL();
|
||||
} catch (MalformedURLException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tracker {@link URI}
|
||||
*
|
||||
* @return the tracker uri
|
||||
*/
|
||||
public URI getURI() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the backup {@link URI}s
|
||||
*
|
||||
* @return the tracker's backup URIS
|
||||
*/
|
||||
public Set<URI> getBackup() {
|
||||
return Collections.unmodifiableSet(backup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((uri == null) ? 0 : uri.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
TorrentTracker other = (TorrentTracker) obj;
|
||||
if (uri == null) {
|
||||
if (other.uri != null)
|
||||
return false;
|
||||
} else if (!uri.equals(other.uri))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
260
src/main/java/net/torrent/torrent/context/TorrentBitfield.java
Normal file
260
src/main/java/net/torrent/torrent/context/TorrentBitfield.java
Normal file
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.torrent.context;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.BitSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
import net.torrent.torrent.TorrentPiece;
|
||||
|
||||
/**
|
||||
* This is a part bitfield. The only way to check if there is a full piece is to
|
||||
* test all parts.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class TorrentBitfield implements Iterable<TorrentPiece> {
|
||||
/**
|
||||
* The torrent context
|
||||
*/
|
||||
private final TorrentContext context;
|
||||
/**
|
||||
* {@link BitSet} containing all completed pieces
|
||||
*/
|
||||
private final BitSet bits;
|
||||
|
||||
/**
|
||||
* Creates a new instance given the {@link BitSet}.
|
||||
*
|
||||
* @param context
|
||||
* the torrent context
|
||||
* @param bits
|
||||
* the {@link BitSet}.
|
||||
*/
|
||||
public TorrentBitfield(TorrentContext context, BitSet bits) {
|
||||
this.context = context;
|
||||
this.bits = bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param context
|
||||
* the torrent context
|
||||
*/
|
||||
public TorrentBitfield(TorrentContext context) {
|
||||
this(context, new BitSet((context.getTorrent() != null ? context
|
||||
.getTorrent().getPieces().length : 0)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if this bitfield contains the given piece. Redirect the call to
|
||||
* {@link TorrentBitfield#hasPiece(int)}.
|
||||
*
|
||||
* @param piece
|
||||
* the piece to test
|
||||
* @return true if piece is set, false otherwise
|
||||
*/
|
||||
public boolean hasPiece(TorrentPiece piece) {
|
||||
if (piece == null)
|
||||
throw new InvalidParameterException("Piece is null");
|
||||
return hasPiece(piece.getIndex());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if this bitfield contains the given piece <tt>index</tt>
|
||||
*
|
||||
* @param index
|
||||
* the piece index
|
||||
* @return true if piece is set, false otherwise
|
||||
*/
|
||||
public boolean hasPiece(int index) {
|
||||
return bits.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the state of the given piece.
|
||||
*
|
||||
* @param piece
|
||||
* the piece
|
||||
* @param state
|
||||
* the state
|
||||
*/
|
||||
public void setPiece(TorrentPiece piece, boolean state) {
|
||||
if (piece == null)
|
||||
throw new InvalidParameterException("Piece is null");
|
||||
bits.set(piece.getIndex(), state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bits into this bitfield.
|
||||
*
|
||||
* @param bitSet
|
||||
* the bits
|
||||
*/
|
||||
public void setBits(BitSet bitSet) {
|
||||
bits.xor(bitSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the torrent context
|
||||
*
|
||||
* @return the torrent context
|
||||
*/
|
||||
public TorrentContext getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the backing {@link BitSet}.
|
||||
*
|
||||
* @return the bit set
|
||||
*/
|
||||
public BitSet getBits() {
|
||||
return bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an Bitfield representing remaining pieces.
|
||||
*
|
||||
* @return the remain bitfield
|
||||
*/
|
||||
public TorrentBitfield getRemainingPieces() {
|
||||
BitSet set = (BitSet) bits.clone();
|
||||
set.flip(0, bits.size());
|
||||
return new TorrentBitfield(context, set);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((bits == null) ? 0 : bits.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
TorrentBitfield other = (TorrentBitfield) obj;
|
||||
if (bits == null) {
|
||||
if (other.bits != null)
|
||||
return false;
|
||||
} else if (!bits.equals(other.bits))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<TorrentPiece> iterator() {
|
||||
return iterator(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate to all pieces with <tt>type</tt>. True if set, false if not.
|
||||
*
|
||||
* @param type
|
||||
* the type
|
||||
* @return {@link Iterator} for those pieces
|
||||
*/
|
||||
public Iterator<TorrentPiece> iterator(final boolean type) {
|
||||
return new BitfieldIterator(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate to all pieces with <tt>type</tt>. True if set, false if not.
|
||||
*
|
||||
* @param type
|
||||
* the type
|
||||
* @return {@link Iterable} for those pieces
|
||||
* @see TorrentBitfield#iterator(boolean)
|
||||
*/
|
||||
public Iterable<TorrentPiece> iterate(final boolean type) {
|
||||
return new Iterable<TorrentPiece>() {
|
||||
@Override
|
||||
public Iterator<TorrentPiece> iterator() {
|
||||
return TorrentBitfield.this.iterator(type);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The bitfield iterator
|
||||
*
|
||||
* @author Rogiel Josias Sulzbach (<a
|
||||
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
|
||||
*/
|
||||
public class BitfieldIterator implements Iterator<TorrentPiece> {
|
||||
/**
|
||||
* The desired value
|
||||
*/
|
||||
private boolean value = true;
|
||||
|
||||
/**
|
||||
* The current index
|
||||
*/
|
||||
private int index = 0;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param value
|
||||
* the desired state
|
||||
*/
|
||||
public BitfieldIterator(boolean value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return getIndex() >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TorrentPiece next() {
|
||||
int index = -2;
|
||||
try {
|
||||
index = getIndex();
|
||||
return context.getTorrent().getPiece(getIndex());
|
||||
} finally {
|
||||
this.index = index + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the next index, without increasing it.
|
||||
*
|
||||
* @return the next index, -1 if non existent.
|
||||
*/
|
||||
private int getIndex() {
|
||||
if (value)
|
||||
return index = bits.nextSetBit(index);
|
||||
else
|
||||
return bits.nextClearBit(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
188
src/main/java/net/torrent/torrent/context/TorrentContext.java
Normal file
188
src/main/java/net/torrent/torrent/context/TorrentContext.java
Normal file
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.torrent.context;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import net.torrent.protocol.tracker.message.PeerListMessage.PeerInfo;
|
||||
import net.torrent.torrent.Torrent;
|
||||
import net.torrent.torrent.context.TorrentPeerCapabilities.TorrentPeerCapability;
|
||||
|
||||
public class TorrentContext {
|
||||
/**
|
||||
* The torrent metadata object
|
||||
*/
|
||||
private final Torrent torrent;
|
||||
/**
|
||||
* The bitfield
|
||||
*/
|
||||
private final TorrentBitfield bitfield = new TorrentBitfield(this);
|
||||
|
||||
/**
|
||||
* The capabilities
|
||||
*/
|
||||
private final TorrentPeerCapabilities capabilites = new TorrentPeerCapabilities(
|
||||
TorrentPeerCapability.DHT, TorrentPeerCapability.FAST_PEERS);
|
||||
|
||||
private final Set<TorrentPeer> peers = new HashSet<TorrentPeer>();
|
||||
/**
|
||||
* Unknown peers does not have their IDs, consequently they cannot be
|
||||
* queried using their Id and must be done through IP.
|
||||
*/
|
||||
private final Set<TorrentPeer> unknownPeers = new HashSet<TorrentPeer>();
|
||||
|
||||
/**
|
||||
* Creates a new context
|
||||
*
|
||||
* @param torrent
|
||||
* the torrent metadata
|
||||
*/
|
||||
public TorrentContext(Torrent torrent) {
|
||||
this.torrent = torrent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the torrent metadata object
|
||||
*
|
||||
* @return metadata object
|
||||
*/
|
||||
public Torrent getTorrent() {
|
||||
return torrent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the context bitfield
|
||||
*
|
||||
* @return the bitfield
|
||||
*/
|
||||
public TorrentBitfield getBitfield() {
|
||||
return bitfield;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the capabilities of this context
|
||||
*
|
||||
* @return the capabilities
|
||||
*/
|
||||
public TorrentPeerCapabilities getCapabilites() {
|
||||
return capabilites;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if both peer and this context support an given capability.
|
||||
*
|
||||
* @param peer
|
||||
* the peer
|
||||
* @param capability
|
||||
* the capability
|
||||
* @return true if both support this capability
|
||||
*/
|
||||
public boolean supports(TorrentPeer peer, TorrentPeerCapability capability) {
|
||||
return capabilites.supports(capability)
|
||||
&& peer.getCapabilities().supports(capability);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of known peers (have known peerid)
|
||||
*
|
||||
* @return the list of peers
|
||||
*/
|
||||
public Set<TorrentPeer> getPeers() {
|
||||
return Collections.unmodifiableSet(peers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of unknown peers (don't have known peerid)
|
||||
*
|
||||
* @return the list of peers
|
||||
*/
|
||||
public Set<TorrentPeer> getUnknownPeers() {
|
||||
return Collections.unmodifiableSet(unknownPeers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an peer by its PeerID
|
||||
*
|
||||
* @param peerId
|
||||
* the peer id
|
||||
* @return the found peer. Null if not found.
|
||||
*/
|
||||
public TorrentPeer getPeer(TorrentPeerID peerId) {
|
||||
for (final TorrentPeer peer : peers) {
|
||||
if (peer.getPeerID().equals(peerId))
|
||||
return peer;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an peer by its address
|
||||
*
|
||||
* @param address
|
||||
* the address
|
||||
* @return the found peer. Null if not found.
|
||||
*/
|
||||
public TorrentPeer getPeer(InetSocketAddress address) {
|
||||
for (final TorrentPeer peer : peers) {
|
||||
if (peer.getSocketAddress().equals(address))
|
||||
return peer;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup for a peer first by its id, then by address, if still not found,
|
||||
* creates a new entry.
|
||||
*
|
||||
* @param id
|
||||
* the peer id
|
||||
* @param address
|
||||
* the address
|
||||
* @return the found or newly created peer
|
||||
*/
|
||||
public TorrentPeer getPeer(TorrentPeerID id, InetSocketAddress address) {
|
||||
TorrentPeer peer = getPeer(id);
|
||||
if (peer == null) {
|
||||
peer = getPeer(address);
|
||||
if (peer != null) {
|
||||
if (peers.remove(peer))
|
||||
peer = peer.createWithID(id);
|
||||
} else {
|
||||
peer = new TorrentPeer(this, id, null);
|
||||
}
|
||||
peers.add(peer);
|
||||
}
|
||||
return peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this peer already exists, will update its IP.
|
||||
*
|
||||
* @param peerInfo
|
||||
* the peer info object, returned from the tracker
|
||||
*/
|
||||
public TorrentPeer addPeerByPeerInfo(PeerInfo peerInfo) {
|
||||
final TorrentPeerID id = TorrentPeerID.create(peerInfo.getPeerId());
|
||||
final InetSocketAddress address = new InetSocketAddress(
|
||||
peerInfo.getIp(), peerInfo.getPort());
|
||||
TorrentPeer peer = getPeer(id, address);
|
||||
peer.setSocketAddress(address);
|
||||
return peer;
|
||||
}
|
||||
}
|
||||
369
src/main/java/net/torrent/torrent/context/TorrentPeer.java
Normal file
369
src/main/java/net/torrent/torrent/context/TorrentPeer.java
Normal file
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.torrent.context;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Object representing a peer in the swarm.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class TorrentPeer {
|
||||
/**
|
||||
* The torrent context
|
||||
*/
|
||||
private final TorrentContext context;
|
||||
/**
|
||||
* The peer id
|
||||
*/
|
||||
private final TorrentPeerID peerID;
|
||||
|
||||
/**
|
||||
* Set of peer capabilities
|
||||
*/
|
||||
private final TorrentPeerCapabilities capabilities = new TorrentPeerCapabilities();
|
||||
|
||||
/**
|
||||
* The bitfield of the peer
|
||||
*/
|
||||
private final TorrentBitfield bitfield;
|
||||
/**
|
||||
* Local choking state to this peer. By default is
|
||||
* {@link ChokingState#CHOKED choked}.
|
||||
*/
|
||||
private ChokingState chokingState = ChokingState.CHOKED;
|
||||
/**
|
||||
* Remote choking state of this peer. By default is
|
||||
* {@link ChokingState#CHOKED choked}.
|
||||
*/
|
||||
private ChokingState peerChokingState = ChokingState.CHOKED;
|
||||
|
||||
/**
|
||||
* The choking states
|
||||
*
|
||||
* @author Rogiel Josias Sulzbach (<a
|
||||
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
|
||||
*/
|
||||
public enum ChokingState {
|
||||
/**
|
||||
* Choked: communication is "blocked". No pieces can be requested nor
|
||||
* sent. By stantard, this is the default interest once the connection
|
||||
* is made.
|
||||
*/
|
||||
CHOKED,
|
||||
/**
|
||||
* Unchoked: communication is "open": pieces can be requested and
|
||||
* uploaded.
|
||||
*/
|
||||
UNCHOKED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Local interest to this peer. By default is
|
||||
* {@link InterestState#UNINTERESTED not interested}
|
||||
*/
|
||||
private InterestState interestState = InterestState.UNINTERESTED;
|
||||
/**
|
||||
* Remote interest of this peer. By default is
|
||||
* {@link InterestState#UNINTERESTED not interested}
|
||||
*/
|
||||
private InterestState peerInterestState = InterestState.UNINTERESTED;
|
||||
|
||||
/**
|
||||
* The interest states
|
||||
*
|
||||
* @author Rogiel Josias Sulzbach (<a
|
||||
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
|
||||
*/
|
||||
public enum InterestState {
|
||||
/**
|
||||
* The peer is interested in the other pieces. If the peer is willing to
|
||||
* upload, the remote peer is unchoked and data transfer can begin.
|
||||
*/
|
||||
INTERESTED,
|
||||
|
||||
/**
|
||||
* The peer has not interest in the remote peer pieces. By stantard,
|
||||
* this is the default interest once the connection is made.
|
||||
*/
|
||||
UNINTERESTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* The peer socket address. For incoming connections.
|
||||
*/
|
||||
private InetSocketAddress socketAddress;
|
||||
/**
|
||||
* Last time the peer was found online.
|
||||
*/
|
||||
private Date lastReached = null;
|
||||
/**
|
||||
* State of the peer connectivity. Peer behind firewall or NAT will have
|
||||
* false. By default all peers are accessible.
|
||||
*/
|
||||
private boolean accessible = true;
|
||||
|
||||
/**
|
||||
* Creates a new peer
|
||||
*
|
||||
* @param context
|
||||
* the torrent context
|
||||
* @param peerID
|
||||
* the peer id
|
||||
* @param socketAddress
|
||||
* the peer address
|
||||
*/
|
||||
public TorrentPeer(TorrentContext context, TorrentPeerID peerID,
|
||||
InetSocketAddress socketAddress) {
|
||||
if (peerID == null && socketAddress == null)
|
||||
throw new IllegalArgumentException(
|
||||
"PeerID or SocketAddress must be not null");
|
||||
if (context == null)
|
||||
throw new IllegalArgumentException("Context is null");
|
||||
|
||||
this.context = context;
|
||||
this.peerID = peerID;
|
||||
this.socketAddress = socketAddress;
|
||||
this.bitfield = new TorrentBitfield(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the peer id
|
||||
*
|
||||
* @return the peer id
|
||||
*/
|
||||
public TorrentPeerID getPeerID() {
|
||||
return peerID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the peer address
|
||||
*
|
||||
* @param address
|
||||
* the address
|
||||
*/
|
||||
public void setSocketAddress(InetSocketAddress address) {
|
||||
this.socketAddress = address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the peer address
|
||||
*
|
||||
* @return the address
|
||||
*/
|
||||
public InetSocketAddress getSocketAddress() {
|
||||
return socketAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the peer bitfield
|
||||
*
|
||||
* @return the bitfield
|
||||
*/
|
||||
public TorrentBitfield getBitfield() {
|
||||
return bitfield;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the peer capabilities
|
||||
*
|
||||
* @return the capabilities
|
||||
*/
|
||||
public TorrentPeerCapabilities getCapabilities() {
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local choking state to this peer. By default is
|
||||
* {@link ChokingState#CHOKED choked}.
|
||||
*
|
||||
* @return the choking state
|
||||
*/
|
||||
public ChokingState getChokingState() {
|
||||
return chokingState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the local choking state to this peer. By default is
|
||||
* {@link ChokingState#CHOKED choked}.
|
||||
*
|
||||
* @param chokingState
|
||||
* the choking state
|
||||
*/
|
||||
public void setChokingState(ChokingState chokingState) {
|
||||
this.chokingState = chokingState;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Get the remote choking state of this peer. By default is
|
||||
* {@link ChokingState#CHOKED choked}.
|
||||
*
|
||||
* @return the peer choking state
|
||||
*/
|
||||
public ChokingState getPeerChokingState() {
|
||||
return peerChokingState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the remote choking state of this peer. By default is
|
||||
* {@link ChokingState#CHOKED choked}.
|
||||
*
|
||||
* @param peerChokingState
|
||||
* the peer choking state
|
||||
*/
|
||||
public void setPeerChokingState(ChokingState peerChokingState) {
|
||||
this.peerChokingState = peerChokingState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local interest to this peer. By default is
|
||||
* {@link InterestState#UNINTERESTED not interested}
|
||||
*
|
||||
* @return the local interest
|
||||
*/
|
||||
public InterestState getInterestState() {
|
||||
return interestState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the local interest to this peer. By default is
|
||||
* {@link InterestState#UNINTERESTED not interested}
|
||||
*
|
||||
* @param interestState
|
||||
* the local interest
|
||||
*/
|
||||
public void setInterestState(InterestState interestState) {
|
||||
this.interestState = interestState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the peer remote interest of this peer. By default is
|
||||
* {@link InterestState#UNINTERESTED not interested}
|
||||
*
|
||||
* @return the peer remote interest
|
||||
*/
|
||||
public InterestState getPeerInterestState() {
|
||||
return peerInterestState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the peer remote interest of this peer. By default is
|
||||
* {@link InterestState#UNINTERESTED not interested}
|
||||
*
|
||||
* @param peerInterestState
|
||||
* the peer remote interest
|
||||
*/
|
||||
public void setPeerInterestState(InterestState peerInterestState) {
|
||||
this.peerInterestState = peerInterestState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last time the peer has been reached by an connection
|
||||
*
|
||||
* @return the date of last connection
|
||||
*/
|
||||
public Date getLastReached() {
|
||||
return lastReached;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last time the peer has been reached by an connection
|
||||
*
|
||||
* @param lastReached
|
||||
* the date of last connection
|
||||
*/
|
||||
public void setLastReached(Date lastReached) {
|
||||
this.lastReached = lastReached;
|
||||
}
|
||||
|
||||
/**
|
||||
* State of the peer connectivity. Peer behind firewall or NAT will have
|
||||
* false. By default all peers are accessible.
|
||||
*
|
||||
* @return true if is accessible, false otherwise.
|
||||
*/
|
||||
public boolean isAccessible() {
|
||||
return accessible;
|
||||
}
|
||||
|
||||
/**
|
||||
* State of the peer connectivity. Peer behind firewall or NAT will have
|
||||
* false. By default all peers are accessible.
|
||||
*
|
||||
* @param accessible
|
||||
* true if is accessible, false otherwise.
|
||||
*/
|
||||
public void setAccessible(boolean accessible) {
|
||||
this.accessible = accessible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the torrent context
|
||||
*
|
||||
* @return the torrent context
|
||||
*/
|
||||
public TorrentContext getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an clone of this peer with the new {@link TorrentPeerID id}.
|
||||
*
|
||||
* @param id
|
||||
* the new peer id
|
||||
* @return the new peer with id.
|
||||
*/
|
||||
public TorrentPeer createWithID(TorrentPeerID id) {
|
||||
return new TorrentPeer(context, id, socketAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
TorrentPeer other = (TorrentPeer) obj;
|
||||
if (peerID == null) {
|
||||
if (other.peerID != null)
|
||||
return false;
|
||||
if (socketAddress == null) {
|
||||
if (other.socketAddress != null)
|
||||
return false;
|
||||
} else if (!socketAddress.equals(other.socketAddress))
|
||||
return false;
|
||||
} else if (!peerID.equals(other.peerID))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
if (peerID != null) {
|
||||
result = prime * result + peerID.hashCode();
|
||||
} else if (socketAddress != null) {
|
||||
result = prime * result + socketAddress.hashCode();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.torrent.context;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Object containing peers support for certain capabilities.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class TorrentPeerCapabilities {
|
||||
/**
|
||||
* BitSet containing available capabilities
|
||||
*/
|
||||
private BitSet capabilities = new BitSet(64);
|
||||
|
||||
/**
|
||||
* Create a new instance
|
||||
*/
|
||||
public TorrentPeerCapabilities() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance with the given <tt>capabilities</tt>
|
||||
*
|
||||
* @param capabilities
|
||||
* the capabilities supported
|
||||
*/
|
||||
public TorrentPeerCapabilities(TorrentPeerCapability... capabilities) {
|
||||
for (final TorrentPeerCapability capability : capabilities) {
|
||||
this.capabilities.set(capability.bit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance with the given <tt>capabilities</tt>
|
||||
*
|
||||
* @param capabilities
|
||||
* the capabilities {@link BitSet} supported
|
||||
*/
|
||||
public TorrentPeerCapabilities(BitSet capabilitites) {
|
||||
this.capabilities = capabilitites;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the given <tt>capability</tt> is supported.
|
||||
*
|
||||
* @param capability
|
||||
* the capability
|
||||
* @return true if capability is supported, false otherwise.
|
||||
*/
|
||||
public boolean supports(TorrentPeerCapability capability) {
|
||||
return capabilities.get(capability.getBit());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an {@link List} of capabilities supported.
|
||||
*
|
||||
* @return the list os capabilities
|
||||
*/
|
||||
public List<TorrentPeerCapability> getCapabilities() {
|
||||
final List<TorrentPeerCapability> capabilitites = new ArrayList<TorrentPeerCapability>();
|
||||
for (final TorrentPeerCapability capability : TorrentPeerCapability
|
||||
.values()) {
|
||||
if (supports(capability))
|
||||
capabilitites.add(capability);
|
||||
}
|
||||
return capabilitites;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the capabilities {@link BitSet}.
|
||||
*
|
||||
* @param capabilitites
|
||||
* the bitset
|
||||
*/
|
||||
public void setCapabilities(BitSet capabilitites) {
|
||||
this.capabilities = capabilitites;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this matrix of capabilties to a {@link BitSet}.
|
||||
*
|
||||
* @return an {@link BitSet}.
|
||||
*/
|
||||
public BitSet toBitSet() {
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumeration of known capabilities.
|
||||
*
|
||||
* @author Rogiel Josias Sulzbach (<a
|
||||
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
|
||||
*/
|
||||
public enum TorrentPeerCapability {
|
||||
/**
|
||||
* Location aware protocol
|
||||
*/
|
||||
LOCATION_AWARE_PROTOCOL(21),
|
||||
/**
|
||||
* Extension protocol
|
||||
*/
|
||||
EXTENSION_PROTOCOL(44),
|
||||
/**
|
||||
* Fast peers support
|
||||
*/
|
||||
FAST_PEERS(62),
|
||||
/**
|
||||
* DHT Support
|
||||
*/
|
||||
DHT(64);
|
||||
|
||||
/**
|
||||
* The bit index for this capability
|
||||
*/
|
||||
private final int bit;
|
||||
|
||||
/**
|
||||
* Creates a new capability
|
||||
*
|
||||
* @param bit
|
||||
* the bit marking this capability
|
||||
*/
|
||||
TorrentPeerCapability(int bit) {
|
||||
this.bit = bit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bit marking this capability
|
||||
*
|
||||
* @return the bit marking the capability
|
||||
*/
|
||||
public int getBit() {
|
||||
return bit;
|
||||
}
|
||||
}
|
||||
}
|
||||
87
src/main/java/net/torrent/torrent/context/TorrentPeerID.java
Normal file
87
src/main/java/net/torrent/torrent/context/TorrentPeerID.java
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.torrent.context;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Creates a new Peer id for an peer.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class TorrentPeerID {
|
||||
/**
|
||||
* The peer id in bytes (20 bytes)
|
||||
*/
|
||||
private byte[] peerID;
|
||||
|
||||
/**
|
||||
* Creates a new PeerID.
|
||||
*
|
||||
* @param peerID
|
||||
* the bytes of the peer id
|
||||
*/
|
||||
protected TorrentPeerID(byte[] peerID) {
|
||||
this.peerID = peerID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new String(peerID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to a byte array
|
||||
*
|
||||
* @return the id in bytes
|
||||
*/
|
||||
public byte[] toByteArray() {
|
||||
return Arrays.copyOf(peerID, peerID.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ID
|
||||
*
|
||||
* @param peerID
|
||||
* the id byte array
|
||||
* @return the new instance of {@link TorrentPeerID peer id}
|
||||
*/
|
||||
public static final TorrentPeerID create(byte[] peerID) {
|
||||
return new TorrentPeerID(peerID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.hashCode(peerID);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
TorrentPeerID other = (TorrentPeerID) obj;
|
||||
if (!Arrays.equals(peerID, other.peerID))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
37
src/main/java/net/torrent/torrent/piece/PieceSelector.java
Normal file
37
src/main/java/net/torrent/torrent/piece/PieceSelector.java
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.torrent.piece;
|
||||
|
||||
import net.torrent.torrent.TorrentPiece;
|
||||
import net.torrent.torrent.context.TorrentPeer;
|
||||
|
||||
/**
|
||||
* The {@link PieceSelector} is used to select the desired piece to be
|
||||
* downloaded. Implementations must make sure the piece is not being downloaded
|
||||
* already and that the peer has it.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public interface PieceSelector {
|
||||
/**
|
||||
* Selects the next piece suitable for download.
|
||||
*
|
||||
* @param peer
|
||||
* the peer chosen for download
|
||||
* @return the {@link TorrentPiece piece} selected for download.
|
||||
*/
|
||||
public TorrentPiece select(TorrentPeer peer);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.torrent.piece;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import net.torrent.protocol.peerwire.manager.TorrentManager;
|
||||
import net.torrent.torrent.TorrentPiece;
|
||||
|
||||
/**
|
||||
* Selects randomly an piece. Once this instance is created it takes all pieces
|
||||
* and put them into an list, then the list is sorted by an random algorithm.
|
||||
* Once the next piece is called the pieces are returned in-list order.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class RandomPieceSelector extends SortedListPieceSelector {
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param manager
|
||||
* the torrent manager
|
||||
*/
|
||||
public RandomPieceSelector(TorrentManager manager) {
|
||||
super(manager, Arrays.asList(manager.getTorrent().getPieces()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sort(List<TorrentPiece> pieces) {
|
||||
Collections.shuffle(pieces);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.torrent.piece;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import net.torrent.protocol.peerwire.manager.TorrentManager;
|
||||
import net.torrent.torrent.TorrentPiece;
|
||||
import net.torrent.util.comparator.PieceIndexComparator;
|
||||
|
||||
/**
|
||||
* Select pieces in sequential order. Can be used for streaming purposes.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class SequentialPieceSelector extends SortedListPieceSelector {
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param manager
|
||||
* the torrent manager
|
||||
*/
|
||||
public SequentialPieceSelector(TorrentManager manager) {
|
||||
super(manager, Arrays.asList(manager.getTorrent().getPieces()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void sort(List<TorrentPiece> pieces) {
|
||||
Collections.sort(pieces, PieceIndexComparator.SHARED_INSTANCE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.torrent.piece;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.torrent.protocol.peerwire.manager.TorrentManager;
|
||||
import net.torrent.torrent.TorrentPiece;
|
||||
import net.torrent.torrent.context.TorrentPeer;
|
||||
|
||||
/**
|
||||
* An sorted {@link PieceSelector} select pieces sequentially (whenever
|
||||
* possible) in a pre-sorted list. The list is sorted on construction by the
|
||||
* {@link SortedListPieceSelector#sort(List) sort} method.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public abstract class SortedListPieceSelector implements PieceSelector {
|
||||
/**
|
||||
* The torrent manager
|
||||
*/
|
||||
private final TorrentManager manager;
|
||||
/**
|
||||
* The sorted list of pieces
|
||||
*/
|
||||
private final List<TorrentPiece> pieces;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param manager
|
||||
* the torrent manager
|
||||
* @param pieces
|
||||
* the <b>unsorted</b> list of pieces
|
||||
*/
|
||||
protected SortedListPieceSelector(TorrentManager manager,
|
||||
List<TorrentPiece> pieces) {
|
||||
this.manager = manager;
|
||||
this.pieces = pieces;
|
||||
this.sort(this.pieces);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized TorrentPiece select(TorrentPeer peer) {
|
||||
for (int index = 0; index < pieces.size(); index++) {
|
||||
final TorrentPiece piece = pieces.get(index);
|
||||
if (manager.getContext().getBitfield().hasPiece(piece))
|
||||
continue;
|
||||
if (!peer.getBitfield().hasPiece(piece))
|
||||
continue;
|
||||
if (manager.getDownloadManager().isDownloading(piece))
|
||||
continue;
|
||||
return piece;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the set using an implementation specific algorithm.
|
||||
*
|
||||
* @param pieces
|
||||
* the unsorted pieces list that will be sorted.
|
||||
*/
|
||||
protected abstract void sort(List<TorrentPiece> pieces);
|
||||
}
|
||||
34
src/main/java/net/torrent/util/PeerCallback.java
Normal file
34
src/main/java/net/torrent/util/PeerCallback.java
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.util;
|
||||
|
||||
import net.torrent.protocol.peerwire.PeerWirePeer;
|
||||
import net.torrent.protocol.peerwire.manager.PeerManager;
|
||||
|
||||
/**
|
||||
* Callback used in {@link PeerManager#execute(PeerCallback)}
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public interface PeerCallback {
|
||||
/**
|
||||
* Execute the desired action for <tt>peer</tt>
|
||||
*
|
||||
* @param peer
|
||||
* the peer
|
||||
*/
|
||||
void callback(PeerWirePeer peer);
|
||||
}
|
||||
178
src/main/java/net/torrent/util/Range.java
Normal file
178
src/main/java/net/torrent/util/Range.java
Normal file
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* This class represents an interval.
|
||||
*
|
||||
* @author dante
|
||||
*/
|
||||
public final class Range implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final long start, length;
|
||||
|
||||
/**
|
||||
* Creates a range with the given parameters
|
||||
*
|
||||
* @param start
|
||||
* the beginning of the range
|
||||
* @param length
|
||||
* the length of the range
|
||||
* @return a range
|
||||
*/
|
||||
public static Range getRangeByLength(long start, long length) {
|
||||
return new Range(start, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the smallest possible range containing the given numbers.
|
||||
*
|
||||
* @param number1
|
||||
* number to be within the range
|
||||
* @param number2
|
||||
* number to be within the range
|
||||
* @return
|
||||
*/
|
||||
public static Range getRangeByNumbers(long number1, long number2) {
|
||||
long s = Math.min(number1, number2);
|
||||
return new Range(s, Math.max(number1, number2) - s + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new range
|
||||
*
|
||||
* @param start
|
||||
* @param length
|
||||
*/
|
||||
private Range(long start, long length) {
|
||||
if (start < 0)
|
||||
throw new IllegalArgumentException("start must be >= 0");
|
||||
if (length < 0)
|
||||
throw new IllegalArgumentException("length must be >= 0");
|
||||
|
||||
this.start = start;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param range
|
||||
* @return true if the given range is contained within this range
|
||||
*/
|
||||
public boolean contains(Range range) {
|
||||
return getStart() <= range.getStart() && getEnd() >= range.getEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pos
|
||||
* @return true if the given point is contained within this range
|
||||
*/
|
||||
public boolean contains(long pos) {
|
||||
return getStart() <= pos && getEnd() >= pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof Range) {
|
||||
Range r = (Range) obj;
|
||||
return getStart() == r.getStart() && getLength() == r.getLength();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the last index contained in this range
|
||||
*/
|
||||
public long getEnd() {
|
||||
return start + length - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the length of this range
|
||||
*/
|
||||
public long getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the first index contained in this range
|
||||
*/
|
||||
public long getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if this range defines a zero-sized range.
|
||||
*
|
||||
* @return true if zero-sized.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return length == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (int) ((getStart() * 13) & (getEnd() * 137));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a range which contains only the indices contained in the
|
||||
* intersection of this range and the given range.
|
||||
*
|
||||
* @param range
|
||||
* the range to intersect with
|
||||
* @return the intersected range or null if the ranges don't overlap
|
||||
*/
|
||||
public Range intersection(Range range) {
|
||||
if (!intersects(range)) {
|
||||
return null;
|
||||
}
|
||||
return getRangeByNumbers(Math.max(getStart(), range.getStart()),
|
||||
Math.min(getEnd(), range.getEnd()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of indices which are in this range and the given
|
||||
* range.
|
||||
*
|
||||
* @param r
|
||||
* @return 0 if the ranges don't overlap, the length of the intersection
|
||||
* between them otherwise
|
||||
*/
|
||||
public long intersectionLength(Range r) {
|
||||
if (!intersects(r)) {
|
||||
return 0;
|
||||
}
|
||||
return Math.min(getEnd(), r.getEnd())
|
||||
- Math.max(getStart(), r.getStart()) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param range
|
||||
* the range to intersect test with
|
||||
* @return true if the ranges overlap
|
||||
*/
|
||||
public boolean intersects(Range range) {
|
||||
return getStart() <= range.getEnd() && getEnd() >= range.getStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + getStart() + " - " + getEnd() + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package net.torrent.util.bencoding;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* BDecodingException: an error has occured when decoding bencoded data.
|
||||
*
|
||||
* @author Dennis "Bytekeeper" Waldherr
|
||||
*/
|
||||
public class BDecodingException extends IOException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public BDecodingException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
public BDecodingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
package net.torrent.util.bencoding;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PushbackInputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Helper class to decode "bencoded" data.
|
||||
*
|
||||
* @author Dennis "Bytekeeper" Waldherr
|
||||
*
|
||||
*/
|
||||
public class BEncodedInputStream extends InputStream {
|
||||
private static final int MAX_STRING_LENGTH = 1024 * 1024;
|
||||
|
||||
private final PushbackInputStream in;
|
||||
|
||||
public BEncodedInputStream(InputStream in) {
|
||||
this.in = new PushbackInputStream(in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to decode exactly one bencoded element.
|
||||
*
|
||||
* @param bencode
|
||||
* @return
|
||||
* @throws BDecodingException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static Object bdecode(byte[] bencode) throws BDecodingException {
|
||||
BEncodedInputStream in = new BEncodedInputStream(
|
||||
new ByteArrayInputStream(bencode));
|
||||
try {
|
||||
return in.readElement();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads one bencoded element.
|
||||
*
|
||||
* @param in
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws BDecodingException
|
||||
*/
|
||||
public Object readElement() throws IOException, BDecodingException {
|
||||
return bdecode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string result from bencoding to a java String. If the data is
|
||||
* already a String or null it is simply returned.
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
* @throws BTypeException
|
||||
*/
|
||||
private static String stringOf(Object data) throws BTypeException {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
if (data instanceof byte[]) {
|
||||
return new String((byte[]) data, "UTF-8");
|
||||
} else if (data instanceof String) {
|
||||
return (String) data;
|
||||
} else {
|
||||
throw new BTypeException("Unsupported type: " + data.getClass());
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a constructed or decoded integer to a java Integer. If the data
|
||||
* is already an Integer or null it is simply returned.
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
* @throws BTypeException
|
||||
*/
|
||||
private static Integer intOf(Object data) throws BTypeException {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
if (data instanceof BigInteger) {
|
||||
return ((BigInteger) data).intValue();
|
||||
} else if (data instanceof Integer) {
|
||||
return (Integer) data;
|
||||
} else {
|
||||
throw new BTypeException("Unsupported type: " + data.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a constructed or decoded integer to a java Integer. If the data
|
||||
* is already an Integer or null it is simply returned.
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
* @throws BTypeException
|
||||
*/
|
||||
private static Long longOf(Object data) throws BTypeException {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
if (data instanceof BigInteger) {
|
||||
return ((BigInteger) data).longValue();
|
||||
} else if (data instanceof Integer) {
|
||||
return Long.valueOf((Integer) data);
|
||||
} else if (data instanceof Long) {
|
||||
return (Long) data;
|
||||
} else {
|
||||
throw new BTypeException("Unsupported type: " + data.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
private Object bdecode() throws IOException, BDecodingException {
|
||||
int head = in.read();
|
||||
switch (head) {
|
||||
case -1:
|
||||
throw new EOFException();
|
||||
case 'l':
|
||||
return bdecodeList();
|
||||
case 'i':
|
||||
return bdecodeInteger();
|
||||
case 'd':
|
||||
return bdecodeDictionary();
|
||||
default:
|
||||
if (Character.isDigit(head)) {
|
||||
in.unread(head);
|
||||
return bdecodeString();
|
||||
}
|
||||
}
|
||||
throw new BDecodingException("Parameter is not bencoded data.");
|
||||
}
|
||||
|
||||
private BMap bdecodeDictionary() throws IOException, BDecodingException {
|
||||
assert in != null;
|
||||
|
||||
BMap map = new BMapImpl();
|
||||
int head;
|
||||
while ((head = in.read()) != 'e') {
|
||||
if (head < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
in.unread(head);
|
||||
String key;
|
||||
try {
|
||||
key = stringOf(bdecodeString());
|
||||
} catch (BTypeException e) {
|
||||
throw new BDecodingException(e);
|
||||
}
|
||||
map.put(key, bdecode());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private byte[] bdecodeString() throws IOException, BDecodingException {
|
||||
assert in != null;
|
||||
int len = 0;
|
||||
int head;
|
||||
while ((head = in.read()) != ':') {
|
||||
if (head < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
len = len * 10 + (head - '0');
|
||||
if (len > MAX_STRING_LENGTH) {
|
||||
throw new BDecodingException("Encoded string length exceeds "
|
||||
+ MAX_STRING_LENGTH + " bytes!");
|
||||
}
|
||||
}
|
||||
byte data[] = new byte[len];
|
||||
int off = 0;
|
||||
while (len > 0) {
|
||||
int read = in.read(data, off, len);
|
||||
len -= read;
|
||||
off += read;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private Object bdecodeInteger() throws IOException, BDecodingException {
|
||||
assert in != null;
|
||||
|
||||
StringBuilder b = new StringBuilder();
|
||||
int head;
|
||||
while ((head = in.read()) != 'e') {
|
||||
if (head < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
if (!Character.isDigit(head)) {
|
||||
throw new BDecodingException("Expected digit but got: " + head);
|
||||
}
|
||||
b.append((char) head);
|
||||
}
|
||||
return new BigInteger(b.toString());
|
||||
}
|
||||
|
||||
private BList bdecodeList() throws IOException, BDecodingException {
|
||||
assert in != null;
|
||||
|
||||
BList list = new BListImpl();
|
||||
int head;
|
||||
while ((head = in.read()) != 'e') {
|
||||
if (head < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
in.unread(head);
|
||||
list.add(bdecode());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return in.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
return in.read(b, off, len);
|
||||
}
|
||||
|
||||
private static class BListImpl extends ArrayList<Object> implements BList {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Integer getInteger(int index) throws BTypeException {
|
||||
return intOf(get(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getLong(int index) throws BTypeException {
|
||||
return longOf(get(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(int index) throws BTypeException {
|
||||
return stringOf(get(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BList getList(int index) throws BTypeException {
|
||||
Object tmp = get(index);
|
||||
if (tmp instanceof BList) {
|
||||
return (BList) tmp;
|
||||
}
|
||||
throw new BTypeException("Unexpected type: " + tmp.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BMap getMap(int index) throws BTypeException {
|
||||
Object tmp = get(index);
|
||||
if (tmp instanceof BMap) {
|
||||
return (BMap) tmp;
|
||||
}
|
||||
throw new BTypeException("Unexpected type: " + tmp.getClass());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class BMapImpl extends HashMap<String, Object> implements
|
||||
BMap {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Integer getInteger(String key) throws BTypeException {
|
||||
return intOf(get(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getLong(String key) throws BTypeException {
|
||||
return longOf(get(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String key) throws BTypeException {
|
||||
return stringOf(get(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BList getList(String key) throws BTypeException {
|
||||
Object tmp = get(key);
|
||||
if (tmp instanceof BList) {
|
||||
return (BList) tmp;
|
||||
}
|
||||
if (tmp != null)
|
||||
throw new BTypeException("Unexpected type: " + tmp.getClass());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BMap getMap(String key) throws BTypeException {
|
||||
Object tmp = get(key);
|
||||
if (tmp instanceof BMap) {
|
||||
return (BMap) tmp;
|
||||
}
|
||||
throw new BTypeException("Unexpected type: " + tmp.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package net.torrent.util.bencoding;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper class to "BEncode" data.
|
||||
*
|
||||
* @author Dennis "Bytekeeper" Waldherr
|
||||
*
|
||||
*/
|
||||
public final class BEncodedOutputStream extends OutputStream {
|
||||
static final Comparator<String> BYTE_COMPARATOR = new Comparator<String>() {
|
||||
@Override
|
||||
public int compare(String o1, String o2) {
|
||||
byte b1[] = bytesOf(o1);
|
||||
byte b2[] = bytesOf(o2);
|
||||
int len = Math.min(b1.length, b2.length);
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (b1[i] > b2[i]) {
|
||||
return 1;
|
||||
}
|
||||
if (b1[i] < b2[i]) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return b1.length - b2.length;
|
||||
}
|
||||
};
|
||||
|
||||
private final OutputStream out;
|
||||
|
||||
/**
|
||||
* Converts a string into the raw byte array representation used to store
|
||||
* into bencoded data.
|
||||
*
|
||||
* @param s
|
||||
* @return
|
||||
*/
|
||||
public static byte[] bytesOf(String s) {
|
||||
try {
|
||||
return s.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private BEncodedOutputStream(OutputStream out) {
|
||||
if (out == null)
|
||||
throw new InvalidParameterException("output strean is null");
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param obj
|
||||
* @return
|
||||
*/
|
||||
public static byte[] bencode(Object obj) {
|
||||
if (obj == null)
|
||||
throw new InvalidParameterException("Object to encode is null!");
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
try {
|
||||
BEncodedOutputStream bout = new BEncodedOutputStream(out);
|
||||
bout.writeElement(obj);
|
||||
bout.close();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* BEncodes data and outputs it to an OutputStream.
|
||||
*
|
||||
* @param out
|
||||
* @param obj
|
||||
* @throws IOException
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void writeElement(Object obj) throws IOException {
|
||||
if (obj == null)
|
||||
throw new InvalidParameterException("Object to encode is null!");
|
||||
|
||||
if (obj instanceof String) {
|
||||
bencodeString((String) obj);
|
||||
} else if (obj instanceof byte[]) {
|
||||
bencodeString((byte[]) obj);
|
||||
} else if (obj instanceof Integer) {
|
||||
bencodeInteger(BigInteger.valueOf((Integer) obj));
|
||||
} else if (obj instanceof Long) {
|
||||
bencodeInteger(BigInteger.valueOf((Long) obj));
|
||||
} else if (obj instanceof BigInteger) {
|
||||
bencodeInteger((BigInteger) obj);
|
||||
} else if (obj instanceof Collection) {
|
||||
bencodeList((Collection<?>) obj);
|
||||
} else if (obj instanceof Map) {
|
||||
bencodeDictionary((Map<String, ?>) obj);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Type " + obj.getClass()
|
||||
+ " is not bencodable.");
|
||||
}
|
||||
}
|
||||
|
||||
private void bencodeDictionary(Map<String, ?> dict) throws IOException {
|
||||
assert out != null;
|
||||
assert dict != null;
|
||||
|
||||
out.write('d');
|
||||
List<String> sorted = new ArrayList<String>(dict.keySet());
|
||||
Collections.sort(sorted, BYTE_COMPARATOR);
|
||||
for (String key : sorted) {
|
||||
writeElement(key);
|
||||
writeElement(dict.get(key));
|
||||
}
|
||||
out.write('e');
|
||||
}
|
||||
|
||||
private void bencodeList(Collection<?> list) throws IOException {
|
||||
assert out != null;
|
||||
assert list != null;
|
||||
out.write('l');
|
||||
for (Object child : list) {
|
||||
writeElement(child);
|
||||
}
|
||||
out.write('e');
|
||||
}
|
||||
|
||||
private void bencodeInteger(BigInteger integer) throws IOException {
|
||||
assert integer != null;
|
||||
out.write('i');
|
||||
out.write(bytesOf(integer.toString()));
|
||||
out.write('e');
|
||||
}
|
||||
|
||||
private void bencodeString(String string) throws IOException {
|
||||
assert string != null;
|
||||
byte[] bytes = bytesOf(string);
|
||||
bencodeString(bytes);
|
||||
}
|
||||
|
||||
private void bencodeString(byte[] data) throws IOException {
|
||||
assert data != null;
|
||||
out.write(bytesOf(Integer.toString(data.length)));
|
||||
out.write(':');
|
||||
out.write(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int arg0) throws IOException {
|
||||
out.write(arg0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
out.write(b, off, len);
|
||||
}
|
||||
|
||||
}
|
||||
21
src/main/java/net/torrent/util/bencoding/BList.java
Normal file
21
src/main/java/net/torrent/util/bencoding/BList.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package net.torrent.util.bencoding;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Representation of a bencoded list.
|
||||
*
|
||||
* @author Bytekeeper
|
||||
*
|
||||
*/
|
||||
public interface BList extends List<Object> {
|
||||
String getString(int index) throws BTypeException;
|
||||
|
||||
Integer getInteger(int index) throws BTypeException;
|
||||
|
||||
Long getLong(int index) throws BTypeException;
|
||||
|
||||
BList getList(int index) throws BTypeException;
|
||||
|
||||
BMap getMap(int index) throws BTypeException;
|
||||
}
|
||||
21
src/main/java/net/torrent/util/bencoding/BMap.java
Normal file
21
src/main/java/net/torrent/util/bencoding/BMap.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package net.torrent.util.bencoding;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Representation of a bencoded map.
|
||||
*
|
||||
* @author Bytekeeper
|
||||
*
|
||||
*/
|
||||
public interface BMap extends Map<String, Object> {
|
||||
String getString(String key) throws BTypeException;
|
||||
|
||||
Integer getInteger(String key) throws BTypeException;
|
||||
|
||||
Long getLong(String key) throws BTypeException;
|
||||
|
||||
BList getList(String key) throws BTypeException;
|
||||
|
||||
BMap getMap(String key) throws BTypeException;
|
||||
}
|
||||
11
src/main/java/net/torrent/util/bencoding/BTypeException.java
Normal file
11
src/main/java/net/torrent/util/bencoding/BTypeException.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package net.torrent.util.bencoding;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class BTypeException extends IOException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public BTypeException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
32
src/main/java/net/torrent/util/bencoding/HashBMap.java
Normal file
32
src/main/java/net/torrent/util/bencoding/HashBMap.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package net.torrent.util.bencoding;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class HashBMap extends HashMap<String, Object> implements BMap {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Integer getInteger(String key) {
|
||||
return (Integer) get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BList getList(String key) {
|
||||
return (BList) get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getLong(String key) {
|
||||
return (Long) get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BMap getMap(String key) {
|
||||
return (BMap) get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String key) {
|
||||
return (String) get(key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.util.comparator;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import net.torrent.torrent.TorrentPiece;
|
||||
|
||||
/**
|
||||
* Compare two pieces indexes.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class PieceIndexComparator implements Comparator<TorrentPiece> {
|
||||
/**
|
||||
* Shared instance
|
||||
*/
|
||||
public static final PieceIndexComparator SHARED_INSTANCE = new PieceIndexComparator();
|
||||
|
||||
@Override
|
||||
public int compare(TorrentPiece piece1, TorrentPiece piece2) {
|
||||
if (piece1 == null)
|
||||
return Integer.MAX_VALUE;
|
||||
if (piece2 == null)
|
||||
return Integer.MAX_VALUE;
|
||||
return piece2.getIndex() - piece1.getIndex();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.util.comparator;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Random comparator, values returned on each call are always different.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
|
||||
*/
|
||||
public class RandomComparator implements Comparator<Object> {
|
||||
/**
|
||||
* Shared instance
|
||||
*/
|
||||
public static final RandomComparator SHARED_INSTANCE = new RandomComparator();
|
||||
|
||||
/**
|
||||
* Random number generator
|
||||
*/
|
||||
private final Random random = new Random();
|
||||
|
||||
@Override
|
||||
public int compare(Object o1, Object o2) {
|
||||
return random.nextInt(250 * 10 * (random.nextInt(100) + 1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.bittorrent.protocol.tracker;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import net.torrent.protocol.tracker.HttpTorrentTrackerAnnouncer;
|
||||
import net.torrent.torrent.Torrent;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class HttpTorrentTrackerAnnouncerTest {
|
||||
@Test
|
||||
public void testAnnounce() throws IOException, URISyntaxException,
|
||||
InterruptedException {
|
||||
final Torrent torrent = Torrent
|
||||
.load(new File(
|
||||
"src/test/resources/Tim Besamusca - Running Away EP Urban Sickness Audio USA1008.torrent"));
|
||||
final HttpTorrentTrackerAnnouncer announcer = new HttpTorrentTrackerAnnouncer();
|
||||
System.out.println(announcer.announce(torrent, torrent.getTrackers()
|
||||
.iterator().next()));
|
||||
|
||||
Thread.sleep(10 * 60 * 1000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.bittorrent.util.comparator;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import net.torrent.util.comparator.RandomComparator;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class RandomComparatorTest {
|
||||
@Test
|
||||
public void testCompare() {
|
||||
final RandomComparator comparator = RandomComparator.SHARED_INSTANCE;
|
||||
|
||||
Assert.assertNotSame(comparator.compare(null, null),
|
||||
comparator.compare(null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSorting() {
|
||||
/*
|
||||
* List<String> list1 = Arrays.asList("a", "b", "c", "d"); List<String>
|
||||
* list2 = Arrays.asList("a", "b", "c", "d"); Collections.sort(list1,
|
||||
* RandomComparator.SHARED_INSTANCE); Collections.sort(list2,
|
||||
* RandomComparator.SHARED_INSTANCE);
|
||||
* Assert.assertFalse(list1.equals(list2));
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import net.torrent.BitTorrentClient;
|
||||
import net.torrent.BitTorrentClientFactory;
|
||||
import net.torrent.torrent.Torrent;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class PeerWireManagerTest {
|
||||
@Test
|
||||
public void testPeerWire() throws IOException, InterruptedException,
|
||||
URISyntaxException {
|
||||
final Torrent torrent = Torrent.load(new File(
|
||||
"src/test/resources/oth.s01e13.avi.torrent"));
|
||||
System.out.println(torrent.getInfoHash());
|
||||
|
||||
final BitTorrentClient client = new BitTorrentClientFactory(torrent)
|
||||
.newBitTorrentClient();
|
||||
client.start(new InetSocketAddress("192.168.1.100", 25944));
|
||||
// client.start(new InetSocketAddress("192.168.1.110", 51413));
|
||||
|
||||
Thread.sleep(60 * 1000 * 30);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2011 Rogiel Josias Sulzbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.torrent.protocol.peerwire;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import net.torrent.BitTorrentClient;
|
||||
import net.torrent.BitTorrentClientFactory;
|
||||
import net.torrent.torrent.Torrent;
|
||||
|
||||
public class TorrentProfile {
|
||||
public static void main(String[] args) throws IOException,
|
||||
InterruptedException, URISyntaxException {
|
||||
final Torrent torrent = Torrent.load(new File(
|
||||
"src/test/resources/oth.s01e13.avi.torrent"));
|
||||
System.out.println(torrent.getInfoHash());
|
||||
|
||||
// System.out.println(torrent.getPiece(700).getParts()[3]);
|
||||
// for (final TorrentPart part : torrent.getPiece(700).getParts()) {
|
||||
// System.out.println(part);
|
||||
// }
|
||||
|
||||
final BitTorrentClient client = new BitTorrentClientFactory(torrent)
|
||||
.newBitTorrentClient();
|
||||
client.start(new InetSocketAddress("192.168.1.100", 25944));
|
||||
// client.start(new InetSocketAddress("192.168.1.110", 51413));
|
||||
|
||||
Thread.sleep(60 * 1000 * 30);
|
||||
}
|
||||
}
|
||||
3
src/test/resources/.gitignore
vendored
Normal file
3
src/test/resources/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/oth.s01e13.avi.torrent
|
||||
/One Tree Hill - 01x13 - (HD) - Hanging By A Mome.(Português).srt.torrent
|
||||
/Tim Besamusca - Running Away EP Urban Sickness Audio USA1008.torrent
|
||||
Reference in New Issue
Block a user