context: //
  // ConnectionManager.cs
  //
  // Authors:
  //   Alan McGovern alan.mcgovern@gmail.com
  //
  // Copyright (C) 2006 Alan McGovern
  //
  // 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.
  //
  
  
  
  using System;
  using MonoTorrent.Common;
  using System.Net.Sockets;
  using System.Threading;
  using MonoTorrent.Client.Encryption;
  using System.Diagnostics;
  using System.Collections.Generic;
  using System.Net;
  using MonoTorrent.Client.Messages;
  using MonoTorrent.Client.Connections;
  using MonoTorrent.Client.Messages.FastPeer;
  using MonoTorrent.Client.Messages.Standard;
  using MonoTorrent.Client.Messages.Libtorrent;
  
  namespace MonoTorrent.Client
  {
      internal delegate void MessagingCallback(PeerId id);
  
      /// <summary>
      /// Main controller class for all incoming and outgoing connections
      /// </summary>
      public class ConnectionManager
      {
          #region Events
  
          public event EventHandler<AttemptConnectionEventArgs> BanPeer;
  
          /// <summary>
          /// Event that's fired every time a message is sent or Received from a Peer
          /// </summary>
          public event EventHandler<PeerMessageEventArgs> PeerMessageTransferred;
  
          #endregion
  
  
          #region Member Variables
          private ClientEngine engine;
          internal static readonly int ChunkLength = 2096 + 64;   // Download in 2kB chunks to allow for better rate limiting
  
          // Create the callbacks and reuse them. Reduces ongoing allocations by a fair few megs
          private AsyncMessageReceivedCallback peerHandshakeReceivedCallback;
          private MessagingCallback handshakeSentCallback;
          private MessagingCallback messageSentCallback;
  
          private AsyncCallback endCheckEncryptionCallback;
          private AsyncIOCallback endCreateConnectionCallback;
          internal AsyncIOCallback incomingConnectionAcceptedCallback;
          private AsyncIOCallback endSendMessageCallback;
          internal AsyncMessageReceivedCallback messageReceivedCallback;
  
          private List<AsyncConnectState> pendingConnects;
  
          /// <summary>
          /// The number of half open connections
          /// </summary>
          public int HalfOpenConnections
          {
              get { return NetworkIO.HalfOpens; }
          }
  
  
          /// <summary>
          /// The maximum number of half open connections
          /// </summary>
          public int MaxHalfOpenConnections
          {
              get { return this.engine.Settings.GlobalMaxHalfOpenConnections; }
          }
  
  
          /// <summary>
          /// The number of open connections
          /// </summary>
          public int OpenConnections
          {
              get
              {
                  return (int) ClientEngine.MainLoop.QueueWait (() =>
                      (int) Toolbox.Accumulate<TorrentManager>(engine.Torrents, (m) =>
                          m.Peers.ConnectedPeers.Count
                      )
                  );
              }
          }
  
  
          /// <summary>
          /// The maximum number of open connections
          /// </summary>
          public int MaxOpenConnections
          {
              get { return this.engine.Settings.GlobalMaxConnections; }
          }
  
          int TryConnectIndex {
              get; set;
          }
          #endregion
  
  
          #region Constructors
  
          /// <summary>
          /// 
          /// </summary>
          /// <param name="settings"></param>
          public ConnectionManager(ClientEngine engine)
          {
              this.engine = engine;
  
              this.endCheckEncryptionCallback = ClientEngine.MainLoop.Wrap(EndCheckEncryption);
              this.endSendMessageCallback = (a, b, c) => ClientEngine.MainLoop.Queue(() => EndSendMessage(a, b, c));
              this.endCreateConnectionCallback = (a, b, c) => ClientEngine.MainLoop.Queue (() => EndCreateConnection (a, b, c));
              this.incomingConnectionAcceptedCallback = (a, b, c) => ClientEngine.MainLoop.Queue (() => IncomingConnectionAccepted(a, b, c));
  
              this.handshakeSentCallback = PeerHandshakeSent;
              this.peerHandshakeReceivedCallback = (a, b, c) => ClientEngine.MainLoop.Queue (() => PeerHandshakeReceived (a, b, c));
              this.messageSentCallback = PeerMessageSent;
              this.messageReceivedCallback = (a, b, c) => ClientEngine.MainLoop.Queue (() => MessageReceived (a, b, c));
  
              this.pendingConnects = new List<AsyncConnectState>();
          }
  
          #endregion
  
  
          #region Async Connection Methods
  
          internal void ConnectToPeer(TorrentManager manager, Peer peer)
          {
              // Connect to the peer.
              IConnection connection = ConnectionFactory.Create(peer.ConnectionUri);
              if (connection == null)
                  return;
  
              peer.LastConnectionAttempt = DateTime.Now;
              AsyncConnectState c = new AsyncConnectState(manager, peer, connection);
              pendingConnects.Add(c);
  
              manager.Peers.ConnectingToPeers.Add(peer);
              NetworkIO.EnqueueConnect(connection, endCreateConnectionCallback, c);
          }
  
          private void EndCreateConnection(bool succeeded, int count, object state)
          {
              AsyncConnectState connect = (AsyncConnectState)state;
              pendingConnects.Remove(connect);
              if (connect.Manager.Engine == null || 
                  !connect.Manager.Mode.CanAcceptConnections)
              {
                  connect.Connection.Dispose();
                  return;
              }
              
              try
              {
                  connect.Manager.Peers.ConnectingToPeers.Remove(connect.Peer);
                  if (!succeeded)
                  {
                      Logger.Log(null, "ConnectionManager - Failed to connect{0}", connect.Peer);
  
                      connect.Manager.RaiseConnectionAttemptFailed(
                          new PeerConnectionFailedEventArgs(connect.Manager, connect.Peer, Direction.Outgoing, "EndCreateConnection"));
                      
                      connect.Peer.FailedConnectionAttempts++;
                      connect.Connection.Dispose();
                      connect.Manager.Peers.BusyPeers.Add(connect.Peer);
                  }
                  else
                  {
                      PeerId id = new PeerId(connect.Peer, connect.Manager);
                      id.Connection = connect.Connection;
                      connect.Manager.Peers.ActivePeers.Add(connect.Peer);
  
                      Logger.Log(id.Connection, "ConnectionManager - Connection opened");
  
                      ProcessFreshConnection(id);
                  }
              }
  
              catch (Exception)
              {
                  // FIXME: Do nothing now?
              }
              finally
              {
                  // Try to connect to another peer
                  TryConnect();
              }
          }
  
          internal void ProcessFreshConnection(PeerId id)
          {
              // If we have too many open connections, close the connection
              if (OpenConnections > this.MaxOpenConnections)
              {
                  CleanupSocket (id, "Too many connections");
                  return;
              }
  
              try
              {
                  id.ProcessingQueue = true;
                  // Increase the count of the "open" connections
                  EncryptorFactory.BeginCheckEncryption(id, 0, this.endCheckEncryptionCallback, id);
                  
                  id.TorrentManager.Peers.ConnectedPeers.Add(id);
                  id.WhenConnected = DateTime.Now;
                  // Baseline the time the last block was received
                  id.LastBlockReceived = DateTime.Now;
              }
              catch (Exception)
              {
                  id.TorrentManager.RaiseConnectionAttemptFailed(
                      new PeerConnectionFailedEventArgs(id.TorrentManager, id.Peer, Direction.Outgoing, "ProcessFreshConnection: failed to encrypt"));
  
                  id.Connection.Dispose();
                  id.Connection = null;
              }
          }
  
          private void EndCheckEncryption(IAsyncResult result)
          {
  
gt:             PeerId id = (PeerId)result.AsyncState;