/*
* PeerTest.java
*
* Created on Feb 2, 2010, 10:26:58 AM
*
* Description: Provides a test of two bit torrent peers, in which one is the leecher and the other is the seed.
*
* Copyright (C) Feb 2, 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.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.texai.network.netty.handler.AbstractBitTorrentHandlerFactory;
import org.texai.network.netty.handler.AbstractHTTPRequestHandlerFactory;
import org.texai.network.netty.handler.BitTorrentEncoder;
import org.texai.network.netty.handler.HTTPRequestHandler;
import org.texai.network.netty.handler.HTTPRequestHandlerFactory;
import org.texai.network.netty.pipeline.PortUnificationChannelPipelineFactory;
import org.texai.torrent.domainEntity.MetaInfo;
import org.texai.util.ThreadUtils;
import org.texai.x509.KeyStoreTestUtils;
import org.texai.x509.X509SecurityInfo;
import static org.junit.Assert.*;
/**
*
* @author reed
*/
public final class PeerTest {
/** the logger */
private static final Logger LOGGER = Logger.getLogger(PeerTest.class);
/** the seed and tracker connection-accepting port */
private static final int SEED_SERVER_PORT = 8088;
/** the downloader connection-accepting port */
private static final int DOWNLOADER_SERVER_PORT = 8089;
/** sets debugging */
// static {
// System.setProperty("javax.net.debug", "all");
// }
public PeerTest() {
}
@BeforeClass
public static void setUpClass() throws Exception {
}
@AfterClass
public static void tearDownClass() throws Exception {
}
@Before
public void setUp() {
final File downloadFile = new File("data/download/SentinelDome.jpg");
if (downloadFile.exists()) {
final boolean isDeleted = downloadFile.delete();
if (isDeleted) {
LOGGER.info("deleted " + downloadFile);
}
}
}
@After
public void tearDown() {
}
/**
* Test of bit torrent peers and tracker.
*/
@Test
@SuppressWarnings({"null", "SleepWhileInLoop"})
public void testBitTorrent() {
LOGGER.info("leech peer & seed peer & tracker");
// configure logging
Logger.getLogger(BitTorrentEncoder.class).setLevel(Level.WARN);
Logger.getLogger(BitTorrentHandler.class).setLevel(Level.WARN);
// configure the HTTP request handler by registering the tracker
final HTTPRequestHandler httpRequestHandler = HTTPRequestHandler.getInstance();
final Tracker tracker = new Tracker();
httpRequestHandler.register(tracker);
// seed peer SSL torrent
final X509SecurityInfo clientX509SecurityInfo = KeyStoreTestUtils.getClientX509SecurityInfo();
final SSLTorrent sslTorrent = new SSLTorrent(
SEED_SERVER_PORT,
clientX509SecurityInfo);
// configure the server channel pipeline factory
final AbstractBitTorrentHandlerFactory bitTorrentHandlerFactory = new BitTorrentHandlerFactory(sslTorrent);
final AbstractHTTPRequestHandlerFactory httpRequestHandlerFactory = new HTTPRequestHandlerFactory();
final X509SecurityInfo serverX509SecurityInfo = KeyStoreTestUtils.getServerX509SecurityInfo();
final ChannelPipelineFactory channelPipelineFactory = new PortUnificationChannelPipelineFactory(
null, // albusHCNMessageHandlerFactory,
bitTorrentHandlerFactory,
httpRequestHandlerFactory,
serverX509SecurityInfo);
// configure the server
final ServerBootstrap serverBootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
assertEquals("{}", serverBootstrap.getOptions().toString());
serverBootstrap.setPipelineFactory(channelPipelineFactory);
// bind and start to accept incoming connections
serverBootstrap.bind(new InetSocketAddress(SEED_SERVER_PORT));
final Object testDone_lock = new Object();
// set up torrent metainfo
MetaInfo metaInfo;
Storage storage = null;
try {
storage = new Storage(
new File("data/upload/SentinelDome.jpg"),
"http://127.0.0.1:" + SEED_SERVER_PORT + "/torrent-tracker/announce",
true); // the indicator whether hidden files are excluded
storage.createPieceHashes();
} catch (IOException ex) {
ex.printStackTrace();
fail(ex.getMessage());
}
assertTrue(storage.isComplete());
metaInfo = storage.getMetaInfo();
LOGGER.info("metaInfo: " + metaInfo);
tracker.addMetainfo(metaInfo);
assertTrue(tracker.isTracking(metaInfo.getURLEncodedInfoHash()));
assertFalse(tracker.hasPeers(metaInfo.getURLEncodedInfoHash()));
LOGGER.info("***************************************************************************************");
// start seeding peer
final SeedPeer seedPeer = new SeedPeer(metaInfo, storage, sslTorrent);
final Thread seedPeerThread = new Thread(seedPeer);
seedPeerThread.start();
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
}
if (tracker.hasPeers(metaInfo.getURLEncodedInfoHash())) {
break;
}
}
LOGGER.info("seed peer updated the tracker");
LOGGER.info("***************************************************************************************");
// start leech peer
final DownloadPeer downloadPeer = new DownloadPeer(metaInfo, testDone_lock);
final Thread downloadPeerThread = new Thread(downloadPeer);
downloadPeerThread.start();
// wait at most a minute for the test to complete
synchronized (testDone_lock) {
try {
testDone_lock.wait(60000);
} catch (InterruptedException ex) {
LOGGER.info("interrupted");
}
}
LOGGER.info("test is done");
seedPeer.quit();
final Timer timer = new Timer();
timer.schedule(new ShutdownTimerTask(), 5000);
// shut down executor threads to exit
LOGGER.info("releasing server resources");
serverBootstrap.releaseExternalResources();
timer.cancel();
}
/** Provides a process to run the seeding peer at 127.0.0.1:8088. */
private static final class SeedPeer implements Runnable {
/** the indicator that the beginSeeding peer should be halted */
private final AtomicBoolean isQuit = new AtomicBoolean(false);
/** the metainfo */
private final MetaInfo metaInfo;
/** the torrent file/directory storage */
private final Storage storage;
/** the SSL torrent */
private final SSLTorrent sslTorrent;
/** Constructs a new SeedPeer instance.
*
* @param metaInfo the metainfo
* @param storage the storage
* @param sslTorrent he SSL torrent
*/
SeedPeer(
final MetaInfo metaInfo,
final Storage storage,
final SSLTorrent sslTorrent) {
this.metaInfo = metaInfo;
this.storage = storage;
this.sslTorrent = sslTorrent;
}
/** Runs the seeding peer. */
@Override
@SuppressWarnings("SleepWhileInLoop")
public void run() {
Thread.currentThread().setName("seed peer");
sslTorrent.beginSeeding(metaInfo, storage);
assertTrue(storage.isComplete());
assertTrue(sslTorrent.isSeeding(metaInfo));
while (!isQuit.get()) {
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
}
}
sslTorrent.endSeeding(metaInfo);
assertFalse(sslTorrent.isSeeding(metaInfo));
LOGGER.info("seed peer completed");
}
/** Quits the seeding peer. */
void quit() {
isQuit.set(true);
}
}
/** Provides a process to run the download peer at 127.0.0.1:8089. */
private static final class DownloadPeer implements Runnable, DownloadListener {
/** the metainfo */
private final MetaInfo metaInfo;
/** the lock upon which the main thread will await test completion */
private final Object testDone_lock;
/** Constructs a new LeechPeer instance.
*
* @param metaInfo
* @param testDone_lock the lock upon which the main thread will await test completion
*/
DownloadPeer(final MetaInfo metaInfo, final Object testDone_lock) {
this.metaInfo = metaInfo;
this.testDone_lock = testDone_lock;
}
/** Runs the download peer. */
@Override
public void run() {
Thread.currentThread().setName("download peer");
final X509SecurityInfo x509SecurityInfo = KeyStoreTestUtils.getClientX509SecurityInfo();
final SSLTorrent sslTorrent = new SSLTorrent(
DOWNLOADER_SERVER_PORT,
x509SecurityInfo);
LOGGER.info("download peer: " + sslTorrent.getOurTrackedPeerInfo());
sslTorrent.download(metaInfo, "data/download/", this);
assertTrue(sslTorrent.isDownloading(metaInfo));
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
}
LOGGER.info("download peer completed");
synchronized (testDone_lock) {
testDone_lock.notifyAll();
}
}
/** Receives notification that the associated download has completed.
*
* @param metaInfo the meta info
*/
@Override
public void downloadCompleted(final MetaInfo metaInfo) {
LOGGER.info("download completed: " + metaInfo.getName());
}
}
/** Provides a task to run when the external resources cannot be released. */
private static final class ShutdownTimerTask extends TimerTask {
/** Runs the timer task. */
@Override
public void run() {
LOGGER.info("server resources not released");
Thread.currentThread().setName("shutdown timer task");
ThreadUtils.logThreads();
System.exit(0);
}
}
}