/* * The MIT License * * Copyright 2014 sorrge. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.nyan.dch.communication; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; import org.nyan.dch.misc.RandomSet; import org.nyan.dch.node.IRemoteResponseListener; /** * * @author sorrge */ public class Connections implements IConnections { public static final int WantConnections = 10, SendAcceptanceConfirmationRequests = 1; private static final Logger log = Logger.getLogger(Connections.class.getName()); private final RandomSet<IAddress> knownAddresses = new RandomSet<>(); private final INetworkTransport transport; public final IRemoteResponseListener node; private final Random rand; private final IDiscovery discovery; private int acceptanceConfirmationsAsked = 0; public Connections(INetworkTransport transport, IRemoteResponseListener node, Random rand) { this.transport = transport; this.node = node; this.rand = rand; discovery = transport.GetDiscovery(); transport.SetConnectionsListener(this); discovery.SetDiscoveryListener(this); discovery.BeginDiscovery(); } @Override public boolean AddAddress(IAddress address) { if(discovery.GetMyAddress() != null && discovery.GetMyAddress().equals(address)) return false; synchronized(knownAddresses) { if(!knownAddresses.add(address)) return false; log.log(Level.INFO, "New address discovered: {0}", address); if(knownAddresses.size() > WantConnections * 3) discovery.EndDiscovery(); } EnsureConnected(); return true; } @Override public void Connected(IRemoteHost host, boolean iAmTheInitiator) { System.out.printf("%s connection established, %d connections in total\n", iAmTheInitiator ? "Outgoing" : "Incoming", transport.GetConnectionsCount()); RemoteNodeMessages msg = new RemoteNodeMessages(host, this); msg.SetResponseListener(node, iAmTheInitiator); host.SetReceiveListener(msg); IAddress remoteAddress = host.GetAddress(); if(remoteAddress != null) AddAddress(remoteAddress); synchronized(knownAddresses) { try { if(!iAmTheInitiator) { if(knownAddresses.size() > 1) { HashSet<IAddress> toGive = new HashSet<>(); if(knownAddresses.size() <= RemoteNodeMessages.GiveAddressesCount) toGive.addAll(knownAddresses); else while(toGive.size() < RemoteNodeMessages.GiveAddressesCount) toGive.add(knownAddresses.get(rand.nextInt(knownAddresses.size()))); msg.GiveAddresses(toGive); } } if(iAmTheInitiator && discovery.GetAcceptance().CanAcceptConnections()) { // System.out.printf("Connection to %s established, sending my address %s\n", host.GetAddress(), discovery.GetMyAddress()); msg.GiveMyAddress(discovery.GetMyAddress(), discovery.GetAcceptance().NeedToConfirmAcceptance() && acceptanceConfirmationsAsked < SendAcceptanceConfirmationRequests); if(discovery.GetAcceptance().NeedToConfirmAcceptance()) { ++acceptanceConfirmationsAsked; if(acceptanceConfirmationsAsked == SendAcceptanceConfirmationRequests) discovery.GetAcceptance().AcceptanceConfirmationRequested(); } } } catch (ProtocolException ex) { System.err.println(ex.getMessage()); } } } @Override public void Disconnected(IRemoteHost host) { System.out.printf("Connection dropped, %d connections in total\n", transport.GetConnectionsCount()); EnsureConnected(); } @Override public void ConnectionFailed(IAddress host) { if(transport.GetConnectedHost(host) == null) synchronized(knownAddresses) { knownAddresses.remove(host); } if(knownAddresses.size() <= WantConnections * 3) discovery.BeginDiscovery(); EnsureConnected(); } public void EnsureConnected() { // System.out.printf("Connections.EnsureConnected in %s called: %d connections + %d connecting\n", this, transport.GetConnectionsCount(), // transport.GetCurrentConnectionAttempts()); synchronized(knownAddresses) { while(transport.GetConnectionsCount() + transport.GetCurrentConnectionAttempts() < Math.min(WantConnections, knownAddresses.size())) { // System.out.printf("\tConnections.EnsureConnected in %s: %d connections + %d connecting: trying to connect\n", this, transport.GetConnectionsCount(), // transport.GetCurrentConnectionAttempts()); IAddress addr = knownAddresses.get(rand.nextInt(knownAddresses.size())); if(transport.GetConnectedHost(addr) == null && !discovery.GetMyAddress().equals(addr)) transport.Connect(addr, false); } } // System.out.printf("Connections.EnsureConnected in %s finished: %d connections + %d connecting\n", this, transport.GetConnectionsCount(), // transport.GetCurrentConnectionAttempts()); } @Override public INetworkTransport GetTransport() { return transport; } @Override public void AddressSupplied(IRemoteHost host, IAddress address, long cookie, boolean wandAcceptanceConfirmation) { IRemoteHost alreadyConnected = transport.GetConnectedHost(address); host.SetAddress(address, cookie); // System.out.printf("Received cookie from %s\n", host); if(alreadyConnected != null && alreadyConnected != host) { // assert(alreadyConnected != host); // System.out.printf("Oops, already connected to %s\n", addr); // System.out.printf("This connection's cookie: %d, the other's: %d\n", host.GetCookie(), alreadyConnected.GetCookie()); long c1 = alreadyConnected.GetCookie(), c2 = host.GetCookie(); try { if(c1 > c2) host.Disconnect(); else alreadyConnected.Disconnect(); } catch(IOException ex) { } } else { AddAddress(address); if(wandAcceptanceConfirmation) transport.Connect(address, true); } } }