Net-QUIC

 view release on metacpan or  search on metacpan

t/10-shared-server-tls.t  view on Meta::CPAN

use strict;
use warnings;

use File::Temp qw(tempdir);
use FindBin ();
use Socket qw(inet_aton pack_sockaddr_in);
use Test2::V0;
use Time::HiRes qw(sleep);

use Net::QUIC::Endpoint;

my $server_local = pack_sockaddr_in(4440, inet_aton('127.0.0.1'));
my @client_local = (
    pack_sockaddr_in(40009, inet_aton('127.0.0.1')),
    pack_sockaddr_in(40010, inet_aton('127.0.0.1')),
);
my $alpn = 'net-quic-shared-server-tls-test';
my $source_cert = "$FindBin::Bin/data/server-cert.pem";
my $source_key = "$FindBin::Bin/data/server-key.pem";

sub copy_file {
    my ($source, $dest) = @_;

    open my $in, '<:raw', $source
        or die "cannot open $source: $!";
    open my $out, '>:raw', $dest
        or die "cannot create $dest: $!";

    while (1) {
        my $read = read($in, my $buf, 8192);
        die "cannot read $source: $!"
            if !defined $read;
        last if !$read;

        print {$out} $buf
            or die "cannot write $dest: $!";
    }

    close $out or die "cannot close $dest: $!";
    close $in or die "cannot close $source: $!";
    return;
}

my $dir = tempdir(CLEANUP => 1);
my $cert_file = "$dir/server-cert.pem";
my $key_file = "$dir/server-key.pem";

copy_file($source_cert, $cert_file);
copy_file($source_key, $key_file);

my $server = Net::QUIC::Endpoint->server(
    alpn             => $alpn,
    certificate_file => $cert_file,
    private_key_file => $key_file,
);

ok(unlink($cert_file), 'certificate file can be removed after server construction');
ok(unlink($key_file), 'private key file can be removed after server construction');

my @client = map {
    Net::QUIC::Endpoint->client(
        local       => $_,
        peer        => $server_local,
        alpn        => $alpn,
        server_name => 'localhost',
        ca_file     => $source_cert,
    )
} @client_local;

my %client_for_peer = map {
    $client_local[$_] => $client[$_]
} 0 .. $#client;

sub pump_all {
    my $progress = 0;

    while (my $datagram = $server->next_datagram) {
        ++$progress;
        my $client = $client_for_peer{$datagram->peer};
        die "server produced datagram for unknown client"
            if !defined $client;

        $client->receive_datagram(
            $datagram->data,
            $datagram->peer,
            $server_local,
        );
    }

    for my $i (0 .. $#client) {
        while (my $datagram = $client[$i]->next_datagram) {
            ++$progress;
            $server->receive_datagram(
                $datagram->data,
                $server_local,
                $client_local[$i],
            );
        }
    }

    my @timeouts;

    my $server_after = $server->timeout_after;
    push @timeouts, [$server_after, sub { $server->handle_timeout }]
        if defined $server_after;

    for my $client (@client) {
        my $after = $client->timeout_after;
        push @timeouts, [$after, sub { $client->handle_timeout }]
            if defined $after;
    }

    for my $timer (@timeouts) {
        if ($timer->[0] <= 0) {
            ++$progress;
            $timer->[1]->();
        }
    }

    if (!$progress) {
        my @positive = sort { $a <=> $b }
            map { $_->[0] }
            grep { $_->[0] > 0 } @timeouts;

        if (@positive) {
            my $nap = $positive[0] > 0.01 ? 0.01 : $positive[0] + 0.001;
            sleep($nap);
            ++$progress;
        }
    }

    return $progress;
}

for my $i (0 .. $#client) {
    my $initial = $client[$i]->next_datagram;
    ok(defined($initial), "client $i produces Initial");

    $server->receive_datagram(
        $initial->data,
        $server_local,
        $client_local[$i],
    );
}

my @accepted;
while (my $connection = $server->next_connection) {
    push @accepted, $connection;
}

is(scalar(@accepted), 2, 'server accepts two connections after credential files are gone');

for (1 .. 500) {
    last if $client[0]->connection->ready
        && $client[1]->connection->ready
        && $accepted[0]->ready
        && $accepted[1]->ready;

    last if !pump_all();
}

ok($client[0]->connection->ready, 'first client handshake completes');
ok($client[1]->connection->ready, 'second client handshake completes');
ok($accepted[0]->ready, 'first server connection uses shared TLS context');
ok($accepted[1]->ready, 'second server connection uses shared TLS context');

like(
    dies {
        Net::QUIC::Endpoint->server(
            alpn             => $alpn,
            certificate_file => "$dir/missing-cert.pem",
            private_key_file => "$dir/missing-key.pem",
        );
    },
    qr/unable to load Picotls server certificate/,
    'missing certificate fails when Endpoint is constructed',
);

my $valid_cert = "$dir/valid-cert.pem";
copy_file($source_cert, $valid_cert);

like(
    dies {
        Net::QUIC::Endpoint->server(
            alpn             => $alpn,
            certificate_file => $valid_cert,
            private_key_file => "$dir/missing-key.pem",
        );
    },
    qr/unable to open Picotls server private key/,
    'missing private key fails when Endpoint is constructed',
);

done_testing;



( run in 2.829 seconds using v1.01-cache-2.11-cpan-036bef1c656 )