private void RequestHeadReadCallback()
        {
            ClientFromBuffer.RequireRead(3 + 2, out ArraySegment <byte> _buffer);
            var buffer = (IList <byte>)_buffer;


            int atyp = buffer[3];

            switch (atyp)
            {
            case 1:     // IPv4 address, 4 bytes
                ReadAtLeast(3 + 1 + 4 + 2, OnRequestFullyReadCallback);
                break;

            case 3:     // domain name, length + str
                int len = buffer[4];
                ReadAtLeast(3 + 1 + 1 + len + 2, OnRequestFullyReadCallback);
                break;

            case 4:     // IPv6 address, 16 bytes
                ReadAtLeast(3 + 1 + 16 + 2, OnRequestFullyReadCallback);
                break;

            default:
                Debug.WriteLine("Unsupported ATYP=" + atyp);
                Stop();
                break;
            }
        }
        private void AuthMethodRecvCallback()
        {
            {
                // Parse client methods
                ClientFromBuffer.RequireRead(ClientFromBuffer.Size, out ArraySegment <byte> _buffer);
                var buffer = (IList <byte>)_buffer;
                if (buffer[0] != 0x5)
                {
                    ConnectionReject(true);
                    return;
                }

                int nMethods      = buffer[1];
                var authMethodLen = nMethods + 2;
                if (authMethodLen < buffer.Count)
                {
                    ReadAtLeast(authMethodLen, AuthMethodRecvCallback);
                    return;
                }

                var hasNoAuthRequired = false;
                var j = 0;
                for (var i = 2; j < nMethods; i++, j++)
                {
                    if (buffer[i] == 0x0)
                    {
                        hasNoAuthRequired = true;
                        break;
                    }
                }

                ClientFromBuffer.ConfirmRead(authMethodLen);

                if (!hasNoAuthRequired)
                {
                    ConnectionReject(false);
                    return;
                }
            }

            {
                // Select method
                ClientToBuffer.Clear();
                ClientToBuffer.RequireWrite(2, true, false, out ArraySegment <byte> _buffer);
                var buffer = (IList <byte>)_buffer;
                buffer[0] = 0x5;
                buffer[1] = 0x0;
                ClientToBuffer.ConfirmWrite(2);

                Send(ClientToBuffer.Size, AuthMethodSendCallback);
            }
        }
        private void OnRequestFullyReadCallback()
        {
            ClientFromBuffer.RequireRead(ClientFromBuffer.Size, out ArraySegment <byte> _buffer);
            var buffer = (IList <byte>)_buffer;

            int    cmd  = buffer[1];
            int    atyp = buffer[3];
            string dstAddr;
            int    dstPort;
            int    headerLen;

            switch (atyp)
            {
            case 1:     // IPv4 address, 4 bytes
                dstAddr   = new IPAddress(buffer.Skip(4).Take(4).ToArray()).ToString();
                dstPort   = (buffer[4 + 4] << 8) + buffer[4 + 4 + 1];
                headerLen = 4 + 4 + 2;
                break;

            case 3:     // domain name, length + str
                int len = buffer[4];
                dstAddr   = Encoding.UTF8.GetString(_buffer.Array, _buffer.Offset + 4 + 1, len);
                dstPort   = (buffer[4 + 1 + len] << 8) + buffer[4 + 1 + len + 1];
                headerLen = 4 + 1 + len + 2;

                break;

            case 4:     // IPv6 address, 16 bytes
                dstAddr   = $"[{new IPAddress(buffer.Skip(4).Take(16).ToArray())}]";
                dstPort   = (buffer[4 + 16] << 8) + buffer[4 + 16 + 1];
                headerLen = 4 + 16 + 2;

                break;

            default:
                Debug.WriteLine("Unsupported ATYP=" + atyp);
                Stop();
                return;
            }

            ClientFromBuffer.ConfirmRead(headerLen);

            Debug.WriteLine($"connect to {dstAddr}:{dstPort}");

            // Handle cmd
            switch (cmd)
            {
            case 1:
                Debug.WriteLine("CMD=" + cmd);
                _server.OnClientRequestConnect(dstAddr, dstPort);
                break;

            case 3:
                Debug.WriteLine("Unsupported CMD=" + cmd);
                Reply(ServerReply.CommandNotSupported, Stop);
                break;

            default:
                Debug.WriteLine("Unsupported CMD=" + cmd);
                Reply(ServerReply.CommandNotSupported, Stop);
                break;
            }
        }