#!/usr/bin/perl

use strict;
use warnings;
use File::Basename;
use lib dirname (__FILE__);
use TestUtils;
use TestHTTPD;
use File::Temp;
use IO::Socket::INET;

my $bad_requests = [
    pack("C*", 0xff, 0xff, 0xff, 0xff, 0xff),
    pack("C*", 0x16,
               0x04, 0x10,
               0x00, 0x00),
    pack("C*", 0x16,
               0x03, 0x00,
               0x00, 0x7f,
                   0x01,
                   0x00, 0x00, 0x7b,
                   0x03, 0x00,
                   0x53, 0x11, 0x25, 0xc2, 0x92, 0xd6, 0xca, 0xf1,
                   0x79, 0x90, 0xba, 0x38, 0x8f, 0xad, 0xc8, 0x13,
                   0xa3, 0x1b, 0x57, 0xd9, 0xf4, 0x3e, 0xd2, 0x8b,
                   0xb6, 0x5e, 0xe3, 0x12, 0xca, 0x81, 0x2f, 0xc5,
                   0x00,
                   0x00, 0x54,
                       0xc0, 0x14,
                       0xc0, 0x0a,
                       0xc0, 0x22,
                       0xc0, 0x21,
                       0x00, 0x39,
                       0x00, 0x38,
                       0xc0, 0x0f,
                       0xc0, 0x05,
                       0x00, 0x35,
                       0xc0, 0x12,
                       0xc0, 0x08,
                       0xc0, 0x1c,
                       0xc0, 0x1b,
                       0x00, 0x16,
                       0x00, 0x13,
                       0xc0, 0x0d,
                       0xc0, 0x03,
                       0x00, 0x0a,
                       0xc0, 0x13,
                       0xc0, 0x09,
                       0xc0, 0x1f,
                       0xc0, 0x1e,
                       0x00, 0x33,
                       0x00, 0x32,
                       0xc0, 0x0e,
                       0xc0, 0x04,
                       0x00, 0x2f,
                       0xc0, 0x11,
                       0xc0, 0x07,
                       0xc0, 0x0c,
                       0xc0, 0x02,
                       0x00, 0x05,
                       0x00, 0x04,
                       0x00, 0x15,
                       0x00, 0x12,
                       0x00, 0x09,
                       0x00, 0x14,
                       0x00, 0x11,
                       0x00, 0x08,
                       0x00, 0x06,
                       0x00, 0x03,
                       0x00, 0xff,
                   0x01,
                       0x00),
    pack("C*", 0x16,
               0x03, 0x01,
               0x00, 0x48,
                   # Handshake
                   0x01,
                   0x00, 0x00, 0x42,
                   0x03, 0x03,
                   # Random
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0x00,
                   0x00, 0x04,
                       0x00, 0x01,
                       0x00, 0xff,
                   0x01,
                       0x00,
                   0x01, 0x17,
                       # Extension
                       0x00, 0x00,
                       0x00, 0x0e,
                       0x00, 0x0c,
                           0x00,
                           0x00, 0x09,
                           0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74,
                       0x00, 0x0f,
                       0x00, 0x01,
                       0x01),
    pack("C*", 0x16, # Content Type: Handshake
               0x03, 0x01, # Version: TLS 1.0
               0x00, 0x48, # Length
                   # Handshake
                   0x01, # Handshake Type: Client Hello
                   0x00, 0x00, 0x42, # Length
                   0x03, 0x03, # Version: TLS 1.2
                   # Random
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0x00, # Session ID Length
                   0x00, 0x04, # Cipher Suites Length
                       0x00, 0x01, # NULL-MD5
                       0x00, 0xff, # RENEGOTIATION INFO SCSV
                   0x01, # Compression Methods
                       0x00, # NULL
                   0x00, 0x17, # Extensions Length
                       # Extension
                       0x00, 0x00, # Extension Type: Server Name
                       0x00, 0x0e, # Length
                       0x00, 0x0c, # Server Name Indication Length
                           0x00, # Server Name Type: host_name
                           0x00, 0x09, # Length
                           # "local\0ost"
                           0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, 0x6f, 0x73, 0x74,
                       # Extension
                       0x00, 0x0f, # Extension Type: Heart Beat
                       0x00, 0x01, # Length
                       0x01 # Mode: Peer allows to send requests
                       ),
    pack("C*", 0x16, # Content Type: Handshake
               0x03, 0x01, # Version: TLS 1.0
               0x00, 0x3a, # Length
                   # Handshake
                   0x01, # Handshake Type: Client Hello
                   0x00, 0x00, 0x34, # Length
                   0x03, 0x03, # Version: TLS 1.2
                   # Random
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0x00, # Session ID Length
                   0x00, 0x04, # Cipher Suites Length
                       0x00, 0x01, # NULL-MD5
                       0x00, 0xff, # RENEGOTIATION INFO SCSV
                   0x01, # Compression Methods
                       0x00, # NULL
                   0x00, 0x09, # Extensions Length
                       # Extension
                       0x00, 0x00, # Extension Type: Server Name
                       0x00, 0x05, # Length
                       0x00, 0x03, # Server Name Indication Length
                           0x00, # Server Name Type: host_name
                           0x00, 0x00 # Length
                       ),
    pack("C*", 0x16, # Content Type: Handshake
               0x03, 0x01, # Version: TLS 1.0
               0x00, 0x3f, # Length
                   # Handshake
                   0x01, # Handshake Type: Client Hello
                   0x00, 0x00, 0x39, # Length
                   0x03, 0x03, # Version: TLS 1.2
                   # Random
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0x00, # Session ID Length
                   0x00, 0x04, # Cipher Suites Length
                       0x00, 0x01, # NULL-MD5
                       0x00, 0xff, # RENEGOTIATION INFO SCSV
                   0x01, # Compression Methods
                       0x00, # NULL
                   0x00, 0x0e, # Extensions Length
                       # Extension
                       0x00, 0x00, # Extension Type: Server Name
                       0x00, 0x0e, # Length
                       0x00, 0x03, # Server Name Indication Length
                           0x00, # Server Name Type: host_name
                           0x00, 0x00, # Length
                       # Extension
                       0x00, 0x0f, # Extension Type: Heart Beat
                       0x00, 0x01, # Length
                       0x01 # Mode: Peer allows to send requests
                       ),
    pack("C*", 0x16, # Content Type: Handshake
               0x03, 0x01, # Version: TLS 1.0
               0x00, 0x48, # Length
                   # Handshake
                   0x01, # Handshake Type: Client Hello
                   0x00, 0x00, 0x42, # Length
                   0x03, 0x03, # Version: TLS 1.2
                   # Random
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0x00, # Session ID Length
                   0x00, 0x04, # Cipher Suites Length
                       0x00, 0x01, # NULL-MD5
                       0x00, 0xff, # RENEGOTIATION INFO SCSV
                   0x01, # Compression Methods
                       0x00, # NULL
                   0x00, 0x17, # Extensions Length
                       # Extension
                       0x00, 0x00, # Extension Type: Server Name
                       0x00, 0x0e, # Length
                       0x00, 0x0c, # Server Name Indication Length
                           0x01, # Server Name Type: host_name
                           0x00, 0x09, # Length
                           # "localhost"
                           0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74,
                       # Extension
                       0x00, 0x0f, # Extension Type: Heart Beat
                       0x00, 0x01, # Length
                       0x01 # Mode: Peer allows to send requests
                       ),
    pack("C*", 0x16, # Content Type: Handshake
               0x03, 0x01, # Version: TLS 1.0
               0x00, 0x31, # Length
                   # Handshake
                   0x01, # Handshake Type: Client Hello
                   0x00, 0x00, 0x2b, # Length
                   0x03, 0x03, # Version: TLS 1.2
                   # Random
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0x00, # Session ID Length
                   0x00, 0x04, # Cipher Suites Length
                       0x00, 0x01, # NULL-MD5
                       0x00, 0xff, # RENEGOTIATION INFO SCSV
                   0x01, # Compression Methods
                       0x00, # NULL
                   0x00, 0x00, # Extensions Length
                       ),
    pack("C*", 0x16, # Content Type: Handshake
               0x03, 0x01, # Version: TLS 1.0
               0x00, 0x35, # Length
                   # Handshake
                   0x01, # Handshake Type: Client Hello
                   0x00, 0x00, 0x2f, # Length
                   0x03, 0x03, # Version: TLS 1.2
                   # Random
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                   0x00, # Session ID Length
                   0x00, 0x04, # Cipher Suites Length
                       0x00, 0x01, # NULL-MD5
                       0x00, 0xff, # RENEGOTIATION INFO SCSV
                   0x01, # Compression Methods
                       0x00, # NULL
                   0x00, 0x04, # Extensions Length
                       0x00, 0x23, # Extension Type: Session Ticket TLS
                       0x00, 0x00, # Length
                       ),
];

sub proxy {
    my $config = shift;

    exec(@_, '../src/sniproxy', '-f', '-c', $config);
}

sub tls_client($$) {
    my $port = shift;
    my $request = shift;

    my $socket = IO::Socket::INET->new(PeerAddr => '127.0.0.1',
                                       PeerPort => $port,
                                       Proto => "tcp",
                                       Type => SOCK_STREAM)
        or die "couldn't connect $!";

    $socket->send($request);

    my $buffer;
    $socket->recv($buffer, 4096);

    $socket->close();

    return "Unexpected response (" . length($buffer) . " bytes)\n" unless $buffer eq pack("C*", 0x15, 0x03, 0x01, 0x00, 0x02, 0x02, 0x28);

    return undef;
}

sub worker($$$$) {
    my ($hostname, $path, $port, $requests) = @_;

    for (my $i = 0; $i < $requests; $i++) {
        my $error = tls_client($port, $bad_requests->[$i % int(@$bad_requests)]);

        die($error) if defined $error;
    }
    # Success
    exit 0;
}

sub make_tls_config($) {
    my $proxy_port = shift;

    my ($fh, $filename) = File::Temp::tempfile();

    # Write out a test config file
    print $fh <<END;
# Minimal test configuration

listen 127.0.0.1 $proxy_port {
    proto tls
    bad_requests log
}

table {
    localhost 127.0.0.1 65535
}
END

    close ($fh);

    return $filename;
}

sub main {
    my $proxy_port = $ENV{SNI_PROXY_PORT} || 8080;
    my $workers = $ENV{WORKERS} || 3;
    my $iterations = $ENV{ITERATIONS} || 10;

    my $config = make_tls_config($proxy_port);
    my $proxy_pid = start_child('server', \&proxy, $config, @ARGV);

    # Wait for proxy to load and parse config
    wait_for_port(port => $proxy_port);

    for (my $i = 0; $i < $workers; $i++) {
        start_child('worker', \&worker, 'localhost', '', $proxy_port, $iterations);
    }

    # Wait for all our children to finish
    wait_for_type('worker');

    # Give the proxy a second to flush buffers and close server connections
    sleep 1;

    # For troubleshooting connections stuck in CLOSE_WAIT state
    #kill 10, $proxy_pid;
    #system("netstat -ptn | grep $proxy_pid\/sniproxy");

    # For troubleshooting 100% CPU usage
    #system("top -n 1 -p $proxy_pid -b");

    # Orderly shutdown of the server
    kill 15, $proxy_pid;
    sleep 1;

    # Delete our test configuration
    unlink($config);

    # Kill off any remaining children
    reap_children();
}

main();
