#ver 0.11 (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;
my @calls;
sub deviceNumber{
	my $deviceNumber = shift;
	if (defined $deviceNumber->{deviceNumber}) {
		$deviceNumber = $deviceNumber->{deviceNumber};
		my $deviceType = $deviceNumber & 0xFFFF0000;
		if 		($deviceType==0x1310000) {
			$deviceType='CO' 
		} elsif ($deviceType==0x1210000) {
			$deviceType='PS'
		} elsif ($deviceType==0x1110000) {
			$deviceType='EXT'
		}
		$deviceNumber = $deviceNumber & 0x0000FFFF;
		return $deviceType.sprintf("%03s",$deviceNumber);
	} elsif (defined $deviceNumber->{dialingNumber}) {
		return $deviceNumber->{dialingNumber}
	}
}
sub localConnectionInfo{
	my $localConnectionInfo = shift;
	if ($localConnectionInfo== 0){
		return 'null'
	} elsif ($localConnectionInfo== 1){
		return 'initiated'
	} elsif ($localConnectionInfo== 2){
		return 'alerting'
	} elsif ($localConnectionInfo== 3){
		return 'connected'
	} elsif ($localConnectionInfo== 4){
		return 'hold'
	} elsif ($localConnectionInfo== 5){
		return 'queued'
	} elsif ($localConnectionInfo== 6){
		return 'fail'
	} else {return $localConnectionInfo}
}

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 SnapshotDeviceRequest{
	my $device = shift;
	my $pdu = $CSTAapdu->encode({svcRequest=>{
                                    invokeID=>9,
									serviceID=>74,
                                    serviceArgs=>{
										snapshotObject=>{
											deviceIdentifier=>{
												deviceNumber=>$device
												}
											}
										}
                                    }
                                });
	#$device = sprintf("%08X",$device);
	#print("SnapshotDeviceRequest: $device\n");
	#my $pdu = "A11002010202014A300830068104".$device;
	#$pdu = pack('H*', $pdu);
	send_pdu($pdu);
}
sub SnapshotDeviceResult{
	my $refSnapshotDeviceResult = shift;
	my $refsnapshotData = ($refSnapshotDeviceResult->{crossRefIDorSnapshotData}->{snapshotData});
	foreach my $SnapshotDeviceResponseInfo (@{$refsnapshotData}) {
		my $callID = $SnapshotDeviceResponseInfo->{connectionIdentifier}->{both}->{callID};
		my $deviceID = deviceNumber($SnapshotDeviceResponseInfo->{connectionIdentifier}->{both}->{deviceID}->{staticID}->{deviceIdentifier});
		foreach my $LocalConnectionState (@{$SnapshotDeviceResponseInfo->{localCallState}->{compoundCallState}}) {
			my $ConnectionState = localConnectionInfo($LocalConnectionState);
			push @calls, [$callID, $deviceID, $ConnectionState];
		}
		
	}
}
sub SystemStatusResult{
	my $invokeID = shift;
	my $pdu = $CSTAapdu->encode({svcResult=>{invokeID=>$invokeID,result=>{serviceID=>211,serviceResult=>{noData=>1}}}});
	send_pdu($pdu); # send SystemStatus Result
}				
# parse ASN.1 desciptions
my $asn = Convert::ASN1->new;
#$asn->configure(tagdefault=>'EXPLICIT');
$asn->prepare(<<ASN1) or die "prepare: ", $asn->error;
CSTAapdu ::= CHOICE {
    svcRequest      ROIVapdu,
    svcResult       RORSapdu
--  svcError        ROERapdu,
--  svcReject       RORJapdu
}
ROIVapdu ::= [1] IMPLICIT SEQUENCE {
	invokeID	  INTEGER,  
	serviceID	  INTEGER, 
	serviceArgs	  ANY DEFINED BY serviceID 
}
RORSapdu ::= [2] IMPLICIT SEQUENCE {
	invokeID	  INTEGER,     
	result            SEQUENCE {
		serviceID	  INTEGER,
		serviceResult ANY DEFINED BY serviceID OPTIONAL
	}
}
EscapeArgument ::= SEQUENCE
{ --escapeRegisterID EscapeRegisterID OPTIONAL,
	--security 		 CSTASecurityData OPTIONAL,
	privateData		 CSTAPrivateData }

CSTAPrivateData ::= CHOICE 
{	string OCTET STRING,
	private	KmeSpecificPrivateData
}
KmeSpecificPrivateData ::= CHOICE
{   kmeSystemData  	[4] KmeSystemData
}
KmeSystemData ::= CHOICE
{ getSystemData 		[0] KmeGetSystemData,
  systemDataLinkedReply [3] EXPLICIT KmeSystemDataLinkedReply,
  getSystemDataPosAck 	[4] EXPLICIT KmeGetSystemDataPosAck
}
KmeGetSystemData ::= CHOICE
{ request KmeGetSystemDataReq 		--!
--result KmeGetSystemDataRsp
}
KmeGetSystemDataReq ::= CHOICE
{
deviceList [4] KmeRequestedDevice 	--!
}
KmeRequestedDevice ::= CHOICE -- for GetSystemData.deviceList
{ --device [0] DeviceID,
  category [1] KmeDeviceCategory} 	--!
  
KmeDeviceCategory ::= CHOICE
{ standardDevice [0] EXPLICIT DeviceCategory--!
 -- kmeDevice [1] KmeOtherDevice
}   
DeviceCategory ::= ENUMERATED
{	acd				(0),
	group			(1),
	networkInterface (2),		--!
	park			(3),
	routeingDevice	(4),
	station			(5), 		--!
	voiceUnit		(6),
	other			(7)
}
KmeSystemDataLinkedReply ::= SEQUENCE
{ crossRefID  [0] EXPLICIT ServiceCrossRefID,
  segmentID   [1] EXPLICIT INTEGER,
  lastSegment [2] EXPLICIT BOOLEAN,
  sysData     [3] EXPLICIT KmeGetSystemDataRsp OPTIONAL
}
ServiceCrossRefID ::= OCTET STRING

KmeGetSystemDataPosAck ::= ServiceCrossRefID

KmeGetSystemDataRsp ::= SEQUENCE
{  deviceList [38] EXPLICIT KmeDeviceStateList OPTIONAL
}
KmeDeviceStateList ::= SEQUENCE OF KmeDeviceStateEntry

KmeDeviceStateEntry ::= SEQUENCE
{ device DeviceID,
  number IA5String OPTIONAL, -- Ext No, CO No, Park Area No.
  status KmeDeviceState 
}
KmeDeviceState ::= ENUMERATED
{ ins (0),
  ous (1)
}
EscapeResult ::= CHOICE
{ extensions CSTACommonArguments,
	noData NULL
}
CSTACommonArguments ::= [APPLICATION 30] IMPLICIT SEQUENCE
{   privateData   [1] IMPLICIT SEQUENCE OF CSTAPrivateData    OPTIONAL }

CSTAPrivateData ::= CHOICE 
{	string OCTET STRING,
	private	KmeSpecificPrivateData
}

--snapshotDevice  OPERATION ::=
--{	ARGUMENT	SnapshotDeviceArgument,
--  RESULT     	SnapshotDeviceResult
--  ERRORS     	{universalFailure}
--	CODE	local: 74}

SnapshotDeviceArgument ::= 	SEQUENCE
{	snapshotObject DeviceID}

SnapshotDeviceResult   ::=	SEQUENCE
{	crossRefIDorSnapshotData
		CHOICE
		{	serviceCrossRefID	ServiceCrossRefID,
			snapshotData		SnapshotDeviceData
		}
}
	
ServiceCrossRefID ::= OCTET STRING

SnapshotDeviceData ::= [APPLICATION 22] IMPLICIT SEQUENCE OF SnapshotDeviceResponseInfo

SnapshotDeviceResponseInfo ::= SEQUENCE
{	connectionIdentifier	ConnectionID,
	localCallState			CallState}

ConnectionID ::= [APPLICATION 11] CHOICE
{
	deviceID	[1]	LocalDeviceID,
	both	SEQUENCE
	{	callID 	 [0] IMPLICIT CallID,
		deviceID [1] LocalDeviceID
	}
}

CallID ::= OCTET STRING 

LocalDeviceID ::= CHOICE
{   staticID        DeviceID}

DeviceID ::= SEQUENCE 
{	deviceIdentifier		CHOICE
	{	dialingNumber		[0] IMPLICIT	NumberDigits,
		deviceNumber		[1] IMPLICIT	DeviceNumber,
		other				[6] IMPLICIT	OtherPlan
	}
}
OtherPlan ::= OCTET STRING
 	
NumberDigits ::= IA5String

DeviceNumber ::= INTEGER

CallState ::= CHOICE
{	compoundCallState	[0] IMPLICIT CompoundCallState}

CompoundCallState ::= SEQUENCE OF LocalConnectionState

LocalConnectionState ::= [APPLICATION 14] IMPLICIT ENUMERATED
{	null		(0),
	initiated	(1),
	alerting	(2),
	connected	(3),
	hold		(4),
	queued		(5),
	fail		(6)
}
systemStatus ::= CHOICE
{	ARGUMENT SystemStatusArg,
	RESULT 	 SystemStatusRes
--	ERRORS 	{universalFailure}
--	CODE	local: 211	
}
SystemStatusArg ::= SEQUENCE
{	systemStatus	SystemStatus}

SystemStatusRes ::= CHOICE 
{	noData	 NULL}

SystemStatus ::= ENUMERATED
{   normal               (2),
    messageLost          (3),
    overloadReached      (6)
}
ASN1
my %serviceArgs = ( 51=> 'EscapeArgument',
					74=> 'SnapshotDeviceArgument',
					211=> 'SystemStatusArg');
foreach (keys %serviceArgs) {
	$asn->registertype('serviceArgs',$_,$asn->find($serviceArgs{$_}));
}
my %serviceResults = (  51=> 'EscapeResult',
						74=> 'SnapshotDeviceResult',
						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);
my $maxCO=0;
while (1) {
	foreach my $number (@devices) {
		SnapshotDeviceRequest($number);
	}
	undef @calls;
	my $alldevices=$#devices+1;
	while ($alldevices) {
		my $pdu = receive_stuff();
		my $out = $CSTAapdu->decode($pdu);
		if  (defined  $out->{svcRequest})   {
			my $serviceID = $out->{svcRequest}->{serviceID};
			my $invokeID  = $out->{svcRequest}->{invokeID};
			if ($serviceID==211) { # if SystemStatus Request
				SystemStatusResult($invokeID);
			}
		} elsif (defined $out->{svcResult}) {
			my $serviceResult= $out->{svcResult}->{result}->{serviceResult};
			my $serviceID    = $out->{svcResult}->{result}->{serviceID};
			if ($serviceID==74) {
				SnapshotDeviceResult($serviceResult);
				$alldevices = $alldevices-1;
			}
		}
	} 
	my $CO=0;
	for (@calls) {  
		$CO++ if $_->[1] =~/^CO/;
	}
	if ($maxCO<$CO) {
		$maxCO=$CO;
		print chr(7);
	}
	system ('CLS'); # for windows, 'clear' for linux
	print strftime("%H:%M:%S ", localtime),"maxCO:",$maxCO," CO:",$CO,"\n";
	print "callID: deviceID: CallState:\n";
	printf ("%-8s%-10s%-10s\n",$_->[0],$_->[1],$_->[2]) for sort {$a -> [0] cmp $b -> [0]} @calls;
	sleep 1;
}