#!perl
use warnings;
use strict;
use constant TIMEOUT_MULT => $ENV{PERL_TEST_TIME_OUT_FACTOR} || ($ENV{AUTOMATED_TESTING} ? 4 : 1);
use Test::More;
use lib 't'; use Utils;
use Socket qw(pack_sockaddr_in inet_aton);

use Feersum;

my $evh = Feersum->new_instance();

plan skip_all => "Feersum not compiled with TLS support"
    unless $evh->has_tls();

undef $evh; # done with feature check

my $cert_file = 'eg/ssl-proxy/server.crt';
my $key_file  = 'eg/ssl-proxy/server.key';

plan skip_all => "no test certificates ($cert_file / $key_file)"
    unless -f $cert_file && -f $key_file;

eval { require IO::Socket::SSL; 1 }
    or plan skip_all => "IO::Socket::SSL not installed";
plan skip_all => "OpenSSL too old for TLS 1.3 client" unless tls_client_ok();

eval { require IO::Socket::INET; 1 }
    or plan skip_all => "IO::Socket::INET not installed";

# ========================================
# Test 1: PROXY v1 + TLS + HTTP/1.1
# ========================================
{
    my ($socket, $port) = get_listen_socket();
    ok $socket, "got listen socket on port $port";

    my $evh1 = Feersum->new_instance();
    $evh1->set_proxy_protocol(1);
    $evh1->set_keepalive(0);
    $evh1->use_socket($socket);

    eval { $evh1->set_tls(cert_file => $cert_file, key_file => $key_file) };
    is $@, '', "set_tls with valid cert/key";

    my @received;
    $evh1->request_handler(sub {
        my $r = shift;
        my $env = $r->env();
        push @received, {
            remote_addr => $env->{REMOTE_ADDR},
            remote_port => $env->{REMOTE_PORT},
            scheme      => $env->{'psgi.url_scheme'},
            path        => $env->{PATH_INFO} || $env->{REQUEST_URI} || '/',
        };

        my $body = "addr=$env->{REMOTE_ADDR}";
        $r->send_response("200 OK", [
            'Content-Type'   => 'text/plain',
            'Content-Length' => length($body),
            'Connection'     => 'close',
        ], $body);
    });

    my $pid = fork();
    die "fork failed: $!" unless defined $pid;

    if ($pid == 0) {
        # Child: send PROXY v1 header as raw bytes, then upgrade to TLS
        select(undef, undef, undef, 0.3 * TIMEOUT_MULT);

        my $raw = IO::Socket::INET->new(
            PeerAddr => '127.0.0.1',
            PeerPort => $port,
            Proto    => 'tcp',
            Timeout  => 5 * TIMEOUT_MULT,
        );
        unless ($raw) {
            warn "TCP connect failed: $!\n";
            exit(1);
        }

        # Send PROXY v1 header on raw TCP before any TLS
        my $proxy_hdr = "PROXY TCP4 1.2.3.4 127.0.0.1 12345 $port\r\n";
        $raw->syswrite($proxy_hdr);

        # Now upgrade to TLS
        IO::Socket::SSL->start_SSL($raw,
            SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(),
        ) or do {
            warn "TLS upgrade failed: " . IO::Socket::SSL::errstr() . "\n";
            exit(2);
        };

        $raw->print(
            "GET /proxy-v1-test HTTP/1.1\r\n" .
            "Host: localhost:$port\r\n" .
            "Connection: close\r\n" .
            "\r\n"
        );

        my $response = '';
        while (defined(my $line = $raw->getline())) {
            $response .= $line;
        }
        $raw->close(SSL_no_shutdown => 1);

        if ($response =~ /200 OK/ && $response =~ /addr=1\.2\.3\.4/) {
            exit(0);
        } else {
            warn "PROXY v1 + TLS: unexpected response: $response\n";
            exit(3);
        }
    }

    # Parent: event loop
    my $cv = AE::cv;
    my $child_status;
    my $timeout = AE::timer(10 * TIMEOUT_MULT, 0, sub {
        diag "timeout waiting for PROXY v1 + TLS client";
        $cv->send('timeout');
    });
    my $child_w = AE::child($pid, sub {
        my ($pid, $status) = @_;
        $child_status = $status >> 8;
        $cv->send('child_done');
    });

    my $reason = $cv->recv;
    isnt $reason, 'timeout', "PROXY v1 + TLS: did not timeout";
    is $child_status, 0, "PROXY v1 + TLS: client got valid response";

    cmp_ok scalar(@received), '>=', 1,
        "PROXY v1 + TLS: server received request(s)";
    if (@received) {
        is $received[0]{remote_addr}, '1.2.3.4',
            "PROXY v1 + TLS: REMOTE_ADDR from PROXY header";
        is $received[0]{remote_port}, '12345',
            "PROXY v1 + TLS: REMOTE_PORT from PROXY header";
        is $received[0]{scheme}, 'https',
            "PROXY v1 + TLS: url_scheme is https (native TLS)";
        is $received[0]{path}, '/proxy-v1-test',
            "PROXY v1 + TLS: request path correct";
    }
}

# ========================================
# Test 2: PROXY v2 + TLS + HTTP/1.1
# ========================================
{
    my ($socket2, $port2) = get_listen_socket();
    ok $socket2, "got listen socket on port $port2";

    my $evh2 = Feersum->new_instance();
    $evh2->set_proxy_protocol(1);
    $evh2->set_keepalive(0);
    $evh2->use_socket($socket2);

    eval { $evh2->set_tls(cert_file => $cert_file, key_file => $key_file) };
    is $@, '', "set_tls for v2 test";

    my @received2;
    $evh2->request_handler(sub {
        my $r = shift;
        my $env = $r->env();
        push @received2, {
            remote_addr => $env->{REMOTE_ADDR},
            remote_port => $env->{REMOTE_PORT},
            scheme      => $env->{'psgi.url_scheme'},
            path        => $env->{PATH_INFO} || $env->{REQUEST_URI} || '/',
        };

        my $body = "addr=$env->{REMOTE_ADDR}";
        $r->send_response("200 OK", [
            'Content-Type'   => 'text/plain',
            'Content-Length' => length($body),
            'Connection'     => 'close',
        ], $body);
    });

    my $pid2 = fork();
    die "fork failed: $!" unless defined $pid2;

    if ($pid2 == 0) {
        select(undef, undef, undef, 0.3 * TIMEOUT_MULT);

        my $raw = IO::Socket::INET->new(
            PeerAddr => '127.0.0.1',
            PeerPort => $port2,
            Proto    => 'tcp',
            Timeout  => 5 * TIMEOUT_MULT,
        );
        unless ($raw) {
            warn "TCP connect failed: $!\n";
            exit(1);
        }

        # Send PROXY v2 binary header
        my $proxy_hdr = build_proxy_v2('PROXY', 'INET',
            '198.51.100.1', '198.51.100.2', 54321, 443);
        $raw->syswrite($proxy_hdr);

        # Upgrade to TLS
        IO::Socket::SSL->start_SSL($raw,
            SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(),
        ) or do {
            warn "TLS upgrade failed: " . IO::Socket::SSL::errstr() . "\n";
            exit(2);
        };

        $raw->print(
            "GET /proxy-v2-test HTTP/1.1\r\n" .
            "Host: localhost:$port2\r\n" .
            "Connection: close\r\n" .
            "\r\n"
        );

        my $response = '';
        while (defined(my $line = $raw->getline())) {
            $response .= $line;
        }
        $raw->close(SSL_no_shutdown => 1);

        if ($response =~ /200 OK/ && $response =~ /addr=198\.51\.100\.1/) {
            exit(0);
        } else {
            warn "PROXY v2 + TLS: unexpected response: $response\n";
            exit(3);
        }
    }

    my $cv2 = AE::cv;
    my $child_status2;
    my $timeout2 = AE::timer(10 * TIMEOUT_MULT, 0, sub {
        diag "timeout waiting for PROXY v2 + TLS client";
        $cv2->send('timeout');
    });
    my $child_w2 = AE::child($pid2, sub {
        my ($pid, $status) = @_;
        $child_status2 = $status >> 8;
        $cv2->send('child_done');
    });

    my $reason2 = $cv2->recv;
    isnt $reason2, 'timeout', "PROXY v2 + TLS: did not timeout";
    is $child_status2, 0, "PROXY v2 + TLS: client got valid response";

    cmp_ok scalar(@received2), '>=', 1,
        "PROXY v2 + TLS: server received request(s)";
    if (@received2) {
        is $received2[0]{remote_addr}, '198.51.100.1',
            "PROXY v2 + TLS: REMOTE_ADDR from PROXY header";
        is $received2[0]{remote_port}, '54321',
            "PROXY v2 + TLS: REMOTE_PORT from PROXY header";
        is $received2[0]{scheme}, 'https',
            "PROXY v2 + TLS: url_scheme is https";
        is $received2[0]{path}, '/proxy-v2-test',
            "PROXY v2 + TLS: request path correct";
    }
}

# ========================================
# Test 3: TLS without PROXY (proxy_protocol off) still works
# ========================================
{
    my ($socket3, $port3) = get_listen_socket();
    ok $socket3, "got listen socket on port $port3";

    my $evh3 = Feersum->new_instance();
    $evh3->set_proxy_protocol(0);
    $evh3->set_keepalive(0);
    $evh3->use_socket($socket3);

    eval { $evh3->set_tls(cert_file => $cert_file, key_file => $key_file) };
    is $@, '', "set_tls for plain TLS test";

    my @received3;
    $evh3->request_handler(sub {
        my $r = shift;
        my $env = $r->env();
        push @received3, {
            scheme => $env->{'psgi.url_scheme'},
            path   => $env->{PATH_INFO} || $env->{REQUEST_URI} || '/',
        };
        my $body = "ok";
        $r->send_response("200 OK", [
            'Content-Type'   => 'text/plain',
            'Content-Length' => length($body),
            'Connection'     => 'close',
        ], $body);
    });

    my $pid3 = fork();
    die "fork failed: $!" unless defined $pid3;

    if ($pid3 == 0) {
        select(undef, undef, undef, 0.3 * TIMEOUT_MULT);

        my $client = IO::Socket::SSL->new(
            PeerAddr        => '127.0.0.1',
            PeerPort        => $port3,
            SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(),
            Timeout         => 5 * TIMEOUT_MULT,
        );
        unless ($client) {
            warn "TLS connect failed: " . IO::Socket::SSL::errstr() . "\n";
            exit(1);
        }

        $client->print(
            "GET /no-proxy HTTP/1.1\r\n" .
            "Host: localhost:$port3\r\n" .
            "Connection: close\r\n" .
            "\r\n"
        );

        my $response = '';
        while (defined(my $line = $client->getline())) {
            $response .= $line;
        }
        $client->close(SSL_no_shutdown => 1);

        exit($response =~ /200 OK/ ? 0 : 1);
    }

    my $cv3 = AE::cv;
    my $child_status3;
    my $timeout3 = AE::timer(10 * TIMEOUT_MULT, 0, sub {
        diag "timeout waiting for plain TLS client";
        $cv3->send('timeout');
    });
    my $child_w3 = AE::child($pid3, sub {
        my ($pid, $status) = @_;
        $child_status3 = $status >> 8;
        $cv3->send('child_done');
    });

    my $reason3 = $cv3->recv;
    isnt $reason3, 'timeout', "plain TLS: did not timeout";
    is $child_status3, 0, "plain TLS: still works without proxy_protocol";

    if (@received3) {
        is $received3[0]{scheme}, 'https', "plain TLS: url_scheme is https";
        is $received3[0]{path}, '/no-proxy', "plain TLS: request path correct";
    }
}

# ========================================
# Test 4: Invalid PROXY header on TLS listener closes connection
# ========================================
{
    my ($socket4, $port4) = get_listen_socket();
    ok $socket4, "got listen socket on port $port4";

    my $evh4 = Feersum->new_instance();
    $evh4->set_proxy_protocol(1);
    $evh4->set_keepalive(0);
    $evh4->use_socket($socket4);

    eval { $evh4->set_tls(cert_file => $cert_file, key_file => $key_file) };
    is $@, '', "set_tls for invalid proxy test";

    $evh4->request_handler(sub {
        my $r = shift;
        $r->send_response("200 OK", [
            'Content-Type'   => 'text/plain',
            'Content-Length' => 2,
        ], 'ok');
    });

    my $pid4 = fork();
    die "fork failed: $!" unless defined $pid4;

    if ($pid4 == 0) {
        select(undef, undef, undef, 0.3 * TIMEOUT_MULT);

        my $raw = IO::Socket::INET->new(
            PeerAddr => '127.0.0.1',
            PeerPort => $port4,
            Proto    => 'tcp',
            Timeout  => 5 * TIMEOUT_MULT,
        );
        unless ($raw) {
            warn "TCP connect failed: $!\n";
            exit(1);
        }

        # Send garbage that is neither PROXY v1 nor v2
        $raw->syswrite("GARBAGE not a proxy header\r\n");

        # Try to read - connection should be closed by server
        my $buf;
        my $n = $raw->sysread($buf, 4096);
        $raw->close();

        # Server should close the connection (n == 0 or undef)
        exit(!defined($n) || $n == 0 ? 0 : 1);
    }

    my $cv4 = AE::cv;
    my $child_status4;
    my $timeout4 = AE::timer(10 * TIMEOUT_MULT, 0, sub {
        diag "timeout waiting for invalid proxy test";
        $cv4->send('timeout');
    });
    my $child_w4 = AE::child($pid4, sub {
        my ($pid, $status) = @_;
        $child_status4 = $status >> 8;
        $cv4->send('child_done');
    });

    my $reason4 = $cv4->recv;
    isnt $reason4, 'timeout', "invalid PROXY + TLS: did not timeout";
    is $child_status4, 0, "invalid PROXY + TLS: connection closed by server";
}

# ========================================
# Test 5: Fragmented PROXY v1 header + TLS
# Send PROXY header in small chunks to exercise the buffering path
# ========================================
{
    my ($socket5, $port5) = get_listen_socket();
    ok $socket5, "got listen socket on port $port5";

    my $evh5 = Feersum->new_instance();
    $evh5->set_proxy_protocol(1);
    $evh5->set_keepalive(0);
    $evh5->header_timeout(15 * TIMEOUT_MULT);
    $evh5->use_socket($socket5);

    eval { $evh5->set_tls(cert_file => $cert_file, key_file => $key_file) };
    is $@, '', "set_tls for fragmented proxy test";

    my @received5;
    $evh5->request_handler(sub {
        my $r = shift;
        my $env = $r->env();
        push @received5, {
            remote_addr => $env->{REMOTE_ADDR},
            remote_port => $env->{REMOTE_PORT},
        };
        my $body = "frag-ok";
        $r->send_response("200 OK", [
            'Content-Type'   => 'text/plain',
            'Content-Length' => length($body),
            'Connection'     => 'close',
        ], $body);
    });

    my $pid5 = fork();
    die "fork failed: $!" unless defined $pid5;

    if ($pid5 == 0) {
        select(undef, undef, undef, 0.3 * TIMEOUT_MULT);

        my $raw = IO::Socket::INET->new(
            PeerAddr => '127.0.0.1',
            PeerPort => $port5,
            Proto    => 'tcp',
            Timeout  => 5 * TIMEOUT_MULT,
        );
        unless ($raw) {
            warn "TCP connect failed: $!\n";
            exit(1);
        }

        # Send PROXY v1 header in small fragments
        my $proxy_hdr = "PROXY TCP4 10.0.0.1 10.0.0.2 11111 $port5\r\n";
        for my $i (0 .. length($proxy_hdr) - 1) {
            $raw->syswrite(substr($proxy_hdr, $i, 1));
            select(undef, undef, undef, 0.01 * TIMEOUT_MULT);
        }

        # Now upgrade to TLS
        IO::Socket::SSL->start_SSL($raw,
            SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(),
        ) or do {
            warn "TLS upgrade failed: " . IO::Socket::SSL::errstr() . "\n";
            exit(2);
        };

        $raw->print(
            "GET /frag-test HTTP/1.1\r\n" .
            "Host: localhost:$port5\r\n" .
            "Connection: close\r\n" .
            "\r\n"
        );

        my $response = '';
        while (defined(my $line = $raw->getline())) {
            $response .= $line;
        }
        $raw->close(SSL_no_shutdown => 1);

        if ($response =~ /200 OK/ && $response =~ /frag-ok/) {
            exit(0);
        } else {
            warn "fragmented PROXY + TLS: unexpected response: $response\n";
            exit(3);
        }
    }

    my $cv5 = AE::cv;
    my $child_status5;
    my $timeout5 = AE::timer(15 * TIMEOUT_MULT, 0, sub {
        diag "timeout waiting for fragmented PROXY + TLS client";
        $cv5->send('timeout');
    });
    my $child_w5 = AE::child($pid5, sub {
        my ($pid, $status) = @_;
        $child_status5 = $status >> 8;
        $cv5->send('child_done');
    });

    my $reason5 = $cv5->recv;
    isnt $reason5, 'timeout', "fragmented PROXY + TLS: did not timeout";
    is $child_status5, 0, "fragmented PROXY + TLS: client got valid response";

    if (@received5) {
        is $received5[0]{remote_addr}, '10.0.0.1',
            "fragmented PROXY + TLS: REMOTE_ADDR correct";
        is $received5[0]{remote_port}, '11111',
            "fragmented PROXY + TLS: REMOTE_PORT correct";
    }
}

# ========================================
# Test 6: PROXY v1 + TLS + keepalive (multiple requests)
# ========================================
{
    my ($socket6, $port6) = get_listen_socket();
    ok $socket6, "got listen socket on port $port6";

    my $evh6 = Feersum->new_instance();
    $evh6->set_proxy_protocol(1);
    $evh6->set_keepalive(1);
    $evh6->use_socket($socket6);

    eval { $evh6->set_tls(cert_file => $cert_file, key_file => $key_file) };
    is $@, '', "set_tls for keepalive test";

    my @received6;
    $evh6->request_handler(sub {
        my $r = shift;
        my $env = $r->env();
        push @received6, {
            remote_addr => $env->{REMOTE_ADDR},
            path        => $env->{PATH_INFO} || $env->{REQUEST_URI} || '/',
        };
        my $body = "req=" . scalar(@received6);
        $r->send_response("200 OK", [
            'Content-Type'   => 'text/plain',
            'Content-Length' => length($body),
        ], $body);
    });

    my $pid6 = fork();
    die "fork failed: $!" unless defined $pid6;

    if ($pid6 == 0) {
        select(undef, undef, undef, 0.3 * TIMEOUT_MULT);

        my $raw = IO::Socket::INET->new(
            PeerAddr => '127.0.0.1',
            PeerPort => $port6,
            Proto    => 'tcp',
            Timeout  => 5 * TIMEOUT_MULT,
        );
        unless ($raw) {
            warn "TCP connect failed: $!\n";
            exit(1);
        }

        # Send PROXY v1 header
        my $proxy_hdr = "PROXY TCP4 172.16.0.1 172.16.0.2 22222 $port6\r\n";
        $raw->syswrite($proxy_hdr);

        # Upgrade to TLS
        IO::Socket::SSL->start_SSL($raw,
            SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(),
        ) or do {
            warn "TLS upgrade failed: " . IO::Socket::SSL::errstr() . "\n";
            exit(2);
        };

        my $ok_count = 0;

        # Send 3 requests on the same keepalive connection
        for my $i (1..3) {
            my $conn_hdr = ($i < 3) ? 'keep-alive' : 'close';
            $raw->print(
                "GET /ka-req-$i HTTP/1.1\r\n" .
                "Host: localhost:$port6\r\n" .
                "Connection: $conn_hdr\r\n" .
                "\r\n"
            );

            # Read response headers
            my $response = '';
            while (defined(my $line = $raw->getline())) {
                $response .= $line;
                last if $response =~ /\r\n\r\n/;
            }

            # Extract Content-Length and read body
            if ($response =~ /Content-Length:\s*(\d+)/i) {
                my $cl = $1;
                my $body = '';
                $raw->read($body, $cl);
                $response .= $body;
            }

            if ($response =~ /200 OK/) {
                $ok_count++;
            } else {
                warn "keepalive request $i: unexpected response: $response\n";
            }
        }
        $raw->close(SSL_no_shutdown => 1);

        exit($ok_count == 3 ? 0 : 4);
    }

    my $cv6 = AE::cv;
    my $child_status6;
    my $timeout6 = AE::timer(10 * TIMEOUT_MULT, 0, sub {
        diag "timeout waiting for keepalive PROXY + TLS client";
        $cv6->send('timeout');
    });
    my $child_w6 = AE::child($pid6, sub {
        my ($pid, $status) = @_;
        $child_status6 = $status >> 8;
        $cv6->send('child_done');
    });

    my $reason6 = $cv6->recv;
    isnt $reason6, 'timeout', "PROXY + TLS + keepalive: did not timeout";
    is $child_status6, 0, "PROXY + TLS + keepalive: all 3 requests succeeded";

    cmp_ok scalar(@received6), '==', 3,
        "PROXY + TLS + keepalive: server received 3 requests";
    if (@received6 >= 3) {
        is $received6[0]{remote_addr}, '172.16.0.1',
            "PROXY + TLS + keepalive: request 1 has PROXY addr";
        is $received6[1]{remote_addr}, '172.16.0.1',
            "PROXY + TLS + keepalive: request 2 retains PROXY addr";
        is $received6[2]{remote_addr}, '172.16.0.1',
            "PROXY + TLS + keepalive: request 3 retains PROXY addr";
        is $received6[0]{path}, '/ka-req-1', "keepalive req 1 path";
        is $received6[1]{path}, '/ka-req-2', "keepalive req 2 path";
        is $received6[2]{path}, '/ka-req-3', "keepalive req 3 path";
    }
}

# ========================================
# Test 7: Fragmented PROXY v2 header + TLS
# Send binary PROXY v2 header in small chunks to exercise buffering
# ========================================
{
    my ($socket7, $port7) = get_listen_socket();
    ok $socket7, "got listen socket on port $port7";

    my $evh7 = Feersum->new_instance();
    $evh7->set_proxy_protocol(1);
    $evh7->set_keepalive(0);
    $evh7->header_timeout(15 * TIMEOUT_MULT);
    $evh7->use_socket($socket7);

    eval { $evh7->set_tls(cert_file => $cert_file, key_file => $key_file) };
    is $@, '', "set_tls for fragmented v2 test";

    my @received7;
    $evh7->request_handler(sub {
        my $r = shift;
        my $env = $r->env();
        push @received7, {
            remote_addr => $env->{REMOTE_ADDR},
            remote_port => $env->{REMOTE_PORT},
        };
        my $body = "frag-v2-ok";
        $r->send_response("200 OK", [
            'Content-Type'   => 'text/plain',
            'Content-Length' => length($body),
            'Connection'     => 'close',
        ], $body);
    });

    my $pid7 = fork();
    die "fork failed: $!" unless defined $pid7;

    if ($pid7 == 0) {
        select(undef, undef, undef, 0.3 * TIMEOUT_MULT);

        my $raw = IO::Socket::INET->new(
            PeerAddr => '127.0.0.1',
            PeerPort => $port7,
            Proto    => 'tcp',
            Timeout  => 5 * TIMEOUT_MULT,
        );
        unless ($raw) {
            warn "TCP connect failed: $!\n";
            exit(1);
        }

        # Build a PROXY v2 binary header
        my $proxy_hdr = build_proxy_v2('PROXY', 'INET',
            '203.0.113.5', '203.0.113.6', 33333, 443);

        # Send in 4-byte fragments with small delays
        my $off = 0;
        while ($off < length($proxy_hdr)) {
            my $chunk = substr($proxy_hdr, $off, 4);
            $raw->syswrite($chunk);
            $off += 4;
            select(undef, undef, undef, 0.01 * TIMEOUT_MULT);
        }

        # Now upgrade to TLS
        IO::Socket::SSL->start_SSL($raw,
            SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(),
        ) or do {
            warn "TLS upgrade failed: " . IO::Socket::SSL::errstr() . "\n";
            exit(2);
        };

        $raw->print(
            "GET /frag-v2-test HTTP/1.1\r\n" .
            "Host: localhost:$port7\r\n" .
            "Connection: close\r\n" .
            "\r\n"
        );

        my $response = '';
        while (defined(my $line = $raw->getline())) {
            $response .= $line;
        }
        $raw->close(SSL_no_shutdown => 1);

        if ($response =~ /200 OK/ && $response =~ /frag-v2-ok/) {
            exit(0);
        } else {
            warn "fragmented PROXY v2 + TLS: unexpected response: $response\n";
            exit(3);
        }
    }

    my $cv7 = AE::cv;
    my $child_status7;
    my $timeout7 = AE::timer(15 * TIMEOUT_MULT, 0, sub {
        diag "timeout waiting for fragmented PROXY v2 + TLS client";
        $cv7->send('timeout');
    });
    my $child_w7 = AE::child($pid7, sub {
        my ($pid, $status) = @_;
        $child_status7 = $status >> 8;
        $cv7->send('child_done');
    });

    my $reason7 = $cv7->recv;
    isnt $reason7, 'timeout', "fragmented PROXY v2 + TLS: did not timeout";
    is $child_status7, 0, "fragmented PROXY v2 + TLS: client got valid response";

    if (@received7) {
        is $received7[0]{remote_addr}, '203.0.113.5',
            "fragmented PROXY v2 + TLS: REMOTE_ADDR correct";
        is $received7[0]{remote_port}, '33333',
            "fragmented PROXY v2 + TLS: REMOTE_PORT correct";
    }
}

done_testing;
