public static OperationStatus ValidateDataLength(NetConnection connection, int dataLength)
        {
            if (connection == null)
            {
                throw new ArgumentNullException(nameof(connection));
            }

            if (dataLength > NetManager.MaxClientPacketSize)
            {
                connection.Kick($"Packet data length {dataLength} exceeds {NetManager.MaxClientPacketSize}.");
                return(OperationStatus.InvalidData);
            }
            return(OperationStatus.Done);
        }
        /// <summary>
        /// Connection flow/lifetime:
        /// Every connection begins in <see cref="EngageClientConnection"/>, where a loop reads
        /// asynchrounsly from the client socket.
        /// Successful reads get copied to the connection's receive buffer.
        /// </summary>
        public async Task EngageClientConnection(NetConnection connection, CancellationToken cancellationToken)
        {
            if (connection == null)
            {
                throw new ArgumentNullException(nameof(connection));
            }

            var readBuffer = new byte[1024 * 4]; // Clients don't really need a big read buffer.
            var readMemory = readBuffer.AsMemory();

            var socket        = connection.Socket;
            var receiveBuffer = connection.ReceiveBuffer;
            var state         = new ReceiveState(
                connection,
                new NetBinaryReader(receiveBuffer),
                cancellationToken);

            try
            {
                int read;
                while ((read = await socket.ReceiveAsync(
                            readMemory, SocketFlags.None, state.CancellationToken).Unchain()) != 0)
                {
                    // Insert received data into beginning of receive buffer.
                    var readSlice = readMemory.Slice(0, read);
                    receiveBuffer.Seek(0, SeekOrigin.End);
                    receiveBuffer.Write(readSlice.Span);
                    receiveBuffer.Seek(0, SeekOrigin.Begin);
                    connection.BytesReceived += readSlice.Length;

                    // We process by the message length (unless it's a legacy server list ping),
                    // so don't worry if we received parts of the next message.
                    OperationStatus handleStatus;
                    while ((handleStatus = HandlePacket(ref state)) == OperationStatus.Done)
                    {
                        if (!connection.IsAlive)
                        {
                            break;
                        }
                    }

                    if (!connection.IsAlive)
                    {
                        break;
                    }

                    receiveBuffer.TrimStart((int)receiveBuffer.Position);
                }
            }
            catch (SocketException sockEx) when(sockEx.SocketErrorCode == SocketError.ConnectionReset)
            {
                // TODO: increment statistic?
            }
            catch (SocketException sockEx) when(sockEx.SocketErrorCode == SocketError.ConnectionAborted)
            {
                Console.WriteLine("Connection aborted for " + connection.RemoteEndPoint);
                // TODO: increment statistic?
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                connection.Kick(ex);
                return;
            }

            connection.Close(immediate: false);
        }