/*
         # shadowsocks UDP Request (before encrypted)
         # +------+----------+----------+----------+
         # | ATYP | DST.ADDR | DST.PORT |   DATA   |
         # +------+----------+----------+----------+
         # |  1   | Variable |    2     | Variable |
         # +------+----------+----------+----------+
         #
         # shadowsocks UDP Response (before encrypted)
         # +------+----------+----------+----------+
         # | ATYP | DST.ADDR | DST.PORT |   DATA   |
         # +------+----------+----------+----------+
         # |  1   | Variable |    2     | Variable |
         # +------+----------+----------+----------+
         */
        public async Task HandleUdp(IClient client, CancellationToken cancellationToken)
        {
            if (null == client)
            {
                return;
            }


            await HandleClient(client, 1500,
                               (pipe) =>
            {
                var cipher = _remoteServerConfig.CreateCipher(_logger);
                IClientFilter cipherFilter = new Cipher.UdpCipherFilter(cipher, _logger);
                pipe.AddFilter(client, cipherFilter);
            },
                               async (targetIPEndPoint) =>
            {
                return(await UdpClient1.ConnectAsync(targetIPEndPoint, _logger));
            },
                               async (request, pipe, targetClient, targetSsAddr) =>
            {
                pipe.ClientB = targetClient;
                IClientFilter filterTarget1 = new UdpRelayEncapsulationFilter(_logger);
                pipe.AddFilter(targetClient, filterTarget1);

                _logger?.LogInformation($"Writing payload before piping...");
                await pipe.GetWriter(targetClient).Write(request.SignificantMemory, cancellationToken);
                request.Dispose();
            },
                               cancellationToken);

            await Task.CompletedTask;
        }
        public async Task HandleUdp(IClient client, CancellationToken cancellationToken)
        {
            if (null == client)
            {
                return;
            }

            //authentication //TODO udp assoc

            var server = _serverLoader.Load(null);

            if (null == server)
            {
                _logger?.LogInformation($"proxy server not found.");
                client.Close();
                return;
            }
            var serverAddr = await server.GetIPEndPoint();

            if (null == serverAddr)
            {
                _logger?.LogInformation($"unable to get proxy server address.");
                client.Close();
                return;
            }


            var relayClient = await UdpClient1.ConnectAsync(serverAddr, _logger);

            if (null == relayClient)
            {
                _logger?.LogInformation($"unable to relay udp");
                client.Close();
                return;
            }

            DuplexPipe    pipe    = new DuplexPipe(client, relayClient, 1500, _logger);
            IClientFilter filter  = new Cipher.UdpCipherFilter(server.CreateCipher(_logger), _logger);
            IClientFilter filter2 = new UdpEncapsulationFilter(_logger);

            pipe.AddFilter(relayClient, filter)
            .AddFilter(relayClient, filter2);

            PipeClient(pipe, cancellationToken);
        }
        public async Task HandleUdp(IClient client, CancellationToken cancellationToken)
        {
            if (null == client)
            {
                return;
            }

            //authentication //TODO udp assoc

            var server = _serverLoader.Load(null);

            if (null == server)
            {
                _logger?.LogInformation($"proxy server not found.");
                client.Close();
                return;
            }
            var serverAddr = await server.GetIPEndPoint();

            if (null == serverAddr)
            {
                _logger?.LogInformation($"unable to get proxy server address.");
                client.Close();
                return;
            }


            var relayClient = await UdpClient1.ConnectAsync(serverAddr, _logger);

            if (null == relayClient)
            {
                _logger?.LogInformation($"unable to relay udp");
                client.Close();
                return;
            }
            PipeUdp(client, relayClient, server.CreateCipher(_logger), cancellationToken);
        }
        /*
         *
         # shadowsocks UDP Request (before encrypted)
         # +------+----------+----------+----------+
         # | ATYP | DST.ADDR | DST.PORT |   DATA   |
         # +------+----------+----------+----------+
         # |  1   | Variable |    2     | Variable |
         # +------+----------+----------+----------+
         #
         # shadowsocks UDP Response (before encrypted)
         # +------+----------+----------+----------+
         # | ATYP | DST.ADDR | DST.PORT |   DATA   |
         # +------+----------+----------+----------+
         # |  1   | Variable |    2     | Variable |
         # +------+----------+----------+----------+
         */
        public async Task HandleUdp(IClient client, CancellationToken cancellationToken = default)
        {
            if (null == client)
            {
                return;
            }
            using (SmartBuffer localRequestCipher = SmartBuffer.Rent(1500))
            {
                localRequestCipher.SignificantLength = await client.ReadAsync(localRequestCipher.Memory, cancellationToken);//A. read a packet

                if (0 == localRequestCipher.SignificantLength)
                {
                    _logger?.LogWarning($"HandleUdp an empty udp packet received, client=[{client.EndPoint.ToString()}]");
                    client.Close();
                    return;
                }
                var cipher = _remoteServerConfig.CreateCipher(_logger);
                using (var localReqest = cipher.DecryptUdp(localRequestCipher.SignificanMemory)) //B. decrypt
                {
                    if (null == localReqest || 0 == localReqest.SignificantLength)               //decrypt failed, available options: 1.leave it. 2.close connection. 3.add to blocklist.
                    {
                        _logger?.LogWarning($"HandleUdp decrypt failed, client=[{client.EndPoint.ToString()}]");
                        client.Close();//->local pipe broken-> local pipe close.
                        return;
                    }
                    IPAddress targetIP = IPAddress.Any;                                                             //TODO target address check
                    if (ShadowsocksAddress.TryResolve(localReqest.SignificanMemory, out ShadowsocksAddress ssaddr)) //C. resolve target address
                    {
                        if (0x3 == ssaddr.ATYP)                                                                     //a domain name
                        {
                            var ips = await _dnsCache.ResolveHost(Encoding.UTF8.GetString(ssaddr.Address.ToArray()));

                            if (ips != null && ips.Length > 0)
                            {
                                targetIP = ips[0];
                            }
                        }
                        else
                        {
                            targetIP = new IPAddress(ssaddr.Address.Span);
                        }
                        if (IPAddress.Any != targetIP)//D. target addr resolved.
                        {
                            IPEndPoint targetEndPoint = new IPEndPoint(targetIP, ssaddr.Port);
                            var        targetClient   = await UdpClient1.ConnectAsync(targetEndPoint, _logger);//E. connect to target

                            if (null == targetClient)
                            {
                                _logger?.LogInformation($"HandleUdp unable to connect target [{targetEndPoint.ToString()}]. client=[{client.EndPoint.ToString()}]");
                                client.Close();
                                return;
                            }
                            await targetClient.WriteAsync(localReqest.Memory.Slice(ssaddr.RawMemory.Length), cancellationToken); //F. send payload .

                            PipeUdp(client, targetClient, cipher, cancellationToken);                                            //G. piping
                        }
                        else//resolve target address failed.
                        {
                            _logger?.LogWarning($"HandleUdp invalid target addr. client=[{client.EndPoint.ToString()}]");
                            client.Close();
                            return;
                        }
                    }
                    else
                    {
                        _logger?.LogWarning($"HandleUdp resolve target addr failed. client=[{client.EndPoint.ToString()}]");
                        client.Close();
                        return;
                    }
                }
            }//end using
        }