#line 1
package Net::EmptyPort;
use strict;
use warnings;
use base qw/Exporter/;
use IO::Socket::INET;
use Time::HiRes ();

our @EXPORT = qw/ empty_port check_port wait_port /;

# get a empty port on 49152 .. 65535
# http://www.iana.org/assignments/port-numbers
sub empty_port {
    my $port = do {
        if (defined $_[0]) {
            my $p = $_[0];
            $p = 49152 unless $p =~ /^[0-9]+$/ && $p < 49152;
            $p;
        } else {
            50000 + (int(rand()*1500) + abs($$)) % 1500;
        }
    };
    my $proto = $_[1] ? lc($_[1]) : 'tcp';

    while ( $port++ < 65000 ) {
        # Remote checks don't work on UDP, and Local checks would be redundant here...
        next if ($proto eq 'tcp' && check_port($port));

        my $sock = IO::Socket::INET->new(
            (($proto eq 'udp') ? () : (Listen => 5)),
            LocalAddr => '127.0.0.1',
            LocalPort => $port,
            Proto     => $proto,
            (($^O eq 'MSWin32') ? () : (ReuseAddr => 1)),
        );
        return $port if $sock;
    }
    die "empty port not found";
}

sub check_port {
    my $port = $_[0];
    my $proto = $_[1] ? lc($_[1]) : 'tcp';

    # for TCP, we do a remote port check
    # for UDP, we do a local port check, like empty_port does
    my $sock = ($proto eq 'tcp') ?
        IO::Socket::INET->new(
            Proto    => 'tcp',
            PeerAddr => '127.0.0.1',
            PeerPort => $port,
        ) :
        IO::Socket::INET->new(
            Proto     => $proto,
            LocalAddr => '127.0.0.1',
            LocalPort => $port,
            (($^O eq 'MSWin32') ? () : (ReuseAddr => 1)),
        )
    ;

    if ($sock) {
        close $sock;
        return 1; # The port is used.
    }
    else {
        return 0; # The port is not used.
    }

}

sub _make_waiter {
    my $max_wait = shift;
    my $waited = 0;
    my $sleep  = 0.001;

    return sub {
        return 0 if $max_wait >= 0 && $waited > $max_wait;

        Time::HiRes::sleep($sleep);
        $waited += $sleep;
        $sleep  *= 2;

        return 1;
    };
}

sub wait_port {
    my ($port, $max_wait, $proto);
    if (@_==4) {
        # backward compat.
        ($port, (my $sleep), (my $retry), $proto) = @_;
        $max_wait = $sleep * $retry;
        $proto = $proto ? lc($proto) : 'tcp';
    } else {
        ($port, $max_wait, $proto) = @_;
        $proto = $proto ? lc($proto) : 'tcp';
    }

    $max_wait = 10 unless defined $max_wait;
    my $waiter = _make_waiter($max_wait);

    while ( $waiter->() ) {
        if ($^O eq 'MSWin32' ? `$^X -MTest::TCP::CheckPort -echeck_port $port $proto` : check_port( $port, $proto )) {
            return 1;
        }
    }
    return 0;
}

1;

__END__

=encoding utf8

#line 210
