package edu.washington.cs.oneswarm.f2f.network; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.net.InetAddress; import java.nio.ByteBuffer; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.global.GlobalManagerStats; import org.gudy.azureus2.core3.util.Average; import org.gudy.azureus2.core3.util.Base32; import org.gudy.azureus2.core3.util.ByteFormatter; import org.gudy.azureus2.core3.util.Debug; import com.aelitis.azureus.core.networkmanager.ConnectionEndpoint; import com.aelitis.azureus.core.networkmanager.IncomingMessageQueue.MessageQueueListener; import com.aelitis.azureus.core.networkmanager.NetworkConnection; import com.aelitis.azureus.core.networkmanager.NetworkConnection.ConnectionListener; import com.aelitis.azureus.core.networkmanager.NetworkManager; import com.aelitis.azureus.core.networkmanager.OutgoingMessageQueue; import com.aelitis.azureus.core.networkmanager.impl.NetworkConnectionImpl; import com.aelitis.azureus.core.networkmanager.impl.osssl.OneSwarmSslTools; import com.aelitis.azureus.core.networkmanager.impl.osssl.OneSwarmSslTransportHelperFilterStream; import com.aelitis.azureus.core.networkmanager.impl.tcp.ProtocolEndpointTCP; import com.aelitis.azureus.core.peermanager.messaging.Message; import com.aelitis.azureus.core.peermanager.messaging.MessageException; import com.aelitis.azureus.core.peermanager.messaging.bittorrent.BTKeepAlive; import com.sun.org.apache.xerces.internal.impl.dv.util.Base64; import edu.washington.cs.oneswarm.f2f.BigFatLock; import edu.washington.cs.oneswarm.f2f.FileList; import edu.washington.cs.oneswarm.f2f.FileListManager; import edu.washington.cs.oneswarm.f2f.Friend; import edu.washington.cs.oneswarm.f2f.OSF2FMain; import edu.washington.cs.oneswarm.f2f.chat.ChatDAO; import edu.washington.cs.oneswarm.f2f.datagram.DatagramConnection; import edu.washington.cs.oneswarm.f2f.datagram.DatagramConnectionManagerImpl; import edu.washington.cs.oneswarm.f2f.datagram.DatagramListener; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FChannelDataMsg; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FChannelMsg; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FChannelReset; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FChat; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FDatagramInit; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FDatagramOk; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FDhtLocation; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FHandshake; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FHashSearch; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FHashSearchResp; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FMessage; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FMessageDecoder; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FMessageEncoder; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FMetaInfoReq; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FMetaInfoResp; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FSearch; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FSearchCancel; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FSearchResp; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FTextSearch; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FTextSearchResp; import edu.washington.cs.oneswarm.f2f.network.DelayedExecutorService.DelayedExecutor; import edu.washington.cs.oneswarm.f2f.network.OverlayManager.FriendConnectionListener; import edu.washington.cs.oneswarm.f2f.network.OverlayTransport.WriteQueueWaiter; import edu.washington.cs.oneswarm.f2f.network.QueueManager.QueueBuckets; import edu.washington.cs.oneswarm.f2f.servicesharing.OSF2FServiceDataMsg; import edu.washington.cs.oneswarm.plugins.PluginCallback; public class FriendConnection implements DatagramListener { private static BigFatLock lock = OverlayManager.lock; public final static Logger logger = Logger.getLogger(FriendConnection.class.getName()); /* * the max search rate, average over 10 s */ public final static double MAX_OUTGOING_SEARCH_RATE = 300; // This is set to 1000 for legacy clients who had a MAX_OUTGOING_SEARCH_RATE // of 1000. // We don't want to ban these people just yet. private final static double MAX_INCOMING_SEARCH_RATE = 1000 * 1.5; private final Average incomingSearchRate = Average.getInstance(1000, 10); private final Average outgoingSearchRate = Average.getInstance(1000, 10); // we are a bit more aggressive with the keep alives // a keepalive has to be sent every 15 s // if nothing has been received in 60 s we disconnect public static final int KEEP_ALIVE_FREQ = 15 * 1000; public static final int KEEP_ALIVE_TIMEOUT = 65 * 1000; private static final int TIMOUT_FILELIST = 2 * 60 * 1000; static final int INITIAL_HANDSHAKE_TIMEOUT = 60 * 1000; // max age of an overlay, 5 minutes should be enough // less conserves resource, more allows searches to expire later public static final int OVERLAY_FORWARD_TIMEOUT = 5 * 60 * 1000; private static final long RECENTLY_CLOSED_TIME = 90 * 1000; final double FORWARD_SEARCH_PROBABILITY = COConfigurationManager.getFloatParameter( "f2f_forward_search_probability", 0.50f); private int activeOverlays = 0; private final LinkedList<OSF2FMessage> bufferedMessages = new LinkedList<OSF2FMessage>(); private final NetworkConnection connection; // private boolean connectionRegistered = false; private final long connectionTime; private long dataBytesDownloaded = 0; Random random = new Random(); private final FileListManager filelistManager; private final FileListRequestHandler fileListRequestHandler = new FileListRequestHandler(); private final FriendConnectionQueue friendConnectionQueue; private boolean filelistReceived = false; private volatile boolean handShakeReceived = false; private int lastFileListSentToFriend = Integer.MAX_VALUE; private long lastByteRecvTime = System.currentTimeMillis(); // private final Queue<WriteQueueWaiter> writeWaiters = new // ConcurrentLinkedQueue<WriteQueueWaiter>(); // // private volatile int queueLengthBytes = 0; // // private volatile long queueLengthMs = 0; // private ConcurrentHashMap<Integer, Long> messageQueueTimes = new // ConcurrentHashMap<Integer, Long>(); private final FriendConnectionListener listener; private final MetaInfoRequestHandler metaInfoRequestHandler; private final boolean outgoing; /* * even though we are using concurrent hash maps, calls to add or remove * should be synchronized to avoid problems with duplicate keys */ private final ConcurrentHashMap<Integer, OverlayForward> overlayForwards = new ConcurrentHashMap<Integer, OverlayForward>(); private final ConcurrentHashMap<Integer, Boolean> overlayTransportPathsId = new ConcurrentHashMap<Integer, Boolean>(); private final ConcurrentHashMap<Integer, EndpointInterface> overlayTransports = new ConcurrentHashMap<Integer, EndpointInterface>(); /* * map to keep track of received searches to avoid sending the search back * to people we got the search from */ private final ConcurrentHashMap<Integer, Long> receivedSearches = new ConcurrentHashMap<Integer, Long>(); private long protocolBytesDownloaded = 0; private final QueueManager queueManager; private final RandomnessManager randomnessManager = new RandomnessManager(); private byte[] remoteFlags; private final Friend remoteFriend; private int remoteFriendKeyHash = -1; private final GlobalManagerStats stats; private final DebugMessageLog debugMessageLog; private IncomingQueueListener incomingListener; private OutgoingQueueListener outgoingQueueListener; private final ConcurrentHashMap<Integer, Long> recentlyClosedChannels = new ConcurrentHashMap<Integer, Long>(); private boolean mClosing; private PacketListener setupPacketListener; DatagramConnectionManagerImpl udpManager = DatagramConnectionManagerImpl.get(); DatagramConnection udpConnection; /** * outgoing * * @param _manager * @param remoteFriendAddr * @param _remoteFriend */ public FriendConnection(GlobalManagerStats stats, QueueManager _queueManager, ConnectionEndpoint remoteFriendAddr, Friend _remoteFriend, FileListManager _filelistManager, FriendConnectionListener _listener) { remoteFriendAddr .addProtocol(new ProtocolEndpointTCP(remoteFriendAddr.getNotionalAddress())); if (COConfigurationManager.getBooleanParameter("oneswarm.beta.updates")) { debugMessageLog = new DebugMessageLog(); } else { debugMessageLog = null; } this.queueManager = _queueManager; this.outgoing = true; this.filelistManager = _filelistManager; this.remoteFriend = _remoteFriend; this.listener = _listener; this.stats = stats; this.metaInfoRequestHandler = new MetaInfoRequestHandler(); byte[][] sharedSecret = new byte[2][0]; sharedSecret[0] = OneSwarmSslTransportHelperFilterStream.SHARED_SECRET_FOR_SSL_STRING .getBytes(); sharedSecret[1] = getRemotePublicKey(); this.connectionTime = System.currentTimeMillis(); logger.finer(getDescription() + ": making outgoing connection to:\n" + new String(Base64.encode(getRemotePublicKey()))); this.connection = NetworkManager.getSingleton().createConnection(remoteFriendAddr, new OSF2FMessageEncoder(), new OSF2FMessageDecoder(), true, false, sharedSecret); this.friendConnectionQueue = queueManager .registerConnectionForQueueHandling(FriendConnection.this); // this.hash = getHashOf(remoteFriend.getPublicKey(), // this.getRemoteIp(), this.getRemotePort()); this.connection.connect(null, false, new ConnectionListener() { @Override public void connectFailure(Throwable failure_msg) { logger.fine(getDescription() + ": " + connection + " : connect error: " + failure_msg.getMessage()); updateFriendConnectionLog(true, "connect attempt failed: " + failure_msg.getMessage()); close(); } @Override public void connectStarted() { } @Override public void connectSuccess(ByteBuffer remaining_initial_data) { if (!connection.getTransport().getEncryption() .startsWith(OneSwarmSslTransportHelperFilterStream.SSL_NAME)) { Debug.out("closing outgoing f2f connection without SSL: " + connection + " (" + connection.getTransport().getEncryption() + ")"); close(); return; } updateFriendConnectionLog(true, "Connected to: " + getRemoteIp().getHostAddress() + ":" + getRemotePort()); boolean registered = listener.connectSuccess(FriendConnection.this); if (!registered || !connection.isConnected()) { updateFriendConnectionLog(true, "Parallel connection closed"); return; } addQueueListener(); updateFriendConnectionLog(true, "Added queue listener"); if (debugMessageLog != null) { connection.getOutgoingMessageQueue().registerQueueListener( new OutgoingQueueListener()); } NetworkManager.getSingleton().startTransferProcessing(connection); enableFastMessageProcessing(true); sendHandshake(); updateFriendConnectionLog(true, "Sent handshake..."); } @Override public void exceptionThrown(Throwable error) { connectionException(error); } @Override public String getDescription() { return "connection listener: OSF2F session, outgoing"; } }); } public void setPacketListener(PacketListener listener) { this.setupPacketListener = listener; friendConnectionQueue.setPacketListener(listener); } private void addQueueListener() { if (incomingListener == null) { incomingListener = new IncomingQueueListener(); connection.getIncomingMessageQueue().registerQueueListener(incomingListener); } else { Debug.out("tried to register incoming listener multiple times"); } if (debugMessageLog != null) { if (outgoingQueueListener == null) { outgoingQueueListener = new OutgoingQueueListener(); connection.getOutgoingMessageQueue().registerQueueListener(outgoingQueueListener); } else { Debug.out("tried to register outgoing listener multiple times"); } } } /** * Used in ClientServiceConnection and ServerServiceConnection unit tests. * Creates a minimal FriendConnection marked as having received a handshake * so it thinks it's been started and will try to send data. */ public static FriendConnection createStubForTests(QueueManager _queueManager, NetworkConnection _conn, Friend _remoteFriend) { return new FriendConnection(_queueManager, _conn, _remoteFriend); } private FriendConnection(QueueManager _queueManager, NetworkConnection _conn, Friend _remoteFriend) { // Setting handShakeReceived = true tells AbstractServiceChannelEndpoint // that this connection has been started this.handShakeReceived = true; this.remoteFriend = _remoteFriend; this.queueManager = _queueManager; this.connection = _conn; this.friendConnectionQueue = queueManager .registerConnectionForQueueHandling(FriendConnection.this); this.outgoing = false; this.metaInfoRequestHandler = null; this.listener = null; this.filelistManager = null; this.debugMessageLog = null; this.connectionTime = 0; this.stats = null; } /** * creates a new incoming connection * * @param _connection * @param _remoteFriend * @param _filelistManager */ public FriendConnection(GlobalManagerStats stats, QueueManager _queueManager, NetworkConnection _connection, Friend _remoteFriend, FileListManager _filelistManager, FriendConnectionListener _listener) { this.queueManager = _queueManager; this.outgoing = false; this.connection = _connection; this.remoteFriend = _remoteFriend; this.listener = _listener; this.filelistManager = _filelistManager; this.connectionTime = System.currentTimeMillis(); this.stats = stats; this.metaInfoRequestHandler = new MetaInfoRequestHandler(); if (COConfigurationManager.getBooleanParameter("oneswarm.beta.updates")) { debugMessageLog = new DebugMessageLog(); } else { debugMessageLog = null; } friendConnectionQueue = queueManager.registerConnectionForQueueHandling(this); // this.hash = getHashOf(remoteFriend.getPublicKey(), // this.getRemoteIp(), this.getRemotePort()); // register our connection listener addQueueListener(); connection.connect(true, new ConnectionListener() { @Override public void connectFailure(Throwable failure_msg) { logger.fine(getDescription() + ": " + connection + " : connect error: " + failure_msg.getMessage()); close(); } @Override public void connectStarted() { // nop } @Override public void connectSuccess(ByteBuffer remaining_initial_data) { updateFriendConnectionLog(false, "incoming connection from: " + getRemoteIp().getHostAddress()); boolean registered = listener.connectSuccess(FriendConnection.this); if (!registered || !connection.isConnected()) { updateFriendConnectionLog(true, "parallel connection closed"); return; } } @Override public void exceptionThrown(Throwable error) { // ok, something strange happened, // notify connection and manager connectionException(error); } @Override public String getDescription() { return "connection listener: OSF2F session, incoming"; } }); NetworkManager.getSingleton().startTransferProcessing(connection); enableFastMessageProcessing(true); this.sendHandshake(); } public void clearTimedOutForwards() { List<Integer> timedOutIds = new LinkedList<Integer>(); for (Integer key : overlayForwards.keySet()) { if (overlayForwards.get(key).isTimedOut()) { timedOutIds.add(key); } } for (Integer key : timedOutIds) { OverlayForward f = overlayForwards.remove(key); if (overlayPathLogger.isEnabled()) { try { if (f != null) { overlayPathLogger.log(System.currentTimeMillis() + ", timeout_forward, " + f.getChannelId() + ", " + f.getBytesForwarded() + ", " + f.getAge() + ", " + f.getRemoteFriend().getNick() + ", " + f.getRemoteIpPort()); } } catch (Exception e) { e.printStackTrace(); } } } /* * and clear the recently closed channels maps */ for (Iterator<Integer> iterator = recentlyClosedChannels.keySet().iterator(); iterator .hasNext();) { Integer channel = iterator.next(); if (System.currentTimeMillis() - recentlyClosedChannels.get(channel) > RECENTLY_CLOSED_TIME) { iterator.remove(); } } } public void clearTimedOutTransports() { List<Integer> timedOutIds = new LinkedList<Integer>(); for (Integer key : overlayTransports.keySet()) { if (overlayTransports.get(key).isTimedOut()) { timedOutIds.add(key); } } for (Integer key : timedOutIds) { overlayTransports.get(key).closeConnectionClosed(this, "channel timed out"); } } public void clearTimedOutSearchRecords() { long currTime = System.currentTimeMillis(); for (Iterator<Long> iterator = receivedSearches.values().iterator(); iterator.hasNext();) { Long searchTime = iterator.next(); long age = currTime - searchTime; // delete old ones but keep a slighly longer history to make sure // that we don't let any messages through that were in the queue if (age > SearchManager.MAX_SEARCH_AGE + 15 * 1000) { iterator.remove(); } } } public void close() { mClosing = true; if (connection != null) { try { /* * watch out on this one, it will get the AEMonitor lock which * can dead lock us! */ if (!((NetworkConnectionImpl) connection).isClosed()) { connection.close(); } } catch (Exception e) { logger.warning("got exception when closing network connection: " + e.getMessage()); } } // we need to terminate all overlay transports List<EndpointInterface> transports = new LinkedList<EndpointInterface>( overlayTransports.values()); for (EndpointInterface overlayTransport : transports) { overlayTransport.closeConnectionClosed(this, "friend closed connection"); } // and all forwards List<OverlayForward> forwards = new LinkedList<OverlayForward>(overlayForwards.values()); for (OverlayForward overlayForward : forwards) { deregisterOverlayForward(overlayForward.getChannelId(), true); } // and deregister from the queue manager queueManager.deregisterForQueueHandling(this); listener.disconnected(FriendConnection.this); fileListRequestHandler.close(); if (udpConnection != null) { udpConnection.close(); udpConnection = null; } // updateFriendConnectionLog(true, "network connection closed"); } private void connectionException(Throwable error) { logger.warning(getDescription() + ": got exception in " + "OSF2F session (" + connection + "/" + remoteFriend.getNick() + ") disconnecting: " + error.getMessage() + " (from: " + error.toString() + ")"); String friendLogMessage = error.getMessage(); boolean expectedError = false; if (friendLogMessage == null) { expectedError = false; } else if (friendLogMessage.startsWith("transport closed")) { expectedError = true; } else if (friendLogMessage.startsWith("Connection reset by peer")) { expectedError = true; } else if (friendLogMessage .startsWith("An existing connection was forcibly closed by the remote host")) { expectedError = true; } if (!expectedError) { StringWriter st = new StringWriter(); error.printStackTrace(new PrintWriter(st)); String stackTrace = st.toString(); friendLogMessage += "\n" + stackTrace; } this.updateFriendConnectionLog(true, "got exception: " + friendLogMessage); close(); } private void updateFriendConnectionLog(boolean append, String message) { updateFriendConnectionLog(append, message, Level.FINE); } private void updateFriendConnectionLog(boolean append, String message, Level level) { remoteFriend.updateConnectionLog(append, this.hashCode(), message); logger.log(level, remoteFriend.getNick() + ": " + message); } static RotatingLogger overlayPathLogger = new RotatingLogger("overlay_connections"); private void deregisterOverlayForward(int channelId, boolean sendReset) { OverlayForward f = null; lock.lock(); try { f = overlayForwards.remove(channelId); recentlyClosedChannels.put(channelId, System.currentTimeMillis()); activeOverlays--; } finally { lock.unlock(); } if (f != null && sendReset) { f.sendReset(); } if (overlayPathLogger.isEnabled()) { try { if (f != null) { overlayPathLogger.log(System.currentTimeMillis() + ", deregistered_forward, " + f.getChannelId() + ", " + f.getBytesForwarded() + ", " + f.getAge() + ", " + f.getRemoteFriend().getNick() + ", " + f.getRemoteIpPort()); } } catch (Exception e) { e.printStackTrace(); } } friendConnectionQueue.clearForwardChannel(channelId); } void deregisterOverlayTransport(EndpointInterface transport) { lock.lock(); try { for (int pathID : transport.getPathID()) { overlayTransportPathsId.remove(pathID); if (overlayPathLogger.isEnabled()) { overlayPathLogger.log(System.currentTimeMillis() + ", deregistered_path, " + pathID); } } int channelId = transport.getChannelId(); EndpointInterface exists = overlayTransports.remove(channelId); recentlyClosedChannels.put(channelId, System.currentTimeMillis()); if (exists != null) { activeOverlays--; } if (overlayPathLogger.isEnabled()) { overlayPathLogger .log(System.currentTimeMillis() + ", deregistered_transport, " + exists.getChannelId() + ", " + exists.getBytesIn() + ", " + exists.getBytesOut() + ", " + exists.getAge() + ", " + exists.getPathID()); } } finally { lock.unlock(); } } public void doKeepAliveCheck() { if (friendConnectionQueue.getLastMessageSentTime() > KEEP_ALIVE_FREQ) { connection.getOutgoingMessageQueue().addMessage( new BTKeepAlive(OSF2FMessage.CURRENT_VERSION), false); } if (udpConnection != null && udpConnection.getLastMessageSentTime() > KEEP_ALIVE_FREQ) { udpConnection.sendUdpOK(); } } public void enableFastMessageProcessing(boolean enable) { logger.finer(getDescription() + ": setting fast message processing=" + enable); if (enable) { NetworkManager.getSingleton().upgradeTransferProcessing(connection, null); connection.getOutgoingMessageQueue().registerQueueListener( new LowLatencyMessageWriter(this.connection)); } else { // always enable this // NetworkManager.getSingleton().upgradeTransferProcessing(connection // ); } } /** * don't override, used in overlaymanager.handleSearch */ @Override public boolean equals(Object obj) { return super.equals(obj); } public long getConnectionAge() { return System.currentTimeMillis() - connectionTime; } public int getCurrentUploadSpeed() { return friendConnectionQueue.getCurrentUploadSpeedInBps(); } public long getDataBytesDownloaded() { return dataBytesDownloaded; } public long getDataBytesUploaded() { return friendConnectionQueue.getDataBytesUploaded(); } public int getForwardQueueLengthBytes() { return friendConnectionQueue.getForwardQueueBytes(); } public long getLastMessageRecvTime() { return System.currentTimeMillis() - lastByteRecvTime; } public long getLastMessageSentTime() { return friendConnectionQueue.getLastMessageSentTime(); } // Used for unit testing of ClientServiceConnection and // ServerServiceConnection public OSF2FMessage getLastMessageQueued() { return friendConnectionQueue.getLastMessageQueued(); } NetworkConnection getNetworkConnection() { return connection; } public Map<Integer, OverlayForward> getOverlayForwards() { return overlayForwards; } public Map<Integer, EndpointInterface> getOverlayTransports() { return overlayTransports; } public long getProtocolBytesDownloaded() { return protocolBytesDownloaded; } public long getProtocolBytesUploaded() { return friendConnectionQueue.getProtocolBytesUploaded(); } public Friend getRemoteFriend() { return remoteFriend; } @Override public InetAddress getRemoteIp() { return connection.getEndpoint().getNotionalAddress().getAddress(); } public int getRemotePort() { return connection.getEndpoint().getNotionalAddress().getPort(); } public byte[] getRemotePublicKey() { return remoteFriend.getPublicKey(); } public int getRemotePublicKeyHash() { if (remoteFriendKeyHash == -1) { remoteFriendKeyHash = Arrays.hashCode(remoteFriend.getPublicKey()); } return remoteFriendKeyHash; } public int getSendQueueCurrentCapacity(int channelId) { if (udpConnection != null && udpConnection.isSendingActive()) { return udpConnection.getCapacityForChannel(channelId); } else if (this.overlayTransports.size() > 0) { return (FriendConnectionQueue.MAX_FRIEND_QUEUE_LENGTH - friendConnectionQueue .getForwardQueueBytes()) / this.overlayTransports.size(); } else { return 0; } } public int getSendQueuePotentialCapacity(int channelId) { if (udpConnection != null && udpConnection.isSendingActive()) { return udpConnection.getPotentialCapacityForChannel(channelId); } else if (this.overlayTransports.size() > 0) { return (FriendConnectionQueue.MAX_FRIEND_QUEUE_LENGTH) / this.overlayTransports.size(); } else { return 0; } } private void handleChannelMsg(Message message) { if (message instanceof OSF2FChannelDataMsg) { OSF2FChannelDataMsg msg = (OSF2FChannelDataMsg) message; int channelId = msg.getChannelId(); if (overlayTransports.containsKey(channelId)) { // ok, this is a msg to us EndpointInterface t = overlayTransports.get(channelId); msg.setForward(false); // this might we the first message we get in this channel // means that the other side responded to our channel setup // we need to start the peer transport above the overlay if (!t.isStarted()) { t.start(); } // and tell it that we got a message t.incomingOverlayMsg(msg); } else if (overlayForwards.containsKey(channelId)) { // this is a message we should forward OverlayForward f = overlayForwards.get(channelId); msg.setForward(true); f.forwardMessage(msg); } else { if (!recentlyClosedChannels.containsKey(msg.getChannelId())) { logger.warning(getDescription() + ": got channel message for unregistered channel id " + msg.getChannelId() + " / " + msg.getCreatedTime()); } } } else { logger.warning(getDescription() + ": handleChannelMsg " + "got non OSF2FChannelMsg message: " + message.getDescription()); } } private void handleChannelReset(Message message) { if (message instanceof OSF2FChannelReset) { OSF2FChannelReset msg = (OSF2FChannelReset) message; if (overlayTransports.containsKey(msg.getChannelId())) { overlayTransports.get(msg.getChannelId()).closeChannelReset(); } else if (overlayForwards.containsKey(msg.getChannelId())) { final OverlayForward overlayForward = overlayForwards.get(msg.getChannelId()); // if we get a channel reset it means that the source of the // reset doesn't want to get any more messages with that channel // id, figure out where we get them from and close the forward + // flush the queue overlayForward.gotChannelReset(); } else { if (!recentlyClosedChannels.containsKey(msg.getChannelId())) { logger.warning(getDescription() + ": got channel reset for unregistered channel id " + msg.getChannelId()); } } } else { Debug.out(remoteFriend.getNick() + ": handleChannelReset " + "got non OSF2FChannelReset message: " + message.getDescription()); } } private void handleChannelSetup(Message message) { if (message instanceof OSF2FHashSearchResp) { OSF2FHashSearchResp msg = (OSF2FHashSearchResp) message; listener.gotSearchResponse(this, msg); } else { Debug.out(remoteFriend.getNick() + ": handleChannelSetup " + "got non channelSetup message: " + message.getDescription()); } } private final static int MAX_FILE_LIST_REQUESTS = 10; private int numFileListRequestsReceived = 0; private void handleFileListRequest(OSF2FTextSearch message) { try { if (numFileListRequestsReceived > 0) { updateFriendConnectionLog(true, "strange, remote friend is sending more than 1 file list request"); logger.warning(getDescription() + ": more than 1 file list request received"); } else if (numFileListRequestsReceived > MAX_FILE_LIST_REQUESTS) { String warnMsg = "banning friend for 10 minutes, sent more than " + MAX_FILE_LIST_REQUESTS + " file list requests (DOS?)"; updateFriendConnectionLog(true, warnMsg); remoteFriend.setFriendBannedUntil("file list request spam", System.currentTimeMillis() + 10 * 60 * 1000); logger.warning(warnMsg); close(); return; } numFileListRequestsReceived++; int prevId = Integer.parseInt(message.getSearchString()); this.sendFileListResponse(message.getRequestType(), message.getSearchID(), prevId, true); } catch (NumberFormatException e) { Debug.out("error when parsing file list request, '" + message.getSearchString() + "' is not a number"); } } private void handleHandshake(Message message) { if (message instanceof OSF2FHandshake) { OSF2FHandshake hs = (OSF2FHandshake) message; handShakeReceived = true; updateFriend(); remoteFlags = hs.getFlags(); String extras = ""; if (this.hasChatSupport()) { // System.out.println(this + " has chat"); extras += "(Chat)"; } if (this.hasExtendedFileListsSupport()) { // System.out.println(this + " has extended file lists"); extras += "(Extended file lists)"; } if (this.hasExtendedDHTKeyNegotiationSupport()) { extras += "(dht loc)"; } if (this.hasUdpSupport()) { extras += "(udp)"; } /* * check that we still are connected, if we are mark friends * connected */ if (!connection.isConnected()) { Debug.out("got handshake message but connection is already closed: " + remoteFriend.getNick()); updateFriendConnectionLog(true, "got handshake message but connection is already closed"); close(); return; } remoteFriend.connected(); updateFriendConnectionLog(true, "received remote handshake, " + extras); // check if we need to sync up on the dht publish location if (outgoing && this.hasExtendedDHTKeyNegotiationSupport() && remoteFriend.isDhtLocationConfirmed() == false) { // if we are outgoing, send the dht location updateFriendConnectionLog(true, "proposing dht location"); sendDhtLocation(); } // If the remote side supports UDP, send over the keys if (this.hasUdpSupport()) { try { udpConnection = udpManager.createConnection(this); initDatagramConnection(); } catch (Exception e) { logger.warning("Unable to create udp connection"); e.printStackTrace(); } } if (remoteFriend.isRequestFileList()) { sendFileListRequest(OSF2FMessage.FILE_LIST_TYPE_COMPLETE, null); // handshake is considered complete once the friend send the // filelist updateFriendConnectionLog(true, "filelist requested"); } else { // else, just spoof a new file list FileList fileList = new FileList(); filelistManager.receivedFriendFileList(remoteFriend, OSF2FMessage.FILE_LIST_TYPE_COMPLETE, fileList); fileListRequestHandler.notifyListenerComplete(fileList); } while (bufferedMessages.size() != 0) { sendMessage(bufferedMessages.remove(), true); } } else { Debug.out("handleHandshake got non " + "handshake message: " + message.getDescription()); } } @Override public void initDatagramConnection() { if (udpConnection != null) { updateFriendConnectionLog(true, "sending over udp crypto key"); sendMessage(udpConnection.createInitMessage(), true); } } private void sendDhtLocation() { /* * protocol: * * if( no address is confirmed) the user connecting (u1) generate 2 * addresses, start to publish and read from both the special locations * and the public key based locations * * u1 sends the addresses * * the user responding (u2) will save the addresses, set the dht * location status to confirmed and then send back a new message with * the addresses * * u1 receives the addresses, sets dht location status to confirmed and * stops updating the public key based addresses */ byte[] proposedDhtReadLocation = new byte[20]; byte[] proposedDhtWriteLocation = new byte[20]; Random r = new Random(); r.nextBytes(proposedDhtReadLocation); r.nextBytes(proposedDhtWriteLocation); remoteFriend.setDhtReadLocation(proposedDhtReadLocation); remoteFriend.setDhtWriteLocation(proposedDhtWriteLocation); OSF2FMain.getSingelton().getFriendManager().flushToDisk(true, true, false); updateFriendConnectionLog(true, "dht locations flushed to disk"); /* * the read and write locations in hte message are from the point of the * receiver, which means that they are swapped compared to what we have * stored locally * * the location we write to is the location the other user should read * from, and similarly for the read location */ sendMessage(new OSF2FDhtLocation(OSF2FMessage.CURRENT_VERSION, remoteFriend.getDhtWriteLocation(), remoteFriend.getDhtReadLocation())); } public boolean hasChatSupport() { if (remoteFlags == null) { return false; } return (remoteFlags[0] & OSF2FHandshake.SUPPORTS_CHAT) == OSF2FHandshake.SUPPORTS_CHAT; } public boolean hasExtendedFileListsSupport() { if (remoteFlags == null) { return false; } return (remoteFlags[0] & OSF2FHandshake.SUPPORTS_EXTENDED_FILE_LISTS) == OSF2FHandshake.SUPPORTS_EXTENDED_FILE_LISTS; } public boolean hasExtendedDHTKeyNegotiationSupport() { if (remoteFlags == null) { return false; } return (remoteFlags[0] & OSF2FHandshake.SUPPORTS_DHT_LOCATION_HS) == OSF2FHandshake.SUPPORTS_DHT_LOCATION_HS; } public boolean hasUdpSupport() { if (remoteFlags == null) { return false; } return (remoteFlags[0] & OSF2FHandshake.SUPPORTS_UDP) == OSF2FHandshake.SUPPORTS_UDP; } public int getImageMetaInfoQueueSize() { return metaInfoRequestHandler.imageRequests.size(); } public void clearOldMetainfoRequests() { metaInfoRequestHandler.clearOldRequests(); } public int getTorrentMetaInfoQueueSize() { return metaInfoRequestHandler.torrentRequests.size(); } private void handleMetainfoRequest(Message message) { if (message instanceof OSF2FMetaInfoReq) { OSF2FMetaInfoReq req = (OSF2FMetaInfoReq) message; int channelId = req.getChannelId(); // first, check if we can handle it... if (metaInfoRequestHandler.canRespond(req)) { metaInfoRequestHandler.handleMetaInfoRequest(req); } else if (overlayForwards.containsKey(channelId)) { // we have this path registered, sent it to the other end overlayForwards.get(channelId).forwardMessage(req); } else { Debug.out("got meta info request message " + "with unknown channel Id: " + req.getDescription()); } } else { Debug.out("handleMetainfoRequest got non " + "metainfoRequest message: " + message.getDescription()); } } private void handleMetainfoResponse(Message message) { if (message instanceof OSF2FMetaInfoResp) { OSF2FMetaInfoResp resp = (OSF2FMetaInfoResp) message; int channelId = resp.getChannelId(); // channel id 0 means that we shouldn't forward, this is just // between friends if (channelId == 0) { metaInfoRequestHandler.handleMetaInfoResponse(resp); } else if (metaInfoRequestHandler.handleMetaInfoResponse(resp)) { // we sent this, all is good. } else if (overlayForwards.containsKey(channelId)) { overlayForwards.get(channelId).forwardMessage(resp); } else { Debug.out("got meta info resp message " + "with unknown channel Id: " + resp.getDescription()); } } else { Debug.out("handleMetaInfoResponse got non " + "metaInfoResponse message: " + message.getDescription()); } } private void handleChat(Message message) { if (message instanceof OSF2FChat) { OSF2FChat chat = (OSF2FChat) message; if (remoteFriend.isAllowChat() == false) { logger.warning(getDescription() + "Received chat from " + this + " even though not allowed, dropping."); return; } logger.finer(getDescription() + "received chat: " + chat.getPlainText() + " from " + this.getRemoteFriend().getNick()); ChatDAO.get().queuePlaintextMessageForProcessing(chat.getPlainText(), this.getRemoteFriend()); } else { Debug.out(getDescription() + "handleChat got non " + "chat message: " + message.getDescription()); } } private void handleSearch(Message message) { boolean possiblePrune = true; // Update the stats based on the _raw_ volume of searches we observe. if (message instanceof OSF2FTextSearch) { OSF2FTextSearch cast = (OSF2FTextSearch) message; if (cast.getSearchString().startsWith("sha1;")) { stats.sha1PrefixSearchReceived(); } else if (cast.getSearchString().startsWith("ed2k;")) { stats.ed2kPrefixSearchReceived(); } else if (cast.getSearchString().startsWith("id;")) { stats.idPrefixSearchReceived(); } else { stats.textSearchReceived(); } } else if (message instanceof OSF2FHashSearch) { stats.hashSearchReceived(); } if (message instanceof OSF2FTextSearch) { OSF2FTextSearch asSearch = (OSF2FTextSearch) message; if (asSearch.getSearchString().startsWith("sha1;") == false && asSearch.getSearchString().startsWith("ed2k;") == false) { possiblePrune = false; } else { // Just always skip sha1;, ed2k; searches for now. // No search drop in 0.7.5 // return; } } else { // For now, just disable early drops possiblePrune = false; } if (possiblePrune) { // Early drop if we have it in the bloom filter SearchManager searchManager = OSF2FMain.getSingelton().getOverlayManager() .getSearchManager(); if (message instanceof OSF2FSearch) { OSF2FSearch asSearch = (OSF2FSearch) message; if (searchManager.isSearchInBloomFilter(asSearch)) { logger.fine("Early drop of search in BF: " + asSearch.getDescription()); return; } } // Start probablistically dropping double dropProb = searchManager.getFriendSearchDropProbability(this.getRemoteFriend()); if (random.nextDouble() < dropProb) { logger.fine("SearchQueuePressure drop: " + dropProb + " " + getRemoteFriend().getNick()); return; } } incomingSearchRate.addValue(1); long average = incomingSearchRate.getAverage(); if (average > MAX_INCOMING_SEARCH_RATE) { remoteFriend.updateConnectionLog(true, "Search spam detected, closing connection, friend banned for 10 min"); remoteFriend.setFriendBannedUntil("search spam", System.currentTimeMillis() + 10 * 60 * 1000); this.updateFriendConnectionLog(true, "Search spam detected"); close(); return; } if (logger.isLoggable(Level.FINEST)) { logger.finest("Incoming search. desc: " + getDescription() + " , rate=" + average); } if (message instanceof OSF2FSearch) { OSF2FSearch msg = (OSF2FSearch) message; // check special case, search ID = 0 means no forward if (msg.getSearchID() == 0) { if (message instanceof OSF2FTextSearch) { handleFileListRequest((OSF2FTextSearch) message); } else { Debug.out(getDescription() + "handleSearch got search with id=0, " + "but it wasn't an TextSearch!"); } } else { receivedSearches.put(msg.getSearchID(), System.currentTimeMillis()); listener.gotSearchMessage(this, msg); } } else { Debug.out(getDescription() + "handleSearch got non " + "OSF2FSearch message: " + message.getDescription()); } } private void handleTextSearchReponse(Message message) { if (message instanceof OSF2FTextSearchResp) { OSF2FTextSearchResp resp = (OSF2FTextSearchResp) message; if (resp.getFileListType() == OSF2FMessage.FILE_LIST_TYPE_COMPLETE) { fileListRequestHandler.handleFileListResponse(resp); } else if (resp.getFileListType() == OSF2FMessage.FILE_LIST_TYPE_PARTIAL) { listener.gotSearchResponse(this, resp); } else { Debug.out(getDescription() + "File list type: " + resp.getFileListType() + " not implemented"); } } else { Debug.out(getDescription() + "handleFileListResponse " + "got non fileListResponse message: " + message.getDescription()); } } private void handleDhtLocationMessage(OSF2FDhtLocation message) { // if we are outgoing we already sent the message, check if it matches // and if it does confirm the location if (outgoing) { byte[] readLocation = message.getReadLocation(); byte[] storedReadLocation = remoteFriend.getDhtReadLocation(); if (!Arrays.equals(readLocation, storedReadLocation)) { updateFriendConnectionLog(true, "got inconsistent dht read location data, not confirming dht location"); return; } byte[] writeLocation = message.getWriteLocation(); byte[] storedWriteLocation = remoteFriend.getDhtWriteLocation(); if (!Arrays.equals(writeLocation, storedWriteLocation)) { updateFriendConnectionLog(true, "got inconsistent dht write location data, not confirming dht location"); return; } /* * ok, the other side replied with the right locations, confirm the * location and stop updating the dht redundantly */ OSF2FMain.getSingelton().getFriendManager().flushToDisk(true, true, false); updateFriendConnectionLog(true, "dht location confirmed"); remoteFriend.setDhtLocationConfirmed(true); } else { // incoming byte[] readLocation = message.getReadLocation(); remoteFriend.setDhtReadLocation(readLocation); byte[] writeLocation = message.getWriteLocation(); remoteFriend.setDhtWriteLocation(writeLocation); remoteFriend.setDhtLocationConfirmed(true); OSF2FMain.getSingelton().getFriendManager().flushToDisk(true, true, false); /* * again, the read and write locations in the message are from the * standpoint of the receiver which means that they are swapped from * our standpoint */ sendMessage(new OSF2FDhtLocation(OSF2FMessage.CURRENT_VERSION, remoteFriend.getDhtWriteLocation(), remoteFriend.getDhtReadLocation())); updateFriendConnectionLog(true, "stored dht location, sending dht location confirmation"); } } @Override public int hashCode() { // don't override, return hash; return super.hashCode(); } public boolean hasRegisteredPath(int pathID) { return overlayTransportPathsId.containsKey(pathID); } public boolean isHandshakeReceived() { return handShakeReceived; } public boolean isFileListReceived() { return filelistReceived; } @Override public boolean isLanLocal() { return connection.isLANLocal(); } public boolean isReadyForWrite(WriteQueueWaiter writeQueueWaiter) { return friendConnectionQueue.isReadyForTransportWrite(writeQueueWaiter); } public boolean isTimedOut() { if (!handShakeReceived && System.currentTimeMillis() - connectionTime > INITIAL_HANDSHAKE_TIMEOUT) { // if the handshake hasn't completed in 60s it will never // complete... logger.fine(getDescription() + "handshake timeout, closing: " + this); updateFriendConnectionLog(true, "handshake timeout, closing"); return true; } long timeSinceLastMsg = System.currentTimeMillis() - lastByteRecvTime; if (!isFileListReceived() && System.currentTimeMillis() - connectionTime > TIMOUT_FILELIST) { logger.finer(getDescription() + "closing friend connection: " + this + " (filelist timed out)"); Debug.out("closing friend connection: " + this + " (filelist timed out), timeSinceLastMsg=" + timeSinceLastMsg + " bytes sent=" + (getProtocolBytesUploaded() + getDataBytesUploaded()) + " recv=" + (getProtocolBytesDownloaded() + getDataBytesDownloaded()) + " friend status: " + remoteFriend.getStatus()); updateFriendConnectionLog(true, "closing friend connection (filelist timed out)"); return true; } if (lastByteRecvTime != 0 && timeSinceLastMsg > KEEP_ALIVE_TIMEOUT) { logger.finer(getDescription() + "closing friend connection: " + this + " (timed out," + timeSinceLastMsg + ")"); updateFriendConnectionLog(true, "closing friend connection (timed out)"); return true; } return false; } void registerOverlayForward(OSF2FSearchResp currentSetupMsg, FriendConnection conn, OSF2FSearch search, boolean searcherSide) throws FriendConnection.OverlayRegistrationError { lock.lock(); try { if (overlayForwards.containsKey(currentSetupMsg.getChannelID())) { OverlayForward existing = overlayForwards.get(currentSetupMsg.getChannelID()); final String existingNick = existing.getRemoteFriend().getNick(); final String currentNick = conn.getRemoteFriend().getNick(); String existingType = existing.getSetupMessage().getID(); String currentType = currentSetupMsg.getID(); if (existingNick.equals(currentNick)) { throw new OverlayRegistrationError(getRemoteFriend().getNick(), currentSetupMsg.getChannelID(), "existing channel id: exid=" + Integer.toHexString(existing.getChannelId()) + " exbytes=" + existing.getBytesForwarded() + " exage=" + existing.getAge() + " extarget=" + existingNick + " cutarget=" + currentNick + " exside=" + existing.isSearcherSide() + " cuside=" + searcherSide + " registered=" + overlayForwards.size() + " ctype=" + currentType + " extype=" + existingType); } else { throw new OverlayRegistrationError("collision", currentSetupMsg.getChannelID(), "colliding channel id: exid=" + Integer.toHexString(existing.getChannelId()) + " exbytes=" + existing.getBytesForwarded() + " exage=" + existing.getAge() + " extarget=" + existingNick + " cutarget=" + currentNick + " exside=" + existing.isSearcherSide() + " cuside=" + searcherSide + " registered=" + overlayForwards.size() + " ctype=" + currentType + " extype=" + existingType); } } overlayForwards.put(currentSetupMsg.getChannelID(), new OverlayForward(currentSetupMsg.getChannelID(), conn, search, currentSetupMsg, searcherSide)); activeOverlays++; } finally { lock.unlock(); } } public void registerOverlayTransport(EndpointInterface transport) throws OverlayRegistrationError { lock.lock(); try { int channelId = transport.getChannelId(); if (overlayTransports.containsKey(channelId)) { Debug.out(getDescription() + "tried to register existing channel id, " + "this should _never_ happen " + this); throw new FriendConnection.OverlayRegistrationError(getRemoteFriend().getNick(), channelId, "existing channel id"); } overlayTransports.put(channelId, transport); for (int pathID : transport.getPathID()) { if (overlayTransportPathsId.containsKey(pathID)) { Debug.out(getDescription() + "tried to register existing path id, " + "this should _never_ happen " + this); // TODO(willscott): determine appropriate channelID here. throw new FriendConnection.OverlayRegistrationError( getRemoteFriend().getNick(), 0, "existing path id"); } overlayTransportPathsId.put(pathID, true); } activeOverlays++; } finally { lock.unlock(); } } void sendChannelMsg(OSF2FChannelMsg message, boolean transport) { if (udpConnection != null && message.isDatagram() && udpConnection.isSendingActive() && message instanceof OSF2FServiceDataMsg) { sendUdpPacket((OSF2FServiceDataMsg) message); return; } if (debugMessageLog != null) { debugMessageLog.messageQueuedChannel(message.getDescription()); } if (transport) { friendConnectionQueue.queuePacketForceQueue(QueueBuckets.TRANSPORT, message); } else { friendConnectionQueue.queuePacketForceQueue(QueueBuckets.FORWARD, message); } } void sendChannelRst(OSF2FChannelReset message) { friendConnectionQueue.queuePacket(QueueBuckets.FORWARD, message, false); } void sendChannelSetup(OSF2FHashSearchResp message, boolean forwarded) { // we need to add some deterministic randomness to this. // The goal is to be able to uniquely identify a path // without leaking the connections link id // (new RuntimeException()).printStackTrace(); message.updatePathID(randomnessManager.getDeterministicRandomInt(message.getPathID())); if (forwarded) { friendConnectionQueue.queuePacketForceQueue(QueueBuckets.FORWARD, message); } else { friendConnectionQueue.queuePacketForceQueue(QueueBuckets.TRANSPORT, message); } } public void sendFileListRequest(byte type, PluginCallback<FileList> callback) { if (type == OSF2FMessage.FILE_LIST_TYPE_COMPLETE) { fileListRequestHandler.sendFileListRequest(callback); } else { throw new RuntimeException("File list type: " + type + " not implemented"); } } private void sendFileListResponse(byte type, int searchId, int lastId, boolean sendResponseOnNoChange) { long t = System.currentTimeMillis(); FileList fileListToSendToFriend = filelistManager.getFileListToSendToFriend(remoteFriend); byte[] bytesToSend; if (lastFileListSentToFriend == fileListToSendToFriend.getListId()) { // if there is no change in the file list, skip the response unless // it is specifically asked for logger.finer(getDescription() + "file list for " + this.getRemoteFriend().getNick() + " unchanged, skipping file list send"); if (!sendResponseOnNoChange) { return; } bytesToSend = new byte[0]; } else { logger.finer(getDescription() + "file list for " + this.getRemoteFriend().getNick() + " changed, sending new list"); lastFileListSentToFriend = fileListToSendToFriend.getListId(); if (this.hasExtendedFileListsSupport() == false) { logger.finer(getDescription() + "sending basic flist: " + this.getRemoteFriend().getNick()); bytesToSend = FileListManager.encode_basic(fileListToSendToFriend, true); } else { logger.finer(getDescription() + "sending extended flist" + this.getRemoteFriend().getNick()); bytesToSend = FileListManager.encode_extended(fileListToSendToFriend, true); logger.finest(getDescription() + " the bytes of extended: " + ByteFormatter.encodeString(bytesToSend)); } } logger.finer(getDescription() + "sending custom list to friend, bytes=" + bytesToSend.length + " took " + (System.currentTimeMillis() - t) + "ms to generate"); int channelId = 0; OSF2FTextSearchResp msg = new OSF2FTextSearchResp(OSF2FMessage.CURRENT_VERSION, OSF2FMessage.FILE_LIST_TYPE_COMPLETE, searchId, channelId, bytesToSend); sendMessage(msg, QueueBuckets.CONTROL, true); } public void sendChat(String plaintextMessage) { if (logger.isLoggable(Level.FINE)) { logger.fine("[" + this.getRemoteFriend().getNick() + "]: Sending chat message: " + plaintextMessage); } connection.getOutgoingMessageQueue().addMessage(new OSF2FChat((byte) 1, plaintextMessage), false); } private void sendHandshake() { if (remoteFriend.isAllowChat()) { connection.getOutgoingMessageQueue().addMessage( new OSF2FHandshake((byte) 1, OSF2FHandshake.OS_FLAGS), false); } else { /** * Pretend not to support chat. */ byte[] my_flags = new byte[OSF2FHandshake.OS_FLAGS.length]; System.arraycopy(OSF2FHandshake.OS_FLAGS, 0, my_flags, 0, my_flags.length); my_flags[0] ^= OSF2FHandshake.SUPPORTS_CHAT; connection.getOutgoingMessageQueue().addMessage(new OSF2FHandshake((byte) 1, my_flags), false); } } private void sendMessage(OSF2FMessage msg) { sendMessage(msg, false); } private void sendMessage(OSF2FMessage msg, boolean skipQueue) { sendMessage(msg, QueueBuckets.CONTROL, skipQueue); } private void sendMessage(OSF2FMessage msg, QueueBuckets queueBuckets, boolean skipQueue) { if (!handShakeReceived) { bufferedMessages.add(msg); logger.finer(getDescription() + "waiting for handshake to complete, queue size: " + bufferedMessages.size()); } else { friendConnectionQueue.queuePacket(QueueBuckets.CONTROL, msg, skipQueue); } } public void sendMetaInfoRequest(byte type, int channelId, byte[] infoHash, int lengthHint, PluginCallback<byte[]> callback) { if (type == OSF2FMessage.METAINFO_TYPE_BITTORRENT || type == OSF2FMessage.METAINFO_TYPE_THUMBNAIL) { metaInfoRequestHandler.sendMetaInfoRequest(type, channelId, infoHash, lengthHint, callback); } else { throw new RuntimeException("Meta info type: " + type + " not implemented"); } } public GlobalManagerStats getStats() { return stats; } void sendSearch(OSF2FSearch search, boolean skipQueue) { if (receivedSearches.containsKey(search.getSearchID())) { logger.finer("not sending search, this search id is already received from this friend"); return; } long average = outgoingSearchRate.getAverage(); if (average > MAX_OUTGOING_SEARCH_RATE) { logger.warning(getDescription() + "Dropping search, sending too fast"); return; } outgoingSearchRate.addValue(1); if (search instanceof OSF2FTextSearch) { stats.textSearchSent(); } else if (search instanceof OSF2FHashSearch) { stats.hashSearchSent(); } else if (search instanceof OSF2FSearchCancel) { stats.searchCancelSent(); } if (logger.isLoggable(Level.FINE) && search instanceof OSF2FTextSearch) { logger.finer("Forwarding text search: " + ((OSF2FTextSearch) search).getSearchString()); } logger.finest(getDescription() + "forwarding search, rate=" + average); sendMessage(search, skipQueue); } void sendTextSearchResp(OSF2FTextSearchResp message, boolean forwarded) { if (forwarded) { sendMessage(message, QueueBuckets.FORWARD, false); } else { sendMessage(message, QueueBuckets.TRANSPORT, false); } } private String desc = null; @Override public String toString() { if (desc == null) { desc = connection + " :: " + remoteFriend + " id=" + hashCode(); } return desc; } // private class OutgoingQueueListener implements // OutgoingMessageQueue.MessageQueueListener { // // private long packetNum = 0; // // public void dataBytesSent(int byte_count) { // dataBytesUploaded += byte_count; // remoteFriend.updateUploaded(byte_count); // } // // public boolean messageAdded(Message message) { // // queueLengthBytes = connection.getOutgoingMessageQueue().getTotalSize(); // // System.out.println(" added: " + message + " , queue: " // // + queueLengthBytes + " bytes "); // messageQueueTimes.put(message.hashCode(), System.currentTimeMillis()); // return (true); // } // // public void messageQueued(Message message) { // // System.out.println(" queued: " + message); // } // // public void messageRemoved(Message message) { // // System.out.println(" removed: " + message); // messageQueueTimes.remove(message.hashCode()); // queueLengthBytes = connection.getOutgoingMessageQueue().getTotalSize(); // message.destroy(); // } // // public void messageSent(Message message) { // lastMessageSentTime = System.currentTimeMillis(); // packetNum++; // int len = 0; // DirectByteBuffer[] b = message.getData(); // for (int i = 0; i < b.length; i++) { // len += b[i].position(DirectByteBuffer.SS_NET); // } // queueLengthBytes = connection.getOutgoingMessageQueue().getTotalSize(); // Long queued = messageQueueTimes.remove(message.hashCode()); // // if (queued != null) { // queueLengthMs = System.currentTimeMillis() - queued; // } // if (!message.getID().equals(OSF2FMessage.ID_OS_CHANNEL_MSG) || packetNum // % 100 == 0) { // logger.fine(" sent: " + message.getDescription() + " " + len + // " bytes, queue: " + queueLengthBytes + " (" + queueLengthMs + "ms) \t::" // + FriendConnection.this); // // printRatelimitInfo(); // } // // readyForWrite(); // } // // public void protocolBytesSent(int byte_count) { // protocolBytesUploaded += byte_count; // remoteFriend.updateUploaded(byte_count); // } // // public void flush() { // } // } public void triggerFileListSend() { this.sendFileListResponse(OSF2FMessage.FILE_LIST_TYPE_COMPLETE, 0, -1, false); // if (remoteFriend.isRequestFileList()) { // sendFileListRequest(OSF2FMessage.FILE_LIST_TYPE_COMPLETE, null); // } } public String getDebugMessageLog() { if (debugMessageLog == null) { return "debug messages only available in beta mode, if you just enabled beta mode messages are available after reconnect"; } return debugMessageLog.getLog(); } public String getQueueDebug() { return friendConnectionQueue.getDebug() + "\nrecent search size=" + receivedSearches.size(); } public int getTotalOutgoingQueueLengthBytes() { return friendConnectionQueue.getTotalOutgoingQueueLengthBytes(); } private void updateFriend() { remoteFriend.setLastConnectIP(getRemoteIp()); remoteFriend.updateConnectedDate(); if (outgoing) { remoteFriend .setLastConnectPort(connection.getEndpoint().getNotionalAddress().getPort()); } logger.fine(getDescription() + "updating friend: out=" + outgoing + " friend=" + remoteFriend); } private String getDescription() { return remoteFriend.getNick() + ": "; } public static int getHashOf(byte[] publicKey, InetAddress addr, int port) { return Arrays.hashCode(publicKey) ^ (int) OneSwarmSslTools.unsignedIntToLong(addr.getAddress()) ^ port; } private class DebugMessageLog { private final NumberFormat formatter = new DecimalFormat("0,000.000"); private final NumberFormat numFormatter = new DecimalFormat("000"); private final static int LOG_LENGTH = 30; LinkedList<MessageLogEntry> log = new LinkedList<MessageLogEntry>(); private long bytesRecv = 0; private int sentNum = 0; private int queuedNum = 0; private int recvNum = 0; public synchronized void bytesReceived(long num) { bytesRecv += num; } private long bytesSent = 0; public synchronized void bytesSent(long num) { bytesSent += num; } public synchronized void messageReceived(String msg) { recvNum++; if (log.size() > LOG_LENGTH) { log.removeLast(); } bytesRecv = 0; log.addFirst(new MessageLogEntry("recv ", msg, recvNum)); } public synchronized void messageSent(String msg) { sentNum++; if (log.size() > LOG_LENGTH) { log.removeLast(); } bytesSent = 0; log.addFirst(new MessageLogEntry("sent ", msg, sentNum)); } public synchronized void messageQueuedListener(String msg) { queuedNum++; if (log.size() > LOG_LENGTH) { log.removeLast(); } log.addFirst(new MessageLogEntry("que2 ", msg, queuedNum)); } public synchronized void messageQueuedChannel(String msg) { queuedNum++; if (log.size() > LOG_LENGTH) { log.removeLast(); } log.addFirst(new MessageLogEntry("que1 ", msg, queuedNum)); } public synchronized String getLog() { StringBuilder b = new StringBuilder(); b.append("in transit: in=" + bytesRecv + " out=" + bytesSent + "\n"); b.append("log:\n"); for (MessageLogEntry e : log) { b.append(e + "\n"); } return b.toString(); } class MessageLogEntry { public MessageLogEntry(String action, String msg, int num) { this.time = System.currentTimeMillis() - connectionTime; this.message = msg; this.action = action; this.num = num; } final String action; final String message; final long time; final int num; @Override public String toString() { return formatter.format(time / 1000.0) + "\t" + action + ":" + numFormatter.format(num) + "\t" + message; } } } private class FileListRequestHandler { private final List<PluginCallback<FileList>> listeners = new ArrayList<PluginCallback<FileList>>(); private final byte type = OSF2FMessage.FILE_LIST_TYPE_COMPLETE; public FileListRequestHandler() { } public void close() { synchronized (listeners) { for (PluginCallback<FileList> callback : listeners) { callback.errorOccured("Connection closed"); } } } public void handleFileListResponse(OSF2FTextSearchResp resp) { if (!remoteFriend.isRequestFileList()) { return; } // byte type = resp.getFileListType(); byte[] fileList = resp.getFileList(); notifyListenersProgress(100); try { List<byte[]> newInfoHashes = filelistManager.receivedFriendFileList(remoteFriend, type, fileList, FriendConnection.this.hasExtendedFileListsSupport()); // valid list notifyListenerComplete(filelistManager.getFriendsList(remoteFriend)); // now, try so sync up all the thumbnails final List<byte[]> neededThumbnails = filelistManager.getMetaInfoManager() .getTorrentThumbnailNeeded(newInfoHashes); sendNextImageRequest(neededThumbnails); } catch (IOException e) { logger.warning(getDescription() + ": got decode error when " + "processing file list: " + this + ": " + e.getMessage()); } } public void notifyListenerComplete(FileList fileList) { synchronized (listeners) { if (fileList != null) { for (PluginCallback<FileList> callback : listeners) { callback.requestCompleted(fileList); } } else { for (PluginCallback<FileList> callback : listeners) { callback.errorOccured("file list is null"); } } listeners.clear(); } if (connection.isConnected()) { if (fileList.getFileNum() > 0) { updateFriendConnectionLog(true, "file list received"); } if (!filelistReceived) { remoteFriend.handShakeCompleted(FriendConnection.this.hashCode(), hasExtendedFileListsSupport(), hasChatSupport()); filelistReceived = true; } listener.handshakeCompletedFully(FriendConnection.this); } else { Debug.out("got file list but the network connection is already closed, setting friend as disconnected instead of connected"); remoteFriend.disconnected(FriendConnection.this.hashCode()); } } private void notifyListenersProgress(int progress) { synchronized (listeners) { for (PluginCallback<FileList> callback : listeners) { callback.progressUpdate(progress); } } } public void sendFileListRequest(PluginCallback<FileList> callback) { int lastID = 0; FileList previousList = filelistManager.getFriendsList(remoteFriend); if (previousList != null) { lastID = previousList.getListId(); } OSF2FMessage msg = new OSF2FTextSearch(OSF2FMessage.CURRENT_VERSION, OSF2FMessage.FILE_LIST_TYPE_COMPLETE, 0, "" + lastID); sendMessage(msg, true); if (callback != null) { synchronized (listeners) { listeners.add(callback); } } } private void sendNextImageRequest(final List<byte[]> neededThumbnails) { if (neededThumbnails.size() > 0) { final byte[] hash = neededThumbnails.remove(0); logger.finest(getDescription() + ": sending image request for: " + Base32.encode(hash)); sendMetaInfoRequest(OSF2FMessage.METAINFO_TYPE_THUMBNAIL, 0, hash, 0, new PluginCallback<byte[]>() { @Override public void dataRecieved(long bytes) { } @Override public void errorOccured(String string) { } @Override public void progressUpdate(int progress) { } @Override public void requestCompleted(byte[] data) { filelistManager.getMetaInfoManager().gotImageResponse(hash, data); sendNextImageRequest(neededThumbnails); } }); } } } private class IncomingQueueListener implements MessageQueueListener { @Override public void dataBytesReceived(int byte_count) { if (debugMessageLog != null) { debugMessageLog.bytesReceived(byte_count); } lastByteRecvTime = System.currentTimeMillis(); dataBytesDownloaded += byte_count; remoteFriend.updateDownloaded(byte_count); stats.f2fBytesReceived(byte_count); } @Override public boolean messageReceived(Message message) { if (debugMessageLog != null) { debugMessageLog.messageReceived(message.getDescription()); } lastByteRecvTime = System.currentTimeMillis(); if (logger.isLoggable(Level.FINEST)) { logger.finest(getDescription() + " got message: " + message.getDescription() + "\t::" + FriendConnection.this); } if (message.getID().equals(OSF2FMessage.ID_OS_HASH_SEARCH)) { handleSearch(message); } else if (message.getID().equals(OSF2FMessage.ID_OS_TEXT_SEARCH)) { handleSearch(message); } else if (message.getID().equals(OSF2FMessage.ID_OS_CHANNEL_DATA_MSG)) { handleChannelMsg(message); } else if (message.getID().equals(OSF2FMessage.ID_OS_SEARCH_CANCEL)) { listener.gotSearchCancel(FriendConnection.this, (OSF2FSearchCancel) message); } else if (message.getID().equals(OSF2FMessage.ID_OS_HANDSHAKE)) { handleHandshake(message); } else if (message.getID().equals(OSF2FMessage.ID_OS_CHANNEL_SETUP)) { handleChannelSetup(message); } else if (message.getID().equals(OSF2FMessage.ID_OS_CHANNEL_RST)) { handleChannelReset(message); } else if (message.getID().equals(OSF2FMessage.ID_OS_TEXT_SEARCH_RESP)) { handleTextSearchReponse(message); } else if (message.getID().equals(OSF2FMessage.ID_OS_METAINFO_REQ)) { handleMetainfoRequest(message); } else if (message.getID().equals(OSF2FMessage.ID_OS_METAINFO_RESP)) { handleMetainfoResponse(message); } else if (message.getID().equals(BTKeepAlive.ID_BT_KEEP_ALIVE)) { // ignore } else if (message.getID().equals(OSF2FMessage.ID_OS_CHAT)) { handleChat(message); } else if (message.getID().equals(OSF2FMessage.ID_OS_DHT_LOCATION)) { handleDhtLocationMessage((OSF2FDhtLocation) message); } else if (message.getID().equals(OSF2FMessage.ID_OS_DATAGRAM_INIT)) { if (udpConnection != null) udpConnection.initMessageReceived((OSF2FDatagramInit) message); } else if (message.getID().equals(OSF2FMessage.ID_OS_DATAGRAM_OK)) { if (udpConnection != null) udpConnection.okMessageReceived(); } else { Debug.out(getDescription() + "unknown message: " + message.getDescription()); } return (true); } @Override public void protocolBytesReceived(int byte_count) { if (debugMessageLog != null) { debugMessageLog.bytesReceived(byte_count); } lastByteRecvTime = System.currentTimeMillis(); protocolBytesDownloaded += byte_count; remoteFriend.updateDownloaded(byte_count); stats.f2fBytesReceived(byte_count); } } private class MetaInfoRequestHandler { public final static int MAX_METAINFO_REQUEST_AGE = 10 * 60 * 1000; // private final ConcurrentHashMap<Integer, Long> lastMsgTimes = new // ConcurrentHashMap<Integer, Long>(); // private final ConcurrentHashMap<Integer, Long> sentRequests = new // ConcurrentHashMap<Integer, Long>(); // private final HashMap<Integer, List<PluginCallback<byte[]>>> // callbacks = new HashMap<Integer, List<PluginCallback<byte[]>>>(); private final ConcurrentHashMap<Long, MetaInfoRequest> imageRequests = new ConcurrentHashMap<Long, MetaInfoRequest>(); private final ConcurrentHashMap<Long, MetaInfoRequest> torrentRequests = new ConcurrentHashMap<Long, MetaInfoRequest>(); private final DelayedExecutor delayedExecutor; public MetaInfoRequestHandler() { delayedExecutor = DelayedExecutorService.getInstance().getVariableDelayExecutor(); } public void clearOldRequests() { for (Iterator<MetaInfoRequest> iterator = imageRequests.values().iterator(); iterator .hasNext();) { MetaInfoRequest r = iterator.next(); if (r.getAge() > MAX_METAINFO_REQUEST_AGE) { iterator.remove(); } } for (Iterator<MetaInfoRequest> iterator = torrentRequests.values().iterator(); iterator .hasNext();) { MetaInfoRequest r = iterator.next(); if (r.getAge() > MAX_METAINFO_REQUEST_AGE) { iterator.remove(); } } } public boolean canRespond(OSF2FMetaInfoReq req) { boolean canRespond = false; byte[] infoHash = req.getInfoHash(); byte[] data = filelistManager.getMetaInfoManager().getMetaInfo(remoteFriend, req.getMetaInfoType(), infoHash); if (data != null) { canRespond = true; } logger.finest(getDescription() + "can respond: " + canRespond); return canRespond; } private ConcurrentHashMap<Long, MetaInfoRequest> getRequestMap(byte type) { ConcurrentHashMap<Long, MetaInfoRequest> requests; if (type == OSF2FMessage.METAINFO_TYPE_BITTORRENT) { requests = torrentRequests; } else if (type == OSF2FMessage.METAINFO_TYPE_THUMBNAIL) { requests = imageRequests; } else { requests = null; } return requests; } public void handleMetaInfoRequest(final OSF2FMetaInfoReq req) { final byte[] infoHash = req.getInfoHash(); final byte type = req.getMetaInfoType(); final byte[] data = filelistManager.getMetaInfoManager().getMetaInfo(remoteFriend, type, infoHash); final long infoHashhash = filelistManager.getInfoHashhash(infoHash); final int startByte = req.getStartByte(); final int requestSize = Math.min(OSF2FMessage.METAINFO_CHUNK_SIZE, data.length - startByte); // sanity checks if (requestSize < 0 || startByte + requestSize > data.length) { Debug.out(getDescription() + "got strange metainfo request, length=" + data.length + " startpos=" + startByte + " requestSize=" + requestSize + "\t" + FriendConnection.this); return; } else { int delay = OSF2FMain.getSingelton().getOverlayManager() .getSearchDelayForInfohash(remoteFriend, infoHash); delayedExecutor.queue(delay, new TimerTask() { @Override public void run() { byte[] chunk = new byte[requestSize]; System.arraycopy(data, startByte, chunk, 0, chunk.length); OSF2FMetaInfoResp msg = new OSF2FMetaInfoResp(OSF2FMessage.CURRENT_VERSION, req.getChannelId(), type, infoHashhash, startByte, data.length, chunk); sendMessage(msg); } }); } } /** * return true if we sent this message * * @param msg * @return */ public boolean handleMetaInfoResponse(OSF2FMetaInfoResp msg) { long infoHashHash = msg.getInfoHashHash(); logger.finest(getDescription() + "got response for :" + infoHashHash); // check if it is from us ConcurrentHashMap<Long, MetaInfoRequest> requests = getRequestMap(msg.getMetaInfoType()); MetaInfoRequest request = requests.get(infoHashHash); if (request != null) { // great, we want this stuff... add request.gotResponse(msg); // but it might not have been from us actually, check // channel id if (request.getChannelId() == msg.getChannelId()) { return true; } } return false; } public void sendMetaInfoRequest(byte type, int channelId, byte[] infohash, int lengthHint, PluginCallback<byte[]> callback) { long infohashhash = filelistManager.getInfoHashhash(infohash); logger.finest(getDescription() + "sending request for " + infohashhash); ConcurrentHashMap<Long, MetaInfoRequest> requests = getRequestMap(type); // check if we already have a request for this if (!requests.containsKey(infohashhash)) { requests.put(infohashhash, new MetaInfoRequest(type, channelId, infohash, lengthHint)); logger.finest(getDescription() + "adding to requests: " + infohashhash); } MetaInfoRequest request = requests.get(infohashhash); request.addListener(callback); request.sendInitialMetaInfoRequest(type); } private class MetaInfoRequest { private final static int REQUEST_TIMEOUT = 60 * 1000; private final int channelId; private final byte[] infohash; private final long infoHashHash; private long lastMessageRecieved; private final int lengthHint; private byte[] metaInfo; private int metaInfoLength; private boolean[] receivedBytes; private boolean sentAllRequest = false; private final long createdTime; private final byte type; // private final long startTime; private final List<PluginCallback<byte[]>> subscribers = new LinkedList<PluginCallback<byte[]>>(); public MetaInfoRequest(byte type, int channelId, byte[] infohash, int lengthHint) { this.type = type; this.createdTime = System.currentTimeMillis(); this.infohash = infohash; this.infoHashHash = filelistManager.getInfoHashhash(infohash); this.channelId = channelId; this.lengthHint = lengthHint; } public void addListener(PluginCallback<byte[]> listener) { synchronized (subscribers) { if (isCompleted()) { listener.requestCompleted(metaInfo); } else { subscribers.add(listener); } } } public long getAge() { return System.currentTimeMillis() - createdTime; } public int getChannelId() { return channelId; } public int getPercentComplete() { if (receivedBytes == null) { return 0; } if (receivedBytes.length == 0) { return 100; } int bytesDownloaded = 0; for (int i = 0; i < receivedBytes.length; i++) { if (receivedBytes[i]) { bytesDownloaded++; } } return (100 * bytesDownloaded) / metaInfoLength; } public long getTimeSinceLastMsg() { return System.currentTimeMillis() - lastMessageRecieved; } public void gotResponse(OSF2FMetaInfoResp resp) { if (resp.getInfoHashHash() != this.infoHashHash) { throw new RuntimeException("Got the wrong infohashhash in a metainforequest"); } this.lastMessageRecieved = System.currentTimeMillis(); // check if we already know anything about this if (metaInfo == null) { // no, lets fill in the blanks this.metaInfoLength = resp.getTotalMetaInfoLength(); this.receivedBytes = new boolean[metaInfoLength]; this.metaInfo = new byte[metaInfoLength]; } int startPos = resp.getStartByte(); byte[] payload = resp.getMetaInfo(); // copy in the payload for (int i = 0; i < payload.length; i++) { receivedBytes[startPos + i] = true; metaInfo[startPos + i] = payload[i]; } logger.finest("got metainfo response: %completed=" + getPercentComplete()); // check if we need to send the big request flood if (sentAllRequest == false || getTimeSinceLastMsg() > REQUEST_TIMEOUT) { sendMetaInfoRequests(resp.getMetaInfoType(), metaInfoLength); } synchronized (subscribers) { for (PluginCallback<byte[]> listener : subscribers) { listener.progressUpdate(getPercentComplete()); listener.dataRecieved(payload.length); } if (isCompleted()) { for (PluginCallback<byte[]> listener : subscribers) { listener.requestCompleted(metaInfo); } subscribers.clear(); // and remove from the request map getRequestMap(type).remove(infoHashHash); } } } public boolean isCompleted() { return getPercentComplete() == 100; } public void sendInitialMetaInfoRequest(byte type) { if (lengthHint > 0) { receivedBytes = new boolean[lengthHint]; sendMetaInfoRequests(type, lengthHint); } else { int startByte = 0; OSF2FMetaInfoReq req = new OSF2FMetaInfoReq(OSF2FMessage.CURRENT_VERSION, channelId, type, startByte, infohash); sendMessage(req); } } private void sendMetaInfoRequests(byte type, int length) { sentAllRequest = true; // send requests for everything we don't have yet for (int i = 0; i < receivedBytes.length; i += OSF2FMessage.METAINFO_CHUNK_SIZE) { if (!receivedBytes[i]) { int startByte = i; OSF2FMetaInfoReq req = new OSF2FMetaInfoReq(OSF2FMessage.CURRENT_VERSION, channelId, type, startByte, infohash); sendMessage(req); } } } } } public class OverlayForward { private long bytesForwarded = 0; Average average = Average.getInstance(1000, 10); private final int channelId; private final FriendConnection conn; private long lastMsgTime; private final boolean searcherSide; private final OSF2FSearch sourceMessage; private final OSF2FSearchResp setupMessage; private final long startTime; private boolean service; public OverlayForward(int channelId, FriendConnection conn, OSF2FSearch sourceMessage, OSF2FSearchResp setup, boolean searcherSide) { lastMsgTime = System.currentTimeMillis(); startTime = System.currentTimeMillis(); this.conn = conn; this.channelId = channelId; this.sourceMessage = sourceMessage; this.setupMessage = setup; this.searcherSide = searcherSide; } public void gotChannelReset() { /* * the the remote friend conn to stop sending us channel messages on * this id */ closeChannel(channelId); } public void close() { deregisterOverlayForward(channelId, true); } public void forwardMessage(OSF2FChannelMsg message) { logger.finest("Packet to be forwarded: " + message.getDescription() + " forwarded=" + bytesForwarded); message.setByteInChannel(bytesForwarded); if (message instanceof OSF2FChannelDataMsg) { if (bytesForwarded == 0 || service) { // Check if first packet, detect service or not. try { message = OSF2FServiceDataMsg .fromChannelMessage((OSF2FChannelDataMsg) message); service = true; } catch (MessageException e) { // not service message } } } if (setupPacketListener != null && bytesForwarded == 0) { setupPacketListener.packetAddedToForwardQueue(FriendConnection.this, conn, sourceMessage, setupMessage, searcherSide, message); } lastMsgTime = System.currentTimeMillis(); int numBytes = message.getMessageSize(); bytesForwarded += numBytes; average.addValue(numBytes); /* * count it as sent after it actually gets sent */ // stats.protocolBytesSent(numBytes, conn.isLanLocal()); stats.protocolBytesReceived(numBytes, FriendConnection.this.isLanLocal()); conn.sendChannelMsg(message, false); } public long getAge() { return System.currentTimeMillis() - startTime; } public long getBytesForwarded() { return bytesForwarded; } public int getForwardingRate() { return (int) average.getAverage(); } public int getChannelId() { return channelId; } public long getLastMsgTime() { return System.currentTimeMillis() - lastMsgTime; } public Friend getRemoteFriend() { return conn.getRemoteFriend(); } public String getRemoteIpPort() { return conn.getRemoteIp().getHostAddress() + ":" + conn.getRemotePort(); } public OSF2FSearch getSourceMessage() { return sourceMessage; } public OSF2FSearchResp getSetupMessage() { return setupMessage; } public boolean isSearcherSide() { return searcherSide; } public boolean isTimedOut() { long timeSinceLastMsg = System.currentTimeMillis() - lastMsgTime; if (timeSinceLastMsg > OVERLAY_FORWARD_TIMEOUT) { return true; } return false; } public void sendReset() { conn.sendChannelRst(new OSF2FChannelReset(OSF2FMessage.CURRENT_VERSION, channelId)); } } /* * used for debugging only */ class OutgoingQueueListener implements OutgoingMessageQueue.MessageQueueListener { @Override public void dataBytesSent(int byte_count) { debugMessageLog.bytesSent(byte_count); } @Override public void flush() { } @Override public boolean messageAdded(Message message) { debugMessageLog.messageQueuedListener(message.getDescription()); return true; } @Override public void messageQueued(Message message) { } @Override public void messageRemoved(Message message) { } @Override public void messageSent(Message message) { debugMessageLog.messageSent(message.getDescription()); } @Override public void protocolBytesSent(int byte_count) { debugMessageLog.bytesSent(byte_count); } } public static class OverlayRegistrationError extends Exception { private static final long serialVersionUID = 1L; String setupMessageSource; final int channelId; String direction; public OverlayRegistrationError(String setupMessageSource, int channelID, String message) { super(message); this.setupMessageSource = setupMessageSource; this.channelId = channelID; } @Override public String toString() { return "source=" + setupMessageSource + " channel=" + channelId; } } public void closeChannel(int channelId) { // we must have gotten a channel reset from the remote friend,stop // forwarding on this channel and clear any messages that might be // queued up final OverlayForward overlayForward = overlayForwards.get(channelId); if (overlayForward != null) { overlayForward.close(); if (overlayPathLogger.isEnabled()) { overlayPathLogger.log(System.currentTimeMillis() + ", close_forward, " + overlayForward.getChannelId() + ", " + overlayForward.getBytesForwarded() + ", " + overlayForward.getAge() + ", " + overlayForward.getRemoteFriend().getNick() + ", " + overlayForward.getRemoteIpPort()); } } } public boolean isClosing() { return mClosing; } public PacketListener getSetupPacketListener() { return setupPacketListener; } private final static Set<String> UDP_ENABLED_MESSAGES = new HashSet<String>(); static { UDP_ENABLED_MESSAGES.add(OSF2FMessage.ID_OS_CHANNEL_DATA_MSG); UDP_ENABLED_MESSAGES.add(OSF2FMessage.ID_OS_DATAGRAM_OK); } @Override public void datagramDecoded(Message message, int size) { if (!UDP_ENABLED_MESSAGES.contains(message.getID())) { logger.warning("Got invalid message type from udp channel on friendconnection: " + toString()); return; } stats.protocolBytesReceived(OSF2FMessage.MESSAGE_HEADER_LEN, isLanLocal()); if (message.getType() == Message.TYPE_DATA_PAYLOAD) { incomingListener.dataBytesReceived(size); stats.dataBytesReceived(size, isLanLocal()); } else { incomingListener.protocolBytesReceived(size); stats.protocolBytesReceived(size, isLanLocal()); } incomingListener.messageReceived(message); } private void sendUdpPacket(OSF2FServiceDataMsg message) { if (!UDP_ENABLED_MESSAGES.contains(message.getID())) { logger.warning("Got invalid message type from udp channel on friendconnection: " + toString()); return; } // Notify the setup packet listener if (!friendConnectionQueue.packetListenerNotify(message)) { logger.warning("Packetlistener told us to drop the packet!!!!!!"); message.destroy(); return; } stats.protocolBytesSent(OSF2FMessage.MESSAGE_HEADER_LEN, isLanLocal()); int size = message.getMessageSize(); if (message.getType() == Message.TYPE_DATA_PAYLOAD) { stats.dataBytesSent(size, isLanLocal()); } else { stats.protocolBytesSent(size, isLanLocal()); } udpConnection.sendMessage(message); } @Override public void sendDatagramOk(OSF2FDatagramOk osf2fDatagramOk) { sendMessage(osf2fDatagramOk, true); } }