#!/usr/bin/perl

#################################################################################
#                                                                               
#  Easy PERL NTRIP server for Linux/Unix.                                       
#  									        
#  This program is free software; you can redistribute it and/or modify	        
#  it under the terms of the GNU General Public License as published by	        
#  the Free Software Foundation; either version 2 of the License, or		
#  (at your option) any later version.						
#										
#  This program is distributed in the hope that it will be useful,		
#  but WITHOUT ANY WARRANTY; without even the implied warranty of		
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the		
#  GNU General Public License for more details.					
#										
#  You should have received a copy of the GNU General Public License		
#  along with this program; if not, write to the Free Software			
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA	
#  or read http://www.gnu.org/licenses/gpl.txt				        
#                                                                               
#################################################################################


#
# load modules
#
use strict;
use Socket;
use IO::Select;
use Getopt::Long;
use Fcntl;

my $VERSION = "NTRIP aboServer_v0.2";

sub logerr {
	$_ = sprintf "* NTRIP %s, %s\n", scalar localtime, @_;
	syswrite STDERR, $_, length $_;
}

#
# declare variables
#
my $ntrip;
my $stdin_buffer = 1024;
my $tcpip_buffer = 1024;
my $max_buffer   = 102400;
my $max_sending_errors = 20;
my $buffer = '';
my $tmp_buffer;
my $n;

my $inchr;
my $SELECT;
my $paddr;
my $cl_port;
my $cl_name;
my $iaddr;

my $help;
my $config_file = "/data/etc/ntrip_cl1.conf";
my $version = 0;
my $connection_wanted;
my $no_error;
my $msg;

my $locked = 0;
my $lock_time = 20;

my $n;

my $send_error_count = 0;


#
# check input parameter
# 
( $#ARGV >= -1 ) or die "use $0 -h to get help!\n";
unless ( Getopt::Long::GetOptions (	'help'		=> \$help,
					'conf=s'	=> \$config_file,
					'version'	=> \$version )) {
	help_die ();
}

if ( $help ) {
        help_die ();
}

( $version ) && die "Program version: $VERSION (Armin Boer, BKG-TIGO)\n";

( -e $config_file ) or die "* FATAL: Can\'t find file \"$config_file\"!! Try -h for help!\n";


#
# read ntrip configuration
#
($connection_wanted, $ntrip) = read_ntrip_config ( $config_file );
if ( $connection_wanted ) {
	logerr "SENDING data to $$ntrip{CASTER}:$$ntrip{PORT}, MP: $$ntrip{MOUNTPOINT}";
} else {
	logerr "NO SENDING data";
}

(defined $$ntrip{MOUNTPOINT}) || die "* FATAL: Parameters in \"$config_file\" not accepted!!\n";


#
# catch pipe signal
#
$SIG{PIPE}  = \&sig_pipe;
$SIG{HUP}   = \&sig_hup;
$SIG{ALRM}  = \&sig_alarm;

#
# setup select
#
$SELECT = IO::Select->new;
$SELECT->add( \*STDIN );

#
# do main loop
#
while (1) {
	if ( $SELECT->can_read ) {
		$n = sysread ( STDIN, $inchr, $stdin_buffer);
		$_ = sprintf "read %d from STDIN", $n;
		#logerr "STDIN: $_";
		$buffer = $buffer . $inchr;
		if ( (length $buffer) >= $tcpip_buffer ) {
			if ( fileno NTRIP ) {
			#
			# connection established
			#
				$tmp_buffer = substr $buffer, 0, $tcpip_buffer;
				$_ = length $tmp_buffer;
				$n = send NTRIP, $tmp_buffer, MSG_DONTWAIT;
				#logerr "send: $n of $_ chars";
				if ( (defined $n) and ($n > 0) ) { #### sending was ok
					$send_error_count = 0;
					$buffer = substr $buffer, $n, (length $buffer)-$n;
				} else { #### sending was NOT ok!!
					$send_error_count++;
					#logerr "WARNING: $send_error_count sending errors";
					if ( $send_error_count > $max_sending_errors ) {
						close NTRIP or logerr "ERROR in closing!";
						logerr "Connection to $$ntrip{CASTER} closed! More than $max_sending_errors sending errors!";
						$locked = 1;
						alarm ($lock_time);
						$send_error_count = 0;
					}
				}
				if ( ! $connection_wanted ) {
					close NTRIP or logerr "ERROR in closing!";
					logerr "NOTICE: Connection to $$ntrip{CASTER} closed.";
				}
			} elsif ( $connection_wanted ) {
			#
			# try to make connection
			#
				$buffer = substr $buffer, $stdin_buffer, (length $buffer)-$stdin_buffer;
				unless ( $locked ) {
					#logerr "NOTICE: TRY to make connection!";
					$locked = 1;
					alarm ( $lock_time );	## set alarm to block reconnection for a while
					($no_error, $msg) = open_ntrip_caster ($$ntrip{CASTER}, $$ntrip{PORT}, $$ntrip{PASSWD}, $$ntrip{MOUNTPOINT}, $lock_time);
					if ($no_error ) {
						$locked = 0;
						alarm (0);	## disable alarm
						logerr "NOTICE: Connection to $$ntrip{CASTER} successfully opened: $msg";
					} else {
						logerr "WARNING: Connection error: $msg";
						close NTRIP or logerr "ERROR in closing!";
						#logerr "NOTICE: Socket closed!";
					}
				}
			}

		} ## end if ( (length $buffer) >= $tcpip_buffer ) {
	} ## end select
	#
	# reduce buffer
	#
	if ( (length $buffer) >= $max_buffer ) {
		$buffer = substr $buffer, $stdin_buffer, (length $buffer)-$stdin_buffer;
	}
	select undef, undef, undef, 0.1;
} ## end while


sub sig_pipe {
	my $signal = shift;
	logerr "SIGNAL: got sig $signal";
	if (fileno NTRIP) {
		close NTRIP or logerr "ERROR in closing!";
		logerr "NOTICE: connection closed";
		$locked = 1;
		alarm ($lock_time);
	}
}

sub sig_alarm {
	my $signal = shift;
	logerr "SIGNAL: got sig $signal, permit connection";
	alarm (0);
	$locked = 0;
}


sub sig_hup {
	my $signal = shift;
	logerr "SIGNAL: got sig $signal";
	if (fileno NTRIP) {
		close NTRIP or logerr "ERROR in closing!";
		logerr "NOTICE: close connection to $$ntrip{CASTER}\n";
	}
	#logerr "NOTICE: reading configuration file\n";
	($connection_wanted, $ntrip) = read_ntrip_config ( $config_file );

	if ( $connection_wanted ) {
		logerr "SENDING data to $$ntrip{CASTER}:$$ntrip{PORT}, MP: $$ntrip{MOUNTPOINT}";
	} else {
		logerr "NO SENDING data";
	}

	(defined $$ntrip{MOUNTPOINT}) || logerr "* NOTICE: Ntrip STATUS switched OFF or parameters in \"$config_file\" not accepted! -> NO CONNECTION POSSIBLE!!\n";

	$locked = 1;
	alarm ($lock_time);
}

sub help_die {
        printf STDERR "usage: $0\n\t-h     (for a short help)\n";
        printf STDERR "\t-c     (Configuration file [default: /data/etc/ntrip_cl1.conf])\n";
        printf STDERR "\t-v     version\n";
	die "\tTo reload configuration file send \"kill -s HUP <ps_nr>\"\n";
}

sub read_ntrip_config {
	my $file = shift;
	my %ntrip;

	open CONFIG, "$file" or die "Can\'t open \"$file\"!\n";
	while ( $_ = <CONFIG> ) {
		$_ =~ s/^\s+//;
		unless ( $_ =~ /^#/ ) {
			($ntrip{STATUS}, $ntrip{CASTER}, $ntrip{PORT}, $ntrip{PASSWD}, $ntrip{MOUNTPOINT}) =  ($_=~ /(\w+)\s+([\w\.\-]+)\s+(\d+)\s+([\w\.\-;:#'~\+\*,]+)\s+(.+)/);

			#$_ = sprintf "Status: $ntrip{STATUS}  Caster: $ntrip{CASTER}  Port: $ntrip{PORT}  Passwd: $ntrip{PASSWD}  Mountpoint: $ntrip{MOUNTPOINT}";
			#logerr "SUB_CONFIG: $_";

			if ( defined $ntrip{MOUNTPOINT} ) {
				if ( ($ntrip{STATUS} eq "ON") or ($ntrip{STATUS} eq "on") or ($ntrip{STATUS} eq "On") or ($ntrip{STATUS} eq "oN") ) {
					return (1, \%ntrip);
				}
			}
		}
	}
	return (0, \%ntrip);
}

sub open_ntrip_caster {

	my $host       = shift;
	my $port       = shift;
	my $passwd     = shift;
	my $mountpoint = shift;
	my $lock_time  = shift;
	my $iaddr      = inet_aton ($host);
	my $paddr      = sockaddr_in ($port, $iaddr);
	my $ret;

	my $send_line;

	my $tries_to_connect    = 10;
	my $connect_error_count = 0;
	my $i;

	if ( socket (NTRIP, AF_INET, SOCK_STREAM, getprotobyname('tcp')) ) {
		fcntl (NTRIP, F_SETFL, O_NONBLOCK);
		#logerr "NOTICE: socket created";
		for $i (1..$tries_to_connect) {
			unless ( connect ( NTRIP, $paddr ) ) {
				#logerr "NOTICE: tried $i times to connect...";
				#select undef, undef, undef, 0.5;
				sleep 1;
			} else { ### connect successfull
				#logerr "NOTICE: socket connected";
				if (setsockopt(NTRIP, SOL_SOCKET, SO_SNDBUF, $tcpip_buffer)) {
					#logerr "NOTICE: socket options set";
					$send_line = sprintf "SOURCE %s \/%s\r\n", $passwd, $mountpoint;
					#logerr "send: $send_line";
					$n = send NTRIP, $send_line, undef;
					if ( (defined $n) and ($n < length $send_line) ) {
						logerr "WARNING: wrong SOURCE sending!!";
						close NTRIP or logerr "ERROR in closing!";
					}
					$send_line = sprintf "Source-Agent: %s\r\n\r\n", $VERSION;
					$n = send NTRIP, $send_line, undef;
					#logerr "send: $send_line";
					if ( (defined $n) and ($n < length $send_line) ) {
						logerr "WARNING: wrong Source-Agent sending!!";
						close NTRIP or logerr "ERROR in closing!";
					}
					#logerr "NOTICE: waiting for caster confirmation";
					sleep 2;
					#logerr "NOTICE: before recv";
					##$n = recv NTRIP, $_, 12, undef;
					$_ = <NTRIP>;
					#logerr "NOTICE: after recv: $_";
					#logerr "NOTICE: got caster confirmation: $_";
					if ( $_ =~ /OK\r\n/ ) {
						chop $_;
						chop $_;
						$_ = $_ . " after $i tries";
						return (1, $_);
					}
				}
			last;
			} ### end 
		} ### END $tries_to_connect
	}
	return (0, $!);
}

