private void SendMessage(
            NetOutgoingMessage message,
            LidgrenConnection connection,
            NetDeliveryMethod deliveryMethod,
            int sequenceChannel = 0
            )
        {
            if (message == null)
            {
                throw new ArgumentNullException(nameof(message));
            }

            if (connection == null)
            {
                throw new ArgumentNullException(nameof(connection));
            }

            if (connection.NetConnection == null)
            {
                throw new ArgumentNullException(nameof(connection.NetConnection));
            }

            message.Encrypt(connection.Aes);
            connection.NetConnection.SendMessage(message, deliveryMethod, sequenceChannel);
        }
        public bool Connect()
        {
            if (mNetwork.Configuration.IsServer)
            {
                throw new InvalidOperationException("Server interfaces cannot use Connect().");
            }

            Log.Info($"Connecting to {mNetwork.Configuration.Host}:{mNetwork.Configuration.Port}...");

            var handshakeSecret = new byte[32];

            mRng.GetNonZeroBytes(handshakeSecret);

            var connectionRsa  = new RSACryptoServiceProvider(2048);
            var hailParameters = connectionRsa.ExportParameters(false);
            var hail           = new HailPacket(mRsa, handshakeSecret, SharedConstants.VersionData, hailParameters);

            hail.Encrypt();

            var hailMessage = mPeer.CreateMessage(hail.Data.Length);

            if (hailMessage == null)
            {
                throw new InvalidOperationException();
            }

            hailMessage.Data        = hail.Data;
            hailMessage.LengthBytes = hail.Data.Length;

            if (mPeer.Status == NetPeerStatus.NotRunning)
            {
                mPeer.Start();
            }

            var connection = mPeer.Connect(mNetwork.Configuration.Host, mNetwork.Configuration.Port, hailMessage);
            var server     = new LidgrenConnection(
                mNetwork, Guid.Empty, connection, handshakeSecret, connectionRsa.ExportParameters(true)
                );

            if (mNetwork.AddConnection(server))
            {
                return(true);
            }

            Log.Error("Failed to add connection to list.");
            connection?.Disconnect("client_error");

            return(false);
        }
        private NetIncomingMessage TryHandleInboundMessage()
        {
            Debug.Assert(mPeer != null, "mPeer != null");

            if (!mPeer.ReadMessage(out var message))
            {
                return(null);
            }

            var connection   = message.SenderConnection;
            var lidgrenId    = connection?.RemoteUniqueIdentifier ?? -1;
            var lidgrenIdHex = BitConverter.ToString(BitConverter.GetBytes(lidgrenId));

            switch (message.MessageType)
            {
            case NetIncomingMessageType.Data:

                //Log.Diagnostic($"{message.MessageType}: {message}");
                return(message);

            case NetIncomingMessageType.StatusChanged:
                Debug.Assert(mGuidLookup != null, "mGuidLookup != null");
                Debug.Assert(mNetwork != null, "mNetwork != null");

                switch (connection?.Status ?? NetConnectionStatus.None)
                {
                case NetConnectionStatus.None:
                case NetConnectionStatus.InitiatedConnect:
                case NetConnectionStatus.ReceivedInitiation:
                case NetConnectionStatus.RespondedAwaitingApproval:
                case NetConnectionStatus.RespondedConnect:
                    Log.Diagnostic($"{message.MessageType}: {message} [{connection?.Status}]");

                    break;

                case NetConnectionStatus.Disconnecting:
                    Log.Debug($"{message.MessageType}: {message} [{connection?.Status}]");

                    break;

                case NetConnectionStatus.Connected:
                {
                    LidgrenConnection intersectConnection;
                    if (!mNetwork.Configuration.IsServer)
                    {
                        intersectConnection = mNetwork.FindConnection <LidgrenConnection>(Guid.Empty);
                        if (intersectConnection == null)
                        {
                            Log.Error("Bad state, no connection found.");
                            mNetwork.Disconnect("client_connection_missing");
                            connection?.Disconnect("client_connection_missing");

                            break;
                        }

                        FireHandler(
                            OnConnectionApproved,
                            nameof(OnConnectionApproved),
                            this,
                            new ConnectionEventArgs
                            {
                                NetworkStatus = NetworkStatus.Connecting,
                                Connection    = intersectConnection
                            }
                            );

                        Debug.Assert(connection != null, "connection != null");
                        var approval = (ApprovalPacket)mCeras.Deserialize(connection.RemoteHailMessage.Data);

                        if (!intersectConnection.HandleApproval(approval))
                        {
                            mNetwork.Disconnect(NetworkStatus.HandshakeFailure.ToString());
                            connection.Disconnect(NetworkStatus.HandshakeFailure.ToString());

                            break;
                        }

                        if (!(mNetwork is ClientNetwork clientNetwork))
                        {
                            throw new InvalidOperationException();
                        }

                        clientNetwork.AssignGuid(approval.Guid);

                        Debug.Assert(mGuidLookup != null, "mGuidLookup != null");
                        mGuidLookup.Add(connection.RemoteUniqueIdentifier, Guid.Empty);
                    }
                    else
                    {
                        Log.Diagnostic($"{message.MessageType}: {message} [{connection?.Status}]");
                        if (!mGuidLookup.TryGetValue(lidgrenId, out var guid))
                        {
                            Log.Error($"Unknown client connected ({lidgrenIdHex}).");
                            connection?.Disconnect("server_unknown_client");

                            break;
                        }

                        intersectConnection = mNetwork.FindConnection <LidgrenConnection>(guid);
                    }

                    if (OnConnected != null)
                    {
                        intersectConnection?.HandleConnected();
                    }

                    FireHandler(
                        OnConnected,
                        nameof(OnConnected),
                        this,
                        new ConnectionEventArgs
                        {
                            NetworkStatus = NetworkStatus.Online,
                            Connection    = intersectConnection
                        }
                        );
                }

                break;

                case NetConnectionStatus.Disconnected:
                {
                    Debug.Assert(connection != null, "connection != null");
                    Log.Debug($"{message.MessageType}: {message} [{connection.Status}]");
                    var result = (NetConnectionStatus)message.ReadByte();
                    var reason = message.ReadString();

                    NetworkStatus networkStatus;
                    try
                    {
                        switch (reason)
                        {
                        //Lidgren won't accept a connection with a bad version and sends this message back so we need to manually handle it
                        case "Wrong application identifier!":
                            networkStatus = NetworkStatus.VersionMismatch;
                            break;

                        case "Connection timed out":
                            networkStatus = NetworkStatus.Quitting;
                            break;

                        case "Failed to establish connection - no response from remote host":
                            networkStatus = NetworkStatus.Offline;
                            break;

                        default:
                            networkStatus = (NetworkStatus)Enum.Parse(typeof(NetworkStatus), reason ?? "<null>", true);
                            break;
                        }
                    }
                    catch (Exception exception)
                    {
                        Log.Diagnostic(exception);
                        networkStatus = NetworkStatus.Unknown;
                    }

                    HandleConnectionEvent disconnectHandler;
                    string disconnectHandlerName;
                    switch (networkStatus)
                    {
                    case NetworkStatus.HandshakeFailure:
                    case NetworkStatus.ServerFull:
                    case NetworkStatus.VersionMismatch:
                    case NetworkStatus.Failed:
                        disconnectHandler     = OnConnectionDenied;
                        disconnectHandlerName = nameof(OnConnectionDenied);
                        break;

                    case NetworkStatus.Connecting:
                    case NetworkStatus.Online:
                    case NetworkStatus.Offline:
                    case NetworkStatus.Quitting:
                    case NetworkStatus.Unknown:
                        disconnectHandler     = OnDisconnected;
                        disconnectHandlerName = nameof(OnDisconnected);
                        break;

                    default:
                        throw new ArgumentOutOfRangeException();
                    }

                    if (!mGuidLookup.TryGetValue(lidgrenId, out var guid))
                    {
                        Log.Debug($"Unknown client disconnected ({lidgrenIdHex}).");
                        FireHandler(disconnectHandler, disconnectHandlerName, this, new ConnectionEventArgs {
                                NetworkStatus = networkStatus
                            });

                        break;
                    }

                    var client = mNetwork.FindConnection(guid);
                    if (client != null)
                    {
                        client.HandleDisconnected();

                        FireHandler(disconnectHandler, disconnectHandlerName, this, new ConnectionEventArgs {
                                Connection = client, NetworkStatus = NetworkStatus.Offline
                            });
                        mNetwork.RemoveConnection(client);
                    }

                    mGuidLookup.Remove(connection.RemoteUniqueIdentifier);
                }

                break;

                default:
                    throw new ArgumentOutOfRangeException();
                }

                break;

            case NetIncomingMessageType.UnconnectedData:
                OnUnconnectedMessage?.Invoke(mPeer, message);
                Log.Diagnostic($"Net Incoming Message: {message.MessageType}: {message}");

                break;

            case NetIncomingMessageType.ConnectionApproval:
            {
                try
                {
                    var hail = (HailPacket)mCeras.Deserialize(message.Data);

                    Debug.Assert(SharedConstants.VersionData != null, "SharedConstants.VERSION_DATA != null");
                    Debug.Assert(hail.VersionData != null, "hail.VersionData != null");
                    if (!SharedConstants.VersionData.SequenceEqual(hail.VersionData))
                    {
                        Log.Error($"Bad version detected, denying connection [{lidgrenIdHex}].");
                        connection?.Deny(NetworkStatus.VersionMismatch.ToString());

                        break;
                    }

                    if (OnConnectionApproved == null)
                    {
                        Log.Error($"No handlers for OnConnectionApproved, denying connection [{lidgrenIdHex}].");
                        connection?.Deny(NetworkStatus.Failed.ToString());

                        break;
                    }

                    /* Approving connection from here-on. */
                    var aesKey = new byte[32];
                    mRng?.GetNonZeroBytes(aesKey);
                    var client = new LidgrenConnection(mNetwork, connection, aesKey, hail.RsaParameters);

                    if (!OnConnectionRequested(this, client))
                    {
                        Log.Warn($"Connection blocked due to ban or ip filter!");
                        connection?.Deny(NetworkStatus.Failed.ToString());

                        break;
                    }

                    Debug.Assert(mNetwork != null, "mNetwork != null");
                    if (!mNetwork.AddConnection(client))
                    {
                        Log.Error($"Failed to add the connection.");
                        connection?.Deny(NetworkStatus.Failed.ToString());

                        break;
                    }

                    Debug.Assert(mGuidLookup != null, "mGuidLookup != null");
                    Debug.Assert(connection != null, "connection != null");
                    mGuidLookup.Add(connection.RemoteUniqueIdentifier, client.Guid);

                    Debug.Assert(mPeer != null, "mPeer != null");
                    var approval        = new ApprovalPacket(client.Rsa, hail.HandshakeSecret, aesKey, client.Guid);
                    var approvalMessage = mPeer.CreateMessage();
                    approvalMessage.Data        = approval.Data;
                    approvalMessage.LengthBytes = approvalMessage.Data.Length;
                    connection.Approve(approvalMessage);
                    OnConnectionApproved(this, new ConnectionEventArgs {
                            Connection = client, NetworkStatus = NetworkStatus.Online
                        });
                }
                catch
                {
                    connection?.Deny(NetworkStatus.Failed.ToString());
                }

                break;
            }

            case NetIncomingMessageType.VerboseDebugMessage:
                Log.Diagnostic($"Net Incoming Message: {message.MessageType}: {message.ReadString()}");

                break;

            case NetIncomingMessageType.DebugMessage:
                Log.Debug($"Net Incoming Message: {message.MessageType}: {message.ReadString()}");

                break;

            case NetIncomingMessageType.WarningMessage:
                Log.Warn($"Net Incoming Message: {message.MessageType}: {message.ReadString()}");

                break;

            case NetIncomingMessageType.ErrorMessage:
            case NetIncomingMessageType.Error:
                Log.Error($"Net Incoming Message: {message.MessageType}: {message.ReadString()}");

                break;

            case NetIncomingMessageType.Receipt:
                Log.Info($"Net Incoming Message: {message.MessageType}: {message.ReadString()}");

                break;

            case NetIncomingMessageType.DiscoveryRequest:
            case NetIncomingMessageType.DiscoveryResponse:
            case NetIncomingMessageType.NatIntroductionSuccess:
            case NetIncomingMessageType.ConnectionLatencyUpdated:
                Log.Diagnostic($"Net Incoming Message: {message.MessageType}: {message}");

                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            return(null);
        }
        private NetIncomingMessage TryHandleInboundMessage()
        {
            Debug.Assert(mPeer != null, "mPeer != null");

            if (!mPeer.ReadMessage(out var message))
            {
                return(null);
            }

            var connection   = message.SenderConnection;
            var lidgrenId    = connection?.RemoteUniqueIdentifier ?? -1;
            var lidgrenIdHex = BitConverter.ToString(BitConverter.GetBytes(lidgrenId));

            switch (message.MessageType)
            {
            case NetIncomingMessageType.Data:

                //Log.Diagnostic($"{message.MessageType}: {message}");
                return(message);

            case NetIncomingMessageType.StatusChanged:
                Debug.Assert(mGuidLookup != null, "mGuidLookup != null");
                Debug.Assert(mNetwork != null, "mNetwork != null");

                switch (connection?.Status ?? NetConnectionStatus.None)
                {
                case NetConnectionStatus.None:
                case NetConnectionStatus.InitiatedConnect:
                case NetConnectionStatus.ReceivedInitiation:
                case NetConnectionStatus.RespondedAwaitingApproval:
                case NetConnectionStatus.RespondedConnect:
                    Log.Diagnostic($"{message.MessageType}: {message} [{connection?.Status}]");

                    break;

                case NetConnectionStatus.Disconnecting:
                    Log.Debug($"{message.MessageType}: {message} [{connection?.Status}]");

                    break;

                case NetConnectionStatus.Connected:
                {
                    LidgrenConnection intersectConnection;
                    if (!mNetwork.Configuration.IsServer)
                    {
                        intersectConnection = mNetwork.FindConnection <LidgrenConnection>(Guid.Empty);
                        if (intersectConnection == null)
                        {
                            Log.Error("Bad state, no connection found.");
                            mNetwork.Disconnect("client_connection_missing");
                            connection?.Disconnect("client_connection_missing");

                            break;
                        }

                        FireHandler(
                            OnConnectionApproved, nameof(OnConnectionApproved), this, intersectConnection
                            );

                        Debug.Assert(connection != null, "connection != null");
                        var approval = (ApprovalPacket)mCeras.Deserialize(connection.RemoteHailMessage.Data);

                        if (!intersectConnection.HandleApproval(approval))
                        {
                            mNetwork?.Disconnect("bad_handshake_secret");
                            connection.Disconnect("bad_handshake_secret");

                            break;
                        }

                        var clientNetwork = mNetwork as ClientNetwork;
                        if (clientNetwork == null)
                        {
                            throw new InvalidOperationException();
                        }

                        clientNetwork.AssignGuid(approval.Guid);

                        Debug.Assert(mGuidLookup != null, "mGuidLookup != null");
                        mGuidLookup.Add(connection.RemoteUniqueIdentifier, Guid.Empty);
                    }
                    else
                    {
                        Log.Diagnostic($"{message.MessageType}: {message} [{connection?.Status}]");
                        if (!mGuidLookup.TryGetValue(lidgrenId, out var guid))
                        {
                            Log.Error($"Unknown client connected ({lidgrenIdHex}).");
                            connection?.Disconnect("server_unknown_client");

                            break;
                        }

                        intersectConnection = mNetwork.FindConnection <LidgrenConnection>(guid);
                    }

                    if (OnConnected != null)
                    {
                        intersectConnection?.HandleConnected();
                    }

                    FireHandler(OnConnected, nameof(OnConnected), this, intersectConnection);
                }

                break;

                case NetConnectionStatus.Disconnected:
                {
                    Debug.Assert(connection != null, "connection != null");
                    Log.Debug($"{message.MessageType}: {message} [{connection.Status}]");
                    var result = (NetConnectionStatus)message.ReadByte();
                    var reason = message.ReadString();

                    HandleConnectionEvent disconnectHandler;
                    string disconnectHandlerName;
                    switch (reason)
                    {
                    case RejectBadHail:
                    case RejectBadVersion:
                    case RejectServerError:
                        disconnectHandler     = OnConnectionDenied;
                        disconnectHandlerName = nameof(OnConnectionDenied);

                        break;

                    default:
                        disconnectHandler     = OnDisconnected;
                        disconnectHandlerName = nameof(OnDisconnected);

                        break;
                    }

                    if (!mGuidLookup.TryGetValue(lidgrenId, out var guid))
                    {
                        Log.Debug($"Unknown client disconnected ({lidgrenIdHex}).");
                        FireHandler(disconnectHandler, disconnectHandlerName, this, null);

                        break;
                    }

                    var client = mNetwork.FindConnection(guid);
                    if (client != null)
                    {
                        client.HandleDisconnected();
                        FireHandler(disconnectHandler, disconnectHandlerName, this, client);
                        mNetwork.RemoveConnection(client);
                    }

                    mGuidLookup.Remove(connection.RemoteUniqueIdentifier);
                }

                break;

                default:
                    throw new ArgumentOutOfRangeException();
                }

                break;

            case NetIncomingMessageType.UnconnectedData:
                OnUnconnectedMessage?.Invoke(mPeer, message);
                Log.Diagnostic($"Net Incoming Message: {message.MessageType}: {message}");

                break;

            case NetIncomingMessageType.ConnectionApproval:
            {
                var hail = (HailPacket)mCeras.Deserialize(message.Data);

                Debug.Assert(SharedConstants.VersionData != null, "SharedConstants.VERSION_DATA != null");
                Debug.Assert(hail.VersionData != null, "hail.VersionData != null");
                if (!SharedConstants.VersionData.SequenceEqual(hail.VersionData))
                {
                    Log.Error($"Bad version detected, denying connection [{lidgrenIdHex}].");
                    connection?.Deny(RejectBadVersion);

                    break;
                }

                if (OnConnectionApproved == null)
                {
                    Log.Error($"No handlers for OnConnectionApproved, denying connection [{lidgrenIdHex}].");
                    connection?.Deny(RejectServerError);

                    break;
                }

                /* Approving connection from here-on. */
                var aesKey = new byte[32];
                mRng?.GetNonZeroBytes(aesKey);
                var client = new LidgrenConnection(mNetwork, connection, aesKey, hail.RsaParameters);

                if (!OnConnectionRequested(this, client))
                {
                    Log.Warn($"Connection blocked due to ban or ip filter!");
                    connection?.Deny(RejectServerError);

                    break;
                }

                Debug.Assert(mNetwork != null, "mNetwork != null");
                if (!mNetwork.AddConnection(client))
                {
                    Log.Error($"Failed to add the connection.");
                    connection?.Deny(RejectServerError);

                    break;
                }

                Debug.Assert(mGuidLookup != null, "mGuidLookup != null");
                Debug.Assert(connection != null, "connection != null");
                mGuidLookup.Add(connection.RemoteUniqueIdentifier, client.Guid);

                Debug.Assert(mPeer != null, "mPeer != null");
                var approval        = new ApprovalPacket(client.Rsa, hail.HandshakeSecret, aesKey, client.Guid);
                var approvalMessage = mPeer.CreateMessage();
                approvalMessage.Data        = approval.Data;
                approvalMessage.LengthBytes = approvalMessage.Data.Length;
                connection.Approve(approvalMessage);
                OnConnectionApproved(this, client);

                break;
            }

            case NetIncomingMessageType.VerboseDebugMessage:
                Log.Diagnostic($"Net Incoming Message: {message.MessageType}: {message.ReadString()}");

                break;

            case NetIncomingMessageType.DebugMessage:
                Log.Debug($"Net Incoming Message: {message.MessageType}: {message.ReadString()}");

                break;

            case NetIncomingMessageType.WarningMessage:
                Log.Warn($"Net Incoming Message: {message.MessageType}: {message.ReadString()}");

                break;

            case NetIncomingMessageType.ErrorMessage:
            case NetIncomingMessageType.Error:
                Log.Error($"Net Incoming Message: {message.MessageType}: {message.ReadString()}");

                break;

            case NetIncomingMessageType.Receipt:
                Log.Info($"Net Incoming Message: {message.MessageType}: {message.ReadString()}");

                break;

            case NetIncomingMessageType.DiscoveryRequest:
            case NetIncomingMessageType.DiscoveryResponse:
            case NetIncomingMessageType.NatIntroductionSuccess:
            case NetIncomingMessageType.ConnectionLatencyUpdated:
                Log.Diagnostic($"Net Incoming Message: {message.MessageType}: {message}");

                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            return(null);
        }