package org.torrent.internal.peer.connection;
import java.beans.IndexedPropertyChangeEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.net.Socket;
import java.util.Collection;
import java.util.logging.Logger;
//import org.gudy.azureus2.plugins.dht.mainline.MainlineDHTProvider;
import org.torrent.internal.data.BTPart;
import org.torrent.internal.protocol.message.BTMessageVisitorAdapter;
import org.torrent.internal.protocol.message.BittorrentMessage;
import org.torrent.internal.protocol.message.Choke;
import org.torrent.internal.protocol.message.HandShakeA;
import org.torrent.internal.protocol.message.HandShakeB;
import org.torrent.internal.protocol.message.Interested;
import org.torrent.internal.protocol.message.NotInterested;
import org.torrent.internal.protocol.message.Piece;
import org.torrent.internal.protocol.message.Port;
import org.torrent.internal.protocol.message.Request;
import org.torrent.internal.protocol.message.UnChoke;
import org.torrent.internal.service.ContentStateListener;
import org.torrent.internal.transfer.BTSession;
import org.torrent.internal.transfer.event.BitfieldListener;
import org.torrent.internal.util.BTUtil;
import org.torrent.internal.util.Bits;
import org.torrent.internal.util.Time;
import org.torrent.internal.util.Validator;
public abstract class AbstractBTConnection implements BTConnection {
private static final Logger LOG = Logger.getLogger(AbstractBTConnection.class.getName());
protected Socket socket;
private PeerStatus myPeerStatus = new PeerStatus();
private PeerStatus remotePeerStatus = new PeerStatus();
private final BTMessageSender sender;
private final BTSession controller;
private HandShakeA handshakeA;
private HandShakeB handshakeB;
private boolean sentHandshakeA;
private boolean sentHandshakeB;
private final BTPendingRequestsHolder pendingRequests;
private volatile boolean handshakeComplete;
private final BitfieldHolder bitfieldHolder;
private final BTMessageReceiver receiver;
private final BufferedReceiver bufferedReceiver;
private final BTPendingRequestsHolder remoteRequests;
private HandshakeChecker handshakeChecker;
public interface HandshakeChecker {
boolean accept(HandShakeA handshakeA);
boolean accept(HandShakeB handshakeB);
};
private class PeerStatus implements BTPeerStatus {
private PropertyChangeSupport pcs = new PropertyChangeSupport(
AbstractBTConnection.this);
private volatile boolean choking = true;
private volatile boolean interested = false;
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
@Override
public boolean isChoking() {
return choking;
}
@Override
public boolean isInterested() {
return interested;
}
@Override
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
public void setChoking(boolean b) {
final boolean old = choking;
choking = b;
BTUtil.invokeLater(new Runnable() {
@Override
public void run() {
pcs.firePropertyChange("choking", old, choking);
}
});
}
public void setInterested(boolean b) {
final boolean old = interested;
interested = b;
BTUtil.invokeLater(new Runnable() {
@Override
public void run() {
pcs.firePropertyChange("interested", old, interested);
}
});
}
@Override
public String toString() {
return "Choking: " + choking + ", Interested: " + interested;
}
}
@Override
public String toString() {
return "Connection with id:" + handshakeB.getPeerID() + ", my status: " + myPeerStatus + ", remote status: " + remotePeerStatus;
}
public AbstractBTConnection(BTMessageSender sender, BTSession controller,
Bits remoteBitField, HandshakeChecker checker) {
Validator.nonNull(sender, controller, remoteBitField, checker);
this.controller = controller;
this.handshakeChecker = checker;
// Build outgoing stack
PendingRequestsHoldingSender pr = new PendingRequestsHoldingSender(
sender);
pendingRequests = pr;
sender = pr;
this.sender = sender;
// Build incoming stack
final PendingRequestsHoldingReceiver prhr = new PendingRequestsHoldingReceiver();
remoteRequests = prhr;
BTMessageReceiver recv = new BTMessageReceiver() {
@Override
public void received(BittorrentMessage message) {
LOG.finest("Received " + message);
try {
if (!myPeerStatus.isChoking()) {
prhr.received(message);
}
dispatch(message);
} catch (IOException e) {
LOG.warning(e.getLocalizedMessage());
try {
AbstractBTConnection.this.sender.close();
} catch (IOException e1) {
LOG.warning(e1.getLocalizedMessage());
}
}
}
};
BitfieldHoldingReceiver bhr = new BitfieldHoldingReceiver(recv,
new Bits(remoteBitField), new BitfieldListener() {
@Override
public void bitFieldBitChanged(
IndexedPropertyChangeEvent event) {
dispatchBitfieldEvent(new IndexedPropertyChangeEvent(
AbstractBTConnection.this, event.getPropertyName(),
event.getOldValue(), event.getNewValue(), event.getIndex()));
}
@Override
public void bitFieldChanged(PropertyChangeEvent event) {
dispatchBitfieldEvent(new PropertyChangeEvent(
AbstractBTConnection.this, event.getPropertyName(),
event.getOldValue(), event.getNewValue()));
}
@Override
public boolean isAvailable(BTPart part) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isRequired(
org.torrent.internal.data.TorrentMetaInfo.Piece piece) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isValidated(
org.torrent.internal.data.TorrentMetaInfo.Piece piece) {
// TODO Auto-generated method stub
return false;
}
@Override
public void removeContentStateListener(
ContentStateListener listener) {
// TODO Auto-generated method stub
}
@Override
public void setAvailable(BTPart part) {
// TODO Auto-generated method stub
}
@Override
public void setRequired(
org.torrent.internal.data.TorrentMetaInfo.Piece piece) {
// TODO Auto-generated method stub
}
@Override
public void setValidated(
org.torrent.internal.data.TorrentMetaInfo.Piece piece) {
// TODO Auto-generated method stub
}
//1
@Override
public void addContentStateListener(
ContentStateListener listener) {
// TODO Auto-generated method stub
}
});
bitfieldHolder = bhr;
bufferedReceiver = new BufferedReceiver(bhr);
bufferedReceiver.setEnableBuffering(true);
receiver = new SynchronizingReceiver(bufferedReceiver);
}
public BTMessageReceiver getReceiver() {
return receiver;
}
public BTSession getController() {
return controller;
}
@Override
public synchronized BTPeerStatus getMyPeerStatus() {
return myPeerStatus;
}
@Override
public synchronized Collection<BTPart> getPendingRequests() {
return pendingRequests.getPendingRequests();
}
@Override
public synchronized BTPeerStatus getRemotePeerStatus() {
return remotePeerStatus;
}
@Override
public synchronized void purgePendingRequests() {
pendingRequests.clearPendingRequests();
}
@Override
public void sendChoked(boolean choke) {
send(choke ? Choke.CHOKE : UnChoke.UNCHOKE, null);
}
@Override
public void sendInterested(boolean interested) {
send(interested ? Interested.INTERESTED : NotInterested.NOTINTERESTED,
null);
}
public synchronized void setHandshakeComplete() {
this.handshakeComplete = true;
// Can't be done currently, but should be possible
// handshakeChecker = null;
controller.connectionEstablished(this);
synchronized (bufferedReceiver) {
bufferedReceiver.flush();
bufferedReceiver.setEnableBuffering(false);
}
}
@Override
public boolean isConnectionEstablished() {
return handshakeComplete && isConnected();
}
@Override
public synchronized Bits getRemoteBitField() {
return bitfieldHolder.getBitField();
}
@Override
public synchronized void close() throws IOException {
sender.close();
}
@Override
public synchronized BTPart takeRemoteRequest() {
return remoteRequests.takePendingRequest();
}
@Override
public boolean hasNoPending() {
return sender.hasNoPending();
}
@Override
public synchronized void send(BittorrentMessage msg,
final BTMessageSenderCallback callback) {
LOG.finest("Sending " + msg);
handleOutgoing(msg);
sender.send(msg, new BTMessageSenderCallback() {
@Override
public void sent(BittorrentMessage message) {
if (callback != null) {
callback.sent(message);
}
AbstractBTConnection.this.sent(message);
}
@Override
public void failed(IOException e) {
if (callback != null) {
callback.failed(e);
}
}
});
}
private synchronized void sent(BittorrentMessage msg) {
assert msg != null;
if (!sentHandshakeA) {
if (msg instanceof HandShakeA); else {
throw new IllegalStateException("Sent " + msg + " before HandshakeA!");
}
sentHandshakeA = true;
} else if (!sentHandshakeB) {
if (msg instanceof HandShakeB); else {
throw new IllegalStateException("Sent " + msg + " before HandshakeB!");
}
sentHandshakeB = true;
}
controller.sent(AbstractBTConnection.this, msg);
}
private synchronized void handleOutgoing(final BittorrentMessage msg) {
msg.accept(new BTMessageVisitorAdapter() {
@Override
public void visitRequest(Request request) {
if (request.getLength() > 16384 || request.getLength() == 0) {
//throw new RuntimeException("Requested " + request);
}
}
@Override
public void visitPiece(Piece piece) {
remoteRequests.dequeuePendingRequest(piece.getPieceInfo());
}
@Override
public void visitChoke(Choke choke) {
myPeerStatus.setChoking(true);
remoteRequests.clearPendingRequests();
}
@Override
public void visitUnChoke(UnChoke unChoke) {
myPeerStatus.setChoking(false);
}
@Override
public void visitInterested(Interested interested) {
myPeerStatus.setInterested(true);
}
@Override
public void visitNotInterested(NotInterested notInterested) {
myPeerStatus.setInterested(false);
}
@Override
public void visitPort(Port port) {
System.out.println("RemoteDHTHost: " + socket.getInetAddress().getHostAddress() + " RemoteDHTPort: " + socket.getPort());
// if (!ml_dht_enabled) {return;}
// MainlineDHTProvider provider = getDHTProvider();
// System.out.println("Provider: " + provider);
//
// if (provider == null) {
// return;
// }
//
// try {
// provider.notifyOfIncomingPort(socket.getInetAddress().getHostAddress(), socket.getPort());
// } catch (Throwable t) {
// t.printStackTrace();
// }
}
});
}
protected void dispatchBitfieldEvent(PropertyChangeEvent propertyChangeEvent) {
controller.bitFieldChanged(propertyChangeEvent);
}
private void dispatchBitfieldEvent(
IndexedPropertyChangeEvent indexedPropertyChangeEvent) {
controller.bitFieldBitChanged(indexedPropertyChangeEvent);
}
private void dispatch(final BittorrentMessage msg) throws IOException {
Time time = new Time();
boolean unrequestedPiece = false;
if (handshakeA == null) {
if (msg instanceof HandShakeA) {
handshakeA = (HandShakeA) msg;
if (!handshakeChecker.accept(handshakeA)) {
throw new IOException("HandshakeA not accepted!");
}
} else {
throw new IOException("Expected HandshakeA, but got " + msg);
}
} else if (handshakeB == null) {
if (msg instanceof HandShakeB) {
handshakeB = (HandShakeB) msg;
if (!handshakeChecker.accept(handshakeB)) {
throw new IOException("HandshakeB not accepted!");
}
} else {
throw new IOException("Expected HandshakeB, but got " + msg);
}
} else {
assert !(msg instanceof HandShakeA);
assert !(msg instanceof HandShakeB);
synchronized (this) {
if (msg instanceof Piece) {
unrequestedPiece = !pendingRequests.getPendingRequests().contains(((Piece) msg).getPieceInfo());
}
msg.accept(new BTMessageVisitorAdapter() {
@Override
public void visitChoke(Choke choke) {
remotePeerStatus.setChoking(true);
}
@Override
public void visitHandShakeA(HandShakeA handShakeA) {
throw new IllegalStateException("Received HandShakeA");
}
@Override
public void visitHandShakeB(HandShakeB handShakeB) {
throw new IllegalStateException("Received HandShakeB");
}
@Override
public void visitInterested(Interested interested) {
remotePeerStatus.setInterested(true);
}
@Override
public void visitNotInterested(NotInterested notInterested) {
remotePeerStatus.setInterested(false);
}
@Override
public void visitPiece(Piece piece) {
pendingRequests.dequeuePendingRequest(piece.getPieceInfo());
}
@Override
public void visitUnChoke(UnChoke unChoke) {
remotePeerStatus.setChoking(false);
}
// @Override
// public void visitPort(Port port) {
// System.out.println("RemoteDHTHost: " + socket.getRemoteSocketAddress().toString() + " 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();
// }
// }
});
}
}
if (!unrequestedPiece) {
controller.received(AbstractBTConnection.this, msg);
} else {
controller.unrequestedPiece(AbstractBTConnection.this, (Piece) msg);
}
if (time.delta() > 100) {
LOG.warning(this + " took " + time.delta() + "ms to handle message " + msg);
}
}
// private static MainlineDHTProvider getDHTProvider() {
// return Main.gm.getMainlineDHTProvider();
// }
}