#!/usr/bin/perl -I/srv/server/phexproxy/PhexProxy

#########################################
##### Author:		Sebastian Enger / B.Sc
##### CopyRight:	BitJoe GmbH
##### LastModified	5.03.2007
##### Function:		Hauptdatei für PhexProxy
##### Todo:			for statt foreach: ist schneller
########################################

system("renice 20 $$");
system("clear");

# todo: connection counter - nach 5 mal keine ergebnisse bemühe den result cache
# connection request daten abholen: 20s, 10s, 10s, 7s, 5s, public result cache

#### TODO: zuerst müssen alle Perl Phex Paris Server laufen und dann erst $0 starten !!! ####


###### warten auf paul encrypt server #######


use constant VERSION	=> "PhexDistributedProxy 0.2.0.1 - 24.9.2007";
use constant PIDFILE	=> "/srv/server/phexproxy/phexdistributedproxygzip-pid.txt";

use constant CRLF		=> "\r\n";
use constant SEPARATOR	=> "##";


use strict;
use DB_File;
use IO::Select;
use POSIX qw(floor);
use Data::Dumper;
use PhexProxy::IO;
use PhexProxy::Gzip;
use PhexProxy::Time;
use PhexProxy::Daemon;
use PhexProxy::CheckSum;
use PhexProxy::CryptoLibrary;

# use Data::Dumper;

my $HandyClientIPAdress;
my $HandyClientHostName;
my $HandyClientSocket;


my %ParisHosts			= (
	"127.0.0.1"			=> "3385",
	"81.169.141.129"	=> "3385",
);

my $DONE				= 0;

my $port				= 7773;	# Port von Programm $0
my $MaxResults			= 10;	# 10 Ergebnisse für den HandyClient
my $HasBeenResultCount	= 0;
my $ParisHostCount		= keys( %ParisHosts );
my $ParisSocketsHashRef = ();

my %ParisSocketsHash	= ();
my $ParisSocketsHash	= ();

my $DBFlatFile			= "/srv/server/phexproxy/tmp/connections.db"; unlink $DBFlatFile || system("/bin/rm -rf $DBFlatFile");
my $TakeResultsPerHost	= floor( $MaxResults / $ParisHostCount );

if ( $TakeResultsPerHost <= 0 ) {
	$TakeResultsPerHost = 1;
};

my $IO			= PhexProxy::IO->new();
my $GZIP		= PhexProxy::Gzip->new();
my $TIME		= PhexProxy::Time->new();
my $CHECK		= PhexProxy::CheckSum->new();
my $CRYPTO		= PhexProxy::CryptoLibrary->new();


my $Socket		= PhexProxy::IO->CreateHandyDistributedSocket( $port );
my $IN			= IO::Select->new($Socket);

unlink PIDFILE;
#&init_server(PIDFILE);

die "can't setup $0 socket" unless $Socket;
print "[Server $0 Version " . VERSION . " accepting clients on port $port]\n";

########################
##### while accept loop
########################

while (!$DONE) {

	tie(%ParisSocketsHash, 'DB_File', $DBFlatFile) || die "can't open $DBFlatFile: $!";
	# %ParisSocketsHash	= ();	# later comment out

	next unless $IN->can_read;
	next unless $HandyClientSocket	= $Socket->accept();
	$HandyClientIPAdress			= $HandyClientSocket->sockhost;

	my $child						= launch_child();
	
	unless ($child) {

		close $Socket;

		# erstelle den socket zum paris programm 
		my $COUNT = 0;

		# print "KEYS: " . keys( %ParisSocketsHash ) . " \n";
		# if ( keys( %ParisSocketsHash ) <= 0 ) {
		
		while( my ($host,$port) = each(%ParisHosts)){

			next if ( $COUNT > ($ParisHostCount - 1));
			my $ParisSocket = PhexProxy::IO->CreateSocketToParis( $host,$port );
			if ( ref($ParisSocket) eq "IO::Socket::INET" ) { 
			
				$ParisSocketsHashRef->{$COUNT}->{'HOST'}	= $host;			# diese werte werden im verlauf genutzt
				$ParisSocketsHashRef->{$COUNT}->{'SOCKET'}	= $ParisSocket;
				
				$ParisSocketsHash{'HOST'.$COUNT}			= $host;			# diese werte werden im verlauf nicht genutzt
				$ParisSocketsHash{'SOCKET'.$COUNT}			= $ParisSocket;
				
				$COUNT++;
			}; # if ( ref($ParisSocket) eq "IO::Socket::INET" ) { 

		 }; # while( my($host,$port) = each(%ParisHosts)){

		#};  # if ( keys( %{$ParisSocketsHashRef} ) <= 0 ) {
		

		$COUNT -= 1;	# because from the while( my ($host,$port) = each(%ParisHosts)){ the COUNT goes up to 2, but later we start counting from 0 instead from 1 therefore here -1

		if ( $COUNT == 0 ) {
			$COUNT = ( keys( %ParisSocketsHash ) / keys( %ParisHosts ) ) - 1 ;	# -1 : because in the following programm we start to count at 0 and not at 1 !
			# print "COUNT IS NOW: $COUNT\n";

		}; # if ( $COUNT == 0 ) {
 
		# print Dumper %ParisSocketsHash; 
		# print $ParisSocketsHash{'HOST'.$COUNT}; exit;

		# Nehme den Content vom Handy an
		my $ReadFromClient		= $IO->readSocket( $HandyClientSocket );
		my $ReadFromClientOrg	= $ReadFromClient;

		my @ReadFromClient		= split('##', $ReadFromClient );
		my $CurTime				= $TIME->MySQLDateTime();


		####################### crypto api implementatione #####################
		#	$IOReadFromClient[0] = MD5 Wert der Verschlüsselten Nachricht
		#	$IOReadFromClient[1] = der public schlüssel 
		#	$IOReadFromClient[2] = die verschlüsselte nachricht
			

		my $PrivateCryptoKey = $CRYPTO->GetPrivateCryptoKeyFromDatabase( $ReadFromClient[1] );
		if ( $PrivateCryptoKey == -1 ) {
		
			print "Error: Crypto false privatekey - got: '$ReadFromClient[1]'\n";
			print Dumper $ReadFromClient;
			$IO->writeSocket($HandyClientSocket, $GZIP->gzip_compress_string("Crypto false privatekey") . CRLF);
			exit(0);

		}; # if ( $PrivateKey == -1 ) {}

		my $PlainText				= $CRYPTO->Decrypt( $PrivateCryptoKey, $ReadFromClient[2] );
		my @IOReadFromClient		= ();		# resette das wichtige Array
	
		my ( $stuff			)		= split("\r\n", $PlainText ); 
		my ( @IOReadFromClient )	= split("##", $stuff ); 

		if ( $IOReadFromClient[0] eq 'find' ) {
			
			printf STDOUT "[ Connect from $HandyClientIPAdress at $CurTime ] - Submitting Search '$IOReadFromClient[5] .$IOReadFromClient[1]' to Phex\n";

			#### Verteile die Anfragen an alle Phex Proxys
			my $COUNT = 0;

			for ( %ParisSocketsHash ){	

				my $ParisSocket = $ParisSocketsHashRef->{$COUNT}->{'SOCKET'};	# ACCESS A HASHREF	
				next if ( ref($ParisSocket) ne "IO::Socket::INET" );

				$IO->writeSocket( $ParisSocket , $ReadFromClientOrg );			# suchanfrage des handys direkt an paris.pl weiterreichen	
				my $CompressedQueryID = $IO->readSocket( $ParisSocket );		# komprimierte query id abholen ...
				
				$ParisSocketsHash{'QUERYID'.$COUNT}	= $GZIP->gzip_decompress_string($CompressedQueryID);
				$IO->writeSocket( $HandyClientSocket , $CompressedQueryID );	# ... und an das Handy weitereichen
				
				$COUNT++;

			}; # for ( %ParisSocketsHash ){		

		} elsif ( $IOReadFromClient[0] eq 'result' ){
	
			printf STDOUT "[Connect from $HandyClientIPAdress at $CurTime ]  - Serving Request for Query: '$IOReadFromClient[5]' \n";	
			my $SendString = "";	# Dies ist der String mit den finalen Ergebnissen für das Handy	
		
			# Stelle die Result-Anfrage an jeden Paris Server
			my $COUNT = 0;

			for ( %ParisSocketsHash ){	
				
				####################### crypto api implementatione #####################
				#	$IOReadFromClient[0] = MD5 Wert der Verschlüsselten Nachricht
				#	$IOReadFromClient[1] = der public schlüssel 
				#	$IOReadFromClient[2] = die verschlüsselte nachricht

				my $ParisSocket							= $ParisSocketsHashRef->{$COUNT}->{'SOCKET'};
				next if ( ref($ParisSocket) ne "IO::Socket::INET" );

				my $QueryID								= $ParisSocketsHash{'QUERYID'.$COUNT};
				$IOReadFromClient[6]					= $QueryID;
				my $FinalResultString					= join("##", @IOReadFromClient );
				$FinalResultString						.= CRLF;
		
				my $PublicCryptoKey						= $ReadFromClient[1];
				my $CryptoText							= $CRYPTO->Encrypt( $PrivateCryptoKey, $FinalResultString );
				my $CryptoTextMD5						= $CHECK->MD5ToHEX($CryptoText);
				my $FinalResultSendString				= "$CryptoTextMD5##$PublicCryptoKey##$CryptoText" . CRLF;
			
			#	print "ReadFromClient=$FinalResultSendString\n";
			#	print "CRYPTO=$CryptoText\n";
			#	print "Org Result String: '$stuff' \n";
			#	print "My Result String - $COUNT :  '$FinalResultString' \n";
				
			#	$IO->writeSocket( $ParisSocket , $ReadFromClientOrg );			# suchanfrage des handys direkt an paris.pl weiterreichen	
				$IO->writeSocket( $ParisSocket , $FinalResultSendString );		

				my $TempString = ""; my $buffIO = "";
				
				while (sysread($ParisSocket, $buffIO, 1) == 1) {
					$TempString .= $buffIO;
				}; # while

				# $ParisSocketsHashRef->{$COUNT}->{'RESULTS'} = $TempString;
				$ParisSocketsHash{'RESULTS'.$COUNT} = $TempString;
				$COUNT++;

			}; # for ( %ParisSocketsHash ){	

			print "RESULTS: ";
			print $ParisSocketsHash{'RESULTS'.$COUNT};


			# Setze die Ergebnisse zusammen, nimm dazu von jedem Paris Ergebnishost die Liste und nimm davon genau $TakeResultsPerHost Ergebnisse
			my $COUNT = 0;
		
			for ( %ParisSocketsHash ){	
				
				my $TempResultString = $ParisSocketsHash{'RESULTS'.$COUNT};

				my @ResultContent	 = split("\t\t", $TempResultString );
				next if ( $#ResultContent <= 0 );

				my $ResultCount = 0;
				foreach my $content ( @ResultContent ) {
					
					next if ( $ResultCount > $TakeResultsPerHost ); 
					my ( $RANK, $SIZE, $SHA1, $PEERHOST ) = split("##", $content );
					$SendString .= $RANK .CRLF. $SIZE .CRLF. $SHA1 .CRLF. $PEERHOST .CRLF;
					$HasBeenResultCount++;
					$ResultCount++;

				}; # foreach my $content ( @ResultContent ) {

				$COUNT++;

			}; # for ( %ParisSocketsHash ){	

			$IO->writeSocket($HandyClientSocket, $GZIP->gzip_compress_string($SendString) . CRLF );
		
			# if enaough results then my %ParisSocketsHash	= (); my $ParisSocketsHash	= ();

		}; # if ( $ReadFromClient[0] eq 'find' ) { # } elsif ( $IOReadFromClient[0] eq 'result' ){
		
	
		###### Schließe alle Sockets
		my $COUNT = 0;
		
		for ( %ParisSocketsHash ){	

			next if ( ref( $ParisSocketsHashRef->{$COUNT}->{'SOCKET'} ) ne "IO::Socket::INET" );
			close $ParisSocketsHashRef->{$COUNT}->{'SOCKET'};

			$COUNT++;

		}; # for ( %ParisSocketsHash ){	

		exit(0);

	}; #  unless ($child) {}

	close $HandyClientSocket;
	untie(%ParisSocketsHash);

}; # while (!$DONE) {}

########################
##### while accept loop
########################

die "$0 - Normal termination\n";
exit(0);

### my $ParisSocket = $ParisSocketsHashRef->{$COUNT}->{'SOCKET'};	# ACCESS A HASHREF
### DOESNT WORK! my $ParisSocket = $ParisSocketsHash{'SOCKET'.$COUNT};			# Access a HASH



#SearchString = "find"+"##"+
#      Constant.SELECTED_FILE_FAMILY+","+Constant.SELECTED_FILE_TYPE+"##"+
#      "IMEI"+"##"+
#      Constant.LIZENZ_KEY+"##"+
#      Constant.VERSION+"##"+
#      to_search+"##"+
#      "JAVAVERSION"+"##"+
#      "HandyModell"+"##"+
#     "PROVIDER"+"\r\n";
 
#zweitens resultat abholen:
 
#resultString = "result"+"##"+
#      Constant.SELECTED_FILE_FAMILY+","+Constant.SELECTED_FILE_TYPE+"##"+
#      "IMEI"+"##"+
#      Constant.LIZENZ_KEY+"##"+
#      Constant.VERSION+"##"+
#      to_search+"##"+
#      id+"##"+"\r\n";

