package org.torrent.internal.peer.connection; import java.io.IOException; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.WritableByteChannel; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.LinkedBlockingQueue; import java.util.logging.Logger; import org.gudy.azureus2.plugins.dht.mainline.MainlineDHTProvider; import org.torrent.internal.client.Main; import org.torrent.internal.data.TorrentMetaInfo; import org.torrent.internal.data.Hash; import org.torrent.internal.peer.connection.AbstractBTConnection.HandshakeChecker; import org.torrent.internal.protocol.BTTransform; import org.torrent.internal.protocol.BTTransformImpl; import org.torrent.internal.protocol.BittorrentDecoder; import org.torrent.internal.protocol.BittorrentMessageDecodingException; import org.torrent.internal.protocol.message.BitField; import org.torrent.internal.protocol.message.BittorrentMessage; import org.torrent.internal.protocol.message.HandShakeA; import org.torrent.internal.protocol.message.HandShakeB; import org.torrent.internal.protocol.message.Have; import org.torrent.internal.protocol.message.Port; import org.torrent.internal.protocol.message.Request; import org.torrent.internal.transfer.BTSession; import org.torrent.internal.util.BTUtil; import org.torrent.internal.util.Bits; import org.torrent.internal.util.Validator; public class SocketConnection { private final static Logger LOG = Logger.getLogger(SocketConnection.class .getName()); public static boolean ml_dht_enabled = false; public interface BandwidthLimiter { void allocate(int amount) throws InterruptedException; } public interface Filter { void received(BTConnection con, BittorrentMessage msg); } public enum BittorrentType { STANDARD, REALTIME; // STANDARD(0), REALTIME(1); // // public final Byte type; // // private BittorrentType(Integer type) { // this.type = type != null ? type.byteValue() : null; // } } public static BittorrentType type = BittorrentType.STANDARD; private static class BTSocketConnection extends AbstractBTConnection { // private Socket socket; private final BlockingQueue<Runnable> outQueue; public BTSocketConnection(Socket socket, BlockingQueue<Runnable> outQueue, BTMessageSender transport, BTSession controller, Bits remoteBitField, HandshakeChecker checker) { super(transport, controller, remoteBitField, checker); assert socket != null && outQueue != null; this.socket = socket; this.outQueue = outQueue; } @Override public boolean isConnected() { return socket.isConnected() && !socket.isClosed(); } @Override public String toString() { return "Connection to " + socket.getRemoteSocketAddress(); } } public static AbstractBTConnection createBTConnection( final BTSession session, final TorrentMetaInfo info, final Socket socket, final BandwidthLimiter outLimit, final BandwidthLimiter inLimit, HandshakeChecker hsChecker, final Filter filter) throws IOException { Validator.notNull(socket, "Socket is null!"); Validator.isTrue(socket.isConnected(), "Socket must already be connected!"); if (hsChecker == null) { hsChecker = new HandshakeChecker() { @Override public boolean accept(HandShakeA handshakeA) { return handshakeA.getInfoHash().equals(info.getInfoHash()); } @Override public boolean accept(HandShakeB handshakeB) { return true; } }; } final BlockingQueue<Runnable> outQueue = new LinkedBlockingQueue<Runnable>(); final WritableByteChannel out = Channels.newChannel(socket .getOutputStream()); final BTMessageSender transport = new BTMessageSender() { BTTransform trans = new BTTransformImpl(); @Override public void send(final BittorrentMessage msg, final BTMessageSenderCallback callback) { outQueue.add(messageRunnable(msg, callback)); } private Runnable messageRunnable(final BittorrentMessage msg, final BTMessageSenderCallback callback) { return new Runnable() { public void run() { try { final ByteBuffer buffer = trans.encodeMessage(null, msg); buffer.flip(); assert buffer.getInt(buffer.position()) != 0 : "Invalid buffer for " + msg + " :" + buffer; if (outLimit != null) { outLimit.allocate(buffer.remaining()); } assert buffer.getInt(buffer.position()) != 0; out.write(buffer); if (callback != null) { callback.sent(msg); } } catch (IOException e) { if (callback != null) { callback.failed(e); } } catch (InterruptedException e) { LOG.fine(e.getLocalizedMessage()); } } }; } @Override public void close() throws IOException { socket.close(); outQueue.add(new Runnable() { @Override public void run() { // Hack Thread.currentThread().interrupt(); } }); } @Override public boolean hasNoPending() { return outQueue.isEmpty(); } }; final BTSocketConnection con = new BTSocketConnection(socket, outQueue, transport, session, new Bits(info.getPiecesCount()), hsChecker); session.addConnection(con); final Thread outgoing = new Thread(new Runnable() { @Override public void run() { try { while (socket.isConnected() && !socket.isClosed()) { ((BTSocketConnection) con).outQueue.take().run(); } } catch (InterruptedException e) { LOG.finest(e.getLocalizedMessage()); } } }, "Outgoing queue for " + socket); outgoing.setDaemon(true); Thread incoming = new Thread(new Runnable() { @Override public void run() { try { BittorrentDecoder decoder = new BittorrentDecoder(type); byte[] data = new byte[4096]; int read; while ((read = socket.getInputStream().read(data)) > 0) { if (inLimit != null) { inLimit.allocate(read); } // System.out.println("in: " + read + " bytes."); decoder.update(data, 0, read); BittorrentMessage msg; while ((msg = decoder.nextMessage()) != null) { // System.out.println(msg); if (filter != null) { filter.received(con, msg); } con.getReceiver().received(msg); } } } catch (IOException e) { LOG.finer(e.getLocalizedMessage()); } catch (BittorrentMessageDecodingException e) { LOG.warning(e.getLocalizedMessage()); } catch (InterruptedException e) { LOG.finer(e.getLocalizedMessage()); } finally { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } // System.out.println(result + " - disconnected"); outgoing.interrupt(); con.getController().removeConnection(con); } } }, "Incoming queue for " + socket); incoming.setDaemon(true); outgoing.start(); incoming.start(); return con; } public static BTConnection connect(BTSession session, TorrentMetaInfo dataInfo, BandwidthLimiter out, BandwidthLimiter in, HandshakeChecker hsChecker, final BitfieldProvider bitProv, Socket socket, Hash peerID) throws IOException { final BTSocketConnection con = (BTSocketConnection) createBTConnection( session, dataInfo, socket, out, in, hsChecker, null); con.send(new HandShakeA(dataInfo.getInfoHash()), null); con.send(new HandShakeB(peerID), bitFieldSender(bitProv, con)); return con; } public static BTConnection accept(BTSession session, final TorrentMetaInfo info, BandwidthLimiter out, BandwidthLimiter in, HandshakeChecker hsChecker, final BitfieldProvider bitProv, final Socket socket, final Hash peerID) throws IOException { BTConnection con = createBTConnection(session, info, socket, out, in, hsChecker, new Filter() { public void received(final BTConnection con, BittorrentMessage msg) { if (msg instanceof HandShakeA) { System.out.println("Message of type HandShakeA"); con.send(new HandShakeA(info.getInfoHash()), null); con.send(new HandShakeB(peerID), bitFieldSender( bitProv, (BTSocketConnection) con)); } // } else if (msg instanceof Have) { // // System.out.println("Message of type have"); // System.out.println("Requesting piece index: " + // ((Have) msg).getPieceIndex() + " length: " + // info.getPieceLength()); // // con.send(new Request(((Have) msg).getPieceIndex(), 0, // info.getPieceLength()), null); // // //bitFieldSender(bitProv, (BTSocketConnection) // con)); // // // else if (msg instanceof Port) { // System.out.println("RemoteDHTPort: " + ((Port) // msg).getPort()); // // // if (!ml_dht_enabled) {return;} // MainlineDHTProvider provider = getDHTProvider(); // System.out.println("Provider: " + provider); // // if (provider == null) { // return; // } // // try { // provider.notifyOfIncomingPort(socket.getRemoteSocketAddress().toString(), // ((Port) msg).getPort()); // } catch (Throwable t) { // t.printStackTrace(); // } // } // con.send(new Request(((Have) msg).getPieceIndex(), 0, // info.getPieceLength()), null); // //bitFieldSender(bitProv, (BTSocketConnection) con)); // // } } }); return con; } protected static BTMessageSenderCallback bitFieldSender( final BitfieldProvider bitProv, final BTSocketConnection con) { return new BTMessageSenderCallback() { @Override public void failed(IOException e) { } @Override public void sent(BittorrentMessage message) { try { // BTUtil.invokeAndWait(new Callable<BitField>() { // // @Override // public BitField call() throws Exception { // con.setHandshakeComplete(); // if (bitProv != null) { // Bits bits = bitProv.getBitField(); // if (bits != null) { // con.send(new BitField(bits), null); // } // } // return null; // } // }); BTUtil.invokeLater(new Runnable() { @Override public void run() { // TODO Auto-generated method stub con.setHandshakeComplete(); if (bitProv != null) { Bits bits = bitProv.getBitField(); if (bits != null) { con.send(new BitField(bits), null); } } } }); } catch (Exception e) { e.printStackTrace(); } } }; } // private static MainlineDHTProvider getDHTProvider() { // return Main.gm.getMainlineDHTProvider(); // } }