package edu.washington.cs.oneswarm.f2f.network; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; import org.bouncycastle.util.encoders.Base64; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.config.ParameterListener; import org.gudy.azureus2.core3.download.DownloadManager; import org.gudy.azureus2.core3.global.GlobalManagerStats; import org.gudy.azureus2.core3.torrent.TOTorrent; import org.gudy.azureus2.core3.torrent.TOTorrentException; import org.gudy.azureus2.core3.util.Base32; import org.gudy.azureus2.core3.util.Debug; import com.aelitis.azureus.core.impl.AzureusCoreImpl; import com.aelitis.azureus.core.instancemanager.AZInstance; import com.aelitis.azureus.core.networkmanager.ConnectionEndpoint; import com.aelitis.azureus.core.networkmanager.NetworkConnection; import com.aelitis.azureus.core.networkmanager.impl.tcp.TCPTransportImpl; 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.FriendConnectListener; import edu.washington.cs.oneswarm.f2f.chat.Chat; import edu.washington.cs.oneswarm.f2f.chat.ChatDAO; import edu.washington.cs.oneswarm.f2f.friends.FriendManager; 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.OSF2FMessageFactory; 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.plugins.PluginCallback; import edu.washington.cs.oneswarm.ui.gwt.rpc.StringTools; public class OverlayManager { /** * make sure to not call any az functions when holding this lock */ public static BigFatLock lock = BigFatLock.getInstance(false); // private Friend me; public final static Logger logger = Logger.getLogger(OverlayManager.class.getName()); private int mMIN_DELAY_LINK_LATENCY = COConfigurationManager .getIntParameter("f2f_search_emulate_hops_min") * COConfigurationManager.getIntParameter("f2f_overlay_emulate_link_latency_max"); private int mMAX_DELAY_LINK_LATENCY = COConfigurationManager .getIntParameter("f2f_search_emulate_hops_max") * COConfigurationManager.getIntParameter("f2f_overlay_emulate_link_latency_max"); private int mMIN_RESPONSE_DELAY = COConfigurationManager .getIntParameter("f2f_search_emulate_hops_min") * COConfigurationManager.getIntParameter("f2f_search_forward_delay"); private int mMAX_RESPONSE_DELAY = COConfigurationManager .getIntParameter("f2f_search_emulate_hops_max") * COConfigurationManager.getIntParameter("f2f_search_forward_delay"); private double mForwardSearchProbability = COConfigurationManager .getFloatParameter("f2f_forward_search_probability"); // this is just a way to treat requests for the local file list a bit // differently public final static int OWN_CONNECTION_ID_MAGIC_NUMBER = 0; private final static String RESPONSE_DELAY_SEED_SETTING_KEY = "response_delay_seed"; private static final int TIMEOUT_CHECK_PERIOD = 5 * 1000; private final ConcurrentHashMap<Integer, FriendConnection> connections; private final FileListManager filelistManager; private final LinkedList<FriendConnectListener> friendConnectListeners = new LinkedList<FriendConnectListener>(); private final FriendManager friendManager; private long lastConnectionCheckRun = System.currentTimeMillis(); private final AZInstance myInstance; private final PublicKey ownPublicKey; private final QueueManager queueManager = new QueueManager(); private final RandomnessManager randomnessManager; private final RandomnessManager responseDelayRandomnesManager; private final SearchManager searchManager; public RotatingLogger searchTimingsLogger = new RotatingLogger("search_timing"); private final GlobalManagerStats stats; private boolean stopped = false; private final Timer t = new Timer("FriendConnectionInitialChecker", true); private PacketListener packetListener; public OverlayManager(FriendManager _friendManager, PublicKey _ownPublicKey, FileListManager _fileListManager, GlobalManagerStats _stats) { stats = _stats; myInstance = AzureusCoreImpl.getSingleton().getInstanceManager().getMyInstance(); this.randomnessManager = new RandomnessManager(); this.friendManager = _friendManager; this.filelistManager = _fileListManager; this.ownPublicKey = _ownPublicKey; this.connections = new ConcurrentHashMap<Integer, FriendConnection>(); this.searchManager = new SearchManager(this, filelistManager, randomnessManager, stats); byte[] seedBytes = COConfigurationManager.getByteParameter(RESPONSE_DELAY_SEED_SETTING_KEY); if (seedBytes != null) { responseDelayRandomnesManager = new RandomnessManager(seedBytes); } else { responseDelayRandomnesManager = new RandomnessManager(); COConfigurationManager.setParameter(RESPONSE_DELAY_SEED_SETTING_KEY, randomnessManager.getSecretBytes()); } COConfigurationManager.addAndFireParameterListeners(new String[] { "f2f_overlay_emulate_link_latency_max", "f2f_search_emulate_hops_min", "f2f_search_emulate_hops_max", "f2f_search_forward_delay", "f2f_forward_search_probability" }, new ParameterListener() { @Override public void parameterChanged(String parameterName) { mMIN_DELAY_LINK_LATENCY = COConfigurationManager .getIntParameter("f2f_search_emulate_hops_min") * COConfigurationManager .getIntParameter("f2f_overlay_emulate_link_latency_max"); mMAX_DELAY_LINK_LATENCY = COConfigurationManager .getIntParameter("f2f_search_emulate_hops_max") * COConfigurationManager .getIntParameter("f2f_overlay_emulate_link_latency_max"); mMIN_RESPONSE_DELAY = COConfigurationManager .getIntParameter("f2f_search_emulate_hops_min") * COConfigurationManager.getIntParameter("f2f_search_forward_delay"); mMAX_RESPONSE_DELAY = COConfigurationManager .getIntParameter("f2f_search_emulate_hops_max") * COConfigurationManager.getIntParameter("f2f_search_forward_delay"); mForwardSearchProbability = COConfigurationManager .getFloatParameter("f2f_forward_search_probability"); if (mForwardSearchProbability <= 0 || Math.abs(mForwardSearchProbability - 0.95) < 0.1) { COConfigurationManager.setParameter("f2f_forward_search_probability", 0.5f); mForwardSearchProbability = 0.5; } System.err.println("f2f_search_fwd_p: " + mForwardSearchProbability); } }); OSF2FMessageFactory.init(); Timer timeoutTimer = new Timer("OS Overlay Timeout checker", true); timeoutTimer.schedule(new ConnectionChecker(), 0, TIMEOUT_CHECK_PERIOD); } public void closeAllConnections() { for (FriendConnection c : connections.values()) { c.close(); } stopped = true; } public boolean createIncomingConnection(byte[] publicKey, NetworkConnection netConn) { if (isConnectionAllowed(netConn, publicKey)) { Friend friend = friendManager.getFriend(publicKey); new FriendConnection(stats, queueManager, netConn, friend, filelistManager, new FriendConnectionListener()); return true; } else { System.err.println("INCOMING CONNECTION NOT ALLOWED!!!: " + netConn); } return false; } public void createOutgoingConnection(ConnectionEndpoint remoteFriendAddr, Friend friend) { for (FriendConnection connection : connections.values()) { if (connection.getRemoteFriend().equals(friend)) { String address = remoteFriendAddr.getNotionalAddress().getAddress() .getHostAddress(); if (connection.getRemoteIp().getHostAddress().equals(address)) { logger.fine("Skipping friend connection, already connected to target address"); return; } } } final FriendConnection fc = new FriendConnection(stats, queueManager, remoteFriendAddr, friend, filelistManager, new FriendConnectionListener()); /* * create a check for this connection to verify that we actually get * connected within a reasonable time frame */ t.schedule(new TimerTask() { @Override public void run() { if (fc.isTimedOut()) { fc.close(); } } }, FriendConnection.INITIAL_HANDSHAKE_TIMEOUT + 10 * 1000); } private boolean deregisterConnection(FriendConnection connection) { lock.lock(); try { logger.finer("deregistered connection: " + connection.toString() + " " + connections.containsKey(connection.hashCode()) + " "); boolean res = null != connections.remove(connection.hashCode()); Friend remoteFriend = connection.getRemoteFriend(); /* * check if there are any active connections to the friend, if not, * mark as disconnected * * this check is needed if there are 2 concurrent connections to the * same friend , and one is denied to register because of the other * one already connected */ FriendConnection connectedConn = null; for (FriendConnection c : connections.values()) { if (c.getRemoteFriend().equals(remoteFriend)) { connectedConn = c; } } /* * if the connection id != null, verify that the friend actually * think it is connected to the right connection id */ if (connectedConn != null && connectedConn.isHandshakeReceived()) { int friendConnId = remoteFriend.getConnectionId(); if (connectedConn.hashCode() != friendConnId) { // fix it... boolean fileListReceived = connectedConn.isFileListReceived(); if (!fileListReceived) { logger.finer("connection closed, existing connection found, set to handshaking: " + remoteFriend.getNick()); remoteFriend.setStatus(Friend.STATUS_HANDSHAKING); remoteFriend.setConnectionId(Friend.NOT_CONNECTED_CONNECTION_ID); } else { logger.finer("connection closed, existing connection found, set to connected: " + remoteFriend.getNick()); remoteFriend.setConnectionId(connectedConn.hashCode()); remoteFriend.setStatus(Friend.STATUS_ONLINE); } } } else { // ok, no existing connections, mark as disconnected. remoteFriend.disconnected(connection.hashCode()); } return res; } finally { lock.unlock(); } } public void disconnectFriend(Friend f) { for (FriendConnection conn : connections.values()) { if (conn.getRemoteFriend().equals(f)) { conn.close(); } } } void forwardSearchOrCancel(FriendConnection ignoreConn, OSF2FSearch msg) { for (FriendConnection conn : connections.values()) { if (ignoreConn.hashCode() == conn.hashCode()) { logger.finer("not forwarding search/cancel to: " + conn + " (source friend)"); continue; } logger.finer("forwarding search/cancel to: " + conn); if (shouldForwardSearch(msg, ignoreConn)) { conn.sendSearch(msg.clone(), false); } } } public int getConnectCount() { return connections.size(); } public Map<Integer, Friend> getConnectedFriends() { // sanity checks for (int connectionId : connections.keySet()) { FriendConnection c = connections.get(connectionId); // check status just to make sure final Friend remoteFriend = c.getRemoteFriend(); int status = remoteFriend.getStatus(); if (status == Friend.STATUS_OFFLINE && c.isHandshakeReceived()) { // fix it... boolean handshakeCompletedFully = c.isFileListReceived(); if (!handshakeCompletedFully) { Debug.out("getConnectedFriends, existing connection found, settings to handshaking: " + remoteFriend.getNick()); remoteFriend.setStatus(Friend.STATUS_HANDSHAKING); } else { Debug.out("getConnectedFriends, existing connection found, settings to connected: " + remoteFriend.getNick()); remoteFriend.setConnectionId(c.hashCode()); remoteFriend.setStatus(Friend.STATUS_ONLINE); } } } Map<Integer, Friend> l = new HashMap<Integer, Friend>(connections.size()); /* * we don't show me in friends list anymore */ // l.put(me.getConnectionIds().get(0), me); Friend[] friends = friendManager.getFriends(); for (Friend friend : friends) { if (friend.getStatus() == Friend.STATUS_ONLINE) { // System.out.println("online: " + friend.getNick()); l.put(friend.getConnectionId(), friend); } } return l; } public int getSearchDelayForInfohash(Friend destination, byte[] infohash) { if (destination.isCanSeeFileList()) { return 0; } else { int searchDelay = responseDelayRandomnesManager.getDeterministicNextInt(infohash, mMIN_RESPONSE_DELAY, mMAX_RESPONSE_DELAY); int latencyDelay = getLatencyDelayForInfohash(destination, infohash); return searchDelay + latencyDelay; } } public int getLatencyDelayForInfohash(Friend destination, byte[] infohash) { if (destination.isCanSeeFileList()) { return 0; } else { return responseDelayRandomnesManager.getDeterministicNextInt(infohash, mMIN_DELAY_LINK_LATENCY, mMAX_DELAY_LINK_LATENCY); } } public List<Friend> getDisconnectedFriends() { List<Friend> l = new ArrayList<Friend>(); Friend[] friends = friendManager.getFriends(); for (Friend friend : friends) { if (friend.getStatus() != Friend.STATUS_ONLINE) { l.add(friend); } } return l; } public FileListManager getFilelistManager() { return filelistManager; } public List<FriendConnection> getFriendConnections() { return new ArrayList<FriendConnection>(connections.values()); } public long getLastConnectionCheckRun() { return lastConnectionCheckRun; } public PublicKey getOwnPublicKey() { return ownPublicKey; } public QueueManager getQueueManager() { return queueManager; } public SearchManager getSearchManager() { return searchManager; } public double getTransportDownloadKBps() { long totalDownloadSpeed = 0; LinkedList<FriendConnection> conns = new LinkedList<FriendConnection>(); conns.addAll(connections.values()); for (FriendConnection fc : conns) { final Map<Integer, EndpointInterface> ot = fc.getOverlayTransports(); for (EndpointInterface o : ot.values()) { totalDownloadSpeed += o.getDownloadRate(); } } return totalDownloadSpeed / 1024.0; } public long getTransportSendRate(boolean includeLan) { long totalUploadSpeed = 0; LinkedList<FriendConnection> conns = new LinkedList<FriendConnection>(); conns.addAll(connections.values()); for (FriendConnection fc : conns) { final Map<Integer, EndpointInterface> ot = fc.getOverlayTransports(); for (EndpointInterface o : ot.values()) { if (!includeLan && o.isLANLocal()) { // not including lan local peers } else { totalUploadSpeed += o.getUploadRate(); } } } return totalUploadSpeed; } public boolean isConnectionAllowed(NetworkConnection connection, byte[] remotePubKey) { InetAddress remoteIP = connection.getEndpoint().getNotionalAddress().getAddress(); Friend friend = friendManager.getFriend(remotePubKey); if (stopped) { logger.finer("connection denied: (f2f transfers disabled)"); return false; } // check if we should allow this public key to connect if (Arrays.equals(remotePubKey, ownPublicKey.getEncoded()) && remoteIP.equals(myInstance.getExternalAddress())) { logger.info("connection from self not allowed (if same ip)"); return false; } else if (friend == null) { logger.fine(" access denied (not friend): " + remoteIP); return false; } else if (friend.isBlocked()) { logger.fine(" access denied (friend blocked): " + remoteIP); return false; } else if (friend.getFriendBannedUntil() > System.currentTimeMillis()) { double minutesLeft = friend.getFriendBannedUntil() - System.currentTimeMillis() / (60 * 1000.0); friend.updateConnectionLog(true, "incoming connection denied, friend blocked for " + minutesLeft + " more minutes because of: " + friend.getBannedReason()); logger.fine(" access denied (friend blocked for " + minutesLeft + " more minutes): " + remoteIP); return false; } FriendConnection toDisconnect = null; for (FriendConnection c : connections.values()) { // If we have 2 concurrent connections (and both have not yet // completed the handshake). Disconnect the one with the lowest sum // of port numbers. if (c.getRemoteFriend().equals(friend)) { int existingConnectionPortSum = getPortSum(c.getNetworkConnection()); int newConnectionPortSum = getPortSum(connection); if (existingConnectionPortSum < newConnectionPortSum) { toDisconnect = c; break; } else { // the new connection is "worse", keep the old one. logger.info("new connection concurrent with a better one."); return false; } } } if (toDisconnect != null) { toDisconnect.close(); } logger.fine("friend connection ok: " + remoteIP + " :: " + friend); return true; } /** * make sure to synchronize before calling this function */ private void notifyConnectionListeners(Friend f, boolean connected) { lock.lock(); try { for (FriendConnectListener cb : friendConnectListeners) { if (connected) { cb.friendConnected(f); } else { cb.friendDisconnected(f); } } } finally { lock.unlock(); } } private static int getPortSum(NetworkConnection conn) { TCPTransportImpl transport = (TCPTransportImpl) conn.getTransport(); Socket s = transport.getSocketChannel().socket(); return s.getLocalPort() + s.getPort(); } private boolean registerConnection(FriendConnection connection) { lock.lock(); try { if (isConnectionAllowed(connection.getNetworkConnection(), connection.getRemotePublicKey())) { connections.put(connection.hashCode(), connection); connection.setPacketListener(packetListener); /* * don't mark remote friend as connected until after the * oneswarm handshake message is received */ // connection.getRemoteFriend().connected(connection.hashCode()); logger.finer("registered connection: " + connection); return true; } else { return false; } } finally { lock.unlock(); } } public void registerForConnectNotifications(FriendConnectListener callback) { lock.lock(); try { friendConnectListeners.add(callback); } finally { lock.unlock(); } } public void restartAllConnections() { stopped = false; } public void setPacketListener(PacketListener listener) { this.packetListener = listener; for (FriendConnection conn : connections.values()) { conn.setPacketListener(listener); } } public PacketListener getPacketListener() { return this.packetListener; } public void sendChatMessage(int connectionId, String inPlaintextMessage) { FriendConnection conn = connections.get(connectionId); conn.sendChat(inPlaintextMessage); } void sendDirectedSearch(FriendConnection target, OSF2FHashSearch search) { logger.finer("sending search to " + target); target.sendSearch(search, true); } public boolean sendFileListRequest(int connectionId, long maxCacheAge, PluginCallback<FileList> callback) { // just check if the request is for the local list if (connectionId == OWN_CONNECTION_ID_MAGIC_NUMBER) { callback.requestCompleted(filelistManager.getOwnFileList()); } FriendConnection conn = connections.get(connectionId); if (conn != null) { FileList oldList = filelistManager.getFriendsList(conn.getRemoteFriend()); if (oldList != null && (System.currentTimeMillis() - oldList.getCreated()) < maxCacheAge) { callback.requestCompleted(oldList); } logger.finer("sending filelist request to " + conn); conn.sendFileListRequest(OSF2FMessage.FILE_LIST_TYPE_COMPLETE, callback); return true; } else { System.err .println("tried to get filelist for unknown connection id (friend just went offline?)!, stack trace is:"); new RuntimeException().printStackTrace(); } return false; } // public void startDownload(byte type, byte[] metainfo, // boolean createIfNotExist) { // if (type == OSF2FMessage.METAINFO_TYPE_BITTORRENT) { // public boolean sendMetaInfoRequest(int connectionId, int channelId, byte[] infohash, int lengthHint, PluginCallback<byte[]> callback) { FriendConnection conn = connections.get(connectionId); logger.finer("sending metainfo request to " + conn); if (conn != null) { conn.sendMetaInfoRequest(OSF2FMessage.METAINFO_TYPE_BITTORRENT, channelId, infohash, lengthHint, callback); return true; } return false; } void sendSearchOrCancel(OSF2FSearch search, boolean skipQueue, boolean forceSend) { logger.finer("sending search/cancel to " + connections.size()); int numSent = 0; for (FriendConnection conn : connections.values()) { boolean shouldSend = true; if (!forceSend) { shouldSend = shouldForwardSearch(search, conn); } if (shouldSend) { logger.finer("About to send a search to " + conn.getRemoteFriend().getNick()); conn.sendSearch(search.clone(), skipQueue); numSent++; logger.finer("Sent a search to " + conn.getRemoteFriend().getNick()); } if (search instanceof OSF2FHashSearch) { OSF2FHashSearch hs = (OSF2FHashSearch) search; searchTimingsLogger.log(System.currentTimeMillis() + ", send_search, " + conn.getRemoteFriend().getNick() + ", " + hs.getSearchID() + ", " + hs.getInfohashhash()); } } /* * for searches sent by us, if we didn't send it to anyone try again but * without the randomness linitng who we are sending to */ if (numSent == 0 && !forceSend) { sendSearchOrCancel(search, skipQueue, true); } } /** * to protect against colluding friends we are only forwarding searches with * 95% probability * * if forcesend = true the search will be forwarded anyway even if the * randomness says that friend shouldn't be forwarded */ private boolean shouldForwardSearch(OSF2FSearch search, FriendConnection conn) { boolean shouldSend = true; if (search instanceof OSF2FHashSearch) { shouldSend = shouldForwardSearch(((OSF2FHashSearch) search).getInfohashhash(), conn); } else if (search instanceof OSF2FSearchCancel) { long infohash = searchManager.getInfoHashHashFromSearchId(search.getSearchID()); if (infohash != -1) { shouldSend = shouldForwardSearch(infohash, conn); } else { shouldSend = false; } } return shouldSend; } private boolean shouldForwardSearch(long infohashhash, FriendConnection conn) { if (conn.getRemoteFriend().isCanSeeFileList()) { return true; } byte[] infohashbytes = RandomnessManager.getBytes(infohashhash); byte[] friendHash = RandomnessManager.getBytes(conn.getRemotePublicKeyHash()); byte[] all = new byte[infohashbytes.length + friendHash.length]; System.arraycopy(infohashbytes, 0, all, 0, infohashbytes.length); System.arraycopy(friendHash, 0, all, infohashbytes.length, friendHash.length); int randomVal = randomnessManager.getDeterministicRandomInt(all); if (randomVal < 0) { randomVal = -randomVal; } if (randomVal < Integer.MAX_VALUE * mForwardSearchProbability) { return true; } else { return false; } } public void triggerFileListUpdates() { List<FriendConnection> conns = new LinkedList<FriendConnection>(); lock.lock(); try { conns.addAll(connections.values()); } finally { lock.unlock(); } for (FriendConnection conn : connections.values()) { conn.triggerFileListSend(); } } private class ConnectionChecker extends TimerTask { @Override public void run() { try { // first, check if we have any overlays that are timed out for (FriendConnection connection : connections.values()) { connection.clearTimedOutForwards(); connection.clearTimedOutTransports(); connection.clearTimedOutSearchRecords(); connection.clearOldMetainfoRequests(); } } catch (Throwable t) { Debug.out("F2F Connection Checker: got error when clearing transports/forwards", t); } try { // then, check if we need to send any keepalives for (FriendConnection connection : connections.values()) { connection.doKeepAliveCheck(); } } catch (Throwable t) { Debug.out("F2F Connection Checker: got error when sending keep alives", t); } try { // check if we have any timed out connections List<FriendConnection> timedOut = new ArrayList<FriendConnection>(); for (FriendConnection connection : connections.values()) { if (connection.isTimedOut()) { timedOut.add(connection); } } // and close them for (FriendConnection friendConnection : timedOut) { friendConnection.close(); } } catch (Throwable t) { Debug.out("F2F Connection Checker: got error when clearing friend connections", t); } try { // then, recycle the search IDs searchManager.clearTimedOutSearches(); } catch (Throwable t) { Debug.out("F2F Connection Checker: got error when clearing timed out searches", t); } lastConnectionCheckRun = System.currentTimeMillis(); } } class FriendConnectionListener { public boolean connectSuccess(FriendConnection friendConnection) { if (!registerConnection(friendConnection)) { logger.finer("Unable to register connection, " + "connect count to high, closing connection"); friendConnection.close(); return false; } return true; } public void disconnected(FriendConnection friendConnection) { if (friendConnection.isFileListReceived()) { notifyConnectionListeners(friendConnection.getRemoteFriend(), false); } deregisterConnection(friendConnection); } public void gotSearchCancel(FriendConnection friendConnection, OSF2FSearchCancel msg) { searchManager.handleIncomingSearchCancel(friendConnection, msg); } public void gotSearchMessage(FriendConnection friendConnection, OSF2FSearch msg) { if (msg instanceof OSF2FHashSearch) { OSF2FHashSearch hs = (OSF2FHashSearch) msg; searchTimingsLogger.log(System.currentTimeMillis() + ", search, " + friendConnection.getRemoteFriend().getNick() + ", " + hs.getSearchID() + ", " + hs.getInfohashhash() + ", " + friendConnection.getRemoteIp().getHostAddress()); } searchManager.handleIncomingSearch(friendConnection, msg); } public void gotSearchResponse(FriendConnection friendConnection, OSF2FSearchResp msg) { if (msg instanceof OSF2FHashSearchResp) { OSF2FHashSearchResp hsr = (OSF2FHashSearchResp) msg; searchTimingsLogger.log(System.currentTimeMillis() + ", response, " + friendConnection.getRemoteFriend().getNick() + ", " + hsr.getSearchID() + ", " + hsr.getChannelID() + ", " + friendConnection.getRemoteIp().getHostAddress() + ", " + hsr.getPathID()); } searchManager.handleIncomingSearchResponse(friendConnection, msg); } @SuppressWarnings("unchecked") public void handshakeCompletedFully(final FriendConnection friendConnection) { notifyConnectionListeners(friendConnection.getRemoteFriend(), true); /* * check if we have any running downloads that this friend has */ logger.finer("New friend connected, checking if friend has anything we want"); List<byte[]> runningDownloadHashes = new LinkedList<byte[]>(); List<DownloadManager> dms = AzureusCoreImpl.getSingleton().getGlobalManager() .getDownloadManagers(); for (DownloadManager dm : dms) { if (dm.getState() == DownloadManager.STATE_DOWNLOADING) { try { TOTorrent torrent = dm.getTorrent(); if (torrent != null) { runningDownloadHashes.add(torrent.getHash()); } } catch (TOTorrentException e) { e.printStackTrace(); } } } logger.finer("found " + runningDownloadHashes.size() + " running downloads"); FileList remoteFileList = filelistManager.getFriendsList(friendConnection .getRemoteFriend()); for (byte[] hash : runningDownloadHashes) { if (remoteFileList.contains(hash)) { logger.finer("sending search for '" + Base32.encode(hash) + "' to " + friendConnection.getRemoteFriend()); searchManager.sendDirectedHashSearch(friendConnection, hash); } } /** * Check if we need to send any pending chat messages to this user. * (5/sec) */ Thread queryThread = new Thread("Queued chat SQL query for: " + friendConnection.getRemoteFriend().getNick()) { @Override public void run() { try { Thread.sleep(3 * 1000); } catch (InterruptedException e) { } String base64Key = new String(Base64.encode(friendConnection .getRemotePublicKey())); ChatDAO dao = ChatDAO.get(); List<Chat> out = dao.getQueuedMessagesForUser(base64Key); for (Chat c : out) { if (friendConnection.isTimedOut() || friendConnection.isClosing()) { return; } friendConnection.sendChat(c.getMessage() + " (sent " + StringTools.formatDateAppleLike(new Date(c.getTimestamp()), true) + ")"); dao.markSent(c.getUID()); try { Thread.sleep(200); } catch (InterruptedException e) { } } } }; queryThread.setDaemon(true); queryThread.start(); } } } class RandomnessManager { private byte[] secretBytes = new byte[20]; public RandomnessManager() { SecureRandom random; try { random = SecureRandom.getInstance("SHA1PRNG"); random.nextBytes(secretBytes); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); secretBytes = null; } } public RandomnessManager(byte[] secretBytes) { this.secretBytes = secretBytes; } /** * returns a random int between 0 (inclusive) and n (exclusive) seeded by * seedBytes */ public int getDeterministicNextInt(byte[] seedBytes, int minValue, int maxValue) { int randomInt = getDeterministicRandomInt(seedBytes); if (randomInt < 0) { randomInt = -randomInt; } return minValue + (randomInt % (maxValue - minValue)); } public int getDeterministicNextInt(int seed, int minValue, int maxValue) { byte[] seedBytes = getBytes(seed); return getDeterministicNextInt(seedBytes, minValue, maxValue); } public int getDeterministicNextInt(long seed, int minValue, int maxValue) { byte[] seedBytes = getBytes(seed); return getDeterministicNextInt(seedBytes, minValue, maxValue); } public int getDeterministicRandomInt(byte[] seedBytes) { if (secretBytes != null) { byte[] sha1input = new byte[secretBytes.length + seedBytes.length]; System.arraycopy(secretBytes, 0, sha1input, 0, secretBytes.length); System.arraycopy(seedBytes, 0, sha1input, secretBytes.length, seedBytes.length); MessageDigest md; try { md = MessageDigest.getInstance("SHA-1"); md.update(sha1input); byte[] sha1 = md.digest(); ByteArrayInputStream bis = new ByteArrayInputStream(sha1); DataInputStream in = new DataInputStream(bis); return in.readInt(); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } throw new RuntimeException("unable to generate deterministic random int"); } /** * Returns a random int seeded with the secret appended to the seed. For a * given seed the returned value will always be the same for a given * instance of the RandomnessManager * * @param seed * @return * @throws NoSuchAlgorithmException * @throws IOException */ public int getDeterministicRandomInt(int seed) { byte[] seedBytes = getBytes(seed); return getDeterministicRandomInt(seedBytes); } public int getDeterministicRandomInt(long seed) { byte[] seedBytes = getBytes(seed); return getDeterministicRandomInt(seedBytes); } public byte[] getSecretBytes() { return secretBytes; } static byte[] getBytes(int val) { byte[] b = new byte[4]; b[3] = (byte) (val >>> 0); b[2] = (byte) (val >>> 8); b[1] = (byte) (val >>> 16); b[0] = (byte) (val >>> 24); return b; } static byte[] getBytes(long val) { byte[] b = new byte[8]; b[7] = (byte) (val >>> 0); b[6] = (byte) (val >>> 8); b[5] = (byte) (val >>> 16); b[4] = (byte) (val >>> 24); b[3] = (byte) (val >>> 32); b[2] = (byte) (val >>> 40); b[1] = (byte) (val >>> 48); b[0] = (byte) (val >>> 56); return b; } }