﻿#ver 0.02 (C)Cyr
use strict;
use warnings;
use diagnostics;
use IO::Socket;
use Convert::ASN1;
use Data::Dumper;
use Carp;
use POSIX;
my $CSTAapdu;
my $socket;
my @devices;
sub deviceNumber{
	my $deviceNumber = shift;
	my $deviceType = $deviceNumber & 0xFFFF0000;
	if 		($deviceType==0x1310000) {
		$deviceType='CO' 
	} elsif ($deviceType==0x1210000) {
		$deviceType='PS'
	} elsif ($deviceType==0x1110000) {
		$deviceType='EXT'
	} elsif ($deviceType==0x1550000) {
		$deviceType='DISA'
	}
	$deviceNumber = $deviceNumber & 0x0000FFFF;
	return $deviceType.sprintf("%03s",$deviceNumber);
}
sub decode_msg_header {
	my $bin = shift;
	my $len = unpack('n', $bin);
	return $len;
}
sub encode_msg_header {
	my $len = shift;
	die "Message larger than allowed!" unless ($len <= 240);
	my $bin = pack('n', $len);
	return $bin;
}
sub convert_to_hex {
	my $pdu = $_[0];
	my $hexdata = unpack('H*', $pdu);
	$hexdata =~ tr/a-z/A-Z/;
	$hexdata =~ s/(..)/$1 /g;
	$hexdata =~ s/ $//g;
	return $hexdata;
}
sub send_pdu {
	my $pdu = $_[0];
	my $header = encode_msg_header(length($pdu));
	$socket->write($header); 
	$socket->write($pdu);
#	my $hexdata = convert_to_hex($pdu);
#	print("SENT:    [$hexdata]\n"); 
} 
sub csta_connect {
	my %args = %{$_[0]}; 
	open_csta_socket($args{host}, $args{port});
	# A-ASSOCIATE Request
	my $pdu = "602380020780A10706052B0C00815ABE14281206072B0C00821D8148A007A0050303000800";
    $pdu = pack('H*', $pdu);
	send_pdu($pdu);
	# A-ASSOCIATE Result 
	$pdu = receive_stuff();
	my $hexdata = convert_to_hex($pdu);
	if ($hexdata =~/A2 03 02 01 01/){
		print "rejected-permanent\n";
		exit 0
	}
	#SystemStatus Request
	$pdu = receive_stuff();
	my $out = $CSTAapdu->decode($pdu);
	my $invokeID  = $out->{svcRequest}->{invokeID};
	SystemStatusResult($invokeID);
}
	
sub open_csta_socket {
	my $host = shift;
	my $port = shift;
	$socket = new IO::Socket::INET(
        PeerAddr => $host,
        PeerPort => $port,
		Blocking => 1,
		Proto => 'tcp') || die "Error creating socket: $!\n";
	$socket->autoflush(1);
	print("opened a connection to $host on port $port\n");
}	
sub receive_stuff {
	my $header = '';
	my $pdu = '';
	my $nbytes = $socket->sysread($header, 2);
	if ($nbytes==1) { # фрагмент пакета
		my $header2;
		my $nbytes2= $socket->sysread($header2, 1);
		$header = $header.$header2;
		$nbytes = 2;
	} 
	croak "Didn't receive the specified amount of data (2 bytes)!\n".chr(7) unless ($nbytes == 2);
	my $len = decode_msg_header($header);
	$nbytes = $socket->sysread($pdu, $len);
	if ($nbytes<$len) { # фрагмент пакета
		my $pdu2;
		my $nbytes2= $socket->sysread($pdu2, $len-$nbytes);
		$pdu = $pdu.$pdu2;
		$nbytes = $nbytes + $nbytes2;
	} 
	croak "Didn't receive the specified amount of data ($len bytes)!\n".chr(7) unless ($nbytes == $len);
#	my $hexdata = convert_to_hex($pdu);
#	print("RECEIVED:[$hexdata]\n");
	return $pdu;
}
sub GetSystemData{
	my $DeviceCategory = shift;
	#EXT=5
	#CO	=2
	my $pdu = $CSTAapdu->encode({svcRequest=>{
 									invokeID=>4,
									serviceID=>51,
									serviceArgs=>{
										privateData=>{
											private=>{
												kmeSystemData=>{
													getSystemData=>{
														request=>{
															deviceList=>{
																category=>{
 																	standardDevice=>$DeviceCategory
																	}
																}
															}
														}
													}
												}
											}
										}
 									}
								});
	#my $pdu = "A11602020602020133300DA40BA009A407A105A0030A010".$DeviceCategory;
	#$pdu = pack('H*', $pdu);
	send_pdu($pdu);
	# getSystemDataPosAck
	$pdu = receive_stuff();
	my $out = $CSTAapdu->decode($pdu);
	my $crossRefID = $out->{svcResult}->{result}->{serviceResult}->{extensions}->{privateData}[0]->{private}->{kmeSystemData}->{getSystemDataPosAck};
	my $lastSegment=0;
	# systemDataLinkedReply
	while (!$lastSegment) {
		$pdu = receive_stuff();
		my $out = $CSTAapdu->decode($pdu);
		my $systemDataLinkedReply = $out->{svcRequest}->{serviceArgs}->{privateData}->{private}->{kmeSystemData}->{systemDataLinkedReply};
		if (defined $systemDataLinkedReply) {
			if ($systemDataLinkedReply->{crossRefID} cmp $crossRefID) {next}
			$lastSegment = $systemDataLinkedReply->{lastSegment};
			foreach my $KmeDeviceStateEntry (@{$systemDataLinkedReply->{sysData}->{deviceList}}) {
				if	($KmeDeviceStateEntry->{status}==0) {
#					print "device:",$KmeDeviceStateEntry->{number},"\n";
					push @devices, ($KmeDeviceStateEntry->{device}->{deviceIdentifier}->{deviceNumber});
				}
			}
		}	
	}
}
sub MonitorStartArgument{
my $device = shift;
my $pdu = $CSTAapdu->encode({svcRequest=>{
                                invokeID=>9,
								serviceID=>71,
                                serviceArgs=>{
										monitorObject=>{
											deviceObject=>{
												deviceIdentifier=>{
													deviceNumber=>$device
																}
															}
														},
										requestedMonitorFilter=>{}
											}
										}
                            });
send_pdu($pdu);
#$pdu = receive_stuff();	
}
sub CSTAEventReportArgument{
my $refeventSpecificInfo = shift;
if (defined $refeventSpecificInfo->{eventSpecificInfo}->{callControlEvents}) {
	my $refcallControlEvents = $refeventSpecificInfo->{eventSpecificInfo}->{callControlEvents};
	my $callControlEvents = (keys %{$refcallControlEvents})[0];
	my $Event = $refcallControlEvents->{$callControlEvents};
	#print Dumper($refcallControlEvents);
	if ((($Event->{localConnectionInfo}==2) & (($Event->{cause}==22) | ($Event->{cause}==38))) | 
	(($Event->{localConnectionInfo}==3) & ($Event->{cause}==74))) {
		#localConnectionInfo = allerting and cause=newCall|distributed
		my $CalledID = deviceID($Event->{alertingDevice}->{deviceIdentifier});
		my $CallerID;
		if (defined $Event->{callingDevice}->{notKnown}) {
			$CallerID = deviceID($Event->{associatedCallingDevice}->{deviceIdentifier})
		} else {			
			$CallerID = deviceID($Event->{callingDevice}->{deviceIdentifier})
		}
		print strftime("%H:%M:%S ", localtime);
		print("Call to:$CalledID from:$CallerID\n");
	}
  }
}
sub deviceID{
	my $deviceIdentifier = shift;
	$deviceIdentifier = $deviceIdentifier->{deviceIdentifier};
	if     (defined $deviceIdentifier->{dialingNumber}) {
		return $deviceIdentifier->{dialingNumber}
	} elsif (defined $deviceIdentifier->{deviceNumber}) {
		return deviceNumber($deviceIdentifier->{deviceNumber})
	} elsif (defined $deviceIdentifier->{other}) 		{
		return $deviceIdentifier->{other}
	}
}
sub SystemStatusResult{
	my $invokeID = shift;
	my $pdu = $CSTAapdu->encode({svcResult=>{invokeID=>$invokeID,result=>{serviceID=>211,serviceResult=>{noData=>1}}}});
	send_pdu($pdu); # send SystemStatus Result
}
$Data::Dumper::Indent=1;
$Data::Dumper::Pair=' ';
$Data::Dumper::Quotekeys=0;
$Data::Dumper::Varname='calls';				
# parse ASN.1 desciptions
my $asn = Convert::ASN1->new;
#$asn->configure(tagdefault=>'EXPLICIT');
$asn->prepare_file('d:\111\asn_perl\kxtde.asn') or die "prepare: ", $asn->error;
my %serviceArgs = ( 21=> 'CSTAEventReportArgument',
					51=> 'EscapeArgument',
					71=> 'MonitorStartArgument',
					211=> 'SystemStatusArg');
foreach (keys %serviceArgs) {
	$asn->registertype('serviceArgs',$_,$asn->find($serviceArgs{$_}));
}
my %serviceResults = (  51=> 'EscapeResult',
						71=> 'MonitorStartResult',
						211=> 'SystemStatusRes');
foreach (keys %serviceResults) {
	$asn->registertype('serviceResult',$_,$asn->find($serviceResults{$_}));
} 
$CSTAapdu = $asn->find('CSTAapdu');
csta_connect({'host'=>'192.168.0.101', 'port'=>33333});
GetSystemData(2);
GetSystemData(5);
foreach my $number (@devices) {
	MonitorStartArgument($number);
}
while (1) {
	my $pdu = receive_stuff();
	my $out = $CSTAapdu->decode($pdu);
	if  (defined  $out->{svcRequest})   {
			my $serviceID = $out->{svcRequest}->{serviceID};
			my $invokeID  = $out->{svcRequest}->{invokeID};
			my $serviceArgs = $out->{svcRequest}->{serviceArgs};
			if ($serviceID==21) {
				CSTAEventReportArgument($serviceArgs);
			} elsif ($serviceID==211) { # if SystemStatus Request
				SystemStatusResult($invokeID);
			}
	}
}