/*
* SSLTorrent.java
*
* Created on Jan 31, 2010, 8:06:37 PM
*
* Description: Provides a multiplexed bit torrent framework, in which peers exchange pieces of multiple files
* using Secure Socket Layer (SSL) over HTTP protocol, with a single shared server socket that handles incomming
* peer connections.
*
* Copyright (C) Jan 31, 2010 reed.
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.texai.torrent;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import net.jcip.annotations.NotThreadSafe;
import org.apache.log4j.Logger;
import org.texai.torrent.domainEntity.MetaInfo;
import org.texai.util.ByteUtils;
import org.texai.util.TexaiException;
import org.texai.x509.X509SecurityInfo;
/** Provides a multiplexed bit torrent framework, in which peers exchange pieces of multiple files
* using Secure Socket Layer (SSL) over HTTP protocol, with a single shared Netty socket that handles
* incomming peer connections.
*
* @author reed
*/
@NotThreadSafe
public final class SSLTorrent implements DownloadListener {
/** the logger */
private final static Logger LOGGER = Logger.getLogger(SSLTorrent.class);
/** the peer coordinator dictionary, info hash hex --> peer coordinator */
private final Map<String, PeerCoordinator> peerCoordinatorDictionary = new HashMap<>();
/** the peers dictionary, tracked peer information --> peer */
private final Map<TrackedPeerInfo, Peer> peersDictionary = new HashMap<>();
/** our tracked peer information */
private TrackedPeerInfo trackedPeerInfo;
/** the port for accepting peer connections */
private final int ourConnectionPort;
/** the tracker client that obtains information on new peers. */
private TrackerClient trackerClient;
/** the download listener, or null if seeding */
private DownloadListener downloadListener;
/** the X.509 security information */
final X509SecurityInfo x509SecurityInfo;
/** Constructs a new SSLTorrent instance.
*
* @param ourConnectionPort the port for accepting peer connections
* @param x509SecurityInfo X.509 security information
*/
public SSLTorrent(
final int ourConnectionPort,
final X509SecurityInfo x509SecurityInfo) {
//Preconditions
assert ourConnectionPort > 0 : "ourConnectionPort must be positive";
assert x509SecurityInfo != null : "x509SecurityInfo must not be null";
this.ourConnectionPort = ourConnectionPort;
this.x509SecurityInfo = x509SecurityInfo;
}
/** Returns whether this SSLTorrent is seeding the file or directory specified by the given meta info.
*
* @param metaInfo the given meta info
* @return whether this SSLTorrent is seeding the file or directory
*/
public boolean isSeeding(final MetaInfo metaInfo) {
//Preconditions
assert metaInfo != null : "metaInfo must not be null";
final PeerCoordinator peerCoordinator;
synchronized (peerCoordinatorDictionary) {
peerCoordinator = peerCoordinatorDictionary.get(ByteUtils.toHex(metaInfo.getInfoHash()));
}
if (peerCoordinator == null) {
return false;
} else {
return peerCoordinator.isCompleted();
}
}
/** Returns whether this SSLTorrent is downloading the file or directory specified by the given meta info.
*
* @param metaInfo the given meta info
* @return whether this SSLTorrent is downloading the file or directory
*/
public boolean isDownloading(final MetaInfo metaInfo) {
//Preconditions
assert metaInfo != null : "metaInfo must not be null";
final PeerCoordinator peerCoordinator;
synchronized (peerCoordinatorDictionary) {
peerCoordinator = peerCoordinatorDictionary.get(ByteUtils.toHex(metaInfo.getInfoHash()));
}
if (peerCoordinator == null) {
return false;
} else {
return !peerCoordinator.isCompleted();
}
}
/** Begins seeding, i.e. sharing, the file or directory that is described by the given meta info.
*
* @param metaInfo the given meta info
* @param storage the torrent file/directory storage
*/
public void beginSeeding(final MetaInfo metaInfo, final Storage storage) {
//Preconditions
assert metaInfo != null : "metaInfo must not be null";
assert storage != null : "storage must not be null";
if (isSeeding(metaInfo)) {
LOGGER.info("ignoring duplicate seed");
return;
}
if (isDownloading(metaInfo)) {
LOGGER.info("ignoring seeding when downloading the same file");
return;
}
LOGGER.info("begin seeding: " + metaInfo.getName());
final PeerCoordinator peerCoordinator = new PeerCoordinator(
metaInfo,
storage,
ourConnectionPort,
x509SecurityInfo,
this, // downloadListener
this); // sslTorrent
synchronized (peerCoordinatorDictionary) {
peerCoordinatorDictionary.put(ByteUtils.toHex(metaInfo.getInfoHash()), peerCoordinator);
}
trackerClient = new TrackerClient(
metaInfo,
peerCoordinator,
ourConnectionPort);
final Thread trackerClientThread = new Thread(trackerClient);
trackerClientThread.start();
peerCoordinator.setTrackerClient(trackerClient);
//Postconditions
assert isSeeding(metaInfo);
}
/** Ends seeding, i.e. sharing, the file or directory that is described by the given meta info.
*
* @param metaInfo the given meta info
*/
public void endSeeding(final MetaInfo metaInfo) {
//Preconditions
assert metaInfo != null : "metaInfo must not be null";
assert isSeeding(metaInfo) : "not seeding: " + metaInfo.getName();
final String infoHashHex = ByteUtils.toHex(metaInfo.getInfoHash());
final PeerCoordinator peerCoordinator;
synchronized (peerCoordinatorDictionary) {
peerCoordinator = peerCoordinatorDictionary.get(infoHashHex);
}
peerCoordinator.quit();
peerCoordinatorDictionary.remove(infoHashHex);
}
/** Downloads, the file or directory that is described by the given meta info.
*
* @param metaInfo the given meta info
* @param downloadDirectoryPath the download directory
* @param downloadListener the download listener, which is notified when the download completes
*/
public void download(
final MetaInfo metaInfo,
final String downloadDirectoryPath,
final DownloadListener downloadListener) {
//Preconditions
assert metaInfo != null : "metaInfo must not be null";
if (isDownloading(metaInfo)) {
LOGGER.info("ignoring duplicate download");
return;
}
if (isSeeding(metaInfo)) {
LOGGER.info("ignoring download when seeding the same file");
return;
}
this.downloadListener = downloadListener;
LOGGER.info("downloading: " + metaInfo.getName());
final Storage storage;
try {
storage = new Storage(metaInfo, downloadDirectoryPath);
} catch (IOException ex) {
throw new TexaiException(ex);
}
final PeerCoordinator peerCoordinator = new PeerCoordinator(
metaInfo,
storage,
ourConnectionPort,
x509SecurityInfo,
this, // downloadListener
this); // sslTorrent
synchronized (peerCoordinatorDictionary) {
peerCoordinatorDictionary.put(ByteUtils.toHex(metaInfo.getInfoHash()), peerCoordinator);
}
trackerClient = new TrackerClient(
metaInfo,
peerCoordinator,
ourConnectionPort);
final Thread trackerClientThread = new Thread(trackerClient);
trackerClientThread.start();
peerCoordinator.setTrackerClient(trackerClient);
}
/** Gets our tracked peer information.
*
* @return our tracked peer information
*/
public TrackedPeerInfo getOurTrackedPeerInfo() {
return trackedPeerInfo;
}
/** Gets the peers dictionary, tracked peer information --> peer.
*
* @return the peers dictionary
*/
public Map<TrackedPeerInfo, Peer> getPeersDictionary() {
return peersDictionary;
}
/** Receives notification that the associated download has completed.
*
* @param metaInfo the meta info
*/
@Override
public void downloadCompleted(final MetaInfo metaInfo) {
//Preconditions
assert metaInfo != null : "metaInfo must not be null";
final PeerCoordinator peerCoordinator;
synchronized (peerCoordinatorDictionary) {
peerCoordinator = peerCoordinatorDictionary.get(ByteUtils.toHex(metaInfo.getInfoHash()));
}
peerCoordinator.quit();
if (downloadListener != null) {
downloadListener.downloadCompleted(metaInfo);
}
}
/** Adds a peer coordinator.
*
* @param infoHash
* @param peerCoordinator
*/
public void addPeerCoordinator(
final byte[] infoHash,
final PeerCoordinator peerCoordinator) {
//Preconditions
assert infoHash != null : "infoHash must not be null";
assert peerCoordinator != null : "peerCoordinator must not be null";
synchronized (peerCoordinatorDictionary) {
peerCoordinatorDictionary.put(ByteUtils.toHex(infoHash), peerCoordinator);
}
}
/** Gets the peer coordinator for the given info hash, that identifies the torrent.
*
* @param infoHash the given info hash that identifies the torrent
* @return the peer coordinator for the given info hash
*/
public PeerCoordinator getPeerCoordinator(final byte[] infoHash) {
//Preconditions
assert infoHash != null : "infoHash must not be null";
synchronized (peerCoordinatorDictionary) {
return peerCoordinatorDictionary.get(ByteUtils.toHex(infoHash));
}
}
}