// sends a connection challenge packet to the endpoint
        private void sendConnectionChallenge(NetcodePrivateConnectToken connectToken, EndPoint endpoint)
        {
            log("Sending connection challenge", NetcodeLogLevel.Debug);

            var challengeToken = new NetcodeChallengeToken();

            challengeToken.ClientID = connectToken.ClientID;
            challengeToken.UserData = connectToken.UserData;

            ulong challengeSequence = nextChallengeSequenceNumber++;

            byte[] tokenBytes = BufferPool.GetBuffer(300);
            using (var tokenWriter = ByteArrayReaderWriter.Get(tokenBytes))
                challengeToken.Write(tokenWriter);

            byte[] encryptedToken = BufferPool.GetBuffer(300);
            int    encryptedTokenBytes;

            try
            {
                encryptedTokenBytes = PacketIO.EncryptChallengeToken(challengeSequence, tokenBytes, challengeKey, encryptedToken);
            }
            catch
            {
                BufferPool.ReturnBuffer(tokenBytes);
                BufferPool.ReturnBuffer(encryptedToken);
                return;
            }

            var challengePacket = new NetcodeConnectionChallengeResponsePacket();

            challengePacket.ChallengeTokenSequence = challengeSequence;
            challengePacket.ChallengeTokenBytes    = encryptedToken;

            var cryptIdx = encryptionManager.FindEncryptionMapping(endpoint, time);

            if (cryptIdx == -1)
            {
                return;
            }

            var cryptKey = encryptionManager.GetSendKey(cryptIdx);

            serializePacket(new NetcodePacketHeader()
            {
                PacketType = NetcodePacketType.ConnectionChallenge
            }, (writer) =>
            {
                challengePacket.Write(writer);
            }, endpoint, cryptKey);

            BufferPool.ReturnBuffer(tokenBytes);
            BufferPool.ReturnBuffer(encryptedToken);
        }
        // process an incoming connection response packet
        private void processConnectionResponse(ByteArrayReaderWriter reader, NetcodePacketHeader header, int size, EndPoint sender)
        {
            log("Got connection response", NetcodeLogLevel.Debug);

            // encryption mapping was not registered, so don't bother
            int cryptIdx = encryptionManager.FindEncryptionMapping(sender, time);

            if (cryptIdx == -1)
            {
                log("No crytpo key for sender", NetcodeLogLevel.Debug);
                return;
            }

            // grab the decryption key and decrypt the packet
            var decryptKey = encryptionManager.GetReceiveKey(cryptIdx);

            var connectionResponsePacket = new NetcodeConnectionChallengeResponsePacket()
            {
                Header = header
            };

            if (!connectionResponsePacket.Read(reader, size - (int)reader.ReadPosition, decryptKey, protocolID))
            {
                log("Failed to decrypt packet", NetcodeLogLevel.Debug);
                return;
            }

            var challengeToken = new NetcodeChallengeToken();

            if (!challengeToken.Read(connectionResponsePacket.ChallengeTokenBytes, connectionResponsePacket.ChallengeTokenSequence, challengeKey))
            {
                log("Failed to read challenge token", NetcodeLogLevel.Debug);
                connectionResponsePacket.Release();
                return;
            }

            // if a client from packet source IP / port is already connected, ignore the packet
            if (clientSlots.Any(x => x != null && x.RemoteEndpoint.Equals(sender)))
            {
                log("Client {0} already connected", NetcodeLogLevel.Debug, sender.ToString());
                return;
            }

            // if a client with the same id is already connected, ignore the packet
            if (clientSlots.Any(x => x != null && x.ClientID == challengeToken.ClientID))
            {
                log("Client ID {0} already connected", NetcodeLogLevel.Debug, challengeToken.ClientID);
                return;
            }

            // if the server is full, deny the connection
            int nextSlot = getFreeClientSlot();

            if (nextSlot == -1)
            {
                log("Server full, denying connection", NetcodeLogLevel.Info);
                denyConnection(sender, encryptionManager.GetSendKey(cryptIdx));
                return;
            }

            // assign the endpoint and client ID to a free client slot and set connected to true
            RemoteClient client = new RemoteClient(this);

            client.ClientID         = challengeToken.ClientID;
            client.RemoteEndpoint   = sender;
            client.Connected        = true;
            client.replayProtection = new NetcodeReplayProtection();

            // assign timeout to client
            client.timeoutSeconds = encryptionManager.GetTimeoutSeconds(cryptIdx);

            // assign client to a free slot
            client.ClientIndex         = (uint)nextSlot;
            this.clientSlots[nextSlot] = client;

            encryptionManager.SetClientID(cryptIdx, client.ClientIndex);

            // copy user data so application can make use of it, and set confirmed to false
            client.UserData  = challengeToken.UserData;
            client.Confirmed = false;

            client.Touch(time);

            // respond with a connection keep alive packet
            sendKeepAlive(client);
        }