protected void WriteRawPacket(BitStream stream, NetPacketType packetType)
        {
            WritePacketHeader(stream, packetType);

            if (packetType == NetPacketType.DataPacket)
            {
                var note = AllocNotify();

                ++NumNotifies;

                if (NotifyQueueHead == null)
                {
                    NotifyQueueHead = note;
                }
                else
                {
                    NotifyQueueTail.NextPacket = note;
                }

                NotifyQueueTail = note;

                note.NextPacket = null;
                note.SendTime   = Interface.GetCurrentTime();

                WritePacketRateInfo(stream, note);

                //var start = stream.GetBitPosition();

                stream.SetStringTable(StringTable);

                //Console.WriteLine("NetConnection {0}: START {1}", NetAddress, GetClassName());

                WritePacket(stream, note);

                //Console.WriteLine("NetConnection {0}: END {1} - {2} bits", NetAddress, GetClassName(), stream.GetBitPosition() - start);
            }

            if (SymmetricCipher == null)
            {
                return;
            }

            SymmetricCipher.SetupCounter(LastSendSeq, LastSeqRecvd, (uint)packetType, 0U);

            stream.HashAndEncrypt(MessageSignatureBytes, PacketHeaderByteSize, SymmetricCipher);
        }
        protected bool ReadPacketHeader(BitStream stream)
        {
            var packetType     = (NetPacketType)stream.ReadInt(2);
            var sequenceNumber = stream.ReadInt(5);

            /*var dataPacketFlag = */ stream.ReadFlag();
            sequenceNumber = sequenceNumber | (stream.ReadInt(SequenceNumberBitSize - 5) << 5);

            var highestAck = stream.ReadInt(AckSequenceNumberBitSize);
            var padBits    = stream.ReadInt(PacketHeaderPadBits);

            if (padBits != 0)
            {
                return(false);
            }

            sequenceNumber |= (uint)(LastSeqRecvd & SequenceNumberMask);
            if (sequenceNumber < LastSeqRecvd)
            {
                sequenceNumber += SequenceNumberWindowSize;
            }

            if (sequenceNumber - LastSeqRecvd > (MaxPacketWindowSize - 1))
            {
                return(false);
            }

            highestAck |= (uint)(HighestAckedSeq & AckSequenceNumberMask);
            if (highestAck < HighestAckedSeq)
            {
                highestAck += AckSequenceNumberWindowSize;
            }

            if (highestAck > LastSendSeq)
            {
                return(false);
            }

            if (SymmetricCipher != null)
            {
                SymmetricCipher.SetupCounter(sequenceNumber, highestAck, (uint)packetType, 0);

                if (!stream.DecryptAndCheckHash(MessageSignatureBytes, PacketHeaderByteSize, SymmetricCipher))
                {
                    Console.WriteLine("Packet failed crypto");
                    return(false);
                }
            }

            var ackByteCount = stream.ReadRangedU32(0, MaxAckByteCount);

            if (ackByteCount > MaxAckByteCount || packetType >= NetPacketType.InvalidPacketType)
            {
                return(false);
            }

            var ackMask      = new uint[MaxAckMaskSize];
            var ackWordCount = (ackByteCount + 3) >> 2;

            for (var i = 0U; i < ackWordCount; ++i)
            {
                ackMask[i] = stream.ReadInt((byte)(i == ackWordCount - 1 ? (ackByteCount - (i * 4)) * 8 : 32));
            }

            var sendDelay = (stream.ReadInt(8) << 3) + 4;

            var ackMaskShift = sequenceNumber - LastSeqRecvd;

            while (ackMaskShift > 32)
            {
                for (var i = MaxAckMaskSize - 1; i > 0; --i)
                {
                    AckMask[i] = AckMask[i - 1];
                }

                AckMask[0]    = 0;
                ackMaskShift -= 32;
            }

            var upShifted = packetType == NetPacketType.DataPacket ? 1U : 0U;

            for (var i = 0U; i < MaxAckMaskSize; ++i)
            {
                var nextShift = AckMask[i] >> (int)(32 - ackMaskShift);
                AckMask[i] = (AckMask[i] << (int)ackMaskShift) | upShifted;
                upShifted  = nextShift;
            }

            var notifyCount = highestAck - HighestAckedSeq;

            for (var i = 0U; i < notifyCount; ++i)
            {
                var notifyIndex = HighestAckedSeq + i + 1;

                var ackMaskBit  = (highestAck - notifyIndex) & 0x1FU;
                var ackMaskWord = (highestAck - notifyIndex) >> 5;

                var packetTransmitSuccess = (ackMask[ackMaskWord] & (1U << (int)ackMaskBit)) != 0U;

                HighestAckedSendTime = 0;
                HandleNotify(notifyIndex, packetTransmitSuccess);

                if (HighestAckedSendTime > 0)
                {
                    var roundTripDelta = Interface.GetCurrentTime() - (HighestAckedSendTime + (int)sendDelay);

                    RoundTripTime = RoundTripTime * 0.9f + roundTripDelta * 0.1f;

                    if (RoundTripTime < 0.0f)
                    {
                        RoundTripTime = 0.0f;
                    }
                }

                if (packetTransmitSuccess)
                {
                    LastRecvAckAck = LastSeqRecvdAtSend[notifyIndex & PacketWindowMask];
                }
            }

            if (sequenceNumber - LastRecvAckAck > MaxPacketWindowSize)
            {
                LastRecvAckAck = sequenceNumber - MaxPacketWindowSize;
            }

            HighestAckedSeq  = highestAck;
            PingSendCount    = 0;
            LastPingSendTime = 0;

            KeepAlive();

            var prevLastSequence = LastSeqRecvd;

            LastSeqRecvd = sequenceNumber;

            if (packetType == NetPacketType.PingPacket || (sequenceNumber - LastRecvAckAck > (MaxPacketWindowSize >> 1)))
            {
                SendAckPacket();
            }

            return(prevLastSequence != sequenceNumber && packetType == NetPacketType.DataPacket);
        }