package io.bitsquare.p2p.peers.peerexchange; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.SettableFuture; import io.bitsquare.app.Log; import io.bitsquare.common.Timer; import io.bitsquare.common.UserThread; import io.bitsquare.p2p.network.CloseConnectionReason; import io.bitsquare.p2p.network.Connection; import io.bitsquare.p2p.network.NetworkNode; import io.bitsquare.p2p.peers.PeerManager; import io.bitsquare.p2p.peers.peerexchange.messages.GetPeersRequest; import io.bitsquare.p2p.peers.peerexchange.messages.GetPeersResponse; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.TimeUnit; import static com.google.common.base.Preconditions.checkArgument; class GetPeersRequestHandler { private static final Logger log = LoggerFactory.getLogger(GetPeersRequestHandler.class); private static final long TIME_OUT_SEC = 40; /////////////////////////////////////////////////////////////////////////////////////////// // Listener /////////////////////////////////////////////////////////////////////////////////////////// public interface Listener { void onComplete(); void onFault(String errorMessage, Connection connection); } /////////////////////////////////////////////////////////////////////////////////////////// // Class fields /////////////////////////////////////////////////////////////////////////////////////////// private final NetworkNode networkNode; private final PeerManager peerManager; private final Listener listener; private Timer timeoutTimer; private boolean stopped; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////////////////////////////// public GetPeersRequestHandler(NetworkNode networkNode, PeerManager peerManager, Listener listener) { this.networkNode = networkNode; this.peerManager = peerManager; this.listener = listener; } /////////////////////////////////////////////////////////////////////////////////////////// // API /////////////////////////////////////////////////////////////////////////////////////////// public void handle(GetPeersRequest getPeersRequest, final Connection connection) { Log.traceCall("getPeersRequest=" + getPeersRequest + "\n\tconnection=" + connection + "\n\tthis=" + this); checkArgument(connection.getPeersNodeAddressOptional().isPresent(), "The peers address must have been already set at the moment"); GetPeersResponse getPeersResponse = new GetPeersResponse(getPeersRequest.nonce, peerManager.getConnectedNonSeedNodeReportedPeers(connection.getPeersNodeAddressOptional().get())); checkArgument(timeoutTimer == null, "onGetPeersRequest must not be called twice."); timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions if (!stopped) { String errorMessage = "A timeout occurred at sending getPeersResponse:" + getPeersResponse + " on connection:" + connection; log.debug(errorMessage + " / PeerExchangeHandshake=" + GetPeersRequestHandler.this); log.debug("timeoutTimer called. this=" + this); handleFault(errorMessage, CloseConnectionReason.SEND_MSG_TIMEOUT, connection); } else { log.trace("We have stopped already. We ignore that timeoutTimer.run call."); } }, TIME_OUT_SEC, TimeUnit.SECONDS); SettableFuture<Connection> future = networkNode.sendMessage(connection, getPeersResponse); Futures.addCallback(future, new FutureCallback<Connection>() { @Override public void onSuccess(Connection connection) { if (!stopped) { log.trace("GetPeersResponse sent successfully"); cleanup(); listener.onComplete(); } else { log.trace("We have stopped already. We ignore that networkNode.sendMessage.onSuccess call."); } } @Override public void onFailure(@NotNull Throwable throwable) { if (!stopped) { String errorMessage = "Sending getPeersResponse to " + connection + " failed. That is expected if the peer is offline. getPeersResponse=" + getPeersResponse + "." + "Exception: " + throwable.getMessage(); log.debug(errorMessage); handleFault(errorMessage, CloseConnectionReason.SEND_MSG_FAILURE, connection); } else { log.trace("We have stopped already. We ignore that networkNode.sendMessage.onFailure call."); } } }); peerManager.addToReportedPeers(getPeersRequest.reportedPeers, connection); } /////////////////////////////////////////////////////////////////////////////////////////// // Private /////////////////////////////////////////////////////////////////////////////////////////// private void handleFault(String errorMessage, CloseConnectionReason closeConnectionReason, Connection connection) { cleanup(); //peerManager.shutDownConnection(connection, closeConnectionReason); listener.onFault(errorMessage, connection); } private void cleanup() { stopped = true; if (timeoutTimer != null) { timeoutTimer.stop(); timeoutTimer = null; } } }