1
0
mirror of https://github.com/Rogiel/torrent4j synced 2025-12-07 08:02:49 +00:00

Change-Id: I0f4f7ffe65dcfbaaaa792ebb43566c7d07c7569d

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

View File

@@ -0,0 +1,173 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent;
import java.net.InetSocketAddress;
import java.util.Timer;
import java.util.TimerTask;
import net.torrent.protocol.algorithm.TorrentAlgorithm;
import net.torrent.protocol.peerwire.PeerWireManager;
import net.torrent.protocol.peerwire.manager.TorrentManager;
import net.torrent.torrent.context.TorrentContext;
import net.torrent.torrent.context.TorrentPeer;
/**
* This is the main class used to controll your torrent transfer. It is not
* recommended to directly instantiate this class, instead use
* {@link BitTorrentClientFactory} to create new instances.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class BitTorrentClient implements Runnable {
/**
* Configuration of an BitTorrentClient.
*/
private final BitTorrentConfiguration config;
/**
* The torrent context
*/
private final TorrentContext context;
/**
* The torrent manager
*/
private final TorrentManager manager;
/**
* The peer wire protocol manager
*/
private final PeerWireManager peerWire;
/**
* The torrent algorithm
*/
private final TorrentAlgorithm algorithm;
/**
* Timer used to create new connections
*/
private final Timer connectorTimer = new Timer();
/**
* Creates a new instance
*
* @param config
* the configuration
* @param manager
* the torrent manager
* @param peerWire
* the peer wire protocol manager
* @param algorithm
* the torrent algorithm
*/
public BitTorrentClient(final BitTorrentConfiguration config,
TorrentManager manager, PeerWireManager peerWire,
TorrentAlgorithm algorithm) {
this.config = config;
this.context = manager.getContext();
this.manager = manager;
this.peerWire = peerWire;
this.algorithm = algorithm;
}
/**
* Start this torrent
*/
public void start() {
start((InetSocketAddress[]) null);
}
/**
* Start this torrent. Once network is up, tries to connect to all the peers
* in <tt>addrs</tt>.
*
* @param addrs
* addresses
*/
public void start(InetSocketAddress... addrs) {
if (config.getListenPort() > 0)
peerWire.listen(config.getListenPort());
// run every 10 seconds - only 1 connection per turn
connectorTimer.schedule(new ConnectorTimerTask(), 0, 10 * 1000);
if (addrs != null)
for (final InetSocketAddress addr : addrs) {
peerWire.connect(addr);
}
}
/**
* Task that creates new connections at a certain repeat rate. Only one
* connection per turn.
*
* @author Rogiel Josias Sulzbach (<a
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
*/
public class ConnectorTimerTask extends TimerTask {
@Override
public void run() {
TorrentPeer peer = null;
while ((peer = algorithm.getPeerAlgorithm().connect()) != null) {
peerWire.connect(peer.getSocketAddress());
}
}
}
@Override
public void run() {
this.start();
}
/**
* Get the torrent context
*
* @return the torrent context
*/
public TorrentContext getContext() {
return context;
}
/**
* Get the torrent manager
*
* @return the torrent manager
*/
public TorrentManager getManager() {
return manager;
}
/**
* The peerwire manager
*
* @return the peerwire manager
*/
public PeerWireManager getPeerWire() {
return peerWire;
}
/**
* The torret
*
* @return
*/
public BitTorrentConfiguration getConfig() {
return config;
}
public TorrentAlgorithm getAlgorithm() {
return algorithm;
}
}

View File

@@ -0,0 +1,231 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import net.torrent.protocol.algorithm.TorrentAlgorithm;
import net.torrent.protocol.algorithm.impl.TorrentStdAlgorithm;
import net.torrent.protocol.datastore.TorrentDatastore;
import net.torrent.protocol.datastore.impl.PlainTorrentDatastore;
import net.torrent.protocol.peerwire.PeerWireManager;
import net.torrent.protocol.peerwire.manager.TorrentManager;
import net.torrent.torrent.Torrent;
import net.torrent.torrent.context.TorrentContext;
/**
* Factory class for {@link BitTorrentClient}.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class BitTorrentClientFactory {
/**
* The client's configuration
*/
private BitTorrentConfiguration config = new BitTorrentConfiguration();
/**
* The torrent context
*/
private final TorrentContext context;
/**
* The torrent datastore
*/
private TorrentDatastore datastore;
/**
* The torrent manager
*/
private TorrentManager manager;
/**
* The torrent algorithm
*/
private TorrentAlgorithm algorithm;
/**
* Creates a new standard {@link BitTorrentClient BitTorrent client}
*
* @param file
* the torrent file
* @return a new client
* @throws IOException
* @throws URISyntaxException
*/
public static BitTorrentClient newStandardBitTorrentClient(File file)
throws IOException, URISyntaxException {
return new BitTorrentClientFactory(Torrent.load(file))
.newBitTorrentClient();
}
/**
* Creates a new factory instance
*
* @param torrent
* the torrent
*/
public BitTorrentClientFactory(final Torrent torrent) {
context = new TorrentContext(torrent);
datastore = new PlainTorrentDatastore(new File("store.bin"));
manager = new TorrentManager(context, datastore);
algorithm = new TorrentStdAlgorithm(manager);
}
/**
* Creates a new factory instance
*
* @param torrent
* the torrent
* @param algorithm
* the torrent algorithm
*/
public BitTorrentClientFactory(final Torrent torrent,
final TorrentAlgorithm algorithm) {
context = new TorrentContext(torrent);
datastore = new PlainTorrentDatastore(null);
manager = new TorrentManager(context, datastore);
this.algorithm = algorithm;
}
/**
* Creates a new factory instance
*
* @param torrent
* the torrent
* @param datastore
* the torrent datastore
*/
public BitTorrentClientFactory(final Torrent torrent,
TorrentDatastore datastore) {
context = new TorrentContext(torrent);
this.datastore = datastore;
manager = new TorrentManager(context, datastore);
algorithm = new TorrentStdAlgorithm(manager);
}
/**
* Creates a new factory instance
*
* @param torrent
* the torrent
* @param datastore
* the torrent datastore
* @param algorithm
* the torrent algorithm
*/
public BitTorrentClientFactory(final Torrent torrent,
TorrentDatastore datastore, final TorrentAlgorithm algorithm) {
context = new TorrentContext(torrent);
this.datastore = datastore;
manager = new TorrentManager(context, datastore);
this.algorithm = algorithm;
}
/**
* Create the {@link BitTorrentClient} object
*
* @return the created {@link BitTorrentClient}
*/
public BitTorrentClient newBitTorrentClient() {
final PeerWireManager peerWire = new PeerWireManager(manager, algorithm);
return new BitTorrentClient(config, manager, peerWire, algorithm);
}
/**
* Get the client configuration
*
* @return the client configuration
*/
public BitTorrentConfiguration getConfig() {
return config;
}
/**
* Set the client configuration
*
* @param config
* the client configuration
*/
public void setConfig(BitTorrentConfiguration config) {
this.config = config;
}
/**
* Get the datastore
*
* @return the datastore
*/
public TorrentDatastore getDatastore() {
return datastore;
}
/**
* Set the datastore
*
* @param datastore
* the datastore
*/
public void setDatastore(TorrentDatastore datastore) {
this.datastore = datastore;
}
/**
* Get the torrent manager
*
* @return the torrent manager
*/
public TorrentManager getManager() {
return manager;
}
/**
* Set the torrent manager
*
* @param manager
* the torrent manager
*/
public void setManager(TorrentManager manager) {
this.manager = manager;
}
/**
* Get the torrent algorithm
*
* @return the torrent algorithm
*/
public TorrentAlgorithm getAlgorithm() {
return algorithm;
}
/**
* Set the torrent algorithm
*
* @param algorithm
* the torrent algorithm
*/
public void setAlgorithm(TorrentAlgorithm algorithm) {
this.algorithm = algorithm;
}
/**
* Get the torrent context
*
* @return the torrent context
*/
public TorrentContext getContext() {
return context;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent;
/**
* Configurations for an {@link BitTorrentClient BitTorrent client} instance.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class BitTorrentConfiguration {
/**
* Default peerwire listen port
*/
private int listenPort = 58462;
/**
* Default peer id
*/
private byte[] peerID = null;
/**
* Get the port
*
* @return the port
*/
public int getListenPort() {
return listenPort;
}
/**
* Set the listening port for the server.<br />
* 0 will disable server.
*
* @param listenPort
* the port
*/
public void setListenPort(int listenPort) {
this.listenPort = listenPort;
}
/**
* Get the peer id.
*
* @return the peer id
*/
public byte[] getPeerID() {
return peerID;
}
/**
* Set the peer id. If null a random peerid will be generated.
*
* @param peerID
* the peerid. Can be null.
*/
public void setPeerID(byte[] peerID) {
this.peerID = peerID;
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.algorithm;
import net.torrent.protocol.peerwire.handler.PeerWireAlgorithmHandler;
/**
* An {@link TorrentAlgorithm} defines the rules for download, upload and
* connection management. These algorithms provide limited control, in the
* boundaries of standard torrent behavior. If you wish more control, at the
* protocol layer, try implementing a new {@link PeerWireAlgorithmHandler}.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @see TorrentPeerAlgorithm
* @see TorrentInterestAlgorithm
* @see TorrentPieceDownloadAlgorithm
* @see TorrentPieceUploadAlgorithm
*/
public interface TorrentAlgorithm {
/**
* Creates a new instance of {@link TorrentPeerAlgorithm}.
*
* @return the new {@link TorrentPeerAlgorithm} instance
*/
TorrentPeerAlgorithm getPeerAlgorithm();
/**
* Creates a new instance of {@link TorrentInterestAlgorithm}.
*
* @return the new {@link TorrentInterestAlgorithm} instance
*/
TorrentInterestAlgorithm getInterestAlgorithm();
/**
* Creates a new instance of {@link TorrentPieceDownloadAlgorithm}.
*
* @return the new {@link TorrentPieceDownloadAlgorithm} instance
*/
TorrentPieceDownloadAlgorithm getDownloadAlgorithm();
/**
* Creates a new instance of {@link TorrentPieceUploadAlgorithm}.
*
* @return the new {@link TorrentPieceUploadAlgorithm} instance
*/
TorrentPieceUploadAlgorithm getUploadAlgorithm();
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.algorithm;
import net.torrent.torrent.context.TorrentPeer;
import net.torrent.torrent.context.TorrentPeer.ChokingState;
import net.torrent.torrent.context.TorrentPeer.InterestState;
/**
* Algorithm used to determine the interest and choking in peers.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public interface TorrentInterestAlgorithm {
/**
* Test if we are interested in this peer pieces. Interest is for download
* only.
*
* @param peer
* the peer
* @return our interest
* @see InterestState
*/
InterestState interested(TorrentPeer peer);
/**
* Test if we want to choke this peer. This is normally invoked when we have
* no more interest in the peer pieces.
*
* @param peer
* the peer
* @return the choking state
* @see ChokingState
*/
ChokingState choke(TorrentPeer peer);
}

View File

@@ -0,0 +1,162 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.algorithm;
import net.torrent.torrent.context.TorrentPeer;
import net.torrent.torrent.context.TorrentPeer.ChokingState;
import net.torrent.torrent.context.TorrentPeer.InterestState;
/**
* Algorithm that processes peer interest, choking state and connections.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public interface TorrentPeerAlgorithm {
/**
* Get an peer to be connected. This method is invoked in many situations.
*
* @return the new peer to be connected
* @see TorrentPeer
*/
TorrentPeer connect();
/**
* Once a new peer is discovered, this method is called to test if we want
* to connect to it or not.
*
* @param peer
* the new discovered peer
* @return the desired action
*/
PeerDiscoveredAction discovered(TorrentPeer peer);
/**
* Action to be performed when an peer is idle.
*
* @author Rogiel Josias Sulzbach (<a
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
*/
public enum PeerDiscoveredAction {
/**
* Try to establish an connection to this new peer
*/
CONNECT,
/**
* Remove this peer from list
*/
REMOVE,
/**
* Nothing is done.
*/
NONE;
}
/**
* Test to keep this connection alive. The value sent represents the action
* which the handler will do with the idle peer.
*
* @param peer
* the peer
* @return the action to be done
* @see KeepAliveAction KeepAliveAction for a list of actions
*/
KeepAliveAction keepAlive(TorrentPeer peer);
/**
* Action to be performed when an peer is idle.
*
* @author Rogiel Josias Sulzbach (<a
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
*/
public enum KeepAliveAction {
/**
* Keep this connection alive
*/
KEEP_ALIVE,
/**
* Disconnect the peer. No new connection is made.
*/
DISCONNECT,
/**
* Disconnect the peer AND connects another peer.
*/
CONNECT_NEW_PEER,
/**
* Nothing is done.
*/
NONE;
}
/**
* Called when the peer interested in us changes. Return the desired choke
* or unchoked action.
*
* @param peer
* the peer
* @param interest
* the new interest (it is also already set in the peer object)
* @return the desired choke/unchoke action
* @see ChokingState
*/
ChokingState interested(TorrentPeer peer, InterestState interest);
/**
* Called when the peer choke state change with us (that is, <b>when the
* peer choke or unchoke</b>!).
*
* @param peer
* the peer
* @param state
* the new choking state
* @return the desired action to be taken
* @see PeerChokedAction
*/
PeerChokedAction choked(TorrentPeer peer, ChokingState state);
/**
* Action to be performed when the peer changes it's choke state
*
* @author Rogiel Josias Sulzbach (<a
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
*/
public enum PeerChokedAction {
/**
* Disconnects the current peer and connects a new one
*/
CONNECT_NEW_PEER,
/**
* Only disconnects the peer, does not initiate a new connection with
* anyone.
*/
DISCONNECT,
/**
* Download a new piece (this is only valid if unchoked)
*/
DOWNLOAD,
/**
* Do nothing, ignore.
*/
NONE;
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.algorithm;
import net.torrent.protocol.peerwire.handler.PeerWireAlgorithmHandler;
import net.torrent.torrent.TorrentPart;
import net.torrent.torrent.TorrentPiece;
import net.torrent.torrent.context.TorrentPeer;
/**
* This algorithm is used to return the {@link TorrentPart part} for download
* and validates if an certain {@link TorrentPiece piece} is complete. Please
* note that this algorithm DOES NOT do the checksum in the piece! The checksum
* is done at the {@link PeerWireAlgorithmHandler handler} level.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public interface TorrentPieceDownloadAlgorithm {
/**
* Return the next part desired for download
*
* @param peer
* the peer
* @param part
* the part which has completed. Might be null!
* @return the new part
* @see TorrentPart
*/
TorrentPart getNextPart(TorrentPeer peer, TorrentPart part);
/**
* Issued when an suggestion is received. If wished to accept it, return the
* first part of it, otherwise return null.
*
* @param peer
* the suggesting peer
* @param piece
* the suggested piece
*/
TorrentPart sugested(TorrentPeer peer, TorrentPiece piece);
/**
* Issued when allowed to fast download an given piece, even while choked.
* If multiple pieces are allowed, one call per piece will be done. If
* willing to download pieces, return the first part of the piece.
*
* @param peer
* the allowing peer
* @param piece
* the allowed piece
*/
TorrentPart allowedFast(TorrentPeer peer, TorrentPiece piece);
/**
* Test if an certain piece has all its parts already download. If true, a
* checksum will be performed and a message informing we have this piece
* will be broadcasted. This call is only valid once the next part has
* already been taken.
*
* @param peer
* the peer
* @param piece
* the piece to test
* @return true if complete, false otherwise.
*/
boolean isComplete(TorrentPeer peer, TorrentPiece piece);
/**
* Called when an piece is complete but found to be corrupted
*
* @param peer
* the peer who send the piece (more precisely the completing
* part)
* @param piece
* the piece
* @return the action to be performed
* @see CorruptedAction
*/
CorruptedAction corrupted(TorrentPeer peer, TorrentPiece piece);
/**
* Actions to be taken when a corrupted piece is downloaded.
*
* @author Rogiel Josias Sulzbach (<a
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
*/
public enum CorruptedAction {
/**
* Only disconnects the peer, does not initiate a new connection with
* anyone.
*/
DISCONNECT,
/**
* Disconnects the current peer and connects a new one
*/
CONNECT_NEW_PEER,
/**
* Choke this peer
*/
CHOKE,
/**
* Retry to download the piece
*/
CONTINUE,
/**
* Do nothing, ignore. Might cause peer to become idle.
*/
CANCEL;
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.algorithm;
import net.torrent.torrent.TorrentPart;
import net.torrent.torrent.context.TorrentPeer;
/**
* Algorithm used for upload management. It verifies if we have interest in
* uploading an piece and handles cancel requests. TODO how to handle cancels?
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public interface TorrentPieceUploadAlgorithm {
/**
* Called when an peer has requested a piece to be uploaded.
*
* @param peer
* the peer requesting this piece
* @param part
* the part requested
* @return true if allowed to upload, false otherwise.
*/
RequestAction request(TorrentPeer peer, TorrentPart part);
public enum RequestAction {
/**
* Only disconnects the peer, does not initiate a new connection with
* anyone.
*/
DISCONNECT,
/**
* Disconnects the current peer and connects a new one
*/
CONNECT_NEW_PEER,
/**
* Reject this request (only if supports Fast Extension). If not
* supported will fall back to {@link RequestAction#NONE}
*/
REJECT,
/**
* Choke this peer
*/
CHOKE,
/**
* Upload a new piece (this is only valid if unchoked)
*/
UPLOAD,
/**
* Do nothing, ignore.
*/
NONE;
}
/**
* Cancels a part request.
*
* @param peer
* the peer
* @param part
* the part
* @return TODO
*/
boolean cancel(TorrentPeer peer, TorrentPart part);
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.algorithm.impl;
import net.torrent.protocol.algorithm.TorrentAlgorithm;
import net.torrent.protocol.algorithm.TorrentInterestAlgorithm;
import net.torrent.protocol.algorithm.TorrentPeerAlgorithm;
import net.torrent.protocol.algorithm.TorrentPieceDownloadAlgorithm;
import net.torrent.protocol.algorithm.TorrentPieceUploadAlgorithm;
import net.torrent.protocol.peerwire.manager.TorrentManager;
/**
* Standard torrent algorithm
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class TorrentStdAlgorithm implements TorrentAlgorithm {
private final TorrentPeerAlgorithm peerAlgorithm;
private final TorrentInterestAlgorithm interestAlgorithm;
private final TorrentPieceDownloadAlgorithm downloadAlgorithm;
private final TorrentPieceUploadAlgorithm uploadAlgorithm;
public TorrentStdAlgorithm(final TorrentManager manager) {
peerAlgorithm = new TorrentStdPeerAlgorithm(manager);
interestAlgorithm = new TorrentStdInterestAlgorithm(manager);
downloadAlgorithm = new TorrentStdPieceDownloadAlgorithm(manager);
uploadAlgorithm = new TorrentStdPieceUploadAlgorithm(manager);
}
@Override
public TorrentPeerAlgorithm getPeerAlgorithm() {
return peerAlgorithm;
}
@Override
public TorrentInterestAlgorithm getInterestAlgorithm() {
return interestAlgorithm;
}
@Override
public TorrentPieceDownloadAlgorithm getDownloadAlgorithm() {
return downloadAlgorithm;
}
@Override
public TorrentPieceUploadAlgorithm getUploadAlgorithm() {
return uploadAlgorithm;
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.algorithm.impl;
import net.torrent.protocol.algorithm.TorrentInterestAlgorithm;
import net.torrent.protocol.peerwire.manager.TorrentManager;
import net.torrent.torrent.context.TorrentPeer;
import net.torrent.torrent.context.TorrentPeer.ChokingState;
import net.torrent.torrent.context.TorrentPeer.InterestState;
/**
* Standard torrent interest algorithm
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class TorrentStdInterestAlgorithm implements TorrentInterestAlgorithm {
@SuppressWarnings("unused")
private final TorrentManager manager;
public TorrentStdInterestAlgorithm(TorrentManager manager) {
this.manager = manager;
}
@Override
public InterestState interested(TorrentPeer peer) {
// if(peer.getPort() == 25944)
// return InterestState.UNINTERESTED;
return InterestState.INTERESTED;
}
@Override
public ChokingState choke(TorrentPeer peer) {
return ChokingState.UNCHOKED;
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.algorithm.impl;
import net.torrent.protocol.algorithm.TorrentPeerAlgorithm;
import net.torrent.protocol.peerwire.manager.TorrentManager;
import net.torrent.torrent.context.TorrentPeer;
import net.torrent.torrent.context.TorrentPeer.ChokingState;
import net.torrent.torrent.context.TorrentPeer.InterestState;
/**
* Standard torrent peer algorithm
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class TorrentStdPeerAlgorithm implements TorrentPeerAlgorithm {
@SuppressWarnings("unused")
private final TorrentManager manager;
public TorrentStdPeerAlgorithm(TorrentManager manager) {
this.manager = manager;
}
@Override
public TorrentPeer connect() {
return null;
}
@Override
public PeerDiscoveredAction discovered(TorrentPeer peer) {
return PeerDiscoveredAction.CONNECT;
}
@Override
public KeepAliveAction keepAlive(TorrentPeer peer) {
return KeepAliveAction.KEEP_ALIVE;
}
@Override
public ChokingState interested(TorrentPeer peer, InterestState interest) {
switch (interest) {
case INTERESTED:
return ChokingState.UNCHOKED;
case UNINTERESTED:
return ChokingState.CHOKED;
}
return null;
}
@Override
public PeerChokedAction choked(TorrentPeer peer, ChokingState state) {
switch (state) {
case CHOKED:
return PeerChokedAction.NONE;
case UNCHOKED:
return PeerChokedAction.DOWNLOAD;
}
return null;
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.algorithm.impl;
import java.util.HashSet;
import java.util.Set;
import net.torrent.protocol.algorithm.TorrentPieceDownloadAlgorithm;
import net.torrent.protocol.peerwire.manager.TorrentManager;
import net.torrent.torrent.TorrentPart;
import net.torrent.torrent.TorrentPiece;
import net.torrent.torrent.context.TorrentPeer;
import net.torrent.torrent.piece.PieceSelector;
import net.torrent.torrent.piece.RandomPieceSelector;
/**
* This standard implementation of {@link TorrentPieceDownloadAlgorithm} chooses
* a random missing piece and tries to download all the parts from the same
* peer, following the standard behavior of most of torrent clients.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class TorrentStdPieceDownloadAlgorithm implements
TorrentPieceDownloadAlgorithm {
/**
* The torrent manager
*/
private final TorrentManager manager;
// private final TorrentContext context;
// private final Torrent torrent;
/**
* This selector is used to find the next piece to be downloaded. Parts are
* managed inside this algorithm.
*/
private final PieceSelector selector;
/**
* Maps all unchecked completed pieces. The piece is removed from the list
* once
* {@link TorrentPieceDownloadAlgorithm#isComplete(TorrentPeer, TorrentPiece)}
* is called.
*/
private Set<TorrentPiece> completedPieces = new HashSet<TorrentPiece>();
/**
* Creates a new instance of this algorithm.
*
* @param manager
* the torrent manager instance. With this object is possible to
* retrieve current downloads/uploads and connections.
*/
public TorrentStdPieceDownloadAlgorithm(TorrentManager manager) {
this.manager = manager;
// this.context = this.manager.getContext();
// this.torrent = this.manager.getTorrent();
selector = new RandomPieceSelector(manager);
}
@Override
public TorrentPart getNextPart(TorrentPeer peer, TorrentPart part) {
if (part != null) {
if (part.isLast()) {
completedPieces.add(part.getPiece());
} else {
return part.getNextPart();
}
}
TorrentPiece piece = selector.select(peer);
if (piece == null)
// no piece, return null. The default handler will check, again, the
// interest on this peer.
return null;
return piece.getFirstPart();
}
@Override
public TorrentPart sugested(TorrentPeer peer, TorrentPiece piece) {
return piece.getFirstPart();
}
@Override
public TorrentPart allowedFast(TorrentPeer peer, TorrentPiece piece) {
return piece.getFirstPart();
}
@Override
public boolean isComplete(TorrentPeer peer, TorrentPiece piece) {
if (manager.getContext().getBitfield().hasPiece(piece))
return true;
// minimum overhead possible, will return true if was on list
return completedPieces.remove(piece);
}
@Override
public CorruptedAction corrupted(TorrentPeer peer, TorrentPiece piece) {
// TODO ban peer sending many corrupted pieces
return CorruptedAction.CANCEL;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.algorithm.impl;
import net.torrent.protocol.algorithm.TorrentPieceUploadAlgorithm;
import net.torrent.protocol.peerwire.manager.TorrentManager;
import net.torrent.torrent.TorrentPart;
import net.torrent.torrent.context.TorrentPeer;
/**
* Standard torrent upload algorithm
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class TorrentStdPieceUploadAlgorithm implements
TorrentPieceUploadAlgorithm {
@SuppressWarnings("unused")
private final TorrentManager manager;
public TorrentStdPieceUploadAlgorithm(TorrentManager manager) {
this.manager = manager;
}
@Override
public RequestAction request(TorrentPeer peer, TorrentPart part) {
return RequestAction.NONE;
}
@Override
public boolean cancel(TorrentPeer peer, TorrentPart part) {
// TODO Auto-generated method stub
return false;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.datastore;
import java.io.IOException;
import java.security.MessageDigest;
import net.torrent.protocol.datastore.impl.FileAwareTorrentDatastore;
import net.torrent.torrent.TorrentPiece;
/**
* Abstract implementation of {@link TorrentDatastore}. The only implemented
* method is {@link TorrentDatastore#checksum(TorrentPiece)} since it always the
* same.
* <p>
* Since it is noticeable that this interface does not use the concept of files,
* it might be much more useful implementing {@link FileAwareTorrentDatastore}
* class instead of this one.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public abstract class AbstractTorrentDatastore implements TorrentDatastore {
@Override
public boolean checksum(TorrentPiece piece) throws IOException {
final MessageDigest digest = piece.getHash().getType()
.getMessageDigest();
if (digest == null)
throw new NullPointerException("Digest is null");
byte[] hash = digest.digest(this.read(piece.asSinglePart()).array());
return piece.getHash().compare(hash);
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.datastore;
import java.io.IOException;
import java.nio.ByteBuffer;
import net.torrent.protocol.datastore.impl.FileAwareTorrentDatastore;
import net.torrent.torrent.TorrentPart;
import net.torrent.torrent.TorrentPiece;
/**
* The datastore is responsible for storing data downloaded from peers and read
* them for further upload. The storage should be done as fast as possible,
* since slow operations might and will slowdown download and upload rates.
* <p>
* Since it is noticeable that this interface does not use the concept of files,
* it might be much more useful implementing {@link FileAwareTorrentDatastore}
* class instead of this interface.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public interface TorrentDatastore {
/**
* Read an segment of data relative to {@link TorrentPart part}.
* <p>
* <b>Warning</b>: Synchronization <b>MUST</b> be dealt by implementations.
*
* @param part
* the part readed
* @return {@link ByteBuffer} with the contents
* @throws IOException
* if exception occur at IO level
*/
ByteBuffer read(TorrentPart part) throws IOException;
/**
* Stores an segment of data relative to {@link TorrentPart part}.
* <p>
* <b>Warning</b>: Synchronization <b>MUST</b> be dealt by implementations.
*
* @param part
* the part to written
* @param buffer
* the buffer containing the data
* @return {@link ByteBuffer} with the contents
* @throws IOException
* if exception occur at IO level
*/
boolean write(TorrentPart part, ByteBuffer buffer) throws IOException;
/**
* Calculates the checksum of an {@link TorrentPiece piece}.
*
* @param piece
* the piece to be tested
* @return true if checksum is correct, false otherwise.
* @throws IOException
* if exception occur at IO level
*/
boolean checksum(TorrentPiece piece) throws IOException;
}

View File

@@ -0,0 +1,118 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.datastore.impl;
import java.io.IOException;
import java.nio.ByteBuffer;
import net.torrent.protocol.datastore.AbstractTorrentDatastore;
import net.torrent.protocol.datastore.TorrentDatastore;
import net.torrent.torrent.Torrent;
import net.torrent.torrent.TorrentFile;
import net.torrent.torrent.TorrentPart;
import net.torrent.util.Range;
/**
* Since {@link TorrentDatastore} does not uses the concept of files when
* storing and/or retrieving data, this abstract class provides abstraction for
* that issue and allows, easily to store and/or retrieve data in separated
* files.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public abstract class FileAwareTorrentDatastore extends
AbstractTorrentDatastore {
/**
* Read data contained withing <tt>file</tt> in the given <tt>range</tt>
*
* @param buffer
* the buffer in which data should be readed.
* @param file
* the file to be read
* @param range
* range of data being readed.
* @throws IOException
* if any exception occur at IO level
*/
protected abstract void read(ByteBuffer buffer, TorrentFile file,
Range range) throws IOException;
/**
* Write data contained in <tt>buffer</tt> to <tt>file</tt> in the given
* <tt>range</tt>
*
* @param buffer
* the buffer in which data should be written.
* @param file
* the file to be written
* @param range
* range of data being written.
* @throws IOException
* if any exception occur at IO level
*/
protected abstract boolean write(ByteBuffer buffer, TorrentFile file,
Range range) throws IOException;
@Override
public ByteBuffer read(TorrentPart part) throws IOException {
final ByteBuffer buffer = allocate(part);
final Torrent torrent = part.getTorrent();
Range partRange = part.asTorrentRange();
for (final TorrentFile file : torrent.getFiles()) {
final Range fileRange = file.asTorrentRange();
if (fileRange.intersects(partRange)) {
final Range range = part.asFileRange(file).intersection(
fileRange);
buffer.limit((int) (buffer.position() + range.getLength()));
this.read(buffer, file, range);
}
}
buffer.flip();
return buffer;
}
@Override
public boolean write(TorrentPart part, ByteBuffer buffer)
throws IOException {
final Torrent torrent = part.getTorrent();
Range partRange = part.asTorrentRange();
for (final TorrentFile file : torrent.getFiles()) {
final Range fileRange = file.asFileRange();
if (fileRange.intersects(partRange)) {
final Range range = part.asFileRange(file);
buffer.limit((int) (buffer.position() + range.getLength()));
if (!this.write(buffer, file, range))
return false;
}
}
return true;
}
/**
* Allocates a new {@link ByteBuffer}.
*
* @param part
* the {@link TorrentPart}s
* @return a newly allocated ByteBuffer
*/
private ByteBuffer allocate(TorrentPart part) {
return ByteBuffer.allocate(part.getLength());
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.datastore.impl;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import net.torrent.protocol.datastore.AbstractTorrentDatastore;
import net.torrent.torrent.TorrentPart;
/**
* Stores data into a single plain file, ignoring any file metadata into the
* original .torrent file.
* <p>
* Unless for a single torrent file, data might (and possibly will) not be
* readable outside this library.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class PlainTorrentDatastore extends AbstractTorrentDatastore {
/**
* The target file
*/
private final File file;
/**
* The file channel
*/
private FileChannel channel;
/**
* Creates a new datastore instance
*
* @param file
* the single file to be used
*/
public PlainTorrentDatastore(File file) {
this.file = file;
try {
this.channel = new RandomAccessFile(this.file, "rw").getChannel();
} catch (FileNotFoundException e) {
}
}
@Override
public synchronized ByteBuffer read(TorrentPart part) throws IOException {
final ByteBuffer buffer = ByteBuffer.allocate(part.getLength());
synchronized (channel) {
channel.position(part.getOffset());
while (buffer.hasRemaining()) {
if (channel.read(buffer) == -1) // EOF
break;
}
}
buffer.flip();
return buffer;
}
@Override
public synchronized boolean write(TorrentPart part, ByteBuffer buffer)
throws IOException {
synchronized (channel) {
channel.position(part.getOffset());
while (buffer.hasRemaining()) {
if (channel.write(buffer) == -1)
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.datastore.impl;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Map;
import net.torrent.torrent.TorrentFile;
import net.torrent.util.Range;
/**
* An simple implementation for torrent. Stores files in local file system.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class SimpleTorrentDatastore extends FileAwareTorrentDatastore {
private final Map<TorrentFile, FileChannel> cache = new HashMap<TorrentFile, FileChannel>();
private final File root;
public SimpleTorrentDatastore(File root) {
this.root = root;
}
public SimpleTorrentDatastore() {
this(new File("."));
}
@Override
protected void read(ByteBuffer buffer, TorrentFile file, Range range)
throws IOException {
final FileChannel channel = cache(file);
synchronized (channel) {
channel.position(file.getOffset() + range.getStart());
while (buffer.hasRemaining()) {
if (channel.read(buffer) == -1) // EOF
break;
}
}
// don't flip the buffer!
}
@Override
protected boolean write(ByteBuffer buffer, TorrentFile file, Range range)
throws IOException {
final FileChannel channel = cache(file);
synchronized (channel) {
channel.position(file.getOffset() + range.getStart());
while (buffer.hasRemaining()) {
if (channel.write(buffer) == -1)
return false;
}
}
return true;
}
/**
* Retrieve an cached {@link FileChannel channel}. If not cached, a new will
* be open in read/write mode and cached.
*
* @param file
* the {@link TorrentFile file}
* @return the cached {@link FileChannel channel}
* @throws FileNotFoundException
* if parent folder does not exists.
*/
private FileChannel cache(TorrentFile file) throws FileNotFoundException {
FileChannel channel = cache.get(file);
if (channel == null) {
channel = new RandomAccessFile(new File(root,
file.getRelativePath()), "rw").getChannel();
cache.put(file, channel);
}
return channel;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.datastore.impl;
import java.io.IOException;
import java.nio.ByteBuffer;
import net.torrent.protocol.datastore.AbstractTorrentDatastore;
import net.torrent.torrent.TorrentPart;
import net.torrent.torrent.TorrentPiece;
/**
* This is much like the <tt>/dev/null</tt> and <tt>/dev/zero</tt> pseudo-file
* in UNIX systems. All data written is discarded and all readed data is
* composed of <tt>NULL</tt> (<tt>0x00</tt>). Checksums always succeed.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class VoidTorrentDatastore extends AbstractTorrentDatastore {
@Override
public ByteBuffer read(TorrentPart part) throws IOException {
return ByteBuffer.allocate(part.getLength());
}
@Override
public boolean write(TorrentPart part, ByteBuffer buffer) {
return true;
}
@Override
public boolean checksum(TorrentPiece piece) throws IOException {
return true;
}
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.dht;
/**
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class DHTClient {
}

View File

@@ -0,0 +1,197 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import net.torrent.protocol.algorithm.TorrentAlgorithm;
import net.torrent.protocol.peerwire.handler.PeerWireAlgorithmHandler;
import net.torrent.protocol.peerwire.manager.TorrentManager;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
/**
* Manager for the peerwire protocol. Can be used to start or stop the server
* and initiate new connections.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class PeerWireManager {
/**
* The Netty client's {@link ClientBootstrap bootstrap}
*/
private final ClientBootstrap client = new ClientBootstrap(
new NioClientSocketChannelFactory(Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
/**
* The Netty server's {@link ServerBootstrap bootstrap}
*/
private final ServerBootstrap server = new ServerBootstrap(
new NioServerSocketChannelFactory(Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
/**
* The server's listen address
*/
private InetSocketAddress listenAddress;
/**
* The server's channel
*/
private Channel serverChannel;
/**
* The pipeline channel factory
*/
private final PeerWirePipelineFactory pipelineFactory;
/**
* Creates a new instance
*
* @param manager
* the torrent manager
* @param algorithm
* the torrent algorithm
* @param listenAddress
* the server's listen address
*/
public PeerWireManager(TorrentManager manager, ChannelHandler algorithm,
InetSocketAddress listenAddress) {
pipelineFactory = new PeerWirePipelineFactory(manager, algorithm);
client.setPipelineFactory(pipelineFactory);
server.setPipelineFactory(pipelineFactory);
}
/**
* Creates a new instance
*
* @param manager
* the torrent manager
* @param algorithm
* the torrent algorithm
*/
public PeerWireManager(TorrentManager manager, TorrentAlgorithm algorithm) {
this(manager, new PeerWireAlgorithmHandler(manager, algorithm), null);
}
/**
* Listen the server to the given port
*
* @param port
* the port
* @return the server {@link Channel}.
*/
public Channel listen(int port) {
if (listenAddress == null)
listenAddress = new InetSocketAddress(port);
return (serverChannel = server.bind(listenAddress));
}
/**
* Connect to a new client
*
* @param address
* the address
* @return the {@link ChannelFuture} for monitoring the connection progress
*/
public ChannelFuture connect(InetSocketAddress address) {
return client.connect(address);
}
/**
* Connect to a new client and wait until is complete.
*
* @param address
* the peer address
* @param wait
* wait time
* @param unit
* unit of <tt>wait</tt>
* @return the newly created {@link Channel}
*/
public Channel connectWait(InetSocketAddress address, long wait,
TimeUnit unit) {
final ChannelFuture future = connect(address);
if (future.awaitUninterruptibly(wait, unit)) {
if (future.isSuccess())
return future.getChannel();
}
return null;
}
/**
* Close the server and the client, and then release the external resources.
*/
public void close() {
if (serverChannel != null)
serverChannel.close().awaitUninterruptibly();
client.releaseExternalResources();
}
/**
* Get the server's listening channel
*
* @return
*/
public Channel getServerChannel() {
return serverChannel;
}
/**
* Get the Netty client's {@link ClientBootstrap bootstrap}
*
* @return the {@link ClientBootstrap}
*/
protected ClientBootstrap getClientBootstrap() {
return client;
}
/**
* Get the Netty server's {@link ServerBootstrap bootstrap}
*
* @return the {@link ServerBootstrap}
*/
protected ServerBootstrap getServerBootstrap() {
return server;
}
/**
* Return the current server's listening address
*
* @return the listen address
*/
public InetSocketAddress getListenAddress() {
return listenAddress;
}
/**
* Get the current server's listening address
*
* @param listenAddress
* the listen address
*/
public void setListenAddress(InetSocketAddress listenAddress) {
this.listenAddress = listenAddress;
}
}

View File

@@ -0,0 +1,401 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire;
import java.nio.ByteBuffer;
import java.util.BitSet;
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
import net.torrent.protocol.peerwire.message.BitfieldMessage;
import net.torrent.protocol.peerwire.message.CancelMessage;
import net.torrent.protocol.peerwire.message.ChokeMessage;
import net.torrent.protocol.peerwire.message.HandshakeMessage;
import net.torrent.protocol.peerwire.message.HaveMessage;
import net.torrent.protocol.peerwire.message.InterestedMessage;
import net.torrent.protocol.peerwire.message.KeepAliveMessage;
import net.torrent.protocol.peerwire.message.NotInterestedMessage;
import net.torrent.protocol.peerwire.message.PieceMessage;
import net.torrent.protocol.peerwire.message.PortMessage;
import net.torrent.protocol.peerwire.message.RequestMessage;
import net.torrent.protocol.peerwire.message.UnchokeMessage;
import net.torrent.protocol.peerwire.message.fast.AllowedFastMessage;
import net.torrent.protocol.peerwire.message.fast.HaveAllMessage;
import net.torrent.protocol.peerwire.message.fast.HaveNoneMessage;
import net.torrent.protocol.peerwire.message.fast.RejectMessage;
import net.torrent.protocol.peerwire.message.fast.SuggestPieceMessage;
import net.torrent.torrent.context.TorrentPeer;
import net.torrent.torrent.context.TorrentPeer.ChokingState;
import net.torrent.torrent.context.TorrentPeer.InterestState;
import net.torrent.torrent.context.TorrentPeerCapabilities.TorrentPeerCapability;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
/**
* An PeerWire Peer manages the {@link Channel channel} (the current peer
* connection) and the {@link TorrentPeer torrent peer} object.
* <p>
* This object contains handy methods to send messages to the peer.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class PeerWirePeer {
/**
* The active {@link Channel}
*/
private final Channel channel;
/**
* The active {@link TorrentPeer}. It might no be available until handshake.
*/
private TorrentPeer peer;
/**
* Creates a new instance
*
* @param channel
* the channel
* @param peer
* the peer. Can be null.
*/
public PeerWirePeer(Channel channel, TorrentPeer peer) {
this.channel = channel;
this.peer = peer;
}
/**
* Send an handshake message
*
* @param infohash
* the info hash
* @param peerId
* the peer id
* @param reserved
* the capabilities {@link BitSet}
* @return the {@link ChannelFuture} for the message
*/
public ChannelFuture handshake(byte[] infohash, byte[] peerId,
BitSet reserved) {
return write(new HandshakeMessage(infohash, peerId, reserved));
}
/**
* Send an bitfield message
*
* @param bitset
* the bitfield bits
* @return the {@link ChannelFuture} for the message
*/
public ChannelFuture bitfield(BitSet bitset) {
return write(new BitfieldMessage(bitset));
}
/**
* Send an choke message
*/
public void choke() {
if (peer.getChokingState() == ChokingState.CHOKED)
return;
write(new ChokeMessage()).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future)
throws Exception {
if (future.isSuccess()) {
peer.setChokingState(ChokingState.CHOKED);
}
}
});
}
/**
* Send an unchoke message
*/
public void unchoke() {
if (peer.getChokingState() == ChokingState.UNCHOKED)
return;
write(new UnchokeMessage()).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future)
throws Exception {
if (future.isSuccess()) {
peer.setChokingState(ChokingState.UNCHOKED);
}
}
});
}
/**
* Send an interest message
*/
public void interested() {
if (peer.getInterestState() == InterestState.INTERESTED)
return;
write(new InterestedMessage()).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future)
throws Exception {
if (future.isSuccess()) {
peer.setInterestState(InterestState.INTERESTED);
}
}
});
}
/**
* Send an not interest message
*/
public void uninterested() {
if (peer.getInterestState() == InterestState.UNINTERESTED)
return;
write(new NotInterestedMessage()).addListener(
new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future)
throws Exception {
if (future.isSuccess()) {
peer.setInterestState(InterestState.UNINTERESTED);
}
}
});
}
/**
* Send an request message
*
* @param index
* the piece index
* @param start
* the part start
* @param length
* the part length
* @return the {@link ChannelFuture} for this message
*/
public ChannelFuture request(int index, int start, int length) {
return write(new RequestMessage(index, start, length));
}
/**
* Send an upload message
*
* @param index
* the index
* @param start
* the start
* @param length
* the length
* @param data
* the upload {@link ByteBuffer content}
*/
public void upload(int index, int start, int length, ByteBuffer data) {
this.unchoke();
write(new PieceMessage(index, start, length, data));
}
/**
* Send an have message
*
* @param index
* the piece index
*/
public void have(int index) {
write(new HaveMessage(index));
}
/**
* Send an cancel message
*
* @param index
* the index
* @param start
* the start
* @param length
* the length
*/
public void cancel(int index, int start, int length) {
write(new CancelMessage(index, start, length));
}
/**
* Send an keep alive message
*/
public void keepAlive() {
write(new KeepAliveMessage());
}
// //////////////////////////////////////////////////////////////////////
/**
* *** REQUIRES {@link TorrentPeerCapability#DHT DHT Protocol} ***
* <p>
* Send and port message. The port is used for UDP.
*
* @param port
* the port number
*/
public void port(short port) {
write(new PortMessage(port));
}
/**
* *** REQUIRES {@link TorrentPeerCapability#EXTENSION_PROTOCOL Extension
* Protocol} ***
* <p>
* Send an have none message.
*/
public void haveNone() {
write(new HaveNoneMessage());
}
/**
* *** REQUIRES {@link TorrentPeerCapability#EXTENSION_PROTOCOL Extension
* Protocol} ***
* <p>
* Send an have all message.
*/
public void haveAll() {
write(new HaveAllMessage());
}
/**
* *** REQUIRES {@link TorrentPeerCapability#EXTENSION_PROTOCOL Extension
* Protocol} ***
* <p>
* Send an reject message.
*
* @param index
* the piece index
* @param start
* the part start
* @param length
* the part length
*/
public void reject(int index, int start, int length) {
this.choke();
write(new RejectMessage(index, start, length));
}
/**
* *** REQUIRES {@link TorrentPeerCapability#EXTENSION_PROTOCOL Extension
* Protocol} ***
* <p>
* Send an suggest message
*
* @param index
* the piece index
*/
public void suggest(int index) {
write(new SuggestPieceMessage(index));
}
/**
* *** REQUIRES {@link TorrentPeerCapability#EXTENSION_PROTOCOL Extension
* Protocol} ***
* <p>
* Send an list of pieces allowed to download fast (download even when
* choked)
*
* @param indexes
* the piece indexes
*/
public void allowedFast(int... indexes) {
write(new AllowedFastMessage(indexes));
}
// //////////////////////////////////////////////////////////////////////
/**
* Write an message to the peer
*
* @return an {@link ChannelFuture} to monitor write progress
* @see Channel#write(Object)
*/
public ChannelFuture write(PeerWireWritableMessage message) {
return channel.write(message);
}
/**
* Disconnect the peer
*
* @return an {@link ChannelFuture} to monitor disconnect progress
* @see Channel#diconnect()
*/
public ChannelFuture disconnect() {
return channel.disconnect();
}
/**
* Close the peer {@link Channel channel}
*
* @return an {@link ChannelFuture} to monitor close progress
* @see Channel#close()
*/
public ChannelFuture close() {
return channel.close();
}
/**
* Get the peer {@link Channel channel}
*
* @return the peer active {@link Channel}
*/
public Channel getChannel() {
return channel;
}
/**
* Get the active {@link TorrentPeer}
*
* @return the active peer
*/
public TorrentPeer getTorrentPeer() {
return peer;
}
/**
* Set the active peer
*
* @param peer
* the active peer
*/
public void setTorrentPeer(TorrentPeer peer) {
this.peer = peer;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((channel == null) ? 0 : channel.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
PeerWirePeer other = (PeerWirePeer) obj;
if (channel == null) {
if (other.channel != null)
return false;
} else if (!channel.equals(other.channel))
return false;
return true;
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire;
import static org.jboss.netty.channel.Channels.pipeline;
import net.torrent.protocol.peerwire.codec.PeerWireDecoder;
import net.torrent.protocol.peerwire.codec.PeerWireEncoder;
import net.torrent.protocol.peerwire.handler.PeerWireManagerHeadHandler;
import net.torrent.protocol.peerwire.handler.PeerWireManagerTailHandler;
import net.torrent.protocol.peerwire.manager.TorrentManager;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.handler.logging.LoggingHandler;
import org.jboss.netty.logging.InternalLogLevel;
/**
* The {@link ChannelPipeline} factory for all PeerWire connections.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class PeerWirePipelineFactory implements ChannelPipelineFactory {
/**
* The logging handler
*/
private final LoggingHandler loggingHandler = new LoggingHandler(
InternalLogLevel.WARN);
/**
* The algorithm handler
*/
private final ChannelHandler algorithmHandler;
/**
* The head manager handler
*/
private final PeerWireManagerHeadHandler headManagerHandler;
/**
* The tail manager handler
*/
private final PeerWireManagerTailHandler tailManagerHandler;
/**
* Creates a new instance
*
* @param manager
* the torrent manager
* @param algorithmHandler
* the algorithm handler
*/
public PeerWirePipelineFactory(TorrentManager manager,
ChannelHandler algorithmHandler) {
this.algorithmHandler = algorithmHandler;
this.headManagerHandler = new PeerWireManagerHeadHandler(manager);
this.tailManagerHandler = new PeerWireManagerTailHandler(manager);
}
@Override
public ChannelPipeline getPipeline() throws Exception {
final ChannelPipeline pipeline = pipeline();
// TODO create traffic shape handler
// TODO create firewall handler - block connections from unwanted peers
pipeline.addLast("decoder", new PeerWireDecoder());
pipeline.addLast("encoder", new PeerWireEncoder());
pipeline.addLast("logging", loggingHandler);
pipeline.addLast("head-handler", headManagerHandler);
pipeline.addLast("algorithm", algorithmHandler);
pipeline.addLast("tail-handler", tailManagerHandler);
return pipeline;
}
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.codec;
import net.torrent.protocol.peerwire.message.BitfieldMessage;
import net.torrent.protocol.peerwire.message.CancelMessage;
import net.torrent.protocol.peerwire.message.ChokeMessage;
import net.torrent.protocol.peerwire.message.HandshakeMessage;
import net.torrent.protocol.peerwire.message.HaveMessage;
import net.torrent.protocol.peerwire.message.InterestedMessage;
import net.torrent.protocol.peerwire.message.KeepAliveMessage;
import net.torrent.protocol.peerwire.message.NotInterestedMessage;
import net.torrent.protocol.peerwire.message.PieceMessage;
import net.torrent.protocol.peerwire.message.PortMessage;
import net.torrent.protocol.peerwire.message.RequestMessage;
import net.torrent.protocol.peerwire.message.UnchokeMessage;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.CorruptedFrameException;
import org.jboss.netty.handler.codec.frame.FrameDecoder;
/**
* BitTorrent has two types of message headers:
* <p>
* <h1>Handshake message</h1> Handshake messages are composed of 1 byte, which
* indicates the size of protocol string ("BitTorrent protocol").
* <p>
* <h1>Messages</h1> All other messages are 4-byte integer containing the
* message length, followed by 1 byte opcode.
* <p>
* <p>
* Instances of this class keep channel state content and must not be shared nor
* cached.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class PeerWireDecoder extends FrameDecoder {
private boolean handshaked = false;
@Override
protected Object decode(ChannelHandlerContext ctx, Channel channel,
ChannelBuffer buffer) throws Exception {
buffer.markReaderIndex();
if (!handshaked) {
if (buffer.readableBytes() <= 47) // at least 47 bytes
return null;
final int pstrlen = buffer.readByte();
if (buffer.readableBytes() < pstrlen + 47) {
buffer.resetReaderIndex();
return null;
}
buffer.readerIndex(buffer.readerIndex() - 1);
final HandshakeMessage message = new HandshakeMessage();
message.read(buffer);
handshaked = true;
return message;
} else {
if (buffer.readableBytes() <= 4) {
buffer.resetReaderIndex();
return null;
}
int len = buffer.readInt();
if (len == 0) {
return new KeepAliveMessage();
} else if (buffer.readableBytes() < len) {
buffer.resetReaderIndex();
return null;
}
final byte id = buffer.readByte();
final PeerWireReadableMessage message = getMessage(id);
if (message == null)
// force connection to be closed
throw new CorruptedFrameException("unknown message " + id);
message.read(buffer);
return message;
}
}
/**
* Return the message represented by <tt>id</tt>. Will return null if
* message id is unknown.
*
* @param id
* the id of the message
* @return the message
*/
private PeerWireReadableMessage getMessage(byte id) {
PeerWireReadableMessage message = null;
switch (id) {
case BitfieldMessage.MESSAGE_ID:
message = new BitfieldMessage();
break;
case CancelMessage.MESSAGE_ID:
message = new CancelMessage();
break;
case ChokeMessage.MESSAGE_ID:
message = new ChokeMessage();
break;
case HaveMessage.MESSAGE_ID:
message = new HaveMessage();
break;
case InterestedMessage.MESSAGE_ID:
message = new InterestedMessage();
break;
case NotInterestedMessage.MESSAGE_ID:
message = new NotInterestedMessage();
break;
case PieceMessage.MESSAGE_ID:
message = new PieceMessage();
break;
case PortMessage.MESSAGE_ID:
message = new PortMessage();
break;
case RequestMessage.MESSAGE_ID:
message = new RequestMessage();
break;
case UnchokeMessage.MESSAGE_ID:
message = new UnchokeMessage();
break;
}
return message;
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.codec;
import net.torrent.protocol.peerwire.message.HandshakeMessage;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
/**
* Messages are encoded in {@link PeerWireWritableMessage#write(ChannelBuffer)}
* method. Message length is measured automatically by the encoder.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class PeerWireEncoder extends OneToOneEncoder {
@Override
protected Object encode(ChannelHandlerContext ctx, Channel channel,
Object msg) throws Exception {
ChannelBuffer buffer = ChannelBuffers.dynamicBuffer();
if (!(msg instanceof PeerWireWritableMessage))
return msg;
if (msg instanceof HandshakeMessage) {
final HandshakeMessage message = (HandshakeMessage) msg;
message.write(buffer);
} else {
final PeerWireWritableMessage message = (PeerWireWritableMessage) msg;
buffer.writeInt(0); // allocate 4 bytes for header
message.write(buffer);
int len = buffer.readableBytes();
buffer.setInt(0, len - 4);
}
return buffer;
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.codec;
import java.io.IOException;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* An readable message in the BitTorrent protocol
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public interface PeerWireReadableMessage {
/**
* Read the content of the message contained in this buffer.
*
* @param buffer
* the buffer
* @throws IOException
*/
void read(ChannelBuffer buffer) throws IOException;
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.codec;
import java.io.IOException;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* An writable message in the BitTorrent protocol
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public interface PeerWireWritableMessage {
/**
* Write the content of the message to this buffer.
*
* @param buffer
* the buffer
* @throws IOException
*/
void write(ChannelBuffer buffer) throws IOException;
}

View File

@@ -0,0 +1,481 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.handler;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.BitSet;
import net.torrent.protocol.algorithm.TorrentAlgorithm;
import net.torrent.protocol.algorithm.TorrentInterestAlgorithm;
import net.torrent.protocol.algorithm.TorrentPeerAlgorithm;
import net.torrent.protocol.algorithm.TorrentPieceDownloadAlgorithm;
import net.torrent.protocol.algorithm.TorrentPieceUploadAlgorithm;
import net.torrent.protocol.datastore.TorrentDatastore;
import net.torrent.protocol.peerwire.PeerWirePeer;
import net.torrent.protocol.peerwire.manager.TorrentManager;
import net.torrent.protocol.peerwire.message.BitfieldMessage;
import net.torrent.protocol.peerwire.message.ChokeMessage;
import net.torrent.protocol.peerwire.message.HaveMessage;
import net.torrent.protocol.peerwire.message.InterestedMessage;
import net.torrent.protocol.peerwire.message.KeepAliveMessage;
import net.torrent.protocol.peerwire.message.NotInterestedMessage;
import net.torrent.protocol.peerwire.message.PieceMessage;
import net.torrent.protocol.peerwire.message.RequestMessage;
import net.torrent.protocol.peerwire.message.UnchokeMessage;
import net.torrent.protocol.peerwire.message.fast.AllowedFastMessage;
import net.torrent.protocol.peerwire.message.fast.SuggestPieceMessage;
import net.torrent.torrent.Torrent;
import net.torrent.torrent.TorrentPart;
import net.torrent.torrent.TorrentPiece;
import net.torrent.torrent.context.TorrentPeer;
import net.torrent.torrent.context.TorrentPeer.ChokingState;
import net.torrent.torrent.context.TorrentPeer.InterestState;
import net.torrent.torrent.context.TorrentPeerCapabilities.TorrentPeerCapability;
import net.torrent.util.PeerCallback;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.handler.timeout.IdleStateAwareChannelHandler;
import org.jboss.netty.handler.timeout.IdleStateEvent;
/**
* Standard handler responsible for forwarding calls to {@link TorrentAlgorithm}
* methods. This class handles low-level protocol specific behavior.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class PeerWireAlgorithmHandler extends IdleStateAwareChannelHandler {
/**
* The torrent manager
*/
private final TorrentManager manager;
/**
* The torrent datastore
*/
private final TorrentDatastore datastore;
/**
* The peer algorithm
*/
private final TorrentPeerAlgorithm peerAlgorithm;
/**
* The interest algorithm
*/
private final TorrentInterestAlgorithm interestAlgorithm;
/**
* The download algorithm
*/
private final TorrentPieceDownloadAlgorithm downloadAlgorithm;
/**
* The upload algorithm
*/
private final TorrentPieceUploadAlgorithm uploadAlgorithm;
/**
* Creates a new handler
*
* @param manager
* the torrent manager
* @param algorithm
* the algorithm
*/
public PeerWireAlgorithmHandler(TorrentManager manager,
final TorrentAlgorithm algorithm) {
this.manager = manager;
this.datastore = manager.getDatastore();
this.peerAlgorithm = algorithm.getPeerAlgorithm();
this.interestAlgorithm = algorithm.getInterestAlgorithm();
this.downloadAlgorithm = algorithm.getDownloadAlgorithm();
this.uploadAlgorithm = algorithm.getUploadAlgorithm();
}
@Override
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)
throws Exception {
final PeerWirePeer peer = manager.getPeerManager().getPeer(
e.getChannel());
// TODO handshake with random peer id
peer.handshake(manager.getTorrent().getInfoHash().toByteArray(),
"-TR2050-mcm14ye4h2mq".getBytes(), manager.getContext()
.getCapabilites().toBitSet());
peer.port((short) 1541);
super.channelConnected(ctx, e);
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
throws Exception {
final PeerWirePeer peer = manager.getPeerManager().getPeer(
e.getChannel());
final Object msg = e.getMessage();
if (msg instanceof BitfieldMessage) {
final BitfieldMessage bitfield = (BitfieldMessage) msg;
final BitSet bitset = bitfield.getBitfield();
peer.getTorrentPeer().getBitfield().setBits(bitset);
testInterest(peer);
} else if (msg instanceof InterestedMessage) {
peer.getTorrentPeer()
.setPeerInterestState(InterestState.INTERESTED);
peerIntrestUpdate(peer, peer.getTorrentPeer()
.getPeerInterestState());
} else if (msg instanceof NotInterestedMessage) {
peer.getTorrentPeer().setPeerInterestState(
InterestState.UNINTERESTED);
peerIntrestUpdate(peer, peer.getTorrentPeer()
.getPeerInterestState());
} else if (msg instanceof UnchokeMessage) {
peer.getTorrentPeer().setPeerChokingState(ChokingState.UNCHOKED);
peerChokeUpdate(peer, peer.getTorrentPeer().getPeerChokingState());
} else if (msg instanceof ChokeMessage) {
peer.getTorrentPeer().setPeerChokingState(ChokingState.CHOKED);
peerChokeUpdate(peer, peer.getTorrentPeer().getPeerChokingState());
} else if (msg instanceof RequestMessage) {
final RequestMessage request = (RequestMessage) msg;
final Torrent torrent = manager.getTorrent();
final TorrentPart part = torrent.getPart(request.getIndex(),
request.getStart(), request.getLength());
processRequest(peer, part);
} else if (msg instanceof PieceMessage) {
final PieceMessage pieceMsg = (PieceMessage) msg;
final Torrent torrent = manager.getTorrent();
final TorrentPart part = torrent.getPart(pieceMsg.getIndex(),
pieceMsg.getStart(), pieceMsg.getLength());
processDownload(peer, part, pieceMsg.getBlock());
} else if (msg instanceof HaveMessage) {
final HaveMessage have = (HaveMessage) msg;
final Torrent torrent = manager.getTorrent();
final TorrentPiece piece = torrent.getPiece(have.getPiece());
peer.getTorrentPeer().getBitfield().setPiece(piece, true);
testInterest(peer);
} else if (msg instanceof KeepAliveMessage) {
keepAlive(peer);
} else if (msg instanceof AllowedFastMessage) {
final AllowedFastMessage fast = (AllowedFastMessage) msg;
final Torrent torrent = manager.getTorrent();
for (final int index : fast.getPieces()) {
final TorrentPiece piece = torrent.getPiece(index);
allowedFast(peer, piece);
}
} else if (msg instanceof SuggestPieceMessage) {
final SuggestPieceMessage suggest = (SuggestPieceMessage) msg;
final Torrent torrent = manager.getTorrent();
final TorrentPiece piece = torrent.getPiece(suggest.getPiece());
suggested(peer, piece);
}
super.messageReceived(ctx, e);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
throws Exception {
// any exception thrown, the channel is disconnected.
e.getCause().printStackTrace();
e.getChannel().disconnect();
super.exceptionCaught(ctx, e);
}
@Override
public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e)
throws Exception {
final PeerWirePeer peer = manager.getPeerManager().getPeer(
e.getChannel());
keepAlive(peer);
super.channelIdle(ctx, e);
}
/**
* Test the interest in the peer
*
* @param peer
* the peer
*/
private void testInterest(PeerWirePeer peer) {
switch (interestAlgorithm.interested(peer.getTorrentPeer())) {
case INTERESTED:
peer.interested();
return;
case UNINTERESTED:
testChoke(peer);
return;
}
}
/**
* Test choke interest to this peer
*
* @param peer
* the peer
*/
private void testChoke(PeerWirePeer peer) {
switch (interestAlgorithm.choke(peer.getTorrentPeer())) {
case CHOKED:
peer.choke();
return;
case UNCHOKED:
peer.unchoke();
return;
}
}
/**
* Handles the peer choke interest change.
*
* @param peer
* the peer
* @param state
* the new interest state
*/
private void peerIntrestUpdate(PeerWirePeer peer, InterestState state) {
switch (peerAlgorithm.interested(peer.getTorrentPeer(), state)) {
case CHOKED:
peer.choke();
return;
case UNCHOKED:
peer.unchoke();
return;
}
}
/**
* Handles the peer choke change.
*
* @param peer
* the peer
* @param state
* the choke state
*/
private void peerChokeUpdate(PeerWirePeer peer, ChokingState state) {
switch (peerAlgorithm.choked(peer.getTorrentPeer(), state)) {
case DISCONNECT:
peer.disconnect();
break;
case CONNECT_NEW_PEER:
peer.disconnect();
connect(peerAlgorithm.connect());
return;
case DOWNLOAD:
download(peer,
downloadAlgorithm.getNextPart(peer.getTorrentPeer(), null));
return;
}
}
/**
* Process the upload request
*
* @param peer
* the peer
* @param part
* the part
* @throws IOException
*/
private void processRequest(PeerWirePeer peer, TorrentPart part)
throws IOException {
switch (uploadAlgorithm.request(peer.getTorrentPeer(), part)) {
case DISCONNECT:
peer.disconnect();
break;
case REJECT:
if (!peer.getTorrentPeer().getCapabilities()
.supports(TorrentPeerCapability.FAST_PEERS))
return;
peer.reject(part.getPiece().getIndex(), part.getStart(),
part.getLength());
break;
case CONNECT_NEW_PEER:
peer.disconnect();
connect(peerAlgorithm.connect());
break;
case CHOKE:
peer.choke();
break;
case UPLOAD:
upload(peer, part);
break;
}
}
/**
* Processes an download
*
* @param peer
* the uploader peer
* @param part
* the part
* @param data
* the downloaded content
* @throws IOException
*/
private void processDownload(final PeerWirePeer peer,
final TorrentPart part, ByteBuffer data) throws IOException {
final TorrentPart nextPart = downloadAlgorithm.getNextPart(
peer.getTorrentPeer(), part);
boolean complete = downloadAlgorithm.isComplete(peer.getTorrentPeer(),
part.getPiece());
final ChannelFuture future = download(peer, nextPart);
// store piece
if (!datastore.write(part, data))
return;
if (!complete)
return;
if (datastore.checksum(part.getPiece())) {
manager.getContext().getBitfield().setPiece(part.getPiece(), true);
manager.getPeerManager().executeActive(new PeerCallback() {
@Override
public void callback(PeerWirePeer peer) {
peer.have(part.getPiece().getIndex());
}
});
} else {
System.exit(0);
manager.getContext().getBitfield().setPiece(part.getPiece(), false);
switch (downloadAlgorithm.corrupted(peer.getTorrentPeer(),
part.getPiece())) {
case CHOKE:
if (future != null && !future.cancel())
peer.cancel(nextPart.getPiece().getIndex(),
nextPart.getStart(), nextPart.getLength());
peer.choke();
break;
case DISCONNECT:
peer.disconnect();
break;
case CONNECT_NEW_PEER:
peer.disconnect();
connect(peerAlgorithm.connect());
break;
case CONTINUE:
break;
case CANCEL:
if (future != null && !future.cancel())
peer.cancel(nextPart.getPiece().getIndex(),
nextPart.getStart(), nextPart.getLength());
return;
}
}
}
/**
* Check keep alive interest in this peer.
*
* @param peer
* the peer
*/
private void keepAlive(PeerWirePeer peer) {
switch (peerAlgorithm.keepAlive(peer.getTorrentPeer())) {
case DISCONNECT:
peer.disconnect();
break;
case CONNECT_NEW_PEER:
peer.disconnect();
connect(peerAlgorithm.connect());
break;
case KEEP_ALIVE:
peer.keepAlive();
break;
}
}
/**
* Do the part request
*
* @param peer
* the uploader peer
* @param part
* the part
* @return an {@link ChannelFuture}. Can be used to cancel the message send.
*/
private ChannelFuture download(PeerWirePeer peer, TorrentPart part) {
if (part == null) {
testInterest(peer);
return null;
}
return peer.request(part.getPiece().getIndex(), part.getStart(),
part.getLength());
}
/**
* Do the part upload
*
* @param peer
* the downloader peer
* @param part
* the part
*/
private void upload(PeerWirePeer peer, TorrentPart part) throws IOException {
if (part == null) {
testChoke(peer);
return;
}
final ByteBuffer data = datastore.read(part);
peer.upload(part.getPiece().getIndex(), part.getStart(),
part.getLength(), data);
}
/**
* Tries to establish an connection to <tt>peer</tt>
*
* @param peer
* the peer to be connected.
*/
private void connect(TorrentPeer peer) {
if (peer == null)
return;
// TODO
}
/**
* Processes the suggested piece
*
* @param peer
* the suggesting peer
* @param piece
* the suggested piece
*/
private void suggested(PeerWirePeer peer, TorrentPiece piece) {
final TorrentPart part = downloadAlgorithm.sugested(
peer.getTorrentPeer(), piece);
if (part == null)
return;
download(peer, part);
}
/**
* Processes the allowed fast piece
*
* @param peer
* the allowing peer
* @param piece
* the allowed piece
*/
private void allowedFast(PeerWirePeer peer, TorrentPiece piece) {
final TorrentPart part = downloadAlgorithm.allowedFast(
peer.getTorrentPeer(), piece);
if (part == null)
return;
download(peer, part);
}
}

View File

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

View File

@@ -0,0 +1,145 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.handler;
import net.torrent.protocol.peerwire.PeerWirePeer;
import net.torrent.protocol.peerwire.manager.TorrentManager;
import net.torrent.protocol.peerwire.message.CancelMessage;
import net.torrent.protocol.peerwire.message.PieceMessage;
import net.torrent.protocol.peerwire.message.RequestMessage;
import net.torrent.protocol.peerwire.message.fast.RejectMessage;
import net.torrent.torrent.Torrent;
import net.torrent.torrent.TorrentPart;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
/**
* Handles post-algorithm handler stuff.
* <p>
* Updates {@link TorrentManager} state.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class PeerWireManagerTailHandler extends SimpleChannelHandler {
/**
* The torrent manager
*/
private final TorrentManager manager;
/**
* Creates a new instance
*
* @param manager
* the torrent manager
*/
public PeerWireManagerTailHandler(TorrentManager manager) {
this.manager = manager;
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
throws Exception {
final Object msg = e.getMessage();
if (msg instanceof PieceMessage) {
final PieceMessage pieceMsg = (PieceMessage) msg;
final Torrent torrent = manager.getTorrent();
final TorrentPart part = torrent.getPart(pieceMsg.getIndex(),
pieceMsg.getStart(), pieceMsg.getLength());
manager.getDownloadManager().remove(part);
} else if (msg instanceof RejectMessage) {
final RejectMessage reject = (RejectMessage) msg;
final Torrent torrent = manager.getTorrent();
final TorrentPart part = torrent.getPart(reject.getIndex(),
reject.getStart(), reject.getLength());
manager.getDownloadManager().remove(part);
}
super.messageReceived(ctx, e);
}
@Override
public void writeRequested(ChannelHandlerContext ctx, MessageEvent e)
throws Exception {
final PeerWirePeer peer = manager.getPeerManager().getPeer(
e.getChannel());
final Object msg = e.getMessage();
if (msg instanceof PieceMessage) {
final PieceMessage message = (PieceMessage) msg;
final Torrent torrent = manager.getContext().getTorrent();
final TorrentPart part = torrent.getPart(message.getIndex(),
message.getStart(), message.getLength());
manager.getUploadManager().add(part, peer.getTorrentPeer());
e.getFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future)
throws Exception {
manager.getUploadManager().remove(part);
}
});
} else if (msg instanceof RequestMessage) {
final RequestMessage message = (RequestMessage) msg;
final Torrent torrent = manager.getContext().getTorrent();
final TorrentPart part = torrent.getPart(message.getIndex(),
message.getStart(), message.getLength());
manager.getDownloadManager().add(part, peer.getTorrentPeer());
} else if (msg instanceof CancelMessage) {
final CancelMessage message = (CancelMessage) msg;
final Torrent torrent = manager.getContext().getTorrent();
final TorrentPart part = torrent.getPart(message.getIndex(),
message.getStart(), message.getLength());
e.getFuture().addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future)
throws Exception {
manager.getDownloadManager().remove(part);
}
});
}
super.writeRequested(ctx, e);
}
@Override
public void channelDisconnected(ChannelHandlerContext ctx,
ChannelStateEvent e) throws Exception {
manager.getConnectionManager().update(e.getChannel());
final PeerWirePeer peer = manager.getPeerManager().update(
e.getChannel());
if (peer.getTorrentPeer() != null) {
manager.getDownloadManager().remove(peer.getTorrentPeer());
manager.getUploadManager().remove(peer.getTorrentPeer());
}
super.channelDisconnected(ctx, e);
}
@Override
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
throws Exception {
manager.getConnectionManager().remove(e.getChannel());
final PeerWirePeer peer = manager.getPeerManager().remove(
e.getChannel());
if (peer.getTorrentPeer() != null) {
manager.getDownloadManager().remove(peer.getTorrentPeer());
manager.getUploadManager().remove(peer.getTorrentPeer());
}
super.channelClosed(ctx, e);
}
}

View File

@@ -0,0 +1,165 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.manager;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import net.torrent.torrent.context.TorrentContext;
import org.jboss.netty.channel.Channel;
/**
* Connection manager: manages active and inactive connections.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class ConnectionManager implements Iterable<Channel> {
/**
* The torrent context
*/
private final TorrentContext context;
/**
* The list of active channels
*/
private final Set<Channel> activeChannels = new HashSet<Channel>();
/**
* The list of inactive channels
*/
private final Set<Channel> inactiveChannels = new HashSet<Channel>();
/**
* Creates a new instance
*
* @param context
* the torrent context
*/
public ConnectionManager(TorrentContext context) {
this.context = context;
}
/**
* Registers a new channel
*
* @param channel
* the channel
* @return true if channel was added. False if was already in list.
*/
public boolean add(Channel channel) {
if (channel.isConnected()) {
return activeChannels.add(channel);
} else {
return inactiveChannels.add(channel);
}
}
/**
* Test if contains the channel.
*
* @param channel
* the channel
* @return true if channel is registered
*/
public boolean contains(Channel channel) {
if (activeChannels.contains(channel))
return true;
if (inactiveChannels.contains(channel))
return true;
return false;
}
/**
* Remove the channel
*
* @param channel
* the channel
* @return true if channel was registered
*/
public boolean remove(Channel channel) {
if (activeChannels.remove(channel))
return true;
if (inactiveChannels.remove(channel))
return true;
return false;
}
/**
* Updates the state of the channel. In practice, remove and adds again the
* channel.
*
* @param channel
* the channel
* @return true if channel has been added successfully.
*/
public boolean update(Channel channel) {
if (!remove(channel))
return false;
return add(channel);
}
/**
* Get to count of active connections
*
* @return the amount of active connections
*/
public int getActiveConnections() {
return activeChannels.size();
}
/**
* Get to count of inactive connections
*
* @return the amount of inactive connections
*/
public int getInactiveConnections() {
return inactiveChannels.size();
}
/**
* Get the list of active channels
*
* @return list of active channels
*/
public Set<Channel> getActiveChannels() {
return Collections.unmodifiableSet(activeChannels);
}
/**
* Get the list of inactive channels
*
* @return list of inactive channels
*/
public Set<Channel> getInactiveChannels() {
return Collections.unmodifiableSet(inactiveChannels);
}
@Override
public Iterator<Channel> iterator() {
return activeChannels.iterator();
}
/**
* Get the torent context
*
* @return the torrent context
*/
public TorrentContext getContext() {
return context;
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.manager;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import net.torrent.torrent.TorrentPart;
import net.torrent.torrent.TorrentPiece;
import net.torrent.torrent.context.TorrentContext;
import net.torrent.torrent.context.TorrentPeer;
public class DownloadManager implements Iterable<TorrentPart> {
private final TorrentContext context;
private final Map<TorrentPart, TorrentPeer> activeParts = new HashMap<TorrentPart, TorrentPeer>();
public DownloadManager(TorrentContext context) {
this.context = context;
}
public boolean isDownloading(TorrentPart torrentPart) {
return activeParts.containsKey(torrentPart);
}
public boolean isDownloading(TorrentPiece piece) {
for (final TorrentPart part : activeParts.keySet()) {
if (part.getPiece().equals(piece))
return true;
}
return false;
}
public boolean isDownloading(TorrentPeer peer) {
return activeParts.containsValue(peer);
}
public TorrentPeer getPeer(TorrentPart torrentPart) {
return activeParts.get(torrentPart);
}
public Set<TorrentPart> getTorrentParts(TorrentPeer peer) {
final Set<TorrentPart> parts = new HashSet<TorrentPart>();
for (final Entry<TorrentPart, TorrentPeer> entry : activeParts
.entrySet()) {
if (entry.getValue().equals(peer))
parts.add(entry.getKey());
}
return parts;
}
public boolean isInactive() {
return activeParts.isEmpty();
}
public TorrentPeer add(TorrentPart torrentPart, TorrentPeer peer) {
return activeParts.put(torrentPart, peer);
}
public TorrentPeer remove(TorrentPart torrentPart) {
return activeParts.remove(torrentPart);
}
public Set<TorrentPart> remove(TorrentPeer peer) {
final Set<TorrentPart> parts = new HashSet<TorrentPart>();
for (TorrentPart part : getTorrentParts(peer)) {
if (activeParts.remove(part) != null)
parts.add(part);
}
return parts;
}
public int getActiveDownloadsCount() {
return activeParts.size();
}
public Map<TorrentPart, TorrentPeer> getActiveDownloads() {
return Collections.unmodifiableMap(activeParts);
}
@Override
public Iterator<TorrentPart> iterator() {
return activeParts.keySet().iterator();
}
public TorrentContext getContext() {
return context;
}
}

View File

@@ -0,0 +1,174 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.manager;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import net.torrent.protocol.peerwire.PeerWirePeer;
import net.torrent.torrent.context.TorrentContext;
import net.torrent.torrent.context.TorrentPeer;
import net.torrent.util.PeerCallback;
import org.jboss.netty.channel.Channel;
public class PeerManager implements Iterable<PeerWirePeer> {
private final TorrentContext context;
private final ConnectionManager connectionManager;
private final Map<Channel, PeerWirePeer> activePeers = new HashMap<Channel, PeerWirePeer>();
private final Map<Channel, PeerWirePeer> inactivePeers = new HashMap<Channel, PeerWirePeer>();
public PeerManager(TorrentContext context,
ConnectionManager connectionManager) {
this.context = context;
this.connectionManager = connectionManager;
}
public boolean contains(Channel channel) {
if (activePeers.containsKey(channel))
return true;
if (inactivePeers.containsKey(channel))
return true;
return false;
}
public boolean contains(PeerWirePeer peer) {
if (activePeers.containsValue(peer))
return true;
if (inactivePeers.containsValue(peer))
return true;
return false;
}
public PeerWirePeer getPeer(Channel channel) {
PeerWirePeer peer = activePeers.get(channel);
if (peer == null)
peer = inactivePeers.get(channel);
return peer;
}
public Channel getChannel(PeerWirePeer peer) {
for (final Entry<Channel, PeerWirePeer> entry : activePeers.entrySet()) {
if (entry.getValue().equals(peer))
return entry.getKey();
}
for (final Entry<Channel, PeerWirePeer> entry : inactivePeers
.entrySet()) {
if (entry.getValue().equals(peer))
return entry.getKey();
}
return null;
}
public boolean isEmpty() {
return activePeers.isEmpty();
}
public PeerWirePeer add(Channel channel, TorrentPeer peer) {
if (channel.isConnected()) {
return activePeers.put(channel, new PeerWirePeer(channel, peer));
} else {
return inactivePeers.put(channel, new PeerWirePeer(channel, peer));
}
}
public PeerWirePeer remove(Channel channel) {
PeerWirePeer peer;
if ((peer = activePeers.remove(channel)) != null)
return peer;
if ((peer = inactivePeers.remove(channel)) != null)
return peer;
return null;
}
public PeerWirePeer remove(PeerWirePeer peer) {
final Channel channel = getChannel(peer);
PeerWirePeer peerRemoved;
if ((peerRemoved = activePeers.remove(channel)) != null)
return peerRemoved;
if ((peerRemoved = inactivePeers.remove(channel)) != null)
return peerRemoved;
return null;
}
public PeerWirePeer update(Channel channel) {
PeerWirePeer peer;
if ((peer = remove(channel)) == null)
return null;
return add(channel, peer.getTorrentPeer());
}
public int getActivePeersCount() {
return activePeers.size();
}
public int getImactivePeersCount() {
return activePeers.size();
}
public Map<Channel, PeerWirePeer> getActivePeers() {
return Collections.unmodifiableMap(activePeers);
}
public Map<Channel, PeerWirePeer> getInactivePeers() {
return Collections.unmodifiableMap(inactivePeers);
}
public Set<Channel> getActiveChannels() {
return Collections.unmodifiableSet(activePeers.keySet());
}
public Set<Channel> getInactiveChannels() {
return Collections.unmodifiableSet(inactivePeers.keySet());
}
public void executeActive(PeerCallback callback) {
for (final Entry<Channel, PeerWirePeer> entry : this.activePeers
.entrySet()) {
callback.callback(entry.getValue());
}
}
public void executeInactive(PeerCallback callback) {
for (final Entry<Channel, PeerWirePeer> entry : this.inactivePeers
.entrySet()) {
callback.callback(entry.getValue());
}
}
public void execute(PeerCallback callback) {
executeActive(callback);
executeInactive(callback);
}
@Override
public Iterator<PeerWirePeer> iterator() {
return activePeers.values().iterator();
}
public TorrentContext getContext() {
return context;
}
public ConnectionManager getConnectionManager() {
return connectionManager;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.manager;
import net.torrent.protocol.datastore.TorrentDatastore;
import net.torrent.torrent.Torrent;
import net.torrent.torrent.context.TorrentContext;
public class TorrentManager {
private final TorrentContext context;
private final ConnectionManager connectionManager;
private final PeerManager peerManager;
private final DownloadManager downloadManager;
private final UploadManager uploadManager;
private final TorrentDatastore datastore;
public TorrentManager(TorrentContext context, TorrentDatastore datastore) {
this.context = context;
this.datastore = datastore;
connectionManager = new ConnectionManager(context);
peerManager = new PeerManager(context, connectionManager);
downloadManager = new DownloadManager(context);
uploadManager = new UploadManager(context);
}
public TorrentContext getContext() {
return context;
}
public TorrentDatastore getDatastore() {
return datastore;
}
public ConnectionManager getConnectionManager() {
return connectionManager;
}
public PeerManager getPeerManager() {
return peerManager;
}
public DownloadManager getDownloadManager() {
return downloadManager;
}
public UploadManager getUploadManager() {
return uploadManager;
}
public Torrent getTorrent() {
return context.getTorrent();
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.manager;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import net.torrent.torrent.TorrentPart;
import net.torrent.torrent.context.TorrentContext;
import net.torrent.torrent.context.TorrentPeer;
public class UploadManager implements Iterable<TorrentPart> {
private final TorrentContext context;
private final Map<TorrentPart, TorrentPeer> activeParts = new HashMap<TorrentPart, TorrentPeer>();
public UploadManager(TorrentContext context) {
this.context = context;
}
public boolean isUploading(TorrentPart torrentPart) {
return activeParts.containsKey(torrentPart);
}
public boolean isUploading(TorrentPeer peer) {
return activeParts.containsValue(peer);
}
public TorrentPeer getPeer(TorrentPart torrentPart) {
return activeParts.get(torrentPart);
}
public Set<TorrentPart> getTorrentParts(TorrentPeer peer) {
final Set<TorrentPart> parts = new HashSet<TorrentPart>();
for (final Entry<TorrentPart, TorrentPeer> entry : activeParts
.entrySet()) {
if (entry.getValue().equals(peer))
parts.add(entry.getKey());
}
return parts;
}
public boolean isInactive() {
return activeParts.isEmpty();
}
public TorrentPeer add(TorrentPart torrentPart, TorrentPeer peer) {
return activeParts.put(torrentPart, peer);
}
public TorrentPeer remove(TorrentPart torrentPart) {
return activeParts.remove(torrentPart);
}
public Set<TorrentPart> remove(TorrentPeer peer) {
final Set<TorrentPart> parts = new HashSet<TorrentPart>();
for (TorrentPart part : getTorrentParts(peer)) {
if (activeParts.remove(part) != null)
parts.add(part);
}
return parts;
}
public int getActiveUploadsCount() {
return activeParts.size();
}
public Map<TorrentPart, TorrentPeer> getActiveUploads() {
return Collections.unmodifiableMap(activeParts);
}
@Override
public Iterator<TorrentPart> iterator() {
return activeParts.keySet().iterator();
}
public TorrentContext getContext() {
return context;
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.message;
import java.io.IOException;
import java.util.BitSet;
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* <pre>
* bitfield: [len=0001+X][id=5][bitfield]
* </pre>
*
* The bitfield message may only be sent immediately after the handshaking
* sequence is completed, and before any other messages are sent. It is
* optional, and need not be sent if a client has no pieces. The bitfield
* message is variable length, where X is the length of the bitfield. The
* payload is a bitfield representing the pieces that have been successfully
* downloaded. The high bit in the first byte corresponds to piece index 0. Bits
* that are cleared indicated a missing piece, and set bits indicate a valid and
* available piece. Spare bits at the end are set to zero. A bitfield of the
* wrong length is considered an error. Clients should drop the connection if
* they receive bitfields that are not of the correct size, or if the bitfield
* has any of the spare bits set.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @source BitTorrent documentation
*/
public class BitfieldMessage implements PeerWireWritableMessage,
PeerWireReadableMessage {
public static final byte MESSAGE_ID = 0x05;
/**
* A set of bits with the following scheme:
* <ul>
* <li><b>true</b>: has the piece</li>
* </li><b>false</b>: piece is missing</li>
* </ul>
*/
private BitSet bitfield;
public BitfieldMessage(BitSet bitfield) {
this.bitfield = bitfield;
}
public BitfieldMessage() {
}
@Override
public void read(ChannelBuffer buffer) throws IOException {
buffer.readerIndex(buffer.readerIndex() - 5);
int len = buffer.readInt() - 2;
buffer.readByte(); // unk
bitfield = new BitSet(len * 8);
int i = 0;
int read = 0;
while (read <= len) {
byte b = buffer.readByte();
for (int j = 128; j > 0; j >>= 1) {
bitfield.set(i++, (b & j) != 0);
}
read++;
}
}
@Override
public void write(ChannelBuffer buffer) throws IOException {
buffer.writeByte(MESSAGE_ID);
// byte[] bytes = new byte[bitfield.size() / 8 - 4];
// for (int i = 0; i < bitfield.size(); i++) {
// if((bytes.length - i / 8 - 1) > bytes.length)
// break;
// if (bitfield.get(i)) {
// bytes[bytes.length - i / 8 - 1] |= 1 << (i % 8);
// }
// }
// buffer.put(bytes);
for (int i = 0; i < bitfield.size();) {
byte data = 0;
for (int j = 128; i < bitfield.size() && j > 0; j >>= 1, i++) {
if (bitfield.get(i)) {
data |= j;
}
}
buffer.writeByte(data);
}
}
public BitSet getBitfield() {
return bitfield;
}
public void setBitfield(BitSet bitfield) {
this.bitfield = bitfield;
}
@Override
public String toString() {
return "BitfieldMessage [bitfield=" + bitfield + "]";
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.message;
import java.io.IOException;
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* <pre>
* cancel: [len=0013][id=8][index][begin][length]
* </pre>
*
* The cancel message is fixed length, and is used to cancel block requests. The
* payload is identical to that of the "request" message. It is typically used
* during "End Game" (check for algorithms).
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @source BitTorrent documentation
*/
public class CancelMessage implements PeerWireWritableMessage,
PeerWireReadableMessage {
public static final byte MESSAGE_ID = 0x08;
/**
* The piece index
*/
private int index;
/**
* The start offset
*/
private int start;
/**
* The length
*/
private int length;
public CancelMessage() {
}
public CancelMessage(int index, int start, int length) {
this.index = index;
this.start = start;
this.length = length;
}
@Override
public void read(ChannelBuffer buffer) throws IOException {
this.index = buffer.readInt();
this.start = buffer.readInt();
this.length = buffer.readInt();
}
@Override
public void write(ChannelBuffer buffer) throws IOException {
buffer.writeByte(MESSAGE_ID);
buffer.writeInt(index);
buffer.writeInt(start);
buffer.writeInt(length);
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
@Override
public String toString() {
return "CancelMessage [index=" + index + ", start=" + start
+ ", length=" + length + "]";
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.message;
import java.io.IOException;
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* <pre>
* choke: [len=0001][id=0]
* </pre>
*
* The choke message is fixed-length and has no payload.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @source BitTorrent documentation
*/
public class ChokeMessage implements PeerWireWritableMessage,
PeerWireReadableMessage {
public static final byte MESSAGE_ID = 0x00;
@Override
public void read(ChannelBuffer buffer) throws IOException {
}
@Override
public void write(ChannelBuffer buffer) throws IOException {
buffer.writeByte(MESSAGE_ID);
}
@Override
public String toString() {
return "ChokeMessage []";
}
}

View File

@@ -0,0 +1,167 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.message;
import java.io.IOException;
import java.util.Arrays;
import java.util.BitSet;
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* TODO documentation
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class HandshakeMessage implements PeerWireWritableMessage,
PeerWireReadableMessage {
/**
* Length of "BitTorrent protocol" string.
*/
private int pstrlen = 19;
/**
* The protocol string, must match to validate the protocol connection
*/
private String pstr = "BitTorrent protocol";
/**
* Addons supported by the client (bitfield)
*/
private BitSet reserved = new BitSet(64);
/**
* The torrent's hash
*/
private byte[] infohash;
/**
* The peer id
*/
private byte[] peerId;
public HandshakeMessage(byte[] infohash, byte[] peerId, BitSet reserved) {
this.infohash = infohash;
this.peerId = peerId;
this.reserved = reserved;
}
public HandshakeMessage() {
}
@Override
public void read(ChannelBuffer buffer) throws IOException {
this.pstrlen = buffer.readByte();
this.pstr = new String(buffer.readBytes(pstrlen).array());
for (int i = 0; i < 8; i++) {
byte b = buffer.readByte();
for (int j = 0; j < 8; j++) {
if ((b & (1 << j)) > 0) {
reserved.set(i * 8 + j);
}
}
}
System.out.println(reserved);
// int bit = 0;
// for (int i = 0; i < 8; i++) {
// byte b = buffer.readByte();
// for (int j = 128; j > 0; j >>= 1) {
// reserved.set(bit++, (b & j) != 0);
// }
// }
// this.reserved = buffer.readBytes(8).array();
this.infohash = buffer.readBytes(20).array();
this.peerId = buffer.readBytes(20).array();
}
@Override
public void write(ChannelBuffer buffer) throws IOException {
buffer.writeByte((byte) this.pstr.length());
buffer.writeBytes(this.pstr.getBytes());
for (int i = 0; i < 64;) {
byte data = 0;
for (int j = 128; i < reserved.size() && j > 0; j >>= 1, i++) {
if (reserved.get(i)) {
data |= j;
}
}
buffer.writeByte(data);
}
// buffer.writeBytes(reserved);
buffer.writeBytes(this.infohash);
buffer.writeBytes(this.peerId);
}
public int getPstrlen() {
return pstrlen;
}
public void setPstrlen(int pstrlen) {
this.pstrlen = pstrlen;
}
public String getPstr() {
return pstr;
}
public void setPstr(String pstr) {
this.pstr = pstr;
}
public BitSet getReserved() {
return reserved;
}
public void setReserved(BitSet reserved) {
this.reserved = reserved;
}
public byte[] getInfohash() {
return infohash;
}
public void setInfohash(byte[] infohash) {
this.infohash = infohash;
}
public byte[] getPeerId() {
return peerId;
}
public void setPeerId(byte[] peerId) {
this.peerId = peerId;
}
@Override
public String toString() {
final int maxLen = 10;
return "HandshakeMessage [pstrlen="
+ pstrlen
+ ", pstr="
+ pstr
+ ", reserved="
+ reserved
+ ", infohash="
+ (infohash != null ? Arrays.toString(Arrays.copyOf(infohash,
Math.min(infohash.length, maxLen))) : null)
+ ", peerId="
+ (peerId != null ? Arrays.toString(Arrays.copyOf(peerId,
Math.min(peerId.length, maxLen))) : null) + "]";
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.message;
import java.io.IOException;
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* <pre>
* have: [len=0005][id=4][piece index(int)]
* </pre>
*
* The have message is fixed length. The payload is the zero-based index of a
* that has just been successfully downloaded and verified via the hash.<br>
* <br>
* <b>Implementer's Note</b>: That is the strict definition, in reality some
* games may be played. In particular because peers are extremely unlikely to
* download pieces that they already have, a peer may choose not to advertise
* having a piece to a peer that already has that piece. At a minimum
* "HAVE supression" will result in a 50% reduction in the number of HAVE
* messages, this translates to around a 25-35% reduction in protocol overhead.
* At the same time, it may be worthwhile to send a HAVE message to a peer that
* has that piece already since it will be useful in determining which piece is
* rare. A malicious peer might also choose to advertise having pieces that it
* knows the peer will never download. Due to this attempting to model peers
* using this information is a bad idea.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @source BitTorrent documentation
*/
public class HaveMessage implements PeerWireWritableMessage,
PeerWireReadableMessage {
public static final byte MESSAGE_ID = 0x04;
/**
* The new obtained piece index
*/
private int piece;
public HaveMessage(int piece) {
this.piece = piece;
}
public HaveMessage() {
}
@Override
public void read(ChannelBuffer buffer) throws IOException {
this.piece = buffer.readInt();
}
@Override
public void write(ChannelBuffer buffer) throws IOException {
buffer.writeByte(MESSAGE_ID);
buffer.writeInt(piece);
}
public int getPiece() {
return piece;
}
public void setPiece(int piece) {
this.piece = piece;
}
@Override
public String toString() {
return "HaveMessage [piece=" + piece + "]";
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.message;
import java.io.IOException;
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* <pre>
* interested: [len=0001][id=2]
* </pre>
*
* The interested message is fixed-length and has no payload.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @source BitTorrent documentation
*/
public class InterestedMessage implements PeerWireWritableMessage,
PeerWireReadableMessage {
public static final byte MESSAGE_ID = 0x02;
@Override
public void read(ChannelBuffer buffer) throws IOException {
}
@Override
public void write(ChannelBuffer buffer) throws IOException {
buffer.writeByte(MESSAGE_ID);
}
@Override
public String toString() {
return "InterestedMessage []";
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.message;
import java.io.IOException;
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* <pre>
* keep-alive: [len=0000]
* </pre>
*
* The keep-alive message is a message with zero bytes, specified with the
* length prefix set to zero. There is no message ID and no payload. Peers may
* close a connection if they receive no messages (keep-alive or any other
* message) for a certain period of time, so a keep-alive message must be sent
* to maintain the connection alive if no command have been sent for a given
* amount of time. This amount of time is generally two minutes.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @source BitTorrent documentation
*/
public class KeepAliveMessage implements PeerWireWritableMessage,
PeerWireReadableMessage {
@Override
public void read(ChannelBuffer buffer) throws IOException {
}
@Override
public void write(ChannelBuffer buffer) throws IOException {
}
@Override
public String toString() {
return "KeepAliveMessage []";
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.message;
import java.io.IOException;
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* <pre>
* not interested: [len=0001][id=3]
* </pre>
*
* The not interested message is fixed-length and has no payload.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @source BitTorrent documentation
*/
public class NotInterestedMessage implements PeerWireWritableMessage,
PeerWireReadableMessage {
public static final byte MESSAGE_ID = 0x03;
@Override
public void read(ChannelBuffer buffer) throws IOException {
}
@Override
public void write(ChannelBuffer buffer) throws IOException {
buffer.writeByte(MESSAGE_ID);
}
@Override
public String toString() {
return "NotInterestedMessage []";
}
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.message;
import java.io.IOException;
import java.nio.ByteBuffer;
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* <pre>
* piece: [len=0009+X][id=7][index][begin][block]
* </pre>
*
* The piece message is variable length, where X is the length of the block. The
* payload contains the following information:<br>
* index: integer specifying the zero-based piece index<br>
* begin: integer specifying the zero-based byte offset within the piece<br>
* block: block of data, which is a subset of the piece specified by index.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @source BitTorrent documentation
*/
public class PieceMessage implements PeerWireWritableMessage,
PeerWireReadableMessage {
public static final byte MESSAGE_ID = 0x07;
/**
* The piece index
*/
private int index;
/**
* The part start offset
*/
private int start;
/**
* The part length
*/
private int length;
/**
* The downloaded/uploaded data
*/
private ByteBuffer block;
public PieceMessage(RequestMessage request, ByteBuffer block) {
this.index = request.getIndex();
this.start = request.getStart();
this.length = request.getLength();
this.block = block;
}
public PieceMessage(int index, int start, int length, ByteBuffer block) {
this.index = index;
this.start = start;
this.length = length;
this.block = block;
}
public PieceMessage() {
}
@Override
public void read(ChannelBuffer buffer) throws IOException {
this.index = buffer.readInt();
this.start = buffer.readInt();
this.length = buffer.readableBytes();
this.block = buffer.readBytes(length).toByteBuffer();
}
@Override
public void write(ChannelBuffer buffer) throws IOException {
buffer.writeByte(MESSAGE_ID);
buffer.writeInt(index);
buffer.writeInt(start);
buffer.writeBytes(block);
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public ByteBuffer getBlock() {
return block;
}
public void setBlock(ByteBuffer block) {
this.block = block;
}
@Override
public String toString() {
return "PieceMessage [index=" + index + ", start=" + start
+ ", length=" + length + ", block=" + block + "]";
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.message;
import java.io.IOException;
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* <pre>
* port: [len=0003][id=9][listen-port]
* </pre>
*
* The port message is sent by newer versions of the Mainline that implements a
* DHT tracker. The listen port is the port this peer's DHT node is listening
* on. This peer should be inserted in the local routing table (if DHT tracker
* is supported).
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @source BitTorrent documentation
*/
public class PortMessage implements PeerWireWritableMessage,
PeerWireReadableMessage {
public static final byte MESSAGE_ID = 0x09;
/**
* The DHT port
*/
private short port;
public PortMessage() {
}
public PortMessage(short port) {
this.port = port;
}
@Override
public void read(ChannelBuffer buffer) throws IOException {
this.port = buffer.readShort();
}
@Override
public void write(ChannelBuffer buffer) throws IOException {
buffer.writeByte(MESSAGE_ID);
buffer.writeShort(port);
}
public short getPort() {
return port;
}
public void setPort(short port) {
this.port = port;
}
@Override
public String toString() {
return "PortMessage [port=" + port + "]";
}
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.message;
import java.io.IOException;
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* <pre>
* request: [len=0013][id=6][index][begin][length]
* </pre>
*
* The request message is fixed length, and is used to request a block. The
* payload contains the following information:<br>
* index: integer specifying the zero-based piece index<br>
* begin: integer specifying the zero-based byte offset within the piece<br>
* length: integer specifying the requested length.<br>
* View #1 According to the official specification, "All current implementations
* use 2^15 (32KB), and close connections which request an amount greater than
* 2^17 (128KB)." As early as version 3 or 2004, this behavior was changed to
* use 2^14 (16KB) blocks. As of version 4.0 or mid-2005, the mainline
* disconnected on requests larger than 2^14 (16KB); and some clients have
* followed suit. Note that block requests are smaller than pieces (>=2^18
* bytes), so multiple requests will be needed to download a whole piece.<br>
* Strictly, the specification allows 2^15 (32KB) requests. The reality is near
* all clients will now use 2^14 (16KB) requests. Due to clients that enforce
* that size, it is recommended that implementations make requests of that size.
* Due to smaller requests resulting in higher overhead due to tracking a
* greater number of requests, implementers are advised against going below 2^14
* (16KB). The choice of request block size limit enforcement is not nearly so
* clear cut. With mainline version 4 enforcing 16KB requests, most clients will
* use that size.<br>
* At the same time 2^14 (16KB) is the semi-official (only semi because the
* official protocol document has not been updated) limit now, so enforcing that
* isn't wrong. At the same time, allowing larger requests enlarges the set of
* possible peers, and except on very low bandwidth connections (<256kbps)
* multiple blocks will be downloaded in one choke-timeperiod, thus merely
* enforcing the old limit causes minimal performance degradation. Due to this
* factor, it is recommended that only the older 2^17 (128KB) maximum size limit
* be enforced. View #2 This section has contained falsehoods for a large
* portion of the time this page has existed. This is the third time I (uau) am
* correcting this same section for incorrect information being added, so I
* won't rewrite it completely since it'll probably be broken again... Current
* version has at least the following errors: Mainline started using 2^14
* (16384) byte requests when it was still the only client in existence; only
* the "official specification" still talked about the obsolete 32768 byte value
* which was in reality neither the default size nor maximum allowed. In version
* 4 the request behavior did not change, but the maximum allowed size did
* change to equal the default size. In latest mainline versions the max has
* changed to 32768 (note that this is the first appearance of 32768 for either
* default or max size since the first ancient versions).<br>
* "Most older clients use 32KB requests" is false. Discussion of larger
* requests fails to take latency effects into account.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @source BitTorrent documentation
*/
public class RequestMessage implements PeerWireWritableMessage,
PeerWireReadableMessage {
public static final byte MESSAGE_ID = 0x06;
/**
* The piece index
*/
private int index;
/**
* The piece start offset
*/
private int start;
/**
* The part length
*/
private int length;
public RequestMessage(int index, int start, int length) {
this.index = index;
this.start = start;
this.length = length;
}
public RequestMessage() {
}
@Override
public void read(ChannelBuffer buffer) throws IOException {
this.index = buffer.readInt();
this.start = buffer.readInt();
this.length = buffer.readInt();
}
@Override
public void write(ChannelBuffer buffer) throws IOException {
buffer.writeByte(MESSAGE_ID);
buffer.writeInt(index);
buffer.writeInt(start);
buffer.writeInt(length);
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
@Override
public String toString() {
return "RequestMessage [index=" + index + ", start=" + start
+ ", length=" + length + "]";
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.message;
import java.io.IOException;
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* <pre>
* unchoke: [len=0001][id=1]
* </pre>
*
* The unchoke message is fixed-length and has no payload.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @source BitTorrent documentation
*/
public class UnchokeMessage implements PeerWireWritableMessage,
PeerWireReadableMessage {
public static final byte MESSAGE_ID = 0x01;
@Override
public void read(ChannelBuffer buffer) throws IOException {
}
@Override
public void write(ChannelBuffer buffer) throws IOException {
buffer.writeByte(MESSAGE_ID);
}
@Override
public String toString() {
return "UnchokeMessage []";
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.message.fast;
import java.io.IOException;
import java.util.Arrays;
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* <pre>
* AllowedFast: [len=0x0004+pieces(4 bytes each)][id=0x11][piece indexes(int)]*
* </pre>
*
* With the BitTorrent protocol specified in BEP 0003 [2], new peers take
* several minutes to ramp up before they can effectively engage in BitTorrent's
* tit-for-tat. The reason is simple: starting peers have few pieces to trade.
* <p>
* Allowed Fast is an advisory message which means
* "if you ask for this piece, I'll give it to you even if you're choked."
* Allowed Fast thus shortens the awkward stage during which the peer obtains
* occasional optimistic unchokes but cannot sufficiently reciprocate to remain
* unchoked.
* <p>
* The pieces that can be downloaded when choked constitute a peer's allowed
* fast set. The set is generated using a canonical algorithm that produces
* piece indices unique to the message receiver so that if two peers offer k
* pieces fast it will be the same k, and if one offers k+1 it will be the same
* k plus one more. k should be small enough to avoid abuse, but large enough to
* ramp up tit-for-tat. We currently set k to 10, but peers are free to change
* this number, e.g., to suit load.
* <p>
* The message sender MAY list pieces that the message sender does not have. The
* receiver MUST NOT interpret an Allowed Fast message as meaning that the
* message sender has the piece. This allows peers to generate and communicate
* allowed fast sets at the beginning of a connection. However, a peer MAY send
* Allowed Fast messages at any time.
* <p>
* A peer SHOULD send Allowed Fast messages to any starting peer unless the
* local peer lacks sufficient resources. A peer MAY reject requests for already
* Allowed Fast pieces if the local peer lacks sufficient resources, if the
* requested piece has already been sent to the requesting peer, or if the
* requesting peer is not a starting peer. Our current implementation rejects
* requests for Allowed Fast messages whenever the requesting peer has more than
* * k * pieces.
* <p>
* When the fast extension is disabled, if a peer recieves an Allowed Fast
* message then the peer MUST close the connection.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @source http://www.bittorrent.org/beps/bep_0006.html
*/
public class AllowedFastMessage implements PeerWireWritableMessage,
PeerWireReadableMessage {
public static final byte MESSAGE_ID = 0x0D;
/**
* The pieces indexes
*/
private int[] pieces;
public AllowedFastMessage(int... pieces) {
this.pieces = pieces;
}
public AllowedFastMessage() {
}
@Override
public void read(ChannelBuffer buffer) throws IOException {
this.pieces = new int[buffer.readableBytes() / 4];
for (int i = 0; i < pieces.length; i++) {
pieces[i] = buffer.readInt();
}
}
@Override
public void write(ChannelBuffer buffer) throws IOException {
buffer.writeByte(MESSAGE_ID);
for (final int piece : pieces) {
buffer.writeInt(piece);
}
}
public int[] getPieces() {
return pieces;
}
public void setPieces(int[] pieces) {
this.pieces = pieces;
}
@Override
public String toString() {
return "AllowedFastMessage [pieces="
+ (pieces != null ? Arrays.toString(pieces) : null) + "]";
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.message.fast;
import java.io.IOException;
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* <pre>
* HaveAll: [len=0x0001][id=0x0E]
* </pre>
*
* Have All and Have None specify that the message sender has all or none of the
* pieces respectively. When present, Have All or Have None replace the Have
* Bitfield. Exactly one of Have All, Have None, or Have Bitfield MUST appear
* and only immediately after the handshake. The reason for these messages is to
* save bandwidth. Also slightly to remove the idiosyncrasy of sending no
* message when a peer has no pieces.
* <p>
* When the fast extension is disabled, if a peer receives Have All or Have None
* then the peer MUST close the connection.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @source http://www.bittorrent.org/beps/bep_0006.html
*/
public class HaveAllMessage implements PeerWireWritableMessage,
PeerWireReadableMessage {
public static final byte MESSAGE_ID = 0x0E;
@Override
public void read(ChannelBuffer buffer) throws IOException {
}
@Override
public void write(ChannelBuffer buffer) throws IOException {
buffer.writeByte(MESSAGE_ID);
}
@Override
public String toString() {
return "HaveAllMessage []";
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.message.fast;
import java.io.IOException;
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* <pre>
* HaveNone: [len=0x0001][id=0x0F]
* </pre>
*
* Have All and Have None specify that the message sender has all or none of the
* pieces respectively. When present, Have All or Have None replace the Have
* Bitfield. Exactly one of Have All, Have None, or Have Bitfield MUST appear
* and only immediately after the handshake. The reason for these messages is to
* save bandwidth. Also slightly to remove the idiosyncrasy of sending no
* message when a peer has no pieces.
* <p>
* When the fast extension is disabled, if a peer receives Have All or Have None
* then the peer MUST close the connection.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @source http://www.bittorrent.org/beps/bep_0006.html
*/
public class HaveNoneMessage implements PeerWireWritableMessage,
PeerWireReadableMessage {
public static final byte MESSAGE_ID = 0x0F;
@Override
public void read(ChannelBuffer buffer) throws IOException {
}
@Override
public void write(ChannelBuffer buffer) throws IOException {
buffer.writeByte(MESSAGE_ID);
}
@Override
public String toString() {
return "HaveNoneMessage []";
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.message.fast;
import java.io.IOException;
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* <pre>
* Reject: [len=0013][id=0x10][index][begin][length]
* </pre>
*
* Reject Request notifies a requesting peer that its request will not be
* satisfied.
*
* If the fast extension is disabled and a peer receives a reject request then
* the peer MUST close the connection.
* <p>
* When the fast extension is enabled:
* <ul>
* <li>If a peer receives a reject for a request that was never sent then the
* peer SHOULD close the connection.</li>
* <li>If a peer sends a choke, it MUST reject all requests from the peer to
* whom the choke was sent except it SHOULD NOT reject requests for pieces that
* are in the allowed fast set. A peer SHOULD choke first and then reject
* requests so that the peer receiving the choke does not re-request the pieces.
* </li>
* <li>If a peer receives a request from a peer its choking, the peer receiving
* the request SHOULD send a reject unless the piece is in the allowed fast set.
* </li>
* <li>If a peer receives an excessive number of requests from a peer it is
* choking, the peer receiving the requests MAY close the connection rather than
* reject the request. However, consider that it can take several seconds for
* buffers to drain and messages to propagate once a peer is choked.
* </ul>
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @source http://www.bittorrent.org/beps/bep_0006.html
*/
public class RejectMessage implements PeerWireWritableMessage,
PeerWireReadableMessage {
public static final byte MESSAGE_ID = 0x06;
/**
* The piece index
*/
private int index;
/**
* The piece start offset
*/
private int start;
/**
* The part length
*/
private int length;
public RejectMessage(int index, int start, int length) {
this.index = index;
this.start = start;
this.length = length;
}
public RejectMessage() {
}
@Override
public void read(ChannelBuffer buffer) throws IOException {
this.index = buffer.readInt();
this.start = buffer.readInt();
this.length = buffer.readInt();
}
@Override
public void write(ChannelBuffer buffer) throws IOException {
buffer.writeByte(MESSAGE_ID);
buffer.writeInt(index);
buffer.writeInt(start);
buffer.writeInt(length);
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
@Override
public String toString() {
return "RejectMessage [index=" + index + ", start=" + start
+ ", length=" + length + "]";
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.peerwire.message.fast;
import java.io.IOException;
import net.torrent.protocol.peerwire.codec.PeerWireReadableMessage;
import net.torrent.protocol.peerwire.codec.PeerWireWritableMessage;
import org.jboss.netty.buffer.ChannelBuffer;
/**
* <pre>
* SuggestPiece: [len=0x0005][id=0x0D][piece index(int)]
* </pre>
*
* Suggest Piece is an advisory message meaning
* "you might like to download this piece." The intended usage is for
* 'super-seeding' without throughput reduction, to avoid redundant downloads,
* and so that a seed which is disk I/O bound can upload continguous or
* identical pieces to avoid excessive disk seeks. In all cases, the seed SHOULD
* operate to maintain a roughly equal number of copies of each piece in the
* network. A peer MAY send more than one suggest piece message at any given
* time. A peer receiving multiple suggest piece messages MAY interpret this as
* meaning that all of the suggested pieces are equally appropriate.
* <p>
* When the fast extension is disabled, if a peer receives a Suggest Piece
* message, the peer MUST close the connection.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @source http://www.bittorrent.org/beps/bep_0006.html
*/
public class SuggestPieceMessage implements PeerWireWritableMessage,
PeerWireReadableMessage {
public static final byte MESSAGE_ID = 0x0D;
/**
* The new obtained piece index
*/
private int piece;
public SuggestPieceMessage(int piece) {
this.piece = piece;
}
public SuggestPieceMessage() {
}
@Override
public void read(ChannelBuffer buffer) throws IOException {
this.piece = buffer.readInt();
}
@Override
public void write(ChannelBuffer buffer) throws IOException {
buffer.writeByte(MESSAGE_ID);
buffer.writeInt(piece);
}
public int getPiece() {
return piece;
}
public void setPiece(int piece) {
this.piece = piece;
}
@Override
public String toString() {
return "SuggestPieceMessage [piece=" + piece + "]";
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.tracker;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import net.torrent.protocol.tracker.message.AnnounceMessage;
import net.torrent.protocol.tracker.message.AnnounceMessage.Event;
import net.torrent.torrent.Torrent;
import net.torrent.torrent.TorrentTracker;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
public class HttpTorrentTrackerAnnouncer {
private final ClientBootstrap client = new ClientBootstrap(
new NioClientSocketChannelFactory(Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
public HttpTorrentTrackerAnnouncer() {
client.setPipelineFactory(new HttpTorrentTrackerPipelineFactory());
}
public boolean announce(Torrent torrent, TorrentTracker tracker)
throws UnsupportedEncodingException, MalformedURLException {
final AnnounceMessage announceMessage = new AnnounceMessage(tracker
.getURL().toString(), torrent.getInfoHash().toByteArray(),
torrent.getInfoHash().toByteArray(), 10, 0, 0, 0, true, false,
Event.STARTED);
int port = (tracker.getURL().getPort() > 0 ? tracker.getURL().getPort()
: tracker.getURL().getDefaultPort());
final ChannelFuture chFuture = client.connect(new InetSocketAddress(
tracker.getURL().getHost(), port));
chFuture.awaitUninterruptibly(60, TimeUnit.SECONDS);
if (!chFuture.isSuccess())
return false;
return chFuture.getChannel().write(announceMessage.write())
.awaitUninterruptibly(60, TimeUnit.SECONDS);
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.tracker;
import static org.jboss.netty.channel.Channels.pipeline;
import net.torrent.protocol.tracker.codec.ISO8859HttpRequestEncoder;
import net.torrent.protocol.tracker.codec.TorrentTrackerBDecoder;
import net.torrent.protocol.tracker.codec.TorrentTrackerDecoder;
import net.torrent.protocol.tracker.codec.TorrentTrackerEncoder;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.handler.codec.http.HttpResponseDecoder;
import org.jboss.netty.handler.logging.LoggingHandler;
import org.jboss.netty.logging.InternalLogLevel;
public class HttpTorrentTrackerPipelineFactory implements
ChannelPipelineFactory {
@Override
public ChannelPipeline getPipeline() throws Exception {
final ChannelPipeline pipeline = pipeline();
// log binary data input and object output
// pipeline.addFirst("logging", new LoggingHandler());
pipeline.addLast("tracker.encoder", new TorrentTrackerEncoder());
pipeline.addLast("encoder", new ISO8859HttpRequestEncoder());
pipeline.addLast("interceptor", new Interceptor());
pipeline.addLast("decoder", new HttpResponseDecoder());
pipeline.addLast("bdecoder", new TorrentTrackerBDecoder());
pipeline.addLast("tracker.decoder", new TorrentTrackerDecoder());
pipeline.addLast("handler", new TrackerHandler());
pipeline.addLast("logging", new LoggingHandler(InternalLogLevel.WARN));
return pipeline;
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.tracker;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
public class Interceptor extends OneToOneEncoder {
@Override
protected Object encode(ChannelHandlerContext ctx, Channel channel,
Object msg) throws Exception {
System.out.println(">" + msg);
if (!(msg instanceof ChannelBuffer))
return msg;
final ChannelBuffer buffer = (ChannelBuffer) msg;
System.out.println(new String(buffer.array()));
return msg;
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.tracker;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
public class TrackerHandler extends SimpleChannelUpstreamHandler {
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
throws Exception {
System.out.println(e.getMessage());
super.messageReceived(ctx, e);
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.tracker.codec;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.handler.codec.http.HttpMessage;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpRequestEncoder;
public class ISO8859HttpRequestEncoder extends HttpRequestEncoder {
static final byte SP = 32;
static final byte CR = 13;
static final byte LF = 10;
@Override
protected void encodeInitialLine(ChannelBuffer buf, HttpMessage message)
throws Exception {
HttpRequest request = (HttpRequest) message;
buf.writeBytes(request.getMethod().toString().getBytes());
buf.writeByte(SP);
buf.writeBytes(request.getUri().getBytes());
buf.writeByte(SP);
buf.writeBytes(request.getProtocolVersion().toString().getBytes());
buf.writeByte(CR);
buf.writeByte(LF);
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.tracker.codec;
import java.io.ByteArrayInputStream;
import net.torrent.util.bencoding.BEncodedInputStream;
import net.torrent.util.bencoding.BMap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.http.HttpChunk;
import org.jboss.netty.handler.codec.oneone.OneToOneDecoder;
public class TorrentTrackerBDecoder extends OneToOneDecoder {
@Override
protected Object decode(ChannelHandlerContext ctx, Channel channel,
Object msg) throws Exception {
if (!(msg instanceof HttpChunk))
return msg;
final HttpChunk message = (HttpChunk) msg;
System.out.println(new String(message.getContent().array()));
if (message.getContent().readableBytes() <= 0)
return null;
final BEncodedInputStream in = new BEncodedInputStream(
new ByteArrayInputStream(message.getContent().array()));
final BMap map = (BMap) in.readElement();
return map;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.tracker.codec;
import net.torrent.protocol.tracker.message.PeerListMessage;
import net.torrent.util.bencoding.BMap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.oneone.OneToOneDecoder;
public class TorrentTrackerDecoder extends OneToOneDecoder {
@Override
protected Object decode(ChannelHandlerContext ctx, Channel channel,
Object msg) throws Exception {
if (!(msg instanceof BMap))
return msg;
final BMap bencodedData = (BMap) msg;
final TorrentTrackerResponseMessage message = new PeerListMessage();
message.read(bencodedData);
return message;
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.tracker.codec;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
public class TorrentTrackerEncoder extends OneToOneEncoder {
@Override
protected Object encode(ChannelHandlerContext ctx, Channel channel,
Object msg) throws Exception {
if (!(msg instanceof TorrentTrackerRequestMessage))
return msg;
final TorrentTrackerRequestMessage message = (TorrentTrackerRequestMessage) msg;
return message.write();
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.tracker.codec;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import org.jboss.netty.handler.codec.http.HttpRequest;
public interface TorrentTrackerRequestMessage {
HttpRequest write() throws UnsupportedEncodingException,
MalformedURLException;
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.tracker.codec;
import net.torrent.util.bencoding.BMap;
import net.torrent.util.bencoding.BTypeException;
public interface TorrentTrackerResponseMessage {
void read(BMap map) throws BTypeException;
}

View File

@@ -0,0 +1,173 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.tracker.message;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import net.torrent.protocol.tracker.codec.TorrentTrackerRequestMessage;
import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
import org.jboss.netty.handler.codec.http.HttpMethod;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpVersion;
public class AnnounceMessage implements TorrentTrackerRequestMessage {
private String url;
private byte[] infoHash;
private byte[] peerId;
private int port;
private long uploaded;
private long downloaded;
private long left;
private boolean compact;
private boolean noPeerId;
private Event event;
public enum Event {
UPDATE(null), STARTED("started"), STOPPED("stopped"), COMPLETED(
"completed");
private String urlArg;
Event(String arg) {
urlArg = arg;
}
public String urlArg() {
return urlArg;
}
}
private String ip;
private Integer numWant = 10;
private String key = "avtbyit8";
private String trackerId;
public AnnounceMessage(String url, byte[] infoHash, byte[] peerId,
int port, long uploaded, long downloaded, long left,
boolean compact, boolean noPeerId, Event event, String ip,
Integer numWant, String key, String trackerId) {
this.url = url;
this.infoHash = infoHash;
this.peerId = peerId;
this.port = port;
this.uploaded = uploaded;
this.downloaded = downloaded;
this.left = left;
this.compact = compact;
this.noPeerId = noPeerId;
this.event = event;
this.ip = ip;
this.numWant = numWant;
this.key = key;
this.trackerId = trackerId;
}
public AnnounceMessage(String url, byte[] infoHash, byte[] peerId,
int port, long uploaded, long downloaded, long left,
boolean compact, boolean noPeerId, Event event) {
this.url = url;
this.infoHash = infoHash;
this.peerId = peerId;
this.port = port;
this.uploaded = uploaded;
this.downloaded = downloaded;
this.left = left;
this.compact = compact;
this.noPeerId = noPeerId;
this.event = event;
}
@Override
public HttpRequest write() throws UnsupportedEncodingException,
MalformedURLException {
StringBuilder builder = new StringBuilder(url);
if (url.contains("?")) {
builder.append("&");
} else {
builder.append("?");
}
addLowerCase(builder, "info_hash", infoHash);
add(builder, "peer_id", "-TR2130-g4mvcv2iyehf");
add(builder, "port", port);
add(builder, "uploaded", Long.toString(uploaded));
add(builder, "downloaded", Long.toString(downloaded));
add(builder, "left", Long.toString(left));
add(builder, "compact", compact);
add(builder, "no_peer_id", noPeerId);
if (event != Event.UPDATE)
add(builder, "event", event.urlArg());
add(builder, "ip", ip);
add(builder, "numwant", numWant);
add(builder, "key", key);
add(builder, "trackerid", trackerId);
builder.setLength(builder.length() - 1);// trim last character it is an
// unnecessary &.
final URL url = new URL(builder.toString());
return new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
url.getPath() + "?" + url.getQuery());
}
private void add(StringBuilder builder, String key, String value)
throws UnsupportedEncodingException {
if (value == null)
return;
builder.append(key + "=" + value).append("&");
}
private void add(StringBuilder builder, String key, byte[] value)
throws UnsupportedEncodingException {
if (value == null)
return;
add(builder, key, URLEncoder.encode(new String(value), "ISO-8859-1")
.replaceAll("\\+", "%20"));
}
private void addLowerCase(StringBuilder builder, String key, byte[] value)
throws UnsupportedEncodingException {
if (value == null)
return;
add(builder, key,
URLEncoder
.encode(new String(value, "ISO-8859-1"), "ISO-8859-1")
.replaceAll("\\+", "%20").toLowerCase());
}
private void add(StringBuilder builder, String key, Number value)
throws UnsupportedEncodingException {
if (value == null)
return;
add(builder, key, value.toString());
}
private void add(StringBuilder builder, String key, Boolean value)
throws UnsupportedEncodingException {
if (value == null)
return;
add(builder, key, (value ? "1" : "0"));
}
}

View File

@@ -0,0 +1,181 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.tracker.message;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.List;
import net.torrent.protocol.tracker.codec.TorrentTrackerResponseMessage;
import net.torrent.util.bencoding.BList;
import net.torrent.util.bencoding.BMap;
import net.torrent.util.bencoding.BTypeException;
public class PeerListMessage implements TorrentTrackerResponseMessage {
private static final String FAILURE_REASON = "failure reason";
private static final String WARNING_MESSAGE = "warning message";
private static final String INTERVAL = "interval";
private static final String MIN_INTERVAL = "min interval";
private static final String TRACKER_ID = "tracker id";
private static final String COMPLETE = "complete";
private static final String INCOMPLETE = "incomplete";
private static final String PEERS = "peers";
private String failureReason;
private String warningMessage;
private Integer interval;
private Integer minInterval;
private byte[] trackerID;
private Integer complete;
private Integer incomplete;
private List<PeerInfo> peerList;
private boolean compact;
@Override
public void read(BMap response) throws BTypeException {
if (response == null)
throw new InvalidParameterException("Tracker response is null");
if (response.containsKey(FAILURE_REASON)) {
failureReason = response.getString(FAILURE_REASON);
return;
}
this.interval = response.getInteger(INTERVAL);
warningMessage = response.getString(WARNING_MESSAGE);
interval = response.getInteger(INTERVAL);
minInterval = response.getInteger(MIN_INTERVAL);
trackerID = (byte[]) response.get(TRACKER_ID);
complete = response.getInteger(COMPLETE);
incomplete = response.getInteger(INCOMPLETE);
Object peers = response.get(PEERS);
if (peers instanceof BList) {
BList list = (BList) peers;
peerList = new ArrayList<PeerInfo>(list.size());
for (int i = 0; i < list.size(); i++) {
peerList.add(PeerInfo.fromBMap(list.getMap(i)));
}
} else {
byte[] list = (byte[]) peers;
if (list.length % 6 != 0)
throw new IllegalStateException(
"Peerlist not in format IPv4:port!");
peerList = new ArrayList<PeerInfo>(list.length / 6);
for (int i = 0; i < list.length; i += 6) {
peerList.add(PeerInfo.fromRawIP(list, i, 6));
}
compact = true;
}
}
public String getFailureReason() {
return failureReason;
}
public String getWarningMessage() {
return warningMessage;
}
public Integer getInterval() {
return interval;
}
public Integer getMinInterval() {
return minInterval;
}
public byte[] getTrackerID() {
return trackerID;
}
public Integer getComplete() {
return complete;
}
public Integer getIncomplete() {
return incomplete;
}
public List<PeerInfo> getPeerList() {
return peerList;
}
public boolean isCompact() {
return compact;
}
public static class PeerInfo {
private byte[] peerId;
private String ip;
private int port;
public PeerInfo(byte[] peerId, String ip, int port) {
this.peerId = peerId;
this.ip = ip;
this.port = port;
}
public byte[] getPeerId() {
return peerId;
}
public void setPeerId(byte[] peerId) {
this.peerId = peerId;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public static PeerInfo fromBMap(BMap map) throws BTypeException {
return new PeerInfo((byte[]) map.get("peer id"),
map.getString("peer ip"), map.getInteger("port"));
}
public static PeerInfo fromRawIP(byte[] list, int i, int j) {
byte[] addr = new byte[4];
System.arraycopy(list, i, addr, 0, 4);
InetAddress address;
try {
address = InetAddress.getByAddress(addr);
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
InetSocketAddress socketAddress = new InetSocketAddress(
address.getHostAddress(), ((list[i + j - 2] & 0xFF) << 8)
+ (list[i + j - 1] & 0xFF));
return new PeerInfo(null, socketAddress.getHostName(),
socketAddress.getPort());
}
}
}

View File

@@ -0,0 +1,362 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.torrent;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.InvalidParameterException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.torrent.torrent.TorrentInfo.TorrentType;
import net.torrent.util.bencoding.BEncodedInputStream;
import net.torrent.util.bencoding.BList;
import net.torrent.util.bencoding.BMap;
import net.torrent.util.bencoding.BTypeException;
/**
* An class representing an .torrent file.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class Torrent {
/**
* Dictionary key for <tt>announce</tt>
*/
private static final String ANNOUNCE = "announce";
/**
* Dictionary key for <tt>announce-list</tt>
*/
private static final String ANNOUNCE_LIST = "announce-list";
/**
* Dictionary key for <tt>info</tt>
*/
private static final String INFO = "info";
/**
* Dictionary key for <tt>comment</tt>
*/
private static final String COMMENT = "comment";
/**
* Dictionary key for <tt>creation date</tt>
*/
private static final String CREATION_DATE = "creation date";
/**
* Dictionary key for <tt>created by</tt>
*/
private static final String CREATED_BY = "created by";
/**
* The torrent information
*/
private final TorrentInfo info;
/**
* The list of trackers
*/
private final Set<TorrentTracker> trackers = new HashSet<TorrentTracker>();
/**
* The torrent comment
*/
private final String comment;
/**
* The creation date of the torrent
*/
private final Date creationDate;
/**
* The torrent creator
*/
private final String createdBy;
/**
* Creates a new instance
*
* @param info
* the torrent information
* @param comment
* the comment
* @param creationDate
* the date of creation
* @param createdBy
* the creator
* @param trackers
* the list of trackers
*/
public Torrent(TorrentInfo info, String comment, Date creationDate,
String createdBy, TorrentTracker... trackers) {
if (info == null)
throw new InvalidParameterException("Torrent info is null");
if (trackers == null || trackers.length == 0)
throw new InvalidParameterException(
"Trackers null or empty: DHT is not yet implemented, a tracker is needed.");
Collections.addAll(this.trackers, trackers);
this.info = info;
this.comment = comment;
this.creationDate = creationDate;
this.createdBy = createdBy;
}
/**
* Creates a new instance
*
* @param info
* the torrent information
* @param trackers
* the list o trackers
*/
public Torrent(TorrentInfo info, TorrentTracker... trackers) {
this(info, null, null, null, trackers);
}
/**
* Creates a new instance
*
* @param torrent
* the torrent bencoded map
* @throws BTypeException
* @throws URISyntaxException
*/
protected Torrent(BMap torrent) throws BTypeException, URISyntaxException {
this.comment = torrent.getString(COMMENT);
final Long time = torrent.getLong(CREATION_DATE);
this.creationDate = (time != null ? new Date(time * 1000) : null);
this.createdBy = torrent.getString(CREATED_BY);
BList aLists = torrent.getList(ANNOUNCE_LIST);
if (aLists != null) {
for (int i = 0; i < aLists.size(); i++) {
BList list = aLists.getList(i);
URI primary = null;
URI[] backup = new URI[(list.size() > 1 ? list.size() - 1 : 0)];
int backups = 0;
for (int j = 0; j < list.size(); j++) {
if (j == 0) {
final String url = list.getString(j);
if (!url.startsWith("http"))
break;
primary = new URI(url);
} else {
final String url = list.getString(j);
if (url.startsWith("http"))
backup[backups++] = new URI(url);
}
}
this.trackers.add(new TorrentTracker(this, primary, Arrays
.copyOf(backup, backups)));
}
} else {
if (torrent.containsKey(ANNOUNCE)) {
this.trackers.add(new TorrentTracker(this, new URI(torrent
.getString(ANNOUNCE))));
}
}
info = new TorrentInfo(this, torrent.getMap(INFO));
}
/**
* Get torrent information
*
* @return the torrent information
*/
public TorrentInfo getInfo() {
return info;
}
/**
* The list of trackers
*
* @return the trackers
*/
public Set<TorrentTracker> getTrackers() {
return Collections.unmodifiableSet(trackers);
}
/**
* Get the torrent comment
*
* @return the torrent comment
*/
public String getComment() {
return comment;
}
/**
* Get the creation adte
*
* @return the creation date
*/
public Date getCreationDate() {
return creationDate;
}
/**
* Get the torrent creator
*
* @return the torrent creator
*/
public String getCreatedBy() {
return createdBy;
}
/**
* Get the torrent info hash
*
* @return the torrent info hash
*/
public TorrentHash getInfoHash() {
return info.getInfoHash();
}
/**
* Get list of files
*
* @return the file list
*/
public List<TorrentFile> getFiles() {
return info.getFiles();
}
/**
* Get the list of pieces
*
* @return the list of pieces
*/
public TorrentPiece[] getPieces() {
return info.getPieces();
}
/**
* Get the torrent type
*
* @return the torrent type
*/
public TorrentType getType() {
return info.getType();
}
/**
* Get the piece by index
*
* @param index
* the piece index
* @return the found piece
*/
public TorrentPiece getPiece(int index) {
return info.getPiece(index);
}
/**
* Get an certain part
*
* @param piece
* the piece
* @param start
* the part start
* @param len
* the part length
* @return the found part
*/
public TorrentPart getPart(TorrentPiece piece, int start, int len) {
return info.getPart(piece, start, len);
}
/**
* Get an certain part
*
* @param piece
* the piece index
* @param start
* the part start
* @param len
* the part length
* @return the found part
*/
public TorrentPart getPart(int index, int start, int len) {
return info.getPart(index, start, len);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((info == null) ? 0 : info.getInfoHash().hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Torrent other = (Torrent) obj;
if (info == null) {
if (other.info != null)
return false;
} else if (!info.equals(other.info))
return false;
return true;
}
/**
* Load an torrent from an {@link InputStream}
*
* @param in
* the {@link InputStream}
* @return the loaded {@link Torrent} instance
* @throws IOException
* @throws URISyntaxException
*/
public static Torrent load(InputStream in) throws IOException,
URISyntaxException {
if (in == null)
throw new InvalidParameterException("InputStream cannot be null");
final BMap map = (BMap) new BEncodedInputStream(in).readElement();
return new Torrent(map);
}
/**
* Load an torrent from an {@link File}
*
* @param file
* the {@link File}
* @return the loaded {@link Torrent} instance
* @throws IOException
* @throws URISyntaxException
*/
public static Torrent load(File file) throws IOException,
URISyntaxException {
if (file == null)
throw new InvalidParameterException("File cannot be null");
return load(new FileInputStream(file));
}
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.torrent;
import net.torrent.util.Range;
/**
* An file inside an {@link Torrent torrent} file
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class TorrentFile {
/**
* The torrent information
*/
private final TorrentInfo info;
/**
* The filename
*/
private final String name;
/**
* The file length
*/
private final long length;
/**
* The file offset
*/
private final long offset;
/**
* Creates a new instance
*
* @param info
* the torrent information
* @param name
* the filename
* @param length
* the file length
* @param offset
* the file offset
*/
public TorrentFile(TorrentInfo info, String name, long length, long offset) {
this.info = info;
this.name = name;
this.length = length;
this.offset = offset;
}
/**
* Get the torrent informatiom
*
* @return the torrent information
*/
public TorrentInfo getInfo() {
return info;
}
/**
* Get the filename
*
* @return the filename
*/
public String getName() {
return name;
}
/**
* Get the filesize
*
* @return the filesize
*/
public long getLength() {
return length;
}
/**
* Get the file offset
*
* @return the offset
*/
public long getOffset() {
return offset;
}
/**
* Get the base directory name
*
* @return the base directory name
* @see TorrentInfo#getDirectoryName()
*/
public String getBaseDirectoryName() {
return info.getDirectoryName();
}
/**
* Get the relative name. This is the same as:
* <p>
* <code>
* String name = getBaseDirectoryName() + "/" + getName();
* </code>
*
* @return the relative name
*/
public String getRelativePath() {
if (getBaseDirectoryName() != null) {
return getBaseDirectoryName() + "/" + name;
}
return name;
}
/**
* Get the file as torrent range
*
* @return the torrent range representing the file
*/
public Range asTorrentRange() {
return Range.getRangeByLength(offset, length);
}
/**
* Get the file as file range
*
* @return the file range representing the file
*/
public Range asFileRange() {
return Range.getRangeByLength(0, length);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (length ^ (length >>> 32));
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + (int) (offset ^ (offset >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TorrentFile other = (TorrentFile) obj;
if (length != other.length)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (offset != other.offset)
return false;
return true;
}
}

View File

@@ -0,0 +1,221 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.torrent;
import java.security.InvalidParameterException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
* Represents an {@link TorrentPiece} hash.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class TorrentHash {
/**
* An table containing hexadecimal characters
*/
private static final char[] HEX_TABLE = { '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
/**
* The hash
*/
private final byte[] hash;
/**
* The hash type
*/
private final HashType type;
/**
* The hashes type
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public enum HashType {
/**
* SHA1 -> 20 bytes
*/
SHA1(20, "SHA1"),
/**
* MD5 -> 16 bytes
*/
MD5(16, "MD5");
/**
* The hash length
*/
private final int length;
/**
* The digest algorithm
*/
private final String digestAlgorithm;
/**
* The {@link MessageDigest} instance
*/
private MessageDigest digest;
/**
* Creates a new instance
*
* @param length
* the hash length
* @param digestAlgorithm
* the algorithm
*/
HashType(int length, String digestAlgorithm) {
this.length = length;
this.digestAlgorithm = digestAlgorithm;
try {
this.digest = MessageDigest.getInstance(digestAlgorithm);
} catch (NoSuchAlgorithmException e) {
this.digest = null;
}
}
/**
* Get the hash length
*
* @return the hash length
*/
public int getLength() {
return length;
}
/**
* Get the hash algorithm
*
* @return the hash algorithm
*/
public String getDigestAlgorithm() {
return digestAlgorithm;
}
/**
* Get the {@link MessageDigest}
*
* @return the {@link MessageDigest}
*/
public MessageDigest getMessageDigest() {
return digest;
}
}
/**
* Creates a new instance
*
* @param hash
* the hash
* @param type
* the hash type
*/
public TorrentHash(byte[] hash, HashType type) {
if (hash == null)
throw new IllegalArgumentException("hash is null");
if (type == null)
throw new IllegalArgumentException("type is null");
if (hash.length != type.length)
throw new IllegalArgumentException("hash does not have "
+ type.length + " bytes");
this.hash = Arrays.copyOf(hash, hash.length);
this.type = type;
}
/**
* Get hash as an hexadecimal string
*
* @return
*/
public String asHexString() {
StringBuilder b = new StringBuilder(hash.length * 2);
for (int i = 0; i < hash.length; i++) {
b.append(HEX_TABLE[((hash[i] & 0xff) >> 4)]);
b.append(HEX_TABLE[hash[i] & 15]);
}
return b.toString();
}
/**
* Compare the two hashes
*
* @param hash
* the hash
* @return true if are equal
*/
public boolean compare(byte[] hash) {
if (hash == null)
throw new InvalidParameterException("Hash is null");
return Arrays.equals(this.hash, hash);
}
/**
* Get the hash byte array
*
* @return the hash byte array
*/
public byte[] toByteArray() {
return Arrays.copyOf(hash, hash.length);
}
/**
* Get the hash length
*
* @return the hash length
*/
public int getHashLength() {
return hash.length;
}
/**
* Get the hash type
*
* @return the hash type
*/
public HashType getType() {
return type;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(hash);
result = prime * result + type.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TorrentHash other = (TorrentHash) obj;
if (!Arrays.equals(hash, other.hash))
return false;
if (type != other.type)
return false;
return true;
}
@Override
public String toString() {
return type + ":" + asHexString();
}
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.torrent;
import java.io.File;
import java.math.BigInteger;
import java.security.InvalidParameterException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import net.torrent.torrent.TorrentHash.HashType;
import net.torrent.util.bencoding.BEncodedOutputStream;
import net.torrent.util.bencoding.BList;
import net.torrent.util.bencoding.BMap;
import net.torrent.util.bencoding.BTypeException;
public class TorrentInfo {
private static final String PIECE_LENGTH = "piece length";
private static final String PIECES = "pieces";
private static final String NAME = "name";
private static final String LENGTH = "length";
private static final String FILES = "files";
private static final String PATH = "path";
private static final String PRIVATE = "private";
private final Torrent torrent;
private final TorrentHash infohash;
private final List<TorrentFile> files = new ArrayList<TorrentFile>();
private final TorrentPiece[] pieces;
private long size;
private final int pieceLength;
private final boolean privTracker;
private final TorrentType type;
public enum TorrentType {
SINGLE_FILE, DIRECTORY;
}
private final String directoryName;
public TorrentInfo(Torrent torrent, TorrentHash infohash,
TorrentPiece[] pieces, int pieceLength, boolean privTracker,
TorrentType type, String directoryName) {
this.torrent = torrent;
this.infohash = infohash;
this.pieces = pieces;
this.pieceLength = pieceLength;
this.privTracker = privTracker;
this.type = type;
this.directoryName = directoryName;
}
public TorrentInfo(Torrent torrent, BMap info) throws BTypeException {
if (torrent == null)
throw new InvalidParameterException("Torrent is null");
if (info == null)
throw new InvalidParameterException("Info BMap is null");
this.torrent = torrent;
infohash = calculateInfoHash(info);
privTracker = Integer.valueOf(1).equals(info.getInteger(PRIVATE));
pieceLength = ((BigInteger) info.get(PIECE_LENGTH)).intValue();
String base = info.getString(NAME);
if (base == null)
throw new IllegalStateException("Missing a required key: " + NAME);
if (info.containsKey(LENGTH)) { // single file mode
type = TorrentType.SINGLE_FILE;
directoryName = null;
files.add(new TorrentFile(this, base, ((BigInteger) info
.get(LENGTH)).longValue(), 0));
} else {
type = TorrentType.DIRECTORY;
directoryName = base;
long offset = 0;
BList fList = info.getList(FILES);
for (int i = 0; i < fList.size(); i++) {
BMap file = fList.getMap(i);
StringBuilder path = new StringBuilder();
BList elements = file.getList(PATH);
for (int j = 0; j < elements.size(); j++) {
String element = elements.getString(j);
if (path.length() > 0) {
path.append(File.separatorChar);
}
path.append(element);
}
if (files.add(new TorrentFile(this, path.toString(),
((BigInteger) file.get(LENGTH)).longValue(), offset))) {
offset += ((BigInteger) file.get(LENGTH)).longValue();
}
}
}
calculateSize();
byte[] hashes = (byte[]) info.get(PIECES);
if (hashes == null)
throw new IllegalStateException("Pieces hash is null");
if (hashes.length % 20 != 0)
throw new IllegalStateException(
"Pieces hash dictionary has an invalid size: "
+ hashes.length);
pieces = new TorrentPiece[(hashes.length / 20)];
for (int index = 0; index < pieces.length; index++) {
int len = pieceLength;
if (pieces.length - 1 == index) // last piece
len = (int) (size - (pieceLength * (pieces.length - 1)));
pieces[index] = new TorrentPiece(this, Arrays.copyOfRange(hashes,
index * 20, index * 20 + 20), index, len);
}
// Arrays.sort(pieces, PieceIndexComparator.SHARED_INSTANCE);
}
private void calculateSize() {
for (TorrentFile file : files) {
size += file.getLength();
}
}
private TorrentHash calculateInfoHash(Map<String, ?> info) {
try {
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
sha1.update(BEncodedOutputStream.bencode(info));
return new TorrentHash(sha1.digest(), HashType.SHA1);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public Torrent getTorrent() {
return torrent;
}
public TorrentHash getInfoHash() {
return infohash;
}
public List<TorrentFile> getFiles() {
return files;
}
public TorrentPiece[] getPieces() {
return pieces;
}
public int getPieceLength() {
return pieceLength;
}
public boolean isPrivTracker() {
return privTracker;
}
public TorrentType getType() {
return type;
}
public String getDirectoryName() {
return directoryName;
}
public TorrentPiece getPiece(int index) {
for (final TorrentPiece piece : pieces) {
if (piece.getIndex() == index)
return piece;
}
return null;
}
public TorrentPart getPart(TorrentPiece piece, int start, int len) {
return piece.getPart(start, len);
}
public TorrentPart getPart(int index, int start, int len) {
return getPart(getPiece(index), start, len);
}
}

View File

@@ -0,0 +1,239 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.torrent;
import net.torrent.protocol.peerwire.message.RequestMessage;
import net.torrent.util.Range;
/**
* BitTorrent files are divided in small chunks of hashes, each hash represent
* an <b>piece</b> and each of those pieces are divided into smaller parts. Each
* part can be requested to an peer and received afterwards.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
* @see RequestMessage RequestMessage for more about requests and parts
*/
public class TorrentPart {
/**
* The standard part length -- most clients respect this!
*/
public static final int PART_LENGTH = 16 * 1024;
/**
* The piece index, only for internal management
*/
private int index; // internal use only
/**
* The piece of this part
*/
private final TorrentPiece piece;
/**
* The start offset of data
*/
private final int start;
/**
* The length of data
*/
private final int length;
/**
* Creates a new instance
*
* @param piece
* the piece
* @param start
* the start offset
* @param length
* the length
*/
public TorrentPart(TorrentPiece piece, int start, int length) {
this.index = start / length;
this.piece = piece;
this.start = start;
this.length = length;
}
/**
* Creates a new instance
*
* @param index
* the part index. <b>Note</b>: internal use only!
* @param piece
* the piece
* @param start
* the start offset
* @param length
* the length
*/
public TorrentPart(int index, TorrentPiece piece, int start, int length) {
this.index = index;
this.piece = piece;
this.start = start;
this.length = length;
}
/**
* Get the piece
*
* @return the piece
*/
public TorrentPiece getPiece() {
return piece;
}
/**
* Get the torrent
*
* @return the torrent
*/
public Torrent getTorrent() {
return piece.getTorrent();
}
/**
* Get the data start offset
*
* @return the start offset
*/
public int getStart() {
return start;
}
/**
* Get the data length
*
* @return the data length
*/
public int getLength() {
return length;
}
/**
* Get the offset of part inside the torrent.
*
* @return the part's torrent offset
*/
public long getOffset() {
return piece.getOffset() + start;
}
/**
* Check if this is the last part in this piece.
* <p>
* <code>
* TorrentPart part = ...;<br>
* boolean first = (part.getStart() + part.getLength() == part.getPiece().getLength());
* </code>
*
* @return true if last
*/
public boolean isLast() {
return (start + length == piece.getLength());
}
/**
* Check if this is the first part in this piece.
* <p>
* <code>
* TorrentPart part = ...;<br>
* boolean first = (part.getStart() == 0);
* </code>
*
* @return true if first
*/
public boolean isFirst() {
return start == 0;
}
/**
* Get the next part. Null if last.
*
* @return the next part. Might be null.
*/
public TorrentPart getNextPart() {
if (isLast())
return null;
return piece.getParts()[index + 1];
}
/**
* Get the range inside the piece.
*
* @return the range in piece
*/
public Range asPieceRange() {
return Range.getRangeByLength(start, length);
}
/**
* Get the range inside the torrent
*
* @return the range in torrent
*/
public Range asTorrentRange() {
return Range.getRangeByLength(piece.getOffset() + start, length);
}
/**
* Get the range inside the file
*
* @param file
* the file
* @return the range in file
*/
public Range asFileRange(TorrentFile file) {
return Range.getRangeByLength(
piece.getOffset() - file.getOffset() + start, length)
.intersection(file.asFileRange());
}
@Override
public String toString() {
return "TorrentPart [piece=" + piece + ", start=" + start + ", length="
+ length + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + length;
result = prime * result + ((piece == null) ? 0 : piece.hashCode());
result = prime * result + start;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TorrentPart other = (TorrentPart) obj;
if (length != other.length)
return false;
if (piece == null) {
if (other.piece != null)
return false;
} else if (!piece.equals(other.piece))
return false;
if (start != other.start)
return false;
return true;
}
}

View File

@@ -0,0 +1,252 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.torrent;
import net.torrent.torrent.TorrentHash.HashType;
/**
* Torrent files are divided into small chunks of data called <tt>pieces</tt>.
* Each piece has the same size, except for the last which can be equal or
* smaller (enough to fit all data).
* <p>
* This class handles this distribution and takes care of creating
* {@link TorrentPart parts}.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class TorrentPiece {
/**
* The Torrent information
*/
private final TorrentInfo info;
/**
* The piece hash
*/
private final TorrentHash hash;
/**
* The piece index
*/
private final int index;
/**
* The piece length
*/
private final int length;
/**
* The torrent parts. All parts have the standard length (
* {@link TorrentPart#PART_LENGTH})
*/
private final TorrentPart[] parts;
/**
* Create a new instance
*
* @param info
* the torrent information
* @param hash
* the torrent hash (as {@link TorrentHash})
* @param index
* the piece index
* @param length
* the piece length
*/
public TorrentPiece(TorrentInfo info, final TorrentHash hash, int index,
int length) {
this.info = info;
this.hash = hash;
this.index = index;
this.length = length;
int len = TorrentPart.PART_LENGTH;
if (len > length)
len = length;
int partCount = (length / len);
if ((length % len) > 0)
partCount += 1;
parts = new TorrentPart[partCount];
int remain = length;
for (int i = 0; i < parts.length; i++) {
int plen = remain;
if (remain > len)
plen = len;
parts[i] = new TorrentPart(i, this, length - remain, plen);
remain -= plen;
}
}
/**
* Creates a new instance
*
* @param info
* the torrent info
* @param hash
* the info hash (as byte array)
* @param index
* the piece index
* @param length
* the piece length
*/
public TorrentPiece(TorrentInfo info, final byte[] hash, int index,
int length) {
this(info, new TorrentHash(hash, HashType.SHA1), index, length);
}
/**
* Get the torrent information
*
* @return the torrent information
*/
public TorrentInfo getInfo() {
return info;
}
/**
* Get the torrent
*
* @return the torrent
*/
public Torrent getTorrent() {
return info.getTorrent();
}
/**
* Get the piece hash
*
* @return the piece hash
*/
public TorrentHash getHash() {
return hash;
}
/**
* Get the piece index
*
* @return the piece index
*/
public int getIndex() {
return index;
}
/**
* Get the piece length
*
* @return the piece length
*/
public int getLength() {
return length;
}
/**
* Get the piece offset
*
* @return the piece offset (relative to torrent)
*/
public long getOffset() {
return (long) index * (long) info.getPieceLength();
}
/**
* Get the next sequential part
*
* @return the next part
*/
public TorrentPiece getNextPiece() {
return info.getPiece(index + 1);
}
/**
* Get all parts
*
* @return all the parts
*/
public TorrentPart[] getParts() {
return parts;
}
/**
* Get the first part
*
* @return the first part
*/
public TorrentPart getFirstPart() {
return parts[0];
}
/**
* Get the last part
*
* @return the last part
*/
public TorrentPart getLastPart() {
return parts[parts.length - 1];
}
/**
* The the part starting at <tt>start</tt> with <tt>length</tt>
*
* @param start
* @param length
* @return the found or created part
*/
public TorrentPart getPart(int start, int length) {
for (final TorrentPart part : parts) {
if (part.getStart() == start && part.getLength() == length)
return part;
}
return new TorrentPart(this, start, length);
}
/**
* Get this piece as an entire part
*
* @return the part
*/
public TorrentPart asSinglePart() {
return getPart(0, length);
}
@Override
public String toString() {
return "TorrentPiece [info=" + info + ", hash=" + hash + ", index="
+ index + ", length=" + length + ", parts="
+ (parts != null ? parts.length : null) + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((hash == null) ? 0 : hash.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TorrentPiece other = (TorrentPiece) obj;
if (hash == null) {
if (other.hash != null)
return false;
} else if (!hash.equals(other.hash))
return false;
return true;
}
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.torrent;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* An tracker has the responsibility to discover new peers. Each peer, once
* begin downloading, send its own peer id and got registered into an peer table
* in the tracker. Once the next peer starts, it receives an copy of that peer
* table and connect to some or all peers in that table.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class TorrentTracker {
/**
* The torrent
*/
private final Torrent torrent;
/**
* The tracker URI. There are several types of trackers:
* <ul>
* <li>HTTP</li>
* <li>HTTPS</li>
* <li>UDP</li>
* </ul>
* Each type have its own protocol and methods to retrieve peers.
*/
private final URI uri;
/**
* The tracker backups uris
*/
private final Set<URI> backup = new HashSet<URI>();
/**
* Creates a new instance
*
* @param torrent
* the torrent
* @param uri
* the tracker uri
* @param backup
* the tracker's backup uris
*/
public TorrentTracker(Torrent torrent, URI uri, URI... backup) {
this.torrent = torrent;
this.uri = uri;
if (backup != null && backup.length > 0)
Collections.addAll(this.backup, backup);
}
/**
* Creates a new instance
*
* @param torrent
* the torrent
* @param uri
* the tracker uri
*/
public TorrentTracker(Torrent torrent, URI uri) {
this(torrent, uri, (URI[]) null);
}
/**
* Get the torrent
*
* @return the torrent
*/
public Torrent getTorrent() {
return torrent;
}
/**
* Get the tracker {@link URL}
*
* @return the url
*/
public URL getURL() {
try {
return uri.toURL();
} catch (MalformedURLException e) {
return null;
}
}
/**
* Get the tracker {@link URI}
*
* @return the tracker uri
*/
public URI getURI() {
return uri;
}
/**
* Get the backup {@link URI}s
*
* @return the tracker's backup URIS
*/
public Set<URI> getBackup() {
return Collections.unmodifiableSet(backup);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((uri == null) ? 0 : uri.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TorrentTracker other = (TorrentTracker) obj;
if (uri == null) {
if (other.uri != null)
return false;
} else if (!uri.equals(other.uri))
return false;
return true;
}
}

View File

@@ -0,0 +1,260 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.torrent.context;
import java.security.InvalidParameterException;
import java.util.BitSet;
import java.util.Iterator;
import net.torrent.torrent.TorrentPiece;
/**
* This is a part bitfield. The only way to check if there is a full piece is to
* test all parts.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class TorrentBitfield implements Iterable<TorrentPiece> {
/**
* The torrent context
*/
private final TorrentContext context;
/**
* {@link BitSet} containing all completed pieces
*/
private final BitSet bits;
/**
* Creates a new instance given the {@link BitSet}.
*
* @param context
* the torrent context
* @param bits
* the {@link BitSet}.
*/
public TorrentBitfield(TorrentContext context, BitSet bits) {
this.context = context;
this.bits = bits;
}
/**
* Creates a new instance
*
* @param context
* the torrent context
*/
public TorrentBitfield(TorrentContext context) {
this(context, new BitSet((context.getTorrent() != null ? context
.getTorrent().getPieces().length : 0)));
}
/**
* Test if this bitfield contains the given piece. Redirect the call to
* {@link TorrentBitfield#hasPiece(int)}.
*
* @param piece
* the piece to test
* @return true if piece is set, false otherwise
*/
public boolean hasPiece(TorrentPiece piece) {
if (piece == null)
throw new InvalidParameterException("Piece is null");
return hasPiece(piece.getIndex());
}
/**
* Test if this bitfield contains the given piece <tt>index</tt>
*
* @param index
* the piece index
* @return true if piece is set, false otherwise
*/
public boolean hasPiece(int index) {
return bits.get(index);
}
/**
* Set the state of the given piece.
*
* @param piece
* the piece
* @param state
* the state
*/
public void setPiece(TorrentPiece piece, boolean state) {
if (piece == null)
throw new InvalidParameterException("Piece is null");
bits.set(piece.getIndex(), state);
}
/**
* Set the bits into this bitfield.
*
* @param bitSet
* the bits
*/
public void setBits(BitSet bitSet) {
bits.xor(bitSet);
}
/**
* Get the torrent context
*
* @return the torrent context
*/
public TorrentContext getContext() {
return context;
}
/**
* Get the backing {@link BitSet}.
*
* @return the bit set
*/
public BitSet getBits() {
return bits;
}
/**
* Return an Bitfield representing remaining pieces.
*
* @return the remain bitfield
*/
public TorrentBitfield getRemainingPieces() {
BitSet set = (BitSet) bits.clone();
set.flip(0, bits.size());
return new TorrentBitfield(context, set);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((bits == null) ? 0 : bits.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TorrentBitfield other = (TorrentBitfield) obj;
if (bits == null) {
if (other.bits != null)
return false;
} else if (!bits.equals(other.bits))
return false;
return true;
}
@Override
public Iterator<TorrentPiece> iterator() {
return iterator(true);
}
/**
* Iterate to all pieces with <tt>type</tt>. True if set, false if not.
*
* @param type
* the type
* @return {@link Iterator} for those pieces
*/
public Iterator<TorrentPiece> iterator(final boolean type) {
return new BitfieldIterator(type);
}
/**
* Iterate to all pieces with <tt>type</tt>. True if set, false if not.
*
* @param type
* the type
* @return {@link Iterable} for those pieces
* @see TorrentBitfield#iterator(boolean)
*/
public Iterable<TorrentPiece> iterate(final boolean type) {
return new Iterable<TorrentPiece>() {
@Override
public Iterator<TorrentPiece> iterator() {
return TorrentBitfield.this.iterator(type);
}
};
}
/**
* The bitfield iterator
*
* @author Rogiel Josias Sulzbach (<a
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
*/
public class BitfieldIterator implements Iterator<TorrentPiece> {
/**
* The desired value
*/
private boolean value = true;
/**
* The current index
*/
private int index = 0;
/**
* Creates a new instance
*
* @param value
* the desired state
*/
public BitfieldIterator(boolean value) {
this.value = value;
}
@Override
public boolean hasNext() {
return getIndex() >= 0;
}
@Override
public TorrentPiece next() {
int index = -2;
try {
index = getIndex();
return context.getTorrent().getPiece(getIndex());
} finally {
this.index = index + 1;
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
/**
* Find the next index, without increasing it.
*
* @return the next index, -1 if non existent.
*/
private int getIndex() {
if (value)
return index = bits.nextSetBit(index);
else
return bits.nextClearBit(index);
}
}
}

View File

@@ -0,0 +1,188 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.torrent.context;
import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import net.torrent.protocol.tracker.message.PeerListMessage.PeerInfo;
import net.torrent.torrent.Torrent;
import net.torrent.torrent.context.TorrentPeerCapabilities.TorrentPeerCapability;
public class TorrentContext {
/**
* The torrent metadata object
*/
private final Torrent torrent;
/**
* The bitfield
*/
private final TorrentBitfield bitfield = new TorrentBitfield(this);
/**
* The capabilities
*/
private final TorrentPeerCapabilities capabilites = new TorrentPeerCapabilities(
TorrentPeerCapability.DHT, TorrentPeerCapability.FAST_PEERS);
private final Set<TorrentPeer> peers = new HashSet<TorrentPeer>();
/**
* Unknown peers does not have their IDs, consequently they cannot be
* queried using their Id and must be done through IP.
*/
private final Set<TorrentPeer> unknownPeers = new HashSet<TorrentPeer>();
/**
* Creates a new context
*
* @param torrent
* the torrent metadata
*/
public TorrentContext(Torrent torrent) {
this.torrent = torrent;
}
/**
* Get the torrent metadata object
*
* @return metadata object
*/
public Torrent getTorrent() {
return torrent;
}
/**
* Get the context bitfield
*
* @return the bitfield
*/
public TorrentBitfield getBitfield() {
return bitfield;
}
/**
* Get the capabilities of this context
*
* @return the capabilities
*/
public TorrentPeerCapabilities getCapabilites() {
return capabilites;
}
/**
* Tests if both peer and this context support an given capability.
*
* @param peer
* the peer
* @param capability
* the capability
* @return true if both support this capability
*/
public boolean supports(TorrentPeer peer, TorrentPeerCapability capability) {
return capabilites.supports(capability)
&& peer.getCapabilities().supports(capability);
}
/**
* Get the list of known peers (have known peerid)
*
* @return the list of peers
*/
public Set<TorrentPeer> getPeers() {
return Collections.unmodifiableSet(peers);
}
/**
* Get the list of unknown peers (don't have known peerid)
*
* @return the list of peers
*/
public Set<TorrentPeer> getUnknownPeers() {
return Collections.unmodifiableSet(unknownPeers);
}
/**
* Get an peer by its PeerID
*
* @param peerId
* the peer id
* @return the found peer. Null if not found.
*/
public TorrentPeer getPeer(TorrentPeerID peerId) {
for (final TorrentPeer peer : peers) {
if (peer.getPeerID().equals(peerId))
return peer;
}
return null;
}
/**
* Get an peer by its address
*
* @param address
* the address
* @return the found peer. Null if not found.
*/
public TorrentPeer getPeer(InetSocketAddress address) {
for (final TorrentPeer peer : peers) {
if (peer.getSocketAddress().equals(address))
return peer;
}
return null;
}
/**
* Lookup for a peer first by its id, then by address, if still not found,
* creates a new entry.
*
* @param id
* the peer id
* @param address
* the address
* @return the found or newly created peer
*/
public TorrentPeer getPeer(TorrentPeerID id, InetSocketAddress address) {
TorrentPeer peer = getPeer(id);
if (peer == null) {
peer = getPeer(address);
if (peer != null) {
if (peers.remove(peer))
peer = peer.createWithID(id);
} else {
peer = new TorrentPeer(this, id, null);
}
peers.add(peer);
}
return peer;
}
/**
* If this peer already exists, will update its IP.
*
* @param peerInfo
* the peer info object, returned from the tracker
*/
public TorrentPeer addPeerByPeerInfo(PeerInfo peerInfo) {
final TorrentPeerID id = TorrentPeerID.create(peerInfo.getPeerId());
final InetSocketAddress address = new InetSocketAddress(
peerInfo.getIp(), peerInfo.getPort());
TorrentPeer peer = getPeer(id, address);
peer.setSocketAddress(address);
return peer;
}
}

View File

@@ -0,0 +1,369 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.torrent.context;
import java.net.InetSocketAddress;
import java.util.Date;
/**
* Object representing a peer in the swarm.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class TorrentPeer {
/**
* The torrent context
*/
private final TorrentContext context;
/**
* The peer id
*/
private final TorrentPeerID peerID;
/**
* Set of peer capabilities
*/
private final TorrentPeerCapabilities capabilities = new TorrentPeerCapabilities();
/**
* The bitfield of the peer
*/
private final TorrentBitfield bitfield;
/**
* Local choking state to this peer. By default is
* {@link ChokingState#CHOKED choked}.
*/
private ChokingState chokingState = ChokingState.CHOKED;
/**
* Remote choking state of this peer. By default is
* {@link ChokingState#CHOKED choked}.
*/
private ChokingState peerChokingState = ChokingState.CHOKED;
/**
* The choking states
*
* @author Rogiel Josias Sulzbach (<a
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
*/
public enum ChokingState {
/**
* Choked: communication is "blocked". No pieces can be requested nor
* sent. By stantard, this is the default interest once the connection
* is made.
*/
CHOKED,
/**
* Unchoked: communication is "open": pieces can be requested and
* uploaded.
*/
UNCHOKED;
}
/**
* Local interest to this peer. By default is
* {@link InterestState#UNINTERESTED not interested}
*/
private InterestState interestState = InterestState.UNINTERESTED;
/**
* Remote interest of this peer. By default is
* {@link InterestState#UNINTERESTED not interested}
*/
private InterestState peerInterestState = InterestState.UNINTERESTED;
/**
* The interest states
*
* @author Rogiel Josias Sulzbach (<a
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
*/
public enum InterestState {
/**
* The peer is interested in the other pieces. If the peer is willing to
* upload, the remote peer is unchoked and data transfer can begin.
*/
INTERESTED,
/**
* The peer has not interest in the remote peer pieces. By stantard,
* this is the default interest once the connection is made.
*/
UNINTERESTED;
}
/**
* The peer socket address. For incoming connections.
*/
private InetSocketAddress socketAddress;
/**
* Last time the peer was found online.
*/
private Date lastReached = null;
/**
* State of the peer connectivity. Peer behind firewall or NAT will have
* false. By default all peers are accessible.
*/
private boolean accessible = true;
/**
* Creates a new peer
*
* @param context
* the torrent context
* @param peerID
* the peer id
* @param socketAddress
* the peer address
*/
public TorrentPeer(TorrentContext context, TorrentPeerID peerID,
InetSocketAddress socketAddress) {
if (peerID == null && socketAddress == null)
throw new IllegalArgumentException(
"PeerID or SocketAddress must be not null");
if (context == null)
throw new IllegalArgumentException("Context is null");
this.context = context;
this.peerID = peerID;
this.socketAddress = socketAddress;
this.bitfield = new TorrentBitfield(context);
}
/**
* Get the peer id
*
* @return the peer id
*/
public TorrentPeerID getPeerID() {
return peerID;
}
/**
* Set the peer address
*
* @param address
* the address
*/
public void setSocketAddress(InetSocketAddress address) {
this.socketAddress = address;
}
/**
* Get the peer address
*
* @return the address
*/
public InetSocketAddress getSocketAddress() {
return socketAddress;
}
/**
* Get the peer bitfield
*
* @return the bitfield
*/
public TorrentBitfield getBitfield() {
return bitfield;
}
/**
* Get the peer capabilities
*
* @return the capabilities
*/
public TorrentPeerCapabilities getCapabilities() {
return capabilities;
}
/**
* Get the local choking state to this peer. By default is
* {@link ChokingState#CHOKED choked}.
*
* @return the choking state
*/
public ChokingState getChokingState() {
return chokingState;
}
/**
* Set the local choking state to this peer. By default is
* {@link ChokingState#CHOKED choked}.
*
* @param chokingState
* the choking state
*/
public void setChokingState(ChokingState chokingState) {
this.chokingState = chokingState;
}
/**
*
* Get the remote choking state of this peer. By default is
* {@link ChokingState#CHOKED choked}.
*
* @return the peer choking state
*/
public ChokingState getPeerChokingState() {
return peerChokingState;
}
/**
* Set the remote choking state of this peer. By default is
* {@link ChokingState#CHOKED choked}.
*
* @param peerChokingState
* the peer choking state
*/
public void setPeerChokingState(ChokingState peerChokingState) {
this.peerChokingState = peerChokingState;
}
/**
* Get the local interest to this peer. By default is
* {@link InterestState#UNINTERESTED not interested}
*
* @return the local interest
*/
public InterestState getInterestState() {
return interestState;
}
/**
* Set the local interest to this peer. By default is
* {@link InterestState#UNINTERESTED not interested}
*
* @param interestState
* the local interest
*/
public void setInterestState(InterestState interestState) {
this.interestState = interestState;
}
/**
* Get the peer remote interest of this peer. By default is
* {@link InterestState#UNINTERESTED not interested}
*
* @return the peer remote interest
*/
public InterestState getPeerInterestState() {
return peerInterestState;
}
/**
* Set the peer remote interest of this peer. By default is
* {@link InterestState#UNINTERESTED not interested}
*
* @param peerInterestState
* the peer remote interest
*/
public void setPeerInterestState(InterestState peerInterestState) {
this.peerInterestState = peerInterestState;
}
/**
* Get the last time the peer has been reached by an connection
*
* @return the date of last connection
*/
public Date getLastReached() {
return lastReached;
}
/**
* Set the last time the peer has been reached by an connection
*
* @param lastReached
* the date of last connection
*/
public void setLastReached(Date lastReached) {
this.lastReached = lastReached;
}
/**
* State of the peer connectivity. Peer behind firewall or NAT will have
* false. By default all peers are accessible.
*
* @return true if is accessible, false otherwise.
*/
public boolean isAccessible() {
return accessible;
}
/**
* State of the peer connectivity. Peer behind firewall or NAT will have
* false. By default all peers are accessible.
*
* @param accessible
* true if is accessible, false otherwise.
*/
public void setAccessible(boolean accessible) {
this.accessible = accessible;
}
/**
* Get the torrent context
*
* @return the torrent context
*/
public TorrentContext getContext() {
return context;
}
/**
* Create an clone of this peer with the new {@link TorrentPeerID id}.
*
* @param id
* the new peer id
* @return the new peer with id.
*/
public TorrentPeer createWithID(TorrentPeerID id) {
return new TorrentPeer(context, id, socketAddress);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TorrentPeer other = (TorrentPeer) obj;
if (peerID == null) {
if (other.peerID != null)
return false;
if (socketAddress == null) {
if (other.socketAddress != null)
return false;
} else if (!socketAddress.equals(other.socketAddress))
return false;
} else if (!peerID.equals(other.peerID))
return false;
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
if (peerID != null) {
result = prime * result + peerID.hashCode();
} else if (socketAddress != null) {
result = prime * result + socketAddress.hashCode();
}
return result;
}
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.torrent.context;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
/**
* Object containing peers support for certain capabilities.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class TorrentPeerCapabilities {
/**
* BitSet containing available capabilities
*/
private BitSet capabilities = new BitSet(64);
/**
* Create a new instance
*/
public TorrentPeerCapabilities() {
}
/**
* Create a new instance with the given <tt>capabilities</tt>
*
* @param capabilities
* the capabilities supported
*/
public TorrentPeerCapabilities(TorrentPeerCapability... capabilities) {
for (final TorrentPeerCapability capability : capabilities) {
this.capabilities.set(capability.bit);
}
}
/**
* Create a new instance with the given <tt>capabilities</tt>
*
* @param capabilities
* the capabilities {@link BitSet} supported
*/
public TorrentPeerCapabilities(BitSet capabilitites) {
this.capabilities = capabilitites;
}
/**
* Tests if the given <tt>capability</tt> is supported.
*
* @param capability
* the capability
* @return true if capability is supported, false otherwise.
*/
public boolean supports(TorrentPeerCapability capability) {
return capabilities.get(capability.getBit());
}
/**
* Get an {@link List} of capabilities supported.
*
* @return the list os capabilities
*/
public List<TorrentPeerCapability> getCapabilities() {
final List<TorrentPeerCapability> capabilitites = new ArrayList<TorrentPeerCapability>();
for (final TorrentPeerCapability capability : TorrentPeerCapability
.values()) {
if (supports(capability))
capabilitites.add(capability);
}
return capabilitites;
}
/**
* Set the capabilities {@link BitSet}.
*
* @param capabilitites
* the bitset
*/
public void setCapabilities(BitSet capabilitites) {
this.capabilities = capabilitites;
}
/**
* Convert this matrix of capabilties to a {@link BitSet}.
*
* @return an {@link BitSet}.
*/
public BitSet toBitSet() {
return capabilities;
}
/**
* Enumeration of known capabilities.
*
* @author Rogiel Josias Sulzbach (<a
* href="http://www.rogiel.com/">http://www.rogiel.com/</a>)
*/
public enum TorrentPeerCapability {
/**
* Location aware protocol
*/
LOCATION_AWARE_PROTOCOL(21),
/**
* Extension protocol
*/
EXTENSION_PROTOCOL(44),
/**
* Fast peers support
*/
FAST_PEERS(62),
/**
* DHT Support
*/
DHT(64);
/**
* The bit index for this capability
*/
private final int bit;
/**
* Creates a new capability
*
* @param bit
* the bit marking this capability
*/
TorrentPeerCapability(int bit) {
this.bit = bit;
}
/**
* Get the bit marking this capability
*
* @return the bit marking the capability
*/
public int getBit() {
return bit;
}
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.torrent.context;
import java.util.Arrays;
/**
* Creates a new Peer id for an peer.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class TorrentPeerID {
/**
* The peer id in bytes (20 bytes)
*/
private byte[] peerID;
/**
* Creates a new PeerID.
*
* @param peerID
* the bytes of the peer id
*/
protected TorrentPeerID(byte[] peerID) {
this.peerID = peerID;
}
@Override
public String toString() {
return new String(peerID);
}
/**
* Convert to a byte array
*
* @return the id in bytes
*/
public byte[] toByteArray() {
return Arrays.copyOf(peerID, peerID.length);
}
/**
* Creates a new ID
*
* @param peerID
* the id byte array
* @return the new instance of {@link TorrentPeerID peer id}
*/
public static final TorrentPeerID create(byte[] peerID) {
return new TorrentPeerID(peerID);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(peerID);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TorrentPeerID other = (TorrentPeerID) obj;
if (!Arrays.equals(peerID, other.peerID))
return false;
return true;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.torrent.piece;
import net.torrent.torrent.TorrentPiece;
import net.torrent.torrent.context.TorrentPeer;
/**
* The {@link PieceSelector} is used to select the desired piece to be
* downloaded. Implementations must make sure the piece is not being downloaded
* already and that the peer has it.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public interface PieceSelector {
/**
* Selects the next piece suitable for download.
*
* @param peer
* the peer chosen for download
* @return the {@link TorrentPiece piece} selected for download.
*/
public TorrentPiece select(TorrentPeer peer);
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.torrent.piece;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import net.torrent.protocol.peerwire.manager.TorrentManager;
import net.torrent.torrent.TorrentPiece;
/**
* Selects randomly an piece. Once this instance is created it takes all pieces
* and put them into an list, then the list is sorted by an random algorithm.
* Once the next piece is called the pieces are returned in-list order.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class RandomPieceSelector extends SortedListPieceSelector {
/**
* Creates a new instance
*
* @param manager
* the torrent manager
*/
public RandomPieceSelector(TorrentManager manager) {
super(manager, Arrays.asList(manager.getTorrent().getPieces()));
}
@Override
protected void sort(List<TorrentPiece> pieces) {
Collections.shuffle(pieces);
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.torrent.piece;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import net.torrent.protocol.peerwire.manager.TorrentManager;
import net.torrent.torrent.TorrentPiece;
import net.torrent.util.comparator.PieceIndexComparator;
/**
* Select pieces in sequential order. Can be used for streaming purposes.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class SequentialPieceSelector extends SortedListPieceSelector {
/**
* Creates a new instance
*
* @param manager
* the torrent manager
*/
public SequentialPieceSelector(TorrentManager manager) {
super(manager, Arrays.asList(manager.getTorrent().getPieces()));
}
@Override
protected void sort(List<TorrentPiece> pieces) {
Collections.sort(pieces, PieceIndexComparator.SHARED_INSTANCE);
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.torrent.piece;
import java.util.List;
import net.torrent.protocol.peerwire.manager.TorrentManager;
import net.torrent.torrent.TorrentPiece;
import net.torrent.torrent.context.TorrentPeer;
/**
* An sorted {@link PieceSelector} select pieces sequentially (whenever
* possible) in a pre-sorted list. The list is sorted on construction by the
* {@link SortedListPieceSelector#sort(List) sort} method.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public abstract class SortedListPieceSelector implements PieceSelector {
/**
* The torrent manager
*/
private final TorrentManager manager;
/**
* The sorted list of pieces
*/
private final List<TorrentPiece> pieces;
/**
* Creates a new instance
*
* @param manager
* the torrent manager
* @param pieces
* the <b>unsorted</b> list of pieces
*/
protected SortedListPieceSelector(TorrentManager manager,
List<TorrentPiece> pieces) {
this.manager = manager;
this.pieces = pieces;
this.sort(this.pieces);
}
@Override
public synchronized TorrentPiece select(TorrentPeer peer) {
for (int index = 0; index < pieces.size(); index++) {
final TorrentPiece piece = pieces.get(index);
if (manager.getContext().getBitfield().hasPiece(piece))
continue;
if (!peer.getBitfield().hasPiece(piece))
continue;
if (manager.getDownloadManager().isDownloading(piece))
continue;
return piece;
}
return null;
}
/**
* Sorts the set using an implementation specific algorithm.
*
* @param pieces
* the unsorted pieces list that will be sorted.
*/
protected abstract void sort(List<TorrentPiece> pieces);
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.util;
import net.torrent.protocol.peerwire.PeerWirePeer;
import net.torrent.protocol.peerwire.manager.PeerManager;
/**
* Callback used in {@link PeerManager#execute(PeerCallback)}
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public interface PeerCallback {
/**
* Execute the desired action for <tt>peer</tt>
*
* @param peer
* the peer
*/
void callback(PeerWirePeer peer);
}

View File

@@ -0,0 +1,178 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.util;
import java.io.Serializable;
/**
* This class represents an interval.
*
* @author dante
*/
public final class Range implements Serializable {
private static final long serialVersionUID = 1L;
private final long start, length;
/**
* Creates a range with the given parameters
*
* @param start
* the beginning of the range
* @param length
* the length of the range
* @return a range
*/
public static Range getRangeByLength(long start, long length) {
return new Range(start, length);
}
/**
* Creates the smallest possible range containing the given numbers.
*
* @param number1
* number to be within the range
* @param number2
* number to be within the range
* @return
*/
public static Range getRangeByNumbers(long number1, long number2) {
long s = Math.min(number1, number2);
return new Range(s, Math.max(number1, number2) - s + 1);
}
/**
* Creates a new range
*
* @param start
* @param length
*/
private Range(long start, long length) {
if (start < 0)
throw new IllegalArgumentException("start must be >= 0");
if (length < 0)
throw new IllegalArgumentException("length must be >= 0");
this.start = start;
this.length = length;
}
/**
* @param range
* @return true if the given range is contained within this range
*/
public boolean contains(Range range) {
return getStart() <= range.getStart() && getEnd() >= range.getEnd();
}
/**
* @param pos
* @return true if the given point is contained within this range
*/
public boolean contains(long pos) {
return getStart() <= pos && getEnd() >= pos;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Range) {
Range r = (Range) obj;
return getStart() == r.getStart() && getLength() == r.getLength();
}
return false;
}
/**
* @return the last index contained in this range
*/
public long getEnd() {
return start + length - 1;
}
/**
* @return the length of this range
*/
public long getLength() {
return length;
}
/**
* @return the first index contained in this range
*/
public long getStart() {
return start;
}
/**
* Test if this range defines a zero-sized range.
*
* @return true if zero-sized.
*/
public boolean isEmpty() {
return length == 0;
}
@Override
public int hashCode() {
return (int) ((getStart() * 13) & (getEnd() * 137));
}
/**
* Creates a range which contains only the indices contained in the
* intersection of this range and the given range.
*
* @param range
* the range to intersect with
* @return the intersected range or null if the ranges don't overlap
*/
public Range intersection(Range range) {
if (!intersects(range)) {
return null;
}
return getRangeByNumbers(Math.max(getStart(), range.getStart()),
Math.min(getEnd(), range.getEnd()));
}
/**
* Returns the number of indices which are in this range and the given
* range.
*
* @param r
* @return 0 if the ranges don't overlap, the length of the intersection
* between them otherwise
*/
public long intersectionLength(Range r) {
if (!intersects(r)) {
return 0;
}
return Math.min(getEnd(), r.getEnd())
- Math.max(getStart(), r.getStart()) + 1;
}
/**
* @param range
* the range to intersect test with
* @return true if the ranges overlap
*/
public boolean intersects(Range range) {
return getStart() <= range.getEnd() && getEnd() >= range.getStart();
}
@Override
public String toString() {
return "[" + getStart() + " - " + getEnd() + "]";
}
}

View File

@@ -0,0 +1,21 @@
package net.torrent.util.bencoding;
import java.io.IOException;
/**
* BDecodingException: an error has occured when decoding bencoded data.
*
* @author Dennis "Bytekeeper" Waldherr
*/
public class BDecodingException extends IOException {
private static final long serialVersionUID = 1L;
public BDecodingException(Exception e) {
super(e);
}
public BDecodingException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,320 @@
package net.torrent.util.bencoding;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
/**
* Helper class to decode "bencoded" data.
*
* @author Dennis "Bytekeeper" Waldherr
*
*/
public class BEncodedInputStream extends InputStream {
private static final int MAX_STRING_LENGTH = 1024 * 1024;
private final PushbackInputStream in;
public BEncodedInputStream(InputStream in) {
this.in = new PushbackInputStream(in);
}
/**
* Utility method to decode exactly one bencoded element.
*
* @param bencode
* @return
* @throws BDecodingException
* @throws IOException
*/
public static Object bdecode(byte[] bencode) throws BDecodingException {
BEncodedInputStream in = new BEncodedInputStream(
new ByteArrayInputStream(bencode));
try {
return in.readElement();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Reads one bencoded element.
*
* @param in
* @return
* @throws IOException
* @throws BDecodingException
*/
public Object readElement() throws IOException, BDecodingException {
return bdecode();
}
/**
* Converts a string result from bencoding to a java String. If the data is
* already a String or null it is simply returned.
*
* @param data
* @return
* @throws BTypeException
*/
private static String stringOf(Object data) throws BTypeException {
if (data == null) {
return null;
}
try {
if (data instanceof byte[]) {
return new String((byte[]) data, "UTF-8");
} else if (data instanceof String) {
return (String) data;
} else {
throw new BTypeException("Unsupported type: " + data.getClass());
}
} catch (UnsupportedEncodingException e) {
throw new Error(e);
}
}
/**
* Converts a constructed or decoded integer to a java Integer. If the data
* is already an Integer or null it is simply returned.
*
* @param data
* @return
* @throws BTypeException
*/
private static Integer intOf(Object data) throws BTypeException {
if (data == null) {
return null;
}
if (data instanceof BigInteger) {
return ((BigInteger) data).intValue();
} else if (data instanceof Integer) {
return (Integer) data;
} else {
throw new BTypeException("Unsupported type: " + data.getClass());
}
}
/**
* Converts a constructed or decoded integer to a java Integer. If the data
* is already an Integer or null it is simply returned.
*
* @param data
* @return
* @throws BTypeException
*/
private static Long longOf(Object data) throws BTypeException {
if (data == null) {
return null;
}
if (data instanceof BigInteger) {
return ((BigInteger) data).longValue();
} else if (data instanceof Integer) {
return Long.valueOf((Integer) data);
} else if (data instanceof Long) {
return (Long) data;
} else {
throw new BTypeException("Unsupported type: " + data.getClass());
}
}
private Object bdecode() throws IOException, BDecodingException {
int head = in.read();
switch (head) {
case -1:
throw new EOFException();
case 'l':
return bdecodeList();
case 'i':
return bdecodeInteger();
case 'd':
return bdecodeDictionary();
default:
if (Character.isDigit(head)) {
in.unread(head);
return bdecodeString();
}
}
throw new BDecodingException("Parameter is not bencoded data.");
}
private BMap bdecodeDictionary() throws IOException, BDecodingException {
assert in != null;
BMap map = new BMapImpl();
int head;
while ((head = in.read()) != 'e') {
if (head < 0) {
throw new EOFException();
}
in.unread(head);
String key;
try {
key = stringOf(bdecodeString());
} catch (BTypeException e) {
throw new BDecodingException(e);
}
map.put(key, bdecode());
}
return map;
}
private byte[] bdecodeString() throws IOException, BDecodingException {
assert in != null;
int len = 0;
int head;
while ((head = in.read()) != ':') {
if (head < 0) {
throw new EOFException();
}
len = len * 10 + (head - '0');
if (len > MAX_STRING_LENGTH) {
throw new BDecodingException("Encoded string length exceeds "
+ MAX_STRING_LENGTH + " bytes!");
}
}
byte data[] = new byte[len];
int off = 0;
while (len > 0) {
int read = in.read(data, off, len);
len -= read;
off += read;
}
return data;
}
private Object bdecodeInteger() throws IOException, BDecodingException {
assert in != null;
StringBuilder b = new StringBuilder();
int head;
while ((head = in.read()) != 'e') {
if (head < 0) {
throw new EOFException();
}
if (!Character.isDigit(head)) {
throw new BDecodingException("Expected digit but got: " + head);
}
b.append((char) head);
}
return new BigInteger(b.toString());
}
private BList bdecodeList() throws IOException, BDecodingException {
assert in != null;
BList list = new BListImpl();
int head;
while ((head = in.read()) != 'e') {
if (head < 0) {
throw new EOFException();
}
in.unread(head);
list.add(bdecode());
}
return list;
}
@Override
public int read() throws IOException {
return in.read();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return in.read(b, off, len);
}
private static class BListImpl extends ArrayList<Object> implements BList {
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
public Integer getInteger(int index) throws BTypeException {
return intOf(get(index));
}
@Override
public Long getLong(int index) throws BTypeException {
return longOf(get(index));
}
@Override
public String getString(int index) throws BTypeException {
return stringOf(get(index));
}
@Override
public BList getList(int index) throws BTypeException {
Object tmp = get(index);
if (tmp instanceof BList) {
return (BList) tmp;
}
throw new BTypeException("Unexpected type: " + tmp.getClass());
}
@Override
public BMap getMap(int index) throws BTypeException {
Object tmp = get(index);
if (tmp instanceof BMap) {
return (BMap) tmp;
}
throw new BTypeException("Unexpected type: " + tmp.getClass());
}
}
private static class BMapImpl extends HashMap<String, Object> implements
BMap {
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
public Integer getInteger(String key) throws BTypeException {
return intOf(get(key));
}
@Override
public Long getLong(String key) throws BTypeException {
return longOf(get(key));
}
@Override
public String getString(String key) throws BTypeException {
return stringOf(get(key));
}
@Override
public BList getList(String key) throws BTypeException {
Object tmp = get(key);
if (tmp instanceof BList) {
return (BList) tmp;
}
if (tmp != null)
throw new BTypeException("Unexpected type: " + tmp.getClass());
return null;
}
@Override
public BMap getMap(String key) throws BTypeException {
Object tmp = get(key);
if (tmp instanceof BMap) {
return (BMap) tmp;
}
throw new BTypeException("Unexpected type: " + tmp.getClass());
}
}
}

View File

@@ -0,0 +1,168 @@
package net.torrent.util.bencoding;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
/**
* Helper class to "BEncode" data.
*
* @author Dennis "Bytekeeper" Waldherr
*
*/
public final class BEncodedOutputStream extends OutputStream {
static final Comparator<String> BYTE_COMPARATOR = new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
byte b1[] = bytesOf(o1);
byte b2[] = bytesOf(o2);
int len = Math.min(b1.length, b2.length);
for (int i = 0; i < len; i++) {
if (b1[i] > b2[i]) {
return 1;
}
if (b1[i] < b2[i]) {
return -1;
}
}
return b1.length - b2.length;
}
};
private final OutputStream out;
/**
* Converts a string into the raw byte array representation used to store
* into bencoded data.
*
* @param s
* @return
*/
public static byte[] bytesOf(String s) {
try {
return s.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new Error(e);
}
}
private BEncodedOutputStream(OutputStream out) {
if (out == null)
throw new InvalidParameterException("output strean is null");
this.out = out;
}
/**
* @param obj
* @return
*/
public static byte[] bencode(Object obj) {
if (obj == null)
throw new InvalidParameterException("Object to encode is null!");
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
BEncodedOutputStream bout = new BEncodedOutputStream(out);
bout.writeElement(obj);
bout.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
return out.toByteArray();
}
/**
* BEncodes data and outputs it to an OutputStream.
*
* @param out
* @param obj
* @throws IOException
*/
@SuppressWarnings("unchecked")
public void writeElement(Object obj) throws IOException {
if (obj == null)
throw new InvalidParameterException("Object to encode is null!");
if (obj instanceof String) {
bencodeString((String) obj);
} else if (obj instanceof byte[]) {
bencodeString((byte[]) obj);
} else if (obj instanceof Integer) {
bencodeInteger(BigInteger.valueOf((Integer) obj));
} else if (obj instanceof Long) {
bencodeInteger(BigInteger.valueOf((Long) obj));
} else if (obj instanceof BigInteger) {
bencodeInteger((BigInteger) obj);
} else if (obj instanceof Collection) {
bencodeList((Collection<?>) obj);
} else if (obj instanceof Map) {
bencodeDictionary((Map<String, ?>) obj);
} else {
throw new IllegalArgumentException("Type " + obj.getClass()
+ " is not bencodable.");
}
}
private void bencodeDictionary(Map<String, ?> dict) throws IOException {
assert out != null;
assert dict != null;
out.write('d');
List<String> sorted = new ArrayList<String>(dict.keySet());
Collections.sort(sorted, BYTE_COMPARATOR);
for (String key : sorted) {
writeElement(key);
writeElement(dict.get(key));
}
out.write('e');
}
private void bencodeList(Collection<?> list) throws IOException {
assert out != null;
assert list != null;
out.write('l');
for (Object child : list) {
writeElement(child);
}
out.write('e');
}
private void bencodeInteger(BigInteger integer) throws IOException {
assert integer != null;
out.write('i');
out.write(bytesOf(integer.toString()));
out.write('e');
}
private void bencodeString(String string) throws IOException {
assert string != null;
byte[] bytes = bytesOf(string);
bencodeString(bytes);
}
private void bencodeString(byte[] data) throws IOException {
assert data != null;
out.write(bytesOf(Integer.toString(data.length)));
out.write(':');
out.write(data);
}
@Override
public void write(int arg0) throws IOException {
out.write(arg0);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
}
}

View File

@@ -0,0 +1,21 @@
package net.torrent.util.bencoding;
import java.util.List;
/**
* Representation of a bencoded list.
*
* @author Bytekeeper
*
*/
public interface BList extends List<Object> {
String getString(int index) throws BTypeException;
Integer getInteger(int index) throws BTypeException;
Long getLong(int index) throws BTypeException;
BList getList(int index) throws BTypeException;
BMap getMap(int index) throws BTypeException;
}

View File

@@ -0,0 +1,21 @@
package net.torrent.util.bencoding;
import java.util.Map;
/**
* Representation of a bencoded map.
*
* @author Bytekeeper
*
*/
public interface BMap extends Map<String, Object> {
String getString(String key) throws BTypeException;
Integer getInteger(String key) throws BTypeException;
Long getLong(String key) throws BTypeException;
BList getList(String key) throws BTypeException;
BMap getMap(String key) throws BTypeException;
}

View File

@@ -0,0 +1,11 @@
package net.torrent.util.bencoding;
import java.io.IOException;
public class BTypeException extends IOException {
private static final long serialVersionUID = 1L;
public BTypeException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,32 @@
package net.torrent.util.bencoding;
import java.util.HashMap;
public class HashBMap extends HashMap<String, Object> implements BMap {
private static final long serialVersionUID = 1L;
@Override
public Integer getInteger(String key) {
return (Integer) get(key);
}
@Override
public BList getList(String key) {
return (BList) get(key);
}
@Override
public Long getLong(String key) {
return (Long) get(key);
}
@Override
public BMap getMap(String key) {
return (BMap) get(key);
}
@Override
public String getString(String key) {
return (String) get(key);
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.util.comparator;
import java.util.Comparator;
import net.torrent.torrent.TorrentPiece;
/**
* Compare two pieces indexes.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class PieceIndexComparator implements Comparator<TorrentPiece> {
/**
* Shared instance
*/
public static final PieceIndexComparator SHARED_INSTANCE = new PieceIndexComparator();
@Override
public int compare(TorrentPiece piece1, TorrentPiece piece2) {
if (piece1 == null)
return Integer.MAX_VALUE;
if (piece2 == null)
return Integer.MAX_VALUE;
return piece2.getIndex() - piece1.getIndex();
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.util.comparator;
import java.util.Comparator;
import java.util.Random;
/**
* Random comparator, values returned on each call are always different.
*
* @author <a href="http://www.rogiel.com/">Rogiel Josias Sulzbach</a>
*/
public class RandomComparator implements Comparator<Object> {
/**
* Shared instance
*/
public static final RandomComparator SHARED_INSTANCE = new RandomComparator();
/**
* Random number generator
*/
private final Random random = new Random();
@Override
public int compare(Object o1, Object o2) {
return random.nextInt(250 * 10 * (random.nextInt(100) + 1));
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.bittorrent.protocol.tracker;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import net.torrent.protocol.tracker.HttpTorrentTrackerAnnouncer;
import net.torrent.torrent.Torrent;
import org.junit.Test;
public class HttpTorrentTrackerAnnouncerTest {
@Test
public void testAnnounce() throws IOException, URISyntaxException,
InterruptedException {
final Torrent torrent = Torrent
.load(new File(
"src/test/resources/Tim Besamusca - Running Away EP Urban Sickness Audio USA1008.torrent"));
final HttpTorrentTrackerAnnouncer announcer = new HttpTorrentTrackerAnnouncer();
System.out.println(announcer.announce(torrent, torrent.getTrackers()
.iterator().next()));
Thread.sleep(10 * 60 * 1000);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2011 Rogiel Josias Sulzbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.torrent.protocol.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
View 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