#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket::INET;
use POSIX qw( CLOCKS_PER_SEC );
use Time::HiRes qw( gettimeofday CLOCK_REALTIME );

use constant TRUE      => scalar 1;
use constant ARGUMENTS => scalar 1;
use constant MAXBYTES  => scalar 512;
use constant MAX_PORT  => scalar 65536;
use constant MIN_PORT  => scalar 1;

my $dot = ".";
my ( $rcv_sntp_packet , $root_dispersion , $server_send_microsec_epoc , $precision_client , $client_microsec_ref_d );

if ( @ARGV > ARGUMENTS ) {
    print "\nPlease no more than ".ARGUMENTS." argument input!\n";
    print "\nCorrect Syntax: perl $0 IP:PORT (e.g. 127.0.0.1:12345)\n";
    exit(0);
}
elsif ( @ARGV < ARGUMENTS ) {
    print "\nPlease no less than ".ARGUMENTS." argument input!\n";
    print "\nCorrect Syntax: perl $0 IP:PORT (e.g. 127.0.0.1:12345)\n";
    exit(0);
}
else {

    my $info = $ARGV[0]; # User input IP:PORT;

    my $string = index($info, ':');

    if ($string == '-1') {
	die "Please include ':' in between the IP and Port - ".$info."\n";
    }

    my @input = split( ':' , $info );

    die "\nPlease use PORT number between ".MIN_PORT." - ".MAX_PORT."\n\n" 
	if ( ( $input[1] > MAX_PORT ) || ( $input[1] < MIN_PORT ) );

    my $client_socket = new IO::Socket::INET (
	PeerHost => $input[0],
	Type     => SOCK_DGRAM,
	PeerPort => $input[1], # Default NTP port 123, due to permission denied switch to client set
	Proto    => 'udp'
	) or die "ERROR in Socket Creation: $@\n";

    my $Peer_Port = $client_socket->peerport();
    
    while (TRUE) {
	
	my $li = 0; # is the client we have no warning for asynchronization on the first round
	my $li_b = dec2bin( $li , 8 , "c" );
	$li_b = substr $li_b, -2;

	my $vn = 3; # is the version 3 only IPV4
	my $vn_b = dec2bin( $vn , 8 , "c" );
	$vn_b = substr $vn_b, -3;

	my $mode = 3; # is the client (mode 3)
	my $mode_b = dec2bin( $mode , 8 , "c" );
	$mode_b = substr $mode_b, -3;

	my $stratum = 15; # is the client clock with SNTP synchronization
	my $stratum_b = dec2bin( $stratum , 8 , "C" );

	my $poll = 6; # is the poll interval between messages 2**0 (2 to the power of 0) = 0 sec
	my $poll_b = dec2bin( $poll , 8 , "c" );

	# The precission size on the RFC is 8 bits, anything lower than 1e-03 (0.001) it can not fit on 8 bits. In such cases we round to clossest digit. Never the less the value is so small not even worth mentioning it. Range from -6 for mains-frequency clocks to -20

	( undef , undef , $precision_client , undef , undef ) = POSIX::times() ;

	my $precision_b = dec2bin( $precision_client , 8 , "c" );

	my $root_delay = 0; # Initializing the client root delay is 0 we need at least on receive to have new root delay
	my $root_delay_b = dec2bin( $root_delay , 32 , "f" );

        # is the nominal error relative to the primary reference, we need the hardware clock to know and compare with the current time in order to get the value. And update the value when we will receive the message from the server.
	if ( defined $client_microsec_ref_d ) {
	    $server_send_microsec_epoc = $dot . $server_send_microsec_epoc;
	    $client_microsec_ref_d = $dot . $client_microsec_ref_d;
	    $root_dispersion = $client_microsec_ref_d - $server_send_microsec_epoc;
	}
	else {
	    $root_dispersion = 0;
	}

	my $root_dispersion_b = dec2bin( $root_dispersion , 32 , "f" );

	my $reference_identifier = 0; # this is the client In the case of NTP Version 3 or Version 4 stratum-0 (unspecified)
	my $reference_identifier_b = dec2bin( $reference_identifier , 32 , "N" );
	
	# NTP uses 64-bit Time Stamps to exchange information. Two parts are used, 32-bits for seconds and 32-bits for portions of a second. The 64-bit Time Stamp can manage 136 years of time based from January 1, 1970. Ref. http://www.linux.org/threads/tcp-ip-protocol-network-time-protocol-ntp.4912/

	# Client epoc ref in sec and microsec
	my ( $client_sec_ref_d , $client_microsec_ref_d ) = gettimeofday();

	# Convert to binary epoc ref seconds
	my $client_sec_ref_b = dec2bin( $client_sec_ref_d , 32 , "N" );

	# Convert to binary epoc ref micro seconds
	my $client_microsec_ref_b = dec2bin( $client_microsec_ref_d , 32 , "N" );

	# Concatenate the epoc ref sec and micro sec to a string 64 bits long to send
	my $client_epoc_ref_b = $client_sec_ref_b . $client_microsec_ref_b;

	# Print decimal epoc ref message
	my $client_epoc_ref_d = $client_sec_ref_d . $dot . $client_microsec_ref_d;

	# Client epoc send in sec and microsec
	my ( $client_sec_send_d , $client_microsec_send_d ) = gettimeofday();

	# Convert to binary epoc send seconds
	my $client_sec_send_b = dec2bin( $client_sec_send_d , 32 , "N" );

	# Convert to binary epoc send micro seconds
	my $client_microsec_send_b = dec2bin( $client_microsec_send_d , 32 , "N" );

	# Concatenate the epoc send sec and micro sec to a string 64 bits long to send
	my $client_epoc_send_b = $client_sec_send_b . $client_microsec_send_b;

	# Print decimal epoc send message
	my $client_epoc_send_d = $client_sec_send_d . $dot . $client_microsec_send_d;

	my $send_sntp_packet = $li_b . $vn_b . $mode_b . $stratum_b . $poll_b . $precision_b . $root_delay_b . $root_dispersion_b . $reference_identifier_b . $client_epoc_ref_b . $client_epoc_send_b;

	$client_socket->send( $send_sntp_packet ) 
	    or die "Client error while send: $!\n";

	$client_socket->recv( $rcv_sntp_packet , MAXBYTES )
	    or die "Client error while received: $!\n";

	# Client epoc rcv in sec and microsec
	my ( $client_sec_rcv_d , $client_microsec_rcv_d ) = gettimeofday();

	# Print decimal epoc rcv message
	my $client_epoc_rcv_d = $client_sec_rcv_d . $dot . $client_microsec_rcv_d;

	die "Client check send MSG!\n" if ( $rcv_sntp_packet eq "Invalid Request" );

	my $server_li_binary = substr( $rcv_sntp_packet , 0 , 2 );
	my $server_li = bin2dec( $server_li_binary , 8 , "c" );

	my $server_vn_binary = substr( $rcv_sntp_packet , 2 , 3 );
	my $server_vn = bin2dec( $server_vn_binary , 8 , "c" );

	my $server_mode_binary = substr( $rcv_sntp_packet , 5 , 3 );
	my $server_mode = bin2dec( $server_mode_binary , 8 , "c" );

	my $server_stratum_binary = substr( $rcv_sntp_packet , 8 , 8 );
	my $server_stratum = bin2dec( $server_stratum_binary , 8 , "C" );

	my $server_poll_interval_binary = substr( $rcv_sntp_packet , 16 , 8 );
	my $server_poll_interval = bin2dec( $server_poll_interval_binary , 32 , "N" );

	my $server_precision_binary = substr( $rcv_sntp_packet , 24 , 8 );
	my $server_precision = bin2dec( $server_precision_binary , 32 , "N" );

	my $server_root_delay_binary = substr( $rcv_sntp_packet , 32 , 32 );
	my $server_root_delay = bin2dec( $server_root_delay_binary , 32 , "f" );

	my $server_root_dispersion_binary = substr( $rcv_sntp_packet , 64 , 32 );
	my $server_root_dispersion = bin2dec( $server_root_dispersion_binary , 32 , "N" );

	my $server_ref_identifier_binary = substr( $rcv_sntp_packet , 96 , 32 );
	my $server_ref_identifier  = pack("B32", $server_ref_identifier_binary);

	my $client_ref_sec_epoc_b = substr( $rcv_sntp_packet , 128 , 32 );
	my $client_ref_sec_epoc = bin2dec( $client_ref_sec_epoc_b , 32 , "N" );

	my $client_ref_microsec_epoc_b = substr( $rcv_sntp_packet , 160 , 32 );
	my $client_ref_microsec_epoc = bin2dec( $client_ref_microsec_epoc_b , 32 , "N" );

	my $client_ref_epoc_total = $client_ref_sec_epoc . $dot . $client_ref_microsec_epoc;

	# Concatenate client ref sec and ref microsec for server message transmission
	my $client_epoc_ref_binary = $client_ref_sec_epoc_b . $client_ref_microsec_epoc_b;

	my $client_send_sec_epoc_b = substr( $rcv_sntp_packet , 192 , 32 );
	my $client_send_sec_epoc = bin2dec( $client_send_sec_epoc_b , 32 , "N" );

	my $client_send_microsec_epoc_b = substr( $rcv_sntp_packet , 224 , 32 );
	my $client_send_microsec_epoc = bin2dec( $client_send_microsec_epoc_b , 32 , "N" );

	my $client_send_epoc_d = $client_send_sec_epoc . $dot . $client_send_microsec_epoc;
	
	# Concatenate client ref sec and ref microsec for server message transmission
	my $client_epoc_send_binary = $client_send_sec_epoc_b . $client_send_microsec_epoc_b;

	my $server_rcv_sec_epoc_b = substr( $rcv_sntp_packet , 256 , 32 );
	my $server_rcv_sec_epoc = bin2dec( $server_rcv_sec_epoc_b , 32 , "N" );

	my $server_rcv_microsec_epoc_b = substr( $rcv_sntp_packet , 288 , 32 );
	my $server_rcv_microsec_epoc = bin2dec( $server_rcv_microsec_epoc_b , 32 , "N" );

	my $server_rcv_epoc_d = $server_rcv_sec_epoc . $dot . $server_rcv_microsec_epoc;

	my $server_send_sec_epoc_b = substr( $rcv_sntp_packet , 320 , 32 );
	my $server_send_sec_epoc = bin2dec( $server_send_sec_epoc_b , 32 , "N" );

	my $server_send_microsec_epoc_b = substr( $rcv_sntp_packet , 352 , 32 );
	$server_send_microsec_epoc = bin2dec( $server_send_microsec_epoc_b , 32 , "N" );

	my $server_send_epoc_d = $server_send_sec_epoc . $dot . $server_send_microsec_epoc;

	# RFC2030 reference http://tools.ietf.org/html/rfc2030

	my $d = (($client_epoc_rcv_d -  $client_epoc_send_d) - ($server_rcv_epoc_d - $server_send_epoc_d));
	my $t = ((($server_rcv_epoc_d -  $client_epoc_send_d) + ($server_send_epoc_d - $client_epoc_rcv_d))/2);
	
        # Clear screen for viewing the output
	system $^O eq 'MSWin32' ? 'cls' : 'clear';

	print "
\t Timestamp Name \t ID \t When Generated
\t ------------------------------------------------------------
\t Originate Timestamp \t T1 \t time request sent by client
\t Receive Timestamp \t T2 \t time request received by server
\t Transmit Timestamp \t T3 \t time reply sent by server
\t Destination Timestamp \t T4 \t time reply received by client

\t The roundtrip delay d and local clock offset t are defined as

\t d = (T4 - T1) - (T2 - T3) \t t = ((T2 - T1) + (T3 - T4)) / 2 \n

\t Round Trip delay: ".$d."\n
\t Clock offset: ".$t."\n

\t Field Name \t\t\t Unicast/Anycast
\t\t\t\t Request \t\t Reply
\t ------------------------------------------------------------
\t LI \t\t\t $li \t\t\t $server_li
\t VN \t\t\t $vn \t\t\t $server_vn
\t Mode \t\t\t $mode \t\t\t $server_mode
\t Stratum \t\t $stratum \t\t\t $server_stratum
\t Poll \t\t\t $poll \t\t\t $server_poll_interval
\t Precision \t\t $precision_client \t\t\t $server_precision
\t Root Delay \t\t $root_delay \t\t\t $server_root_delay
\t Root Dispersion \t $root_dispersion \t\t\t $server_root_dispersion
\t Reference Identifier \t $reference_identifier \t\t\t $server_ref_identifier
\t Reference Timestamp \t $client_epoc_ref_d \t $server_rcv_epoc_d
\t Originate Timestamp \t $client_epoc_send_d \t $server_send_epoc_d
\t Receive Timestamp \t $client_epoc_rcv_d \t $server_rcv_epoc_d
\t Transmit Timestamp \t $client_epoc_send_d \t $server_send_epoc_d\n";

	sleep(2**$server_poll_interval);
	# Clear screen for viewing the output
	system $^O eq 'MSWin32' ? 'cls' : 'clear';

    } # End of While (TRUE)

    $client_socket->close(); # Close socket()

} # End of else ARGV provided

sub dec2bin {
    my $bits      = shift;
    my $size      = shift;
    my $template  = shift;
    my $str = unpack("B$size", pack($template, $bits));
    return $str;
}

sub bin2dec {
    my $bits     = shift;
    my $size     = shift;
    my $template = shift;
    return unpack($template, pack("B$size", substr("0" x $size . $bits , -$size)));
}
