context: #if !DISABLE_DHT
  //
  // DhtEngine.cs
  //
  // Authors:
  //   Alan McGovern alan.mcgovern@gmail.com
  //
  // Copyright (C) 2008 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.
  //
  
  
  namespace MonoTorrent.Dht
  {
      using System;
      using System.Net;
      using System.Collections.Generic;
      using System.Runtime.Serialization.Formatters.Binary;
  
      using MonoTorrent;
      using MonoTorrent.Common;
      using MonoTorrent.Client;
      using MonoTorrent.BEncoding;
      using System.IO;
      using MonoTorrent.Dht.Listeners;
      using MonoTorrent.Dht.Messages;
      using MonoTorrent.Client.Messages;
      using MonoTorrent.Dht.Tasks;
  
      internal enum ErrorCode : int
      {
          GenericError = 201,
          ServerError = 202,
          ProtocolError = 203,// malformed packet, invalid arguments, or bad token
          MethodUnknown = 204//Method Unknown
      }
  
      public class DhtEngine : IDisposable, IDhtEngine
      {
          #region Events
  
          public event EventHandler<PeersFoundEventArgs> PeersFound;
          public event EventHandler StateChanged;
  
          #endregion Events
  
          #region Fields
  
          internal static MainLoop MainLoop = new MainLoop("DhtLoop");
  
          bool bootStrap = true;
          TimeSpan bucketRefreshTimeout = TimeSpan.FromMinutes(15);
          bool disposed;
          MessageLoop messageLoop;
          DhtState state = DhtState.NotReady;
          RoutingTable table = new RoutingTable();
          TimeSpan timeout;
          Dictionary<NodeId, List<Node>> torrents = new Dictionary<NodeId, List<Node>>();
          TokenManager tokenManager;
  
          #endregion Fields
  
          #region Properties
  
          internal bool Bootstrap
          {
              get { return bootStrap; }
              set { bootStrap = value; }
          }
  
          internal TimeSpan BucketRefreshTimeout
          {
              get { return bucketRefreshTimeout; }
              set { bucketRefreshTimeout = value; }
          }
  
          public bool Disposed
          {
              get { return disposed; }
          }
  
          internal NodeId LocalId
          {
              get { return RoutingTable.LocalNode.Id; }
          }
  
          internal MessageLoop MessageLoop
          {
              get { return messageLoop; }
          }
  
          internal RoutingTable RoutingTable
          {
              get { return table; }
          }
  
          public DhtState State
          {
              get { return state; }
          }
  
          internal TimeSpan TimeOut
          {
              get { return timeout; }
              set { timeout = value; }
          }
  
          internal TokenManager TokenManager
          {
              get { return tokenManager; }
          }
  
          internal Dictionary<NodeId, List<Node>> Torrents
          {
              get { return torrents; }
          }
  
          #endregion Properties
  
          #region Constructors
  
          public DhtEngine(DhtListener listener)
          {
              if (listener == null)
                  throw new ArgumentNullException("listener");
  
              messageLoop = new MessageLoop(this, listener);
              timeout = TimeSpan.FromSeconds(15); // 15 second message timeout by default
              tokenManager = new TokenManager();
          }
  
          #endregion Constructors
  
          #region Methods
  
          public void Add(BEncodedList nodes)
          {
              // Maybe we should pipeline all our tasks to ensure we don't flood the DHT engine.
              // I don't think it's *bad* that we can run several initialise tasks simultaenously
              // but it might be better to run them sequentially instead. We should also
              // run GetPeers and Announce tasks sequentially.
              InitialiseTask task = new InitialiseTask(this, Node.FromCompactNode (nodes));
              task.Execute();
          }
  
          internal void Add(IEnumerable<Node> nodes)
          {
              if (nodes == null)
                  throw new ArgumentNullException("nodes");
  
              foreach (Node n in nodes)
                  Add(n);
          }
  
          internal void Add(Node node)
          {
              if (node == null)
                  throw new ArgumentNullException("node");
  
              SendQueryTask task = new SendQueryTask(this, new Ping(RoutingTable.LocalNode.Id), node);
              task.Execute();
          }
  
          public void Announce(InfoHash infoHash, int port)
          {
              CheckDisposed();
              Check.InfoHash(infoHash);
              new AnnounceTask(this, infoHash, port).Execute();
          }
  
          void CheckDisposed()
          {
              if (Disposed)
                  throw new ObjectDisposedException(GetType().Name);
          }
  
          public void Dispose()
          {
              if (disposed)
                  return;
  
              // Ensure we don't break any threads actively running right now
              DhtEngine.MainLoop.QueueWait((MainLoopTask)delegate {
                  disposed = true;
              });
          }
  
          public void GetPeers(InfoHash infoHash)
          {
              CheckDisposed();
              Check.InfoHash(infoHash);
              new GetPeersTask(this, infoHash).Execute();
          }
  
          internal void RaiseStateChanged(DhtState newState)
          {
              state = newState;
  
              if (StateChanged != null)
                  StateChanged(this, EventArgs.Empty);
          }
  
          internal void RaisePeersFound(NodeId infoHash, List<Peer> peers)
          {
              if (PeersFound != null)
                  PeersFound(this, new PeersFoundEventArgs(new InfoHash (infoHash.Bytes), peers));
          }
  
          public byte[] SaveNodes()
          {
              BEncodedList details = new BEncodedList();
  
              MainLoop.QueueWait((MainLoopTask)delegate {
                  foreach (Bucket b in RoutingTable.Buckets)
                  {
                      foreach (Node n in b.Nodes)
                          if (n.State != NodeState.Bad)
                              details.Add(n.CompactNode());
  
                      if (b.Replacement != null)
                          if (b.Replacement.State != NodeState.Bad)
                              details.Add(b.Replacement.CompactNode());
                  }
              });
  
              return details.Encode();
          }
  
          public int CountNodes()
          {
              return this.table.CountNodes();
          }
  
          public void Start()
          {
              Start(null);
          }
  
          public void Start(byte[] initialNodes)
          {
              CheckDisposed();
  
              messageLoop.Start();
              if (Bootstrap)
              {
                  new InitialiseTask(this, initialNodes).Execute();
                  RaiseStateChanged(DhtState.Initialising);
                  bootStrap = false;
              }
              else
              {
                  RaiseStateChanged(DhtState.Ready);
              }
  
              DhtEngine.MainLoop.QueueTimeout(TimeSpan.FromSeconds(1), delegate
              {
                  if (Disposed)
                      return false;
  
                  foreach (Bucket b in RoutingTable.Buckets)
                  {
                      if ((DateTime.UtcNow - b.LastChanged) > BucketRefreshTimeout)
                      {
                          b.LastChanged = DateTime.UtcNow;
  
gt:                         RefreshBucketTask task = new RefreshBucketTask(this, b);