#!/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 256;
use constant MAX_PORT  => scalar 65536;
use constant MIN_PORT  => scalar 1;

my $dot = ".";
my ( $rcv_sntp_packet , $li_d , $send_sntp_packet , $precision_server );

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

    my $port = $ARGV[0]; # User input PORT;

    die "\nPlease provide only numeric characters for PORT number!\n\n"
	if ( $port =~ /\D/ );

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

    my $server_socket = IO::Socket::INET->new(
	LocalPort  => $port, # Default NTP port 123
	Proto      => 'udp',
	Type       => SOCK_DGRAM,
	LocalAddr  => 'localhost',
	Broadcast  => 1 ) or die "Can't bind: $@\n";

    printf("\nServer is up, listens on PORT: ".$port." waiting for client...\n");

    while ( TRUE ) {

	my $peer_address = $server_socket->peerhost();
	my $peer_port = $server_socket->peerport();
	
	if ( defined $peer_address ) { print "Peer address: ".$peer_address."\n" };
	if ( defined $peer_port ) { print "Peer port: ".$peer_port."\n" };
	
	# read operation on the socket
	$server_socket->recv( $rcv_sntp_packet , MAXBYTES ) 
	    or die "Server error received: $!\n";
	my ( $server_rcv_epoc_sec , $server_rcv_epoc_microsec ) = gettimeofday(); # Server epoc time in sec and microsec

	# Convert to binary epoc rcv seconds
	my $server_sec_rcv_b = dec2bin( $server_rcv_epoc_sec , 32 , "N" );
	#print "Epoc rcv sec ".$server_sec_rcv_b."\n";

	# Convert to binary epoc rcv micro seconds
	my $server_microsec_rcv_b = dec2bin( $server_rcv_epoc_microsec , 32 , "N" );
	#print "Epoc rcv microsec ".$server_microsec_rcv_b."\n";

	# Concatenate the rcv sec and rcv microsec 64 in total
	my $server_epoc_rcv_b = $server_sec_rcv_b . $server_microsec_rcv_b;
	#print "Length of rcv time server: ".length( $server_epoc_rcv_b )."\n";

	my $server_rcv_epoc_d = $server_rcv_epoc_sec . $dot . $server_rcv_epoc_microsec;

	#print "\n\n" . "Server received: ".$rcv_sntp_packet."\n\n";

	if ( !defined $rcv_sntp_packet ) {
	    $send_sntp_packet = "Invalid Request";

	    $server_socket->send( $send_sntp_packet )
		or die "Server error send:  $!\n";
	}
	else {

	    my $client_li_binary = substr( $rcv_sntp_packet , 0 , 2 );
	    my $client_li = bin2dec( $client_li_binary , 8 , "c" );

	    my $client_vn_binary = substr( $rcv_sntp_packet , 2 , 3 );
	    my $client_vn = bin2dec( $client_vn_binary , 8 , "c" );

	    my $client_mode_binary = substr( $rcv_sntp_packet , 5 , 3 );
	    my $client_mode = bin2dec( $client_mode_binary , 8 , "c" );

	    my $client_stratum_binary = substr( $rcv_sntp_packet , 8 , 8 );
	    my $client_stratum = bin2dec( $client_stratum_binary , 8 , "C" );

	    my $client_poll_interval_binary = substr( $rcv_sntp_packet , 16 , 8 );
	    my $client_poll_interval = bin2dec( $client_poll_interval_binary , 8 , "c" );

	    my $client_precision_binary = substr( $rcv_sntp_packet , 24 , 8 );
	    my $client_precision = bin2dec( $client_precision_binary , 8 , "c" );

	    my $client_root_delay_binary = substr( $rcv_sntp_packet , 32 , 32 );
	    my $client_root_delay = bin2dec( $client_root_delay_binary , 32 , "f" );

	    my $client_root_dispersion_binary = substr( $rcv_sntp_packet , 64 , 32 );
	    my $client_root_dispersion = bin2dec( $client_root_dispersion_binary , 32 , "N" );

	    my $client_ref_identifier_binary = substr( $rcv_sntp_packet , 96 , 32 );
	    my $client_ref_identifier = bin2dec( $client_ref_identifier_binary , 32 , "N" );

	    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_d = $client_ref_sec_epoc . $dot . $client_ref_microsec_epoc;

	    # Concatenate client ref sec and ref microsec for server message transmission
	    my $client_epoc_ref_b = $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_b = $client_send_sec_epoc_b . $client_send_microsec_epoc_b;

	    # Server data preparing message

	    # Root Delay, total roundtrip delay to the primary reference source
	    $server_rcv_epoc_microsec = $dot . $server_rcv_epoc_microsec;
	    my $root_delay_server = $server_rcv_epoc_microsec - $client_root_delay;

	    if ($root_delay_server < 59) {
		$li_d = 0;
	    } elsif ($root_delay_server == 61 ) {		
		$li_d = 1;		
	    } elsif ($root_delay_server == 59) {		
		$li_d = 2;
	    } else {
		$li_d = 3;
	    }

	    # $li = 0; is the server we have no warning for asynchronization
	    my $li_b = dec2bin( $li_d , 8 , "c" );
	    $li_b = substr $li_b, -2;

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

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

	    # $stratum = 1; is the server clock which is primary reference
	    my $stratum_b = dec2bin( 1 , 8 , "c" );

	    # $poll = 6; # is the poll interval between messages 2**6 (2 to the power of 6) = 64 sec
	    my $poll_b = dec2bin( 2 , 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.
	    #my ($realtime, $user, $system, $cuser, $csystem) = POSIX::times() ;
	    
	    ( undef , undef , $precision_server , undef , undef ) = POSIX::times();
	
	    my $precision_b = dec2bin( $precision_server , 8 , "c" );

	    #my $root_delay_server = $server_rcv_epoc_d - $client_root_delay;
	    my $root_delay_server_b = dec2bin( $root_delay_server , 32 , "f" );

	    my $root_dispersion = 0; # is the nominal error relative to the primary reference since the server is the reference we can not have relative error. We assume the source is accurate 100%.
	    my $root_dispersion_b = dec2bin( $root_dispersion , 32 , "N" );

	    # $reference_identifier = 0; if this is the primary server then there is not of offset LOCL or PPS if GPS
	    my $reference_identifier_str = "LOCL";
	    my $reference_identifier_b .= unpack( "B32" , $reference_identifier_str );

	    # 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/
	    # Server epoc receive packet from client in binary $server_epoc_rcv_b in decimal $server_rcv_epoc_d

	    # Server epoc send in sec and microsec
	    my ( $server_sec_send_d , $server_microsec_send_d ) = gettimeofday();

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

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

	    # Concatenate the epoc send sec and micro sec to a string 64 bits long to send
	    my $server_epoc_send_b = $server_sec_send_b . $server_microsec_send_b;

	    # Print decimal epoc send message
	    my $server_epoc_send_d = $server_sec_send_d . $dot . $server_microsec_send_d;

	    $send_sntp_packet = $li_b . $vn_b . $mode_b . $stratum_b . $poll_b . $precision_b . $root_delay_server_b . $root_dispersion_b . $reference_identifier_b . $client_epoc_ref_b . $client_epoc_send_b . $server_epoc_rcv_b . $server_epoc_send_b;

	    $server_socket->send( $send_sntp_packet )
		or die "Server error send:  $!\n";
	    
	} # End of else not Invalid Message

	#print "Send packet: ".$send_sntp_packet."\n";

    } # End of while(TRUE) loop

    $server_socket->close(); # Close socket

} # End of else arguments

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

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