PageRenderTime 235ms CodeModel.GetById 121ms app.highlight 53ms RepoModel.GetById 47ms app.codeStats 1ms

/Utilities/Networking/BaseClient.cs

#
C# | 1140 lines | 584 code | 70 blank | 486 comment | 75 complexity | 4ebcead1a73053f1deb0e33db6df0f37 MD5 | raw file
   1// Helper to figure out networking issues, will write into console only!
   2//#define LOG_STUFF
   3
   4using System;
   5using System.Collections.Generic;
   6using System.IO;
   7using System.Net.Sockets;
   8using System.Threading;
   9using Delta.Utilities.Compression;
  10using Delta.Utilities.Cryptography;
  11using Delta.Utilities.Helpers;
  12using Delta.Utilities.Xml;
  13
  14namespace Delta.Utilities.Networking
  15{
  16	/// <summary>
  17	/// Base class for connected clients. Contains most importantly the
  18	/// connected socket, but also some extra information like the username.
  19	/// Used both on the BaseServer side (then we just care about the socket
  20	/// and the basic data and networking functionality) and in the Client
  21	/// that connects to the BaseServer (then all features are used). Derive
  22	/// from this to add more functionality. If you just want to use this
  23	/// class the MessageReceived is the easiest way to receive network data.
  24	/// <para />
  25	/// This class also support encryption with AES and RSA to make network
  26	/// transmissions really secure. See the constructors for details.
  27	/// <para />
  28	/// Internally also used as a receiving data helper class by defining
  29	/// buffers for receiving data. Thanks to the IAsyncState passed around in
  30	/// StartReceivingMessages this data will be preserved as long as the client
  31	/// is connected. RememberLastData is important for partial message we might
  32	/// receive (if incoming data is bigger than receivedData can hold or if we
  33	/// get multiple messages at once or strange packet sizes, which is very
  34	/// normal for TCP packets).
  35	/// </summary>
  36	public abstract class BaseClient : IDisposable
  37	{
  38		#region Constants
  39		/// <summary>
  40		/// Number of basic message types (add this if you are planing to use
  41		/// the BasicMessageTypes plus own types in derived classes).
  42		/// </summary>
  43		public static readonly byte NumberOfBasicMessageTypes =
  44			(byte)EnumHelper.GetCount<BasicMessageTypes>();
  45
  46		/// <summary>
  47		/// Default time to wait until we timeout a connection attempt. Since this
  48		/// runs in an extra thread we do not actually have to wait this long.
  49		/// The default wait time is 3 seconds, which should be enough for most
  50		/// users (even if a timeout happens, the user can try again).
  51		/// </summary>
  52		private const int DefaultConnectionTimeoutMs = 3000;
  53
  54		/// <summary>
  55		/// Wait time in ms until we try to connect again if the connection failed.
  56		/// We wait for 1 seconds (3 in release mode) and then try again.
  57		/// More is too annoying when developing and this happens (because we
  58		/// might just started the server or just fixed our connection problems).
  59		/// </summary>
  60		private const int WaitTimeTillNextConnectionAttemptMs =
  61			3000;
  62		#endregion
  63
  64		#region Public
  65		/// <summary>
  66		/// Username for this client. We mostly only care on the server what the
  67		/// name of each connected client is (for identification and debugging).
  68		/// This is set after receiving the Connect message from the client in
  69		/// the OnConnected method and used quite a bit on servers and clients.
  70		/// More complex clients with username, passwords, emails, etc. obviously
  71		/// have more data and you don't have to use this at all (just override
  72		/// the Login methods).
  73		/// </summary>
  74		public string Username;
  75
  76		/// <summary>
  77		/// Client socket if we are the server. If we are the client this is the
  78		/// server connection socket we send all data to.
  79		/// </summary>
  80		public Socket Socket
  81		{
  82			get;
  83			protected internal set;
  84		}
  85
  86		/// <summary>
  87		/// Helper method to check if we are successfully logged in. Please do not
  88		/// use this one to check if we have received the login message because
  89		/// this will not be true if the server rejected us and disconnects us.
  90		/// Instead override the OnLogin method to see when we are connected or
  91		/// when we might be disconnected before this ever gets true.
  92		/// </summary>
  93		public bool SuccessfullyLoggedIn
  94		{
  95			get
  96			{
  97				return connected &&
  98				       loggedIn;
  99			}
 100		}
 101
 102		/// <summary>
 103		/// Address to the server. Only set in constructor, cannot be changed.
 104		/// Not used for BaseServer clients.
 105		/// </summary>
 106		protected string ServerAddress
 107		{
 108			get;
 109			private set;
 110		}
 111
 112		/// <summary>
 113		/// List of server ports we can use to connect, they will be tried in this
 114		/// order (useful in case some ports are blocked, e.g. in company networks).
 115		/// Only set in constructor, cannot be changed. Not used for BaseServer
 116		/// clients.
 117		/// </summary>
 118		protected int[] ServerPorts
 119		{
 120			get;
 121			private set;
 122		}
 123
 124		/// <summary>
 125		/// Is the connection made on the client side or is this just the
 126		/// connection from the server to the client (then this returns false).
 127		/// </summary>
 128		public bool IsClientSide
 129		{
 130			get
 131			{
 132				return linkToServer == null;
 133			}
 134		}
 135
 136		#region OnDisconnected event
 137		/// <summary>
 138		/// Delegate for the BaseClient.OnDisconnected event.
 139		/// </summary>
 140		public delegate void DisconnectDelegate();
 141
 142		/// <summary>
 143		/// Delegate to be notified when we lose the connection to the server.
 144		/// Note: This is only called when the server disconnects us (or we lose
 145		/// the network connection somehow), this is not called when we dispose
 146		/// the BaseClient via the Dispose method (which you can override if you
 147		/// want to handle normal disposes).
 148		/// </summary>
 149		public DisconnectDelegate OnDisconnected;
 150		#endregion
 151
 152		#endregion
 153
 154		#region Private
 155		/// <summary>
 156		/// Helper flag to remember if we have successfully connected (for server
 157		/// side client connections). Initially this is false and only after
 158		/// receiving the Connect (on the server) message with success we are
 159		/// fully connected (or once we established the connection on the client
 160		/// side). When not connected any attempt to receive any other message will
 161		/// result in failure and the client will immediately disconnect in the
 162		/// <see cref="OnRawMessageReceived"/> method.
 163		/// </summary>
 164		private bool connected;
 165
 166		/// <summary>
 167		/// Is the client logged in? Initially false and will be set to true if
 168		/// the Login message has been received and is successful (both on the
 169		/// client and the server side). Any attempt to receive or send messages
 170		/// to a non connected and not loggedIn client will immediately result in 
 171		/// a failure and the client will be disconnected.
 172		/// </summary>
 173		protected bool loggedIn;
 174
 175		/// <summary>
 176		/// Maximum number of message types, set in constructor. Only used to
 177		/// provide checks if incoming data is valid and can be converted into
 178		/// a known message type with data.
 179		/// </summary>
 180		internal byte maxNumOfMessageTypes;
 181
 182		/// <summary>
 183		/// Client buffer data, internal to allow SocketHelper access to this
 184		/// and protected to allow derived classes to override the
 185		/// StartReceivingMessagesAndSendConnectMessage method.
 186		/// </summary>
 187		protected internal byte[] clientBufferData =
 188			new byte[SocketHelper.ReceiveBufferSize];
 189
 190		/// <summary>
 191		/// Remember last data, also only used for the SocketHelper class.
 192		/// </summary>
 193		internal byte[] rememberLastData;
 194
 195		/// <summary>
 196		/// Helper thread to make connection initialization go as smoothly as
 197		/// possible. Note: This is only used on the client side (server already
 198		/// has a connected socket when creating a new Client instance).
 199		/// If we are still connecting sending out messages is delayed. If the
 200		/// connection is already established, we can send out messages directly
 201		/// (which is easier and quicker and this thread is not longer needed).
 202		/// </summary>
 203		private Thread connectionThread;
 204
 205		/// <summary>
 206		/// Remember AES cryptography if this is wanted for the client server
 207		/// communication. Both the client and the server instance must be
 208		/// initialized with the same private AES key. The server has to wait
 209		/// until the Connected message arrives from the client to setup the
 210		/// Cryptography class with this same private key and seed value.
 211		/// </summary>
 212		private AES aes;
 213
 214		/// <summary>
 215		/// You can even use an public RSA key to encrypt Connect messages, which
 216		/// can be decrypted the matching secret RSA key on the server. If this
 217		/// variable is null, no RSA cryptography is used.
 218		/// </summary>
 219		private readonly RSA rsa;
 220
 221		/// <summary>
 222		/// Link to the BaseServer if this is a connected client on the server.
 223		/// Needed to check if cryptography data is valid and to decrypt it.
 224		/// </summary>
 225		private readonly BaseServer linkToServer;
 226
 227		/// <summary>
 228		/// Helper list to send out messages in case we are not connected yet!
 229		/// </summary>
 230		private readonly List<byte> remToSendMessageType = new List<byte>();
 231
 232		/// <summary>
 233		/// Helper list for the data of messages to be send out.
 234		/// </summary>
 235		private readonly List<byte[]> remToSendMessageData = new List<byte[]>();
 236
 237		/// <summary>
 238		/// Helper list for the data of messages to be send out if compression
 239		/// should be enabled or not (default is true).
 240		/// </summary>
 241		private readonly List<bool> remToSendMessageCompression = new List<bool>();
 242		#endregion
 243
 244		#region Constructors
 245
 246		#region Constructor (server side)
 247		/// <summary>
 248		/// Create client instance with an already known connected socket. Only
 249		/// used on the server side for client connections. The client side has to
 250		/// use the other constructor to get connected to the server.
 251		/// This constructor will cause the ClientSide property to return false.
 252		/// </summary>
 253		/// <param name="setMaxNumOfMessageTypes">Set max number of message types,
 254		/// which is the same value as the server uses.</param>
 255		/// <param name="setClientSocket">Get client socket</param>
 256		/// <param name="setLinkToServer">
 257		/// Link to the BaseServer if this is a connected client on the server.
 258		/// Needed to check if cryptography data is valid and to decrypt.
 259		/// </param>
 260		protected BaseClient(Socket setClientSocket, byte setMaxNumOfMessageTypes,
 261			BaseServer setLinkToServer)
 262		{
 263			Socket = setClientSocket;
 264			maxNumOfMessageTypes = setMaxNumOfMessageTypes;
 265			linkToServer = setLinkToServer;
 266			// Everything else is either not used or set later.
 267		}
 268		#endregion
 269
 270		#region Constructor (client side)
 271		/// <summary>
 272		/// Create a new tcp client and connect to a remote BaseServer instance.
 273		/// </summary>
 274		/// <param name="setServerAddress">Set server address</param>
 275		/// <param name="setServerPorts">
 276		/// List of server ports we can use to connect, they will be tried in this
 277		/// order (useful in case some ports are blocked, e.g. in company networks).
 278		/// </param>
 279		/// <param name="setMaxMessageTypes">Set max number of message types,
 280		/// which is the same value as the server uses.</param>
 281		/// <param name="keepTryingToConnectEndlessly">Keep trying to connect
 282		/// endlessly, which should only be used when you also call Dispose
 283		/// (e.g. from Application.Close or when closing the window). If this is
 284		/// done without calling Dispose and no server connect is possible, the
 285		/// connectionThread is never quit and you need to kill the process.
 286		/// </param>
 287		/// <param name="setUsername">Username for the Login method,
 288		/// can be overwritten, but this is the default value for it.</param>
 289		/// <param name="privateKey">
 290		/// Private key for encryption, see Cryptography class for details, by
 291		/// default this is null and unused. Please note that for encryption the
 292		/// first message send from the client to the server must be the Connect
 293		/// message (message type 0) and it contains the random seed (optionally
 294		/// even encrypted with a public RSA key from the server).
 295		/// Most importantly the ClientData must also have the same privateKey,
 296		/// which should be kept secret (but even if it is known by an attacked
 297		/// if you encrypted the random seed with RSA, the transmission is still
 298		/// secure).
 299		/// </param>
 300		/// <param name="publicRsaKey">
 301		/// Public RSA key, which must match the secret RSA key on the server
 302		/// (2048 bits) that is used to decrypt incoming Connect messages from
 303		/// clients, which encrypt their seed value in the Connect message.
 304		/// Note: This is ignored if privateKey is null (works only together).
 305		/// </param>
 306		protected BaseClient(string setServerAddress, int[] setServerPorts,
 307			byte setMaxMessageTypes, bool keepTryingToConnectEndlessly,
 308			string setUsername, byte[] privateKey = null,
 309			XmlNode publicRsaKey = null)
 310		{
 311			// Remember server address and port, not really used, but maybe useful
 312			// later for debugging and informational purposes.
 313			ServerAddress = setServerAddress;
 314			ServerPorts = setServerPorts;
 315			if (privateKey != null)
 316			{
 317				// Create AES encryption instance and generate a random seed.
 318				aes = new AES(privateKey);
 319				if (publicRsaKey != null)
 320				{
 321					rsa = new RSA(publicRsaKey);
 322				}
 323			}
 324
 325			// Fill in extra information for serverConnectionAndClientInfo
 326			maxNumOfMessageTypes = setMaxMessageTypes;
 327			Username = setUsername;
 328			ConnectSocketAndStartReceivingMessages(ServerAddress, ServerPorts,
 329				keepTryingToConnectEndlessly);
 330		}
 331
 332		/// <summary>
 333		/// Create a new tcp client and connect to a remote BaseServer instance.
 334		/// </summary>
 335		/// <param name="setServerAddress">Set server address</param>
 336		/// <param name="setServerPort">Set server port</param>
 337		/// <param name="setMaxNumOfMessageTypes">Set max number of message types,
 338		/// which is the same value as the server uses.</param>
 339		/// <param name="keepTryingToConnectEndlessly">Keep trying to connect
 340		/// endlessly, which should only be used when you also call Dispose
 341		/// (e.g. from Application.Close or when closing the window). If this is
 342		/// done without calling Dispose and no server connect is possible, the
 343		/// connectionThread is never quit and you need to kill the process.
 344		/// </param>
 345		/// <param name="setUsername">Username for the Login method,
 346		/// can be overwritten, but this is the default value for it.</param>
 347		/// <param name="setPrivateKey">
 348		/// Private key for encryption, see Cryptography class for details, by
 349		/// default this is null and unused. Please note that for encryption the
 350		/// first message send from the client to the server must be the Connect
 351		/// message (message type 0) and it contains the random seed (optionally
 352		/// even encrypted with a public RSA key from the server).
 353		/// Most importantly the ClientData must also have the same privateKey,
 354		/// which should be kept secret (but even if it is known by an attacked
 355		/// if you encrypted the random seed with RSA, the transmission is still
 356		/// secure).
 357		/// </param>
 358		/// <param name="publicRsaKey">
 359		/// Public RSA key, which must match the secret RSA key on the server
 360		/// (2048 bits) that is used to decrypt incoming Connect messages from
 361		/// clients, which encrypt their seed value in the Connect message.
 362		/// Note: This is ignored if privateKey is null (works only together).
 363		/// </param>
 364		protected BaseClient(string setServerAddress, int setServerPort,
 365			byte setMaxMessageTypes, bool keepTryingToConnectEndlessly,
 366			string setUsername, byte[] privateKey = null,
 367			XmlNode publicRsaKey = null)
 368			: this(setServerAddress, new[]
 369			{
 370				setServerPort
 371			}, setMaxMessageTypes,
 372				keepTryingToConnectEndlessly, setUsername, privateKey, publicRsaKey)
 373		{
 374		}
 375		#endregion
 376
 377		#endregion
 378
 379		#region ConnectSocketAndStartReceivingMessages
 380		/// <summary>
 381		/// Helper method to connect to the server in an extra thread.
 382		/// </summary>
 383		/// <param name="serverAddress">Server address to connect to</param>
 384		/// <param name="serverPorts">Server port(s) to connect to</param>
 385		/// <param name="keepTryingToConnectEndlessly">Keep trying to connect
 386		/// endlessly, which should only be used when you also call Dispose
 387		/// (e.g. from Application.Close or when closing the window). If this is
 388		/// done without calling Dispose and no server connect is possible, the
 389		/// connectionThread is never quit and you need to kill the process.
 390		/// </param>
 391		protected virtual void ConnectSocketAndStartReceivingMessages(
 392			string serverAddress, int[] serverPorts,
 393			bool keepTryingToConnectEndlessly)
 394		{
 395			// Do all this in an extra thread, stuff can fail and take a while!
 396			connectionThread = ThreadHelper.Start(delegate
 397			{
 398				try
 399				{
 400					if (keepTryingToConnectEndlessly)
 401					{
 402						// If we are not connected, try over and over again!
 403						while (Socket == null ||
 404						       Socket.Connected == false)
 405						{
 406							// Try to connect to the server
 407							Socket = SocketHelper.ConnectTcpSocket(
 408								serverAddress, serverPorts, DefaultConnectionTimeoutMs, false);
 409							// Not connected yet? Then wait for a while (10 second delay)
 410							if (Socket == null ||
 411							    Socket.Connected == false)
 412							{
 413								Thread.Sleep(WaitTimeTillNextConnectionAttemptMs);
 414							}
 415						} // while
 416					} // if
 417					else
 418					{
 419						// Try to connect to the server
 420						Socket = SocketHelper.ConnectTcpSocket(
 421							serverAddress, serverPorts, DefaultConnectionTimeoutMs, false);
 422					} // else
 423
 424					// Done with connectionThread, it is not longer needed or used!
 425					connectionThread = null;
 426
 427					// Get out of this thread if the connection failed
 428					if (Socket == null ||
 429					    Socket.Connected == false)
 430					{
 431						Log.Info(
 432							"Unable to connect to server: " +
 433							(String.IsNullOrEmpty(SocketHelper.LastConnectionError)
 434							 	? ""
 435							 	: SocketHelper.LastConnectionError + ": ") +
 436							serverAddress + ":" + serverPorts.Write());
 437						// (keepTryingToConnectEndlessly ? "enabled" : "disabled") + ")
 438						// Notify anyone interested that we failed to login
 439						OnLogin(null);
 440						Socket = null;
 441						return;
 442					}
 443
 444					// We are connected, send the Connect message to setup the
 445					// connection (optionally with encryption).
 446					connected = true;
 447					StartReceivingMessages();
 448					SendConnectMessageToServer();
 449					// Also send out the Login message with all information as defined
 450					// in the SendLoginMessage and the server.OnClientLogin methods.
 451					// Note: The server also sends Login data to us, see OnLogin.
 452					SendLoginMessageToServer();
 453
 454					// In case we tried to send out some messages earlier when the
 455					// socket was not initialized yet, do it now!
 456					lock (remToSendMessageType)
 457					{
 458						for (int i = 0; i < remToSendMessageType.Count; i++)
 459						{
 460							SocketHelper.SendMessageBytes(Socket,
 461								remToSendMessageType[i],
 462								remToSendMessageData[i],
 463								remToSendMessageCompression[i],
 464								aes);
 465						}
 466						remToSendMessageType.Clear();
 467						remToSendMessageData.Clear();
 468						remToSendMessageCompression.Clear();
 469					}
 470				} // try
 471				catch (ThreadAbortException)
 472				{
 473					// Ignore if the thread is being killed (see Dispose)!
 474					OnLogin(null);
 475					Socket = null;
 476				}
 477				catch (Exception ex)
 478				{
 479					Log.Warning("Failed to connect and login: " + ex);
 480					Dispose();
 481				}
 482			});
 483		}
 484		#endregion
 485
 486		#region Dispose
 487		/// <summary>
 488		/// Dispose the client, will kill the threads and sockets. Also called if
 489		/// the host disconnects us and in BaseServer.OnClientDisconnects.
 490		/// </summary>
 491		public virtual void Dispose()
 492		{
 493			// Still connecting? Then kill the connection thread!
 494			if (connectionThread != null)
 495			{
 496				connectionThread.Abort();
 497				connectionThread = null;
 498			}
 499
 500			// And finally disconnect, the thread above should have displayed
 501			// everything we sent by now. Socket connected? Then disconnect it.
 502			if (Socket != null &&
 503			    Socket.Connected)
 504			{
 505				try
 506				{
 507					Socket.Shutdown(SocketShutdown.Both);
 508					Socket.Close();
 509				}
 510				catch (Exception ex)
 511				{
 512					Log.Warning("Failed to shutdown " + this + ": " + ex);
 513				}
 514			} // if
 515
 516			// If this is on the client side and we were not connected yet, inform!
 517			if (linkToServer == null &&
 518			    loggedIn == false)
 519			{
 520				OnLogin(null);
 521			}
 522			Socket = null;
 523			connected = false;
 524		}
 525
 526		/// <summary>
 527		/// Destructor, will just call Dispose to kill the threads and sockets.
 528		/// </summary>
 529		~BaseClient()
 530		{
 531			Dispose();
 532		}
 533		#endregion
 534
 535		#region StartReceivingMessages
 536		/// <summary>
 537		/// Start receiving messages, which will use the SocketHelper functionality
 538		/// by default, but you can override this to get different behavior.
 539		/// All you need for that is a message received delegate, which will be
 540		/// called whenever a full message arrives to one of the clients. This
 541		/// method is all you need after you have a connected socket (either when
 542		/// you connected to a server or a server that listened for clients). By
 543		/// default messageReceived is handled by the SocketHelper automatically.
 544		/// Optionally you can also get notified whenever data arrives in case you
 545		/// want to update progress bars for big data packages.
 546		/// </summary>
 547		internal void StartReceivingMessages()
 548		{
 549			// Start the receiving process with our data and callback method!
 550			SocketHelper.SetupReceiveCallback(Socket, clientBufferData,
 551				SocketHelper.OnReceivedDataAsyncCallback, this);
 552		}
 553		#endregion
 554
 555		#region HandleConnectEncryption
 556		/// <summary>
 557		/// Write encryption information for the Connect message, see the
 558		/// ClientData.SendConnectMessage method for details. This message starts
 559		/// with a helper byte to mark if we use encryption and then adds the
 560		/// seed value (either as a plain byte array or encrypted with RSA).
 561		/// </summary>
 562		/// <param name="writer">Writer to store binary data to</param>
 563		protected void HandleConnectEncryption(BinaryWriter writer)
 564		{
 565			// Do we use a private key for encrypting messages?
 566			// Store if just AES encryption is used (1) or if RSA is used too (2).
 567			writer.Write((byte)
 568			             (aes != null
 569			              	? (rsa != null
 570			              	   	? 2
 571			              	   	: 1)
 572			              	: 0));
 573			if (aes != null)
 574			{
 575				// If we have no RSA enabled, just store the seed value!
 576				if (rsa == null)
 577				{
 578					writer.Write(aes.Seed);
 579				}
 580					// Otherwise encrypt the seed with our public RSA key
 581				else
 582				{
 583					writer.Write(rsa.Encrypt(aes.Seed));
 584				}
 585			}
 586		}
 587		#endregion
 588
 589		#region SendConnectMessageToServer
 590		/// <summary>
 591		/// Send out the Connect message from the client to the server with the
 592		/// optional encryption seed value (which might even be RSA encrypted) for
 593		/// secure network transmissions. This information must always be send via
 594		/// the <see cref="HandleConnectEncryption"/> method. Additional user
 595		/// information (username, password, etc.) can also be send with help of
 596		/// the Login message (see <see cref="SendLoginMessageToServer"/>).
 597		/// <para />
 598		/// Called by <see cref="ConnectSocketAndStartReceivingMessages"/>
 599		/// </summary>
 600		private void SendConnectMessageToServer()
 601		{
 602			MemoryStream connectMessage = new MemoryStream();
 603			BinaryWriter writer = new BinaryWriter(connectMessage);
 604			// Handle encryption Connect message logic.
 605			HandleConnectEncryption(writer);
 606			// And send the Connect message
 607			Send((byte)BasicMessageTypes.Connect, connectMessage);
 608		}
 609		#endregion
 610
 611		#region SendLoginMessageToServer
 612		/// <summary>
 613		/// Send out the Login message from the client to the server (after the
 614		/// Connect message has been set and everything is setup). By default only
 615		/// the username is sent, override this method for more functionality.
 616		/// Note: There are two reasons why Connect and Login is separated. First
 617		/// it makes deriving this method easier as we do not need to handle the
 618		/// Connect and encryption logic here. And it also makes sending,
 619		/// encrypting and receiving and decrypting the Login message easier and
 620		/// more secure (all data in Login is already encrypted and cannot be
 621		/// decrypted without a successful Connect message was processed first).
 622		/// <para />
 623		/// Called by <see cref="ConnectSocketAndStartReceivingMessages"/>
 624		/// </summary>
 625		protected virtual void SendLoginMessageToServer()
 626		{
 627			MemoryStream loginMessage = new MemoryStream();
 628			BinaryWriter writer = new BinaryWriter(loginMessage);
 629			// Write out the username (derived classes might send more)
 630			writer.Write(Username);
 631			// And send the Login message
 632			Send((byte)BasicMessageTypes.Login, loginMessage);
 633		}
 634		#endregion
 635
 636		#region OnLogin
 637		/// <summary>
 638		/// On login helper method, will be called once the connection has been
 639		/// established and we got the Login message from the server. Does nothing
 640		/// here, but derived classes can extend this functionality to receive the
 641		/// login status, username and extra login information from the server.
 642		/// </summary>
 643		/// <param name="data">Data we received from the network</param>
 644		/// <returns>True if we are still connected and are successfully logged in
 645		/// or false otherwise if the server rejected us and we need to reconnect.
 646		/// </returns>
 647		protected virtual bool OnLogin(BinaryReader data)
 648		{
 649			// Nothing else here, but derived classes can extend this functionality
 650			return data != null;
 651		}
 652		#endregion
 653
 654		#region OnMessageReceived
 655		/// <summary>
 656		/// When a message arrived from the server and has been processed, this
 657		/// method will be called from OnRawMessageReceived (if everything is ok).
 658		/// Override for more functionality, this method will however not report
 659		/// the Connect (0) and Login (1) BasicMessageTypes functionality and
 660		/// encryption and decryption is done automatically in the caller for us.
 661		/// <para />
 662		/// Note: This is the only method here you must implement in derived
 663		/// classes to make sense of client server communication. It is however
 664		/// not used if this is a server side client connection (see first
 665		/// constructor).
 666		/// </summary>
 667		/// <param name="messageType">Type of message received (byte)</param>
 668		/// <param name="data">Data we received from the network (uncompressed
 669		/// and un-encrypted, all handled by OnRawMessageReceived)</param>
 670		protected abstract void OnMessageReceived(byte messageType,
 671			BinaryReader data);
 672		#endregion
 673
 674		#region HandleConnectDecryption
 675		/// <summary>
 676		/// Helper method to decrypt incoming Connect messages from clients in
 677		/// case they are encrypted (done on the server side). Will only work if
 678		/// both the client and the server use the same AES cryptography private
 679		/// keys and if used matching  secret and public RSA keys for the AES seed
 680		/// value. If no cryptography is used just a byte with the value 0 is read
 681		/// from the data stream and the OnConnected method will handle all the
 682		/// rest (Username, etc.)
 683		/// <para />
 684		/// This method will disconnect the client right away if anything is wrong
 685		/// like different encryption modes on the server or client.
 686		/// Note: Called only from <see cref="OnRawMessageReceived"/> for servers.
 687		/// </summary>
 688		/// <param name="data">Data we received from the network</param>
 689		/// <returns>True if we could connect successfully, false otherwise
 690		/// (invalid connection attempt, encryption data invalid, etc.)</returns>
 691		protected bool HandleConnectDecryption(BinaryReader data)
 692		{
 693			if (linkToServer == null)
 694			{
 695				Log.Warning(
 696					"This method should only be called on the server side for " +
 697					"connected clients!");
 698				return false;
 699			}
 700
 701			byte encryptionMode = data.ReadByte();
 702			if (encryptionMode >= 1)
 703			{
 704				// Make sure AES is also enabled on the server!
 705				if (linkToServer.privateKey == null)
 706				{
 707					Log.Warning(
 708						"The client sent a Connect request with encryption enabled, but " +
 709						"the server has no encryption setup. Will disconnect the client " +
 710						"now because he is not allowed to connect: " + this);
 711					Dispose();
 712					return false;
 713				}
 714
 715				// Get AES instance from the server and duplicate it with the given
 716				// seed value.
 717				byte[] seed = new byte[AES.SeedLength];
 718
 719				try
 720				{
 721					// Just read the seed value and set it.
 722					if (encryptionMode == 1)
 723					{
 724						data.Read(seed, 0, seed.Length);
 725					}
 726					else
 727					{
 728						// Make sure RSA is also enabled on the server!
 729						if (linkToServer.rsa == null)
 730						{
 731							Log.Warning(
 732								"The client sent an RSA encrypted (mode " +
 733								encryptionMode + ") Connect request, but the server has no " +
 734								"RSA decryption setup. Will disconnect the client now " +
 735								"because we cannot decrypt the AES random seed value, " +
 736								"which is needed: " + this);
 737							Dispose();
 738							return false;
 739						}
 740
 741						// Otherwise the seed is encrypted with the public RSA key, we need
 742						// the server secret RSA key to decrypt it.
 743						byte[] rsaData = new byte[RSA.DataLength];
 744						data.Read(rsaData, 0, rsaData.Length);
 745						seed = linkToServer.rsa.Decrypt(rsaData);
 746					}
 747
 748					aes = new AES(linkToServer.privateKey, seed);
 749				}
 750				catch (Exception ex)
 751				{
 752					// If anything does not fit here (server has no encryption set up
 753					// or any of the keys do not match or work), abort.
 754					Log.Warning(
 755						"Failed to initialize encryption for client " + this +
 756						" (will disconnect client now): " + ex);
 757					Dispose();
 758					return false;
 759				}
 760			}
 761			else
 762			{
 763				// No encryption is wanted, check if the server also has no
 764				// privateKey and no RSA encryption setup. Otherwise abort too!
 765				if (linkToServer.privateKey != null ||
 766				    linkToServer.rsa != null)
 767				{
 768					Log.Warning(
 769						"Encryption is setup on the server, but the client " +
 770						this + " connected without any encryption enabled! Will " +
 771						"disconnect the client now because he is not allowed to connect.");
 772					Dispose();
 773					return false;
 774				}
 775			}
 776
 777			// Otherwise everything went alright, we are connected now!
 778			return true;
 779		}
 780		#endregion
 781
 782		#region OnRawMessageReceived
 783		/// <summary>
 784		/// When a message arrived from the server this method will be called from
 785		/// <see cref="SocketHelper.ReceiveMessageData"/> with the raw message type
 786		/// and data. The data can be compressed and encrypted, this method will
 787		/// handle all the decryption and decompression automatically for us and
 788		/// also handles all the Connect (0) and Login (1) functionality.
 789		/// <para />
 790		/// Note: This method does all the heavy lifting including Connection and
 791		/// Login logic plus decompression and decryption of network data. Read
 792		/// it carefully if you want to understand how the underlying message
 793		/// system works. See the Delta.Utilities.Tests for lots of examples.
 794		/// </summary>
 795		/// <param name="messageType">Type of message received (byte)</param>
 796		/// <param name="data">Data we received from the network (can be
 797		/// encrypted and compressed, see the Send methods for encryption)</param>
 798		/// <param name="isCompressed">Is the incoming data compressed and need
 799		/// to be decompressed?</param>
 800		internal void OnRawMessageReceived(byte messageType, BinaryReader data,
 801			bool isCompressed)
 802		{
 803
 804			// Only allow Connect and Login messages initially on the server side.
 805			if (messageType == (byte)BasicMessageTypes.Connect)
 806			{
 807				if (linkToServer != null)
 808				{
 809					// Already connected? Then this message is not allowed!
 810					if (connected)
 811					{
 812						Log.Warning(
 813							"Client is already connected, it makes no sense to " +
 814							"make another Connect request (resending encryption seed " +
 815							"is currently not supported)! Please check your send code.");
 816						// Just ignore this message then
 817						return;
 818					}
 819
 820					// Handle decryption if needed (decompression is not needed here).
 821					connected = HandleConnectDecryption(data);
 822					// Usually the client is either fully connected or has been
 823					// disconnected already (but he might still be connected if the
 824					// server allows to re-send Connect messages upon Login failure).
 825					// In any case any other message is only allowed when fully
 826					// connected logged in (see connectedAndLoggedIn and below).
 827					return;
 828				}
 829				else
 830				{
 831					Log.Warning(
 832						"Invalid Connect message received for client side " +
 833						"client! This message is only allowed on the server side! " +
 834						"Disconnecting this client now: " + this);
 835					Dispose();
 836					return;
 837				}
 838			} // if (messageType == Connect)
 839
 840			// If we are not connected and this is anything but the Connect message
 841			// type, disconnect immediately!
 842			if (connected == false)
 843			{
 844				Log.Warning(
 845					"Invalid message (type=" + messageType + ") received " +
 846					"for this not yet connected client (only Connect is allowed until " +
 847					"the client is fully connected)! Client will be disconnected now: " +
 848					this);
 849				Dispose();
 850				return;
 851			}
 852
 853			// Otherwise everything is fine and dandy, handle decryption (if enabled)
 854			// and decompression (only for big messages and if enabled by Send) now.
 855			// All methods below this only accept uncompressed and un-encrypted data
 856			if (aes != null &&
 857			    // And we must have some data left, else this could be an empty message
 858			    data.BaseStream.Position < data.BaseStream.Length)
 859			{
 860				try
 861				{
 862					// Get the original data length, might be smaller than data.Length
 863					int originalDataLength =
 864						StreamHelper.ReadNumberMostlySmallerThan254(data);
 865					// And copy all data into a byte array for decryption
 866					byte[] encryptedData =
 867						new byte[data.BaseStream.Length - data.BaseStream.Position];
 868					data.Read(encryptedData, 0, encryptedData.Length);
 869					// Decrypt all the data with our AES cryptography functionality
 870					byte[] decryptedData = aes.Decrypt(encryptedData,
 871						originalDataLength);
 872					if (isCompressed)
 873					{
 874						decryptedData = Zip.Decompress(decryptedData);
 875					}
 876					// Provide a new reader to read the data back
 877					data = new BinaryReader(new MemoryStream(decryptedData));
 878				}
 879				catch (Exception ex)
 880				{
 881					Log.Warning(
 882						"Decrypting data failed, message type=" + messageType +
 883						", data length=" +
 884						(data != null
 885						 	? data.BaseStream.Length
 886						 	: 0) +
 887						", we have to ignore this message data. Error: " + ex.Message +
 888						(ex.Message.Contains("Padding is invalid and cannot be removed")
 889						 	? " This means the incoming encrypted data has a wrong offset " +
 890						 	  "and cannot be decrypted, either something bad happened at the " +
 891						 	  "sender or something else interrupted or changed the " +
 892						 	  "transmission (is the data even encrypted?)!"
 893						 	: "") +
 894						(ex.Message.Contains("The input data is not a complete block")
 895						 	? " This means the incoming data is most likely not even " +
 896						 	  "encrypted and cannot be decrypted, something bad or wrong " +
 897						 	  "happened at sender!"
 898						 	: ""));
 899					return;
 900				}
 901			} // if (AES encrypted)
 902			else if (isCompressed)
 903			{
 904				// If the data was just compressed, we can just decompress it and
 905				// provide a new reader with the decompressed data to the methods.
 906				try
 907				{
 908					byte[] compressedData =
 909						new byte[data.BaseStream.Length - data.BaseStream.Position];
 910					data.Read(compressedData, 0, compressedData.Length);
 911					data = new BinaryReader(new MemoryStream(
 912						Zip.Decompress(compressedData)));
 913				}
 914				catch (Exception ex)
 915				{
 916					Log.Warning(
 917						"Decompressing data failed, the data was probably not " +
 918						"correctly compressed or is corrupted, message type=" +
 919						messageType + ", data length=" +
 920						(data != null
 921						 	? data.BaseStream.Length
 922						 	: 0) +
 923						", we have to ignore this message data. Error: " + ex.Message);
 924					return;
 925				}
 926			} // else if (isCompressed)
 927
 928			// Next step is to handle the login message (both on client and server)
 929			if (messageType == (byte)BasicMessageTypes.Login)
 930			{
 931				// Are we on the server side?
 932				if (linkToServer != null)
 933				{
 934					// And try to Login client with the Login message
 935					loggedIn = linkToServer.HandleClientLogin(this, data);
 936					return;
 937				}
 938					// Only allow Login messages initially on the client side.
 939				else
 940				{
 941					// Assume login worked so far
 942					loggedIn = true;
 943					// The implementation class can decide differently however.
 944					loggedIn = OnLogin(data);
 945					return;
 946				}
 947			} // if (messageType == Login)
 948
 949			// Before we can continue check first if the client is logged in!
 950			if (loggedIn == false)
 951			{
 952				Log.Warning(
 953					"Invalid message " + messageType + " received for this " +
 954					"client, which was not logged in by the server successfully. " +
 955					"Login must have been send and the server must accept this " +
 956					"client! Client will be disconnected now: " + this);
 957				Dispose();
 958				return;
 959			}
 960
 961			// Okay, call OnMessageReceived for clients and use the
 962			// Server.OnMessageReceived for server side clients.
 963			if (linkToServer != null)
 964			{
 965				linkToServer.OnMessageReceived(this, messageType, data);
 966			}
 967			else
 968			{
 969				OnMessageReceived(messageType, data);
 970			}
 971		}
 972		#endregion
 973
 974		#region HandlePreCheckMessage
 975		/// <summary>
 976		/// Handle pre check message
 977		/// </summary>
 978		/// <param name="messageType">Message type</param>
 979		/// <param name="percentageComplete">Percentage complete</param>
 980		internal void HandlePreCheckMessage(byte messageType,
 981			float percentageComplete)
 982		{
 983			OnPreCheckMessage(messageType, percentageComplete);
 984		}
 985		#endregion
 986
 987		#region OnPreCheckMessage
 988		/// <summary>
 989		/// On pre check message is fired when a message is incoming, but not
 990		/// complete yet (should only happen for big messages like file
 991		/// transfers). This way we can update status bars and show percentages.
 992		/// Not used by default, override to do something with this information.
 993		/// <para />
 994		/// Please note that this message does not actually read or provide any
 995		/// of the payload data, all we know is the message type and the
 996		/// percentage of the message that already has been transmitted. None of
 997		/// the decryption, decompression or data reading logic happens here
 998		/// (unlike <see cref="OnRawMessageReceived"/>). If you want to provide
 999		/// additional meta data like a file size or how many MB have been
1000		/// transfered, please just send an extra message before this big one to
1001		/// show this information to the user (TotalMB * percentageComplete).
1002		/// </summary>
1003		/// <param name="messageType">Type of message we are currently receiving
1004		/// (as a byte)</param>
1005		/// <param name="percentageComplete">Percentage complete (0.0-1.0)</param>
1006		protected virtual void OnPreCheckMessage(byte messageType,
1007			float percentageComplete)
1008		{
1009		}
1010		#endregion
1011
1012		#region Send
1013		/// <summary>
1014		/// Send an empty message with just a message type to the server.
1015		/// </summary>
1016		/// <param name="messageType">Message Type</param>
1017		public void Send(byte messageType)
1018		{
1019			SendBytes(messageType, null);
1020		}
1021
1022		/// <summary>
1023		/// Send a message with a message type and some data in a MemoryStream
1024		/// to the server.
1025		/// </summary>
1026		/// <param name="messageType">Message Type</param>
1027		/// <param name="data">Data for this message</param>
1028		public void Send(byte messageType, MemoryStream data)
1029		{
1030			SendBytes(messageType,
1031				data != null
1032					? data.ToArray()
1033					: null);
1034		}
1035
1036		/// <summary>
1037		/// Send a message with a message type and a string as data to the server.
1038		/// </summary>
1039		/// <param name="messageType">Message Type</param>
1040		/// <param name="data">Data as a string, will be converted to bytes</param>
1041		public void Send(byte messageType, string data)
1042		{
1043			SendBytes(messageType, StringHelper.StringToBytes(data));
1044		}
1045
1046		/// <summary>
1047		/// Send a message with a message type and a float as data to the server.
1048		/// </summary>
1049		/// <param name="messageType">Message Type</param>
1050		/// <param name="data">Data as float, will be converted to bytes</param>
1051		public void Send(byte messageType, float data)
1052		{
1053			MemoryStream memStream = new MemoryStream();
1054			BinaryWriter writer = new BinaryWriter(memStream);
1055			writer.Write(data);
1056			SendBytes(messageType, memStream.ToArray());
1057		}
1058		#endregion
1059
1060		#region SendBytes
1061		/// <summary>
1062		/// Send the byte array to the server with the specified message type.
1063		/// Note: Can be overwritten to catch all sends and do some extra logic
1064		/// like collecting statistics.
1065		/// </summary>
1066		/// <param name="messageType">Type of message (as byte)</param>
1067		/// <param name="data">Data</param>
1068		/// <param name="allowToCompressMessage">Allow to automatically compress
1069		/// the message payload data if it is above 1024 bytes (otherwise nothing
1070		/// will happen), on by default. Please note an extra byte with 0xFF (255)
1071		/// is sent to mark that the incoming message is compressed.</param>
1072		protected virtual void SendBytes(byte messageType, byte[] data,
1073			bool allowToCompressMessage = true)
1074		{
1075			if (connectionThread != null)
1076			{
1077				// Sending is done at the end of the connectionThread
1078				if (remToSendMessageType.Count > 100)
1079				{
1080					Log.Warning(
1081						"Unable to remember more messages for client '" + this + "'. " +
1082						"After remembering 100 network messages we should be connected " +
1083						"now. Please make sure to check if you are connected before " +
1084						"sending so much data. Message type=" + messageType);
1085				}
1086				else
1087				{
1088					lock (remToSendMessageType)
1089					{
1090						remToSendMessageType.Add(messageType);
1091						remToSendMessageData.Add(data);
1092						remToSendMessageCompression.Add(allowToCompressMessage);
1093					}
1094				}
1095			}
1096			else if (Socket != null)
1097			{
1098				// Normal sending (with compression on by default and encryption if
1099				// it has been setup for this client instance).
1100				SocketHelper.SendMessageBytes(Socket, messageType,
1101					data, allowToCompressMessage, aes);
1102			}
1103		}
1104		#endregion
1105
1106		#region SendText
1107		/// <summary>
1108		/// Send text message with the predefined TextMessage type (type=2).
1109		/// </summary>
1110		/// <param name="textMessage">Text message</param>
1111		public void SendTextMessage(string textMessage)
1112		{
1113			// Only send if we have a connectionThread or Socket!
1114			if (connectionThread != null ||
1115			    Socket != null)
1116			{
1117				SendBytes((byte)BasicMessageTypes.TextMessage,
1118					StringHelper.StringToBytes(textMessage));
1119			}
1120		}
1121		#endregion
1122
1123		#region ToString
1124		/// <summary>
1125		/// To string method to display some debug information about this client.
1126		/// </summary>
1127		/// <returns>String with detailed client and socket information</returns>
1128		public override string ToString()
1129		{
1130			return "Client " +
1131			       (String.IsNullOrEmpty(ServerAddress) == false
1132			        	? "Server=" + ServerAddress +
1133			        	  ":" + ServerPorts.Write() + ", "
1134			        	: "") +
1135			       "Username=" + Username + ", Socket=" +
1136			       SocketHelper.WriteClientInfo(Socket);
1137		}
1138		#endregion
1139	}
1140}