context: /**
  * This file is part of Tartarus Emulator.
  * 
  * Tartarus is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
  * 
  * Tartarus is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
  * GNU General Public License for more details.
  * 
  * You should have received a copy of the GNU General Public License
  * along with Tartarus.  If not, see<http://www.gnu.org/licenses/>.
  */
  using Common.DataClasses;
  using Common.DataClasses.Network;
  using Common.Utils;
  using System;
  using System.Net;
  using System.Net.Sockets;
  using System.Threading.Tasks;
  
  namespace Common.Service
  {
      public delegate void Disconnect(Session session);
  
      public delegate void ConnectError();
  
      public delegate void Connect(Session session);
  
      public class SocketService
      {
          /// <summary>
          /// Event triggered when a session disconects
          /// </summary>
          public event Disconnect OnSocketDisconnect;
  
          /// <summary>
          /// Event triggered when an attempt to connect to a server failed
          /// </summary>
          public event ConnectError OnConnectionFailed;
  
          /// <summary>
          /// Event triggered when an attemp to connect to a server succed
          /// </summary>
          public event Connect OnConnectionSuccess;
  
          /// <summary>
          /// Size of a packet header
          /// </summary>
          private const int HeaderSize = Packet.HeaderSize;
  
          /// <summary>
          /// Informs if data sent and received is encrypted
          /// </summary>
  		private bool Encrypted;
  
          /// <summary>
          /// If data is Encrypted, this is the encryption key.
          /// </summary>
          private string CipherKey;
  
          /// <summary>
          /// The controller that will handle the packets
          /// </summary>
          private IController Controller;
  
          /// <summary>
          /// Factory used to get a new Session for this connection
          /// </summary>
          private ISessionFactory SessionFactory;
  
          /// <summary>
          /// Is service started?
          /// </summary>
          private bool Started;
  
          /// <summary>
          /// Creates a new SocketService
          /// </summary>
          /// <param name="ip">IP</param>
          /// <param name="port">Port</param>
          /// <param name="isEncrypted">Is data encrypted?</param>
          /// <param name="key">Encryption key</param>
          public SocketService(IController controller, bool isEncrypted, string cipherKey = "", ISessionFactory factory = null)
          {
              this.Encrypted = isEncrypted;
              this.CipherKey = cipherKey;
              this.Controller = controller;
              if (factory == null)
                  this.SessionFactory = new SessionFactory();
              else
                  this.SessionFactory = factory;
  
              this.Started = false;
          }
  
          /// <summary>
          /// Starts to listen for incoming connections.
          /// </summary>
          /// <returns>true in case of success, false otherwise</returns>
          public bool StartListening(ushort port, bool openExternal)
          {
              if (this.Started)
              {
                  ConsoleUtils.ShowFatalError(
                      "Trying to start an already started SocketService.\nAt {0}",
                      "SocketService.StartListening()"
                  );
                  return false;
              }
  
              Socket listener = new Socket(SocketType.Stream, ProtocolType.Tcp);
  
              try
              {
                  if (openExternal)
                  {
                      listener.Bind(new IPEndPoint(IPAddress.Any, port));
                  }
                  else
                  {
                      listener.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), port));
                  }
  
                  listener.Listen(100);
                  listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
              }
              catch (Exception e)
              {
                  ConsoleUtils.ShowFatalError(
                      "Unexpected error: {0}\nAt {1}",
                      e.Message, "SocketService.StartListening()"
                  );
  
                  listener.Close();
                  return false;
              }
  
              this.Started = true;
              ConsoleUtils.ShowInfo(
                  "Listening to connection on port {0}",
                  port
              );
  
              return true;
          }
  
          /// <summary>
          /// Starts to connect to an IP and Port
          /// </summary>
          /// <returns>If it has connected</returns>
          public bool StartConnection(string ip, ushort port)
          {
              if (this.Started)
              {
                  ConsoleUtils.ShowFatalError(
                      "Trying to start an already started SocketService.\nAt {0}",
                      "SocketService.StartConnection()"
                  );
                  return false;
              }
              Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
  
              try
              {
                  socket.BeginConnect(ip, port, new AsyncCallback(ConnectCallback), socket);
              }
              catch (Exception e)
              {
                  ConsoleUtils.ShowFatalError(
                      "Unexpected error: {0}\nAt {1}",
                      e.Message, "SocketService.StartConnection()"
                  );
  
                  socket.Close();
                  return false;
              }
  
              this.Started = true;
              ConsoleUtils.ShowInfo(
                  "Connecting to {0}:{1}",
                  ip,
                  port
              );
  
              return true;
          }
  
          /// <summary>
  		/// Triggered when it connects to Auth
  		/// </summary>
  		/// <param name="ar"></param>
  		private void ConnectCallback(IAsyncResult ar)
          {
              Socket socket = (Socket)ar.AsyncState;
              if (!socket.Connected)
              {
                  this.OnConnectionFailed();
                  return;
              }
              socket.EndConnect(ar);
  
              // Initializes session
              Session session = this.SessionFactory.GetSession(socket, this.CipherKey);
              this.Connected(session);
  
              // Starts to receive data
              socket.BeginReceive(
                  session._NetworkData.Buffer,
                  0,
                  NetworkData.MaxBuffer,
                  SocketFlags.None,
                  new AsyncCallback(ReadCallback),
                  session
              );
          }
  
          /// <summary>
          /// Called when a client tries to connect
          /// </summary>
          /// <param name="ar"></param>
          private void AcceptCallback(IAsyncResult ar)
          {
              Socket listener = (Socket)ar.AsyncState;
              Socket connector = listener.EndAccept(ar);
  
              // Starts to accept another connection
              listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
  
              Session client = this.SessionFactory.GetSession(connector, this.CipherKey);
  
              connector.BeginReceive(
                  client._NetworkData.Buffer,
                  0,
                  NetworkData.MaxBuffer,
                  SocketFlags.None,
                  new AsyncCallback(ReadCallback),
                  client
              );
          }
  
          /// <summary>
          /// Called when client sends data to server
          /// </summary>
          /// <param name="ar"></param>
          private void ReadCallback(IAsyncResult ar)
          {
              Session session = (Session)ar.AsyncState;
  
              try
              {
                  int bytesToRead = session._NetworkData._Socket.EndReceive(ar);
                  int byteCount;
  
                  if (bytesToRead <= 0)
                  {
                      this.SocketDisconnected(session);
                      session._NetworkData._Socket.Close();
                      return;
                  }
  
                  int curOffset = 0;
                  byte[] data;
  
                  if (this.Encrypted)
                  {
                      data = session._NetworkData.InCipher.DoCipher(
                          ref session._NetworkData.Buffer,
                          bytesToRead
                      );
                  }
                  else
                  {
                      data = session._NetworkData.Buffer;
                  }
  
                  do
                  {
                      int packetOffset = session._NetworkData.Offset;
  
                      if (packetOffset + bytesToRead < HeaderSize)
                      {
                          // Packet header incomplete, only copy bytes
                          Buffer.BlockCopy(data, curOffset, session._NetworkData.Message, packetOffset, bytesToRead);
                          packetOffset += bytesToRead;
                          bytesToRead = 0;
                      }
                      else
                      {
                          if (packetOffset < HeaderSize)
                          {
                              byteCount = HeaderSize - packetOffset;
                              Buffer.BlockCopy(data, curOffset, session._NetworkData.Message, packetOffset, byteCount);
                              curOffset += byteCount;
                              packetOffset += byteCount;
                              bytesToRead -= byteCount;
                          }
                          // Packet header complete
                          int length = BitConverter.ToInt32(session._NetworkData.Message, 0);
  
                          if (packetOffset + bytesToRead < length)
                          {
                              // Packet data incomplete, only copy bytes
                              Buffer.BlockCopy(data, curOffset, session._NetworkData.Message, packetOffset, bytesToRead);
                              packetOffset += bytesToRead;
                              bytesToRead = 0;
                          }
                          else
                          {
                              // Packet data complete, notify
  
gt:                             byte[] packet = new byte[length];