diff --git a/Changes b/Changes index 81daaa9..1a76de0 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,9 @@ Revision history for Perl extension iec104. +0.02 Mon Aug 30 20:41:10 2010 + - use microseconds from cp56_2a timestamps + - non-blocking connect() with timeout + 0.01 Thu Mar 13 21:40:19 2008 - original version; created by h2xs 1.23 with options -AX -n iec104 diff --git a/README b/README index d95595f..dc85478 100644 --- a/README +++ b/README @@ -1,16 +1,14 @@ -iec104 version 0.01 -=================== +iec104 - Perl implementation of IEC 60870-5-104 standard (server and client) -The README is used to introduce the module and provide instructions on -how to install the module, any machine dependencies it may have (for -example C compilers and installed libraries) and any other information -that should be provided before the module is installed. +DESCRIPTION -A README file is required for CPAN modules since CPAN extracts the -README file from a module distribution so that people browsing the -archive can use it get an idea of the modules uses. It is usually a -good idea to provide version information here so that people can -decide whether fixes for the module are worth downloading. +This module implement IEC 60870-5-104 standard (also known as IEC 870-5-104). +IEC 870-5-104 is a network access for IEC 60870-5-101 using standard transport profiles (TCP/IP), +its application layer is based on IEC 60870-5-101. IEC 60870-5-104 enables communication between +control station and substation via a standard TCP/IP network. The TCP protocol is used for +connection-oriented secure data transmission. + +Current implementation supports only ASDU NN 30,35,36,37,100,103. Its enough for now. INSTALLATION @@ -24,14 +22,15 @@ To install this module type the following: DEPENDENCIES This module requires these other modules and libraries: - - blah blah blah + Event::Lib => 1.03, + IO::Socket::INET => 1.29, + Carp => 1.0, + Date::Manip => 1.0 + Time::HiRes COPYRIGHT AND LICENCE -Put the correct copyright and licence information here. - -Copyright (C) 2008 by Vladimir Lettiev +Copyright (C) 2008-2010 by Vladimir Lettiev This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.8 or, diff --git a/lib/iec104.pm b/lib/iec104.pm index f4c4104..8c798f2 100644 --- a/lib/iec104.pm +++ b/lib/iec104.pm @@ -20,973 +20,1198 @@ our @ISA = qw(Exporter); # This allows declaration use iec104 ':all'; # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. -our %EXPORT_TAGS = ( 'all' => [ qw( - &connect &listen &send &main_loop &disconnect -) ] ); +our %EXPORT_TAGS = ( + 'all' => [ + qw( + &connect &listen &send &main_loop &disconnect + ) + ] +); our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT = qw( - + ); our $VERSION = '0.02'; ############################################################################### -$|=1; +$| = 1; -our $debug=0; -our $MAX_ASDU_SIZE=248; -our $MAX_S_QUEUE=100; +our $debug = 0; +our $MAX_ASDU_SIZE = 248; +our $MAX_S_QUEUE = 100; our %asdu_type = ( - # Infromation to control direction, ASDU: 1-44 - 30 => {"size"=>8, "name" => "M_SP_TB_1", type=>"TS", - "parse_cb"=>\&parse_asdu_type_0_44,"write_cb"=>\&send_asdu_type_0_44}, - 35 => {"size"=>10,"name" => "M_ME_TE_1", type=>"TI", - "parse_cb"=>\&parse_asdu_type_0_44,"write_cb"=>\&send_asdu_type_0_44}, - 36 => {"size"=>12,"name" => "M_ME_TF_1", type=>"TI", - "parse_cb"=>\&parse_asdu_type_0_44,"write_cb"=>\&send_asdu_type_0_44}, - 37 => {"size"=>12,"name" => "M_IT_TB_1", type=>"TII", - "parse_cb"=>\&parse_asdu_type_0_44,"write_cb"=>\&send_asdu_type_0_44}, - # System information to controlled direction, ASDU: 100-109 - 100 => {"size"=>1, "name" => "C_IC_NA_1", type=>"", - "parse_cb"=>\&parse_asdu_type_100,"write_cb"=>\&send_asdu_type_100}, - 103 => {"size"=>7, "name" => "C_CS_NA_1", type=>"", - "parse_cb"=>\&parse_asdu_type_103,"write_cb"=>\&send_asdu_type_103}, + + # Infromation to control direction, ASDU: 1-44 + 30 => { + "size" => 8, + "name" => "M_SP_TB_1", + type => "TS", + "parse_cb" => \&parse_asdu_type_0_44, + "write_cb" => \&send_asdu_type_0_44 + }, + 35 => { + "size" => 10, + "name" => "M_ME_TE_1", + type => "TI", + "parse_cb" => \&parse_asdu_type_0_44, + "write_cb" => \&send_asdu_type_0_44 + }, + 36 => { + "size" => 12, + "name" => "M_ME_TF_1", + type => "TI", + "parse_cb" => \&parse_asdu_type_0_44, + "write_cb" => \&send_asdu_type_0_44 + }, + 37 => { + "size" => 12, + "name" => "M_IT_TB_1", + type => "TII", + "parse_cb" => \&parse_asdu_type_0_44, + "write_cb" => \&send_asdu_type_0_44 + }, + + # System information to controlled direction, ASDU: 100-109 + 100 => { + "size" => 1, + "name" => "C_IC_NA_1", + type => "", + "parse_cb" => \&parse_asdu_type_100, + "write_cb" => \&send_asdu_type_100 + }, + 103 => { + "size" => 7, + "name" => "C_CS_NA_1", + type => "", + "parse_cb" => \&parse_asdu_type_103, + "write_cb" => \&send_asdu_type_103 + }, ); # Constructor sub new { - my $self = shift; - my %h = @_; - my $class = ref($self) || $self; - croak "wrong type of iec104" if ($h{type} ne "slave" and $h{type} ne "master"); + my $self = shift; + my %h = @_; + my $class = ref($self) || $self; + croak "wrong type of iec104" + if ( $h{type} ne "slave" and $h{type} ne "master" ); - $h{retry_timeout} = (exists $h{retry_timeout})?$h{retry_timeout}:5; - $h{ip} = (exists $h{ip} )?$h{ip}:"0.0.0.0"; - $h{port} = (exists $h{port})?$h{port}:"2404"; + $h{retry_timeout} = ( exists $h{retry_timeout} ) ? $h{retry_timeout} : 5; + $h{ip} = ( exists $h{ip} ) ? $h{ip} : "0.0.0.0"; + $h{port} = ( exists $h{port} ) ? $h{port} : "2404"; - bless \%h, $class; + bless \%h, $class; } # Print debug messages sub DEBUG { - my $d = shift; - if ($debug >= $d) { - print @_; - if ($d < 0) { - printf "<-- at %s:%s", ( caller )[1,2]; - } - unless ($_[$#_] =~ /\s$/) { - print "\n"; - } - } + my $d = shift; + if ( $debug >= $d ) { + print @_; + if ( $d < 0 ) { + printf "<-- at %s:%s", (caller)[ 1, 2 ]; + } + unless ( $_[$#_] =~ /\s$/ ) { + print "\n"; + } + } } # Pack ip-port pair as ID of connection sub get_id { - my $sock = shift; - return pack("C4S",split(/\./,$sock->peerhost),$sock->peerport); + my $sock = shift; + return pack( "C4S", split( /\./, $sock->peerhost ), $sock->peerport ); } # Unpack ip-port pair to printable form sub sid2hex { - return join(".", map { sprintf "%d",$_ } unpack("C4S",shift)); + return join( ".", map { sprintf "%d", $_ } unpack( "C4S", shift ) ); } # Print hex-codes of raw data sub raw2hex { - return join (",", map { sprintf "%02X", $_ } unpack("C*",shift)); + return join( ",", map { sprintf "%02X", $_ } unpack( "C*", shift ) ); } # convert from cp56_2a format of time to gettimeofday sub cp56_2a_2_time { - my $data = shift; - my @tm = unpack("SC5",$data); - my $tm = Date_SecsSince1970GMT($tm[4]&0xF,$tm[3]&0x1F, - 2000+($tm[5]&0x7F),$tm[2]&0x1F,$tm[1]&0x3F,int($tm[0]/1000)); - my $ms = ($tm[0]%1000)*1000; - return ($tm,$ms); + my $data = shift; + my @tm = unpack( "SC5", $data ); + my $tm = Date_SecsSince1970GMT( + $tm[4] & 0xF, + $tm[3] & 0x1F, + 2000 + ( $tm[5] & 0x7F ), + $tm[2] & 0x1F, + $tm[1] & 0x3F, + int( $tm[0] / 1000 ) + ); + my $ms = ( $tm[0] % 1000 ) * 1000; + return ( $tm, $ms ); } # convert from gettimeofday format to cp56_2a sub time_2_cp56_2a { - my $tm = shift; - my $ms = shift; - $ms = int($ms/1000); - my @tm = localtime($tm); - return pack("SC5",($tm[0]*1000+$ms),$tm[1],$tm[2]|($tm[8]<<7), - $tm[3]|($tm[6]<<5),++$tm[4],$tm[5]%100); + my $tm = shift; + my $ms = shift; + $ms = int( $ms / 1000 ); + my @tm = localtime($tm); + return pack( "SC5", + ( $tm[0] * 1000 + $ms ), + $tm[1], + $tm[2] | ( $tm[8] << 7 ), + $tm[3] | ( $tm[6] << 5 ), + ++$tm[4], $tm[5] % 100 ); } # debug info about connection sub sidinfo { - my $self = shift; - my $sid = shift; - my $report; - my $s = \%{$self->{sids}{$sid}}; - - $report = "=====================\n" . - "TYPE: ".$s->{type}."\n" . sprintf( - "IP: %d.%d.%d.%d\n". - "PORT: %d\n", unpack("C4S",$sid)). - "UFUNC: ". $s->{ufunc} . "\n" . - "VS: ". $s->{vs} . "\n" . - "VR: ". $s->{vr} . "\n" . - "AS: ". $s->{as} . "\n" . - "AR: ". $s->{ar} . "\n" . - "s_queue: ". $#{$s->{s_queue}} . "\n"; - foreach my $timer ("t0_timer","t1_timer","t2_timer","t3_timer","ci_timer","sync_timer") { - $report .= $timer.": "; - if ($s->{$timer}->pending) { - $report .= "pending\n"; - } else { - $report .= "not started\n"; - } - } - return $report; + my $self = shift; + my $sid = shift; + my $report; + my $s = \%{ $self->{sids}{$sid} }; + + $report = + "=====================\n" + . "TYPE: " + . $s->{type} . "\n" + . sprintf( "IP: %d.%d.%d.%d\n" . "PORT: %d\n", unpack( "C4S", $sid ) ) + . "UFUNC: " + . $s->{ufunc} . "\n" + . "VS: " + . $s->{vs} . "\n" + . "VR: " + . $s->{vr} . "\n" + . "AS: " + . $s->{as} . "\n" + . "AR: " + . $s->{ar} . "\n" + . "s_queue: " + . $#{ $s->{s_queue} } . "\n"; + foreach my $timer ( + "t0_timer", "t1_timer", "t2_timer", "t3_timer", + "ci_timer", "sync_timer" + ) + { + $report .= $timer . ": "; + if ( $s->{$timer}->pending ) { + $report .= "pending\n"; + } + else { + $report .= "not started\n"; + } + } + return $report; } # public method listen() # start IEC slave station (server) sub listen { - my $self = shift; - my %h = @_; - carp "called without a reference" if (!ref($self)); - - my $server = IO::Socket::INET->new( - LocalAddr => $self->{ip}, - LocalPort => $self->{port}, - Proto => 'tcp', - ReuseAddr => SO_REUSEADDR, - Listen => 1, - Blocking => 0, - ) or carp $@; - return -1 unless ($server); - &DEBUG(1,"Listen on ", $self->{ip},":",$self->{port}); - my $main = event_new($server, EV_READ|EV_PERSIST, - \&handle_incoming, $self); - $main->add; + my $self = shift; + my %h = @_; + carp "called without a reference" if ( !ref($self) ); + + my $server = IO::Socket::INET->new( + LocalAddr => $self->{ip}, + LocalPort => $self->{port}, + Proto => 'tcp', + ReuseAddr => SO_REUSEADDR, + Listen => 1, + Blocking => 0, + ) or carp $@; + return -1 unless ($server); + &DEBUG( 1, "Listen on ", $self->{ip}, ":", $self->{port} ); + my $main = + event_new( $server, EV_READ | EV_PERSIST, \&handle_incoming, $self ); + $main->add; } # public method connect() # start IEC master session sub connect { - my $self = shift; - carp "called without a reference" if (!ref($self)); - - my $client = IO::Socket::INET->new( - Proto => 'tcp', - PeerAddr => $self->{ip}, - PeerPort => $self->{port}, - Blocking => 0 - ) or carp $@; - - my $main = event_new($client, EV_WRITE, \&on_connect, $self); - $main->add(2); + my $self = shift; + carp "called without a reference" if ( !ref($self) ); + + my $client = IO::Socket::INET->new( + Proto => 'tcp', + PeerAddr => $self->{ip}, + PeerPort => $self->{port}, + Blocking => 0 + ) or carp $@; + + my $main = event_new( $client, EV_WRITE, \&on_connect, $self ); + $main->add(2); } sub on_connect { - my ($e,$type,$self) = @_; - my $error = 1; - - if ($type == EV_TIMEOUT) { - &DEBUG(0,"Can't connect to ",$self->{ip},":",$self->{port},", connection timeout"); - } elsif ($type == EV_WRITE) { - my $client = $e->fh; - unless($client->connected) { - &DEBUG(0,"Can't connect to ",$self->{ip},":",$self->{port},", connection rejected"); - } else { - $self->{retry_count} = 0; - $error = 0; - &DEBUG(1,"connected to ", $self->{ip},":",$self->{port}); - my $sid = &get_id($client); - my $main = event_new($client, EV_READ|EV_PERSIST, - \&handle_client, $self, $sid); - $self->init_new_conn( - sid => $sid, - event => $main, - type => "MASTER" - ); - $main->add; - - $self->frame_u_send($sid,"STARTDTACT"); - } - } - - if ($error && $self->{persist}) { - my $timer = timer_new(\&reconnect,$self); - $timer->add($self->{retry_timeout}); - } + my ( $e, $type, $self ) = @_; + my $error = 1; + + if ( $type == EV_TIMEOUT ) { + &DEBUG( 0, "Can't connect to ", + $self->{ip}, ":", $self->{port}, ", connection timeout" ); + } + elsif ( $type == EV_WRITE ) { + my $client = $e->fh; + unless ( $client->connected ) { + &DEBUG( 0, "Can't connect to ", + $self->{ip}, ":", $self->{port}, ", connection rejected" ); + } + else { + $self->{retry_count} = 0; + $error = 0; + &DEBUG( 1, "connected to ", $self->{ip}, ":", $self->{port} ); + my $sid = &get_id($client); + my $main = event_new( $client, EV_READ | EV_PERSIST, + \&handle_client, $self, $sid ); + $self->init_new_conn( + sid => $sid, + event => $main, + type => "MASTER" + ); + $main->add; + + $self->frame_u_send( $sid, "STARTDTACT" ); + } + } + + if ( $error && $self->{persist} ) { + my $timer = timer_new( \&reconnect, $self ); + $timer->add( $self->{retry_timeout} ); + } } # Main event loop sub main_loop { - event_mainloop; + event_mainloop; } # Sync time sub sync_time { - my ($cause,$s,$data); - my $self = shift; - my $csid = undef; - - if($#_ != -1 && defined($_[0])) { - $csid = shift; - } - - if ($self->{type} eq "master"){ - $cause = 6; # activation - } else { - $cause = 3; # sporadic - } - - foreach my $sid (keys %{$self->{sids}}) { - next if (defined($csid) && $csid ne $sid); - $s = \%{$self->{sids}{$sid}}; - &DEBUG(2,"Sync time for CA: ",$s->{ca},", My current time: ", time()); - $data = pack("C2S3C",103,1,$cause,$s->{ca},1,0) . &time_2_cp56_2a(gettimeofday); - $self->frame_i_send($sid,$data); - } + my ( $cause, $s, $data ); + my $self = shift; + my $csid = undef; + + if ( $#_ != -1 && defined( $_[0] ) ) { + $csid = shift; + } + + if ( $self->{type} eq "master" ) { + $cause = 6; # activation + } + else { + $cause = 3; # sporadic + } + + foreach my $sid ( keys %{ $self->{sids} } ) { + next if ( defined($csid) && $csid ne $sid ); + $s = \%{ $self->{sids}{$sid} }; + &DEBUG( 2, "Sync time for CA: ", + $s->{ca}, ", My current time: ", time() ); + $data = + pack( "C2S3C", 103, 1, $cause, $s->{ca}, 1, 0 ) + . &time_2_cp56_2a(gettimeofday); + $self->frame_i_send( $sid, $data ); + } } # handle incoming connections sub handle_incoming { - my $e = shift; - my $etype = shift; - my $self = shift; - my $h = $e->fh; - my $client = $h->accept or croak "$!"; - $client->blocking(0); - &DEBUG(1,"accept connection from ",$client->peerhost,":",$client->peerport); - - my $sid = &get_id($client); - # set up a new event that watches the client socket - my $event = event_new($client, EV_READ|EV_PERSIST, \&handle_client, $self, $sid); - $self->init_new_conn( - sid => $sid, - event => $event, - type => "SLAVE" - ); - $event->add; + my $e = shift; + my $etype = shift; + my $self = shift; + my $h = $e->fh; + my $client = $h->accept or croak "$!"; + $client->blocking(0); + &DEBUG( 1, "accept connection from ", + $client->peerhost, ":", $client->peerport ); + + my $sid = &get_id($client); + + # set up a new event that watches the client socket + my $event = + event_new( $client, EV_READ | EV_PERSIST, \&handle_client, $self, $sid ); + $self->init_new_conn( + sid => $sid, + event => $event, + type => "SLAVE" + ); + $event->add; } # init new structure for connection sub init_new_conn { - my $self = shift; - my %h = @_; - my $sid = $h{"sid"} or croak $@; - - $self->{sids}{$sid} = (); - my $s = \%{$self->{sids}{$sid}}; - - $s->{type} = ($h{type})?$h{type}:"SLAVE"; - $s->{event} = $h{"event"} or croak $@; - $s->{ufunc} = ($self->{ufunc})?$self->{ufunc}:"STOPDT"; - $s->{ca} = ($self->{ca})?$self->{ca}: 1; # common address of asdu - $s->{t0} = ($self->{t0})?$self->{t0}:30; # t0 timeout, sec - $s->{t1} = ($self->{t1})?$self->{t1}:15; # t1 timeout, sec - $s->{t2} = ($self->{t2})?$self->{t2}:10; # t2 timeout, sec - $s->{t3} = ($self->{t3})?$self->{t3}:20; # t3 timeout, sec - $s->{w} = ($self->{w} )?$self->{w} : 8; # w - $s->{k} = ($self->{k} )?$self->{k} :12; # k - $s->{vs} = 0; # Number S sended - $s->{vr} = 0; # Number S received - $s->{as} = 0; # Number S ack by me - $s->{ar} = 0; # Number S ack by peer - - $s->{rcb} = ($self->{read_callback} )?$self->{read_callback} :\&default_read_cb ; - $s->{wcb} = ($self->{write_callback})?$self->{write_callback}:\&default_write_cb; - - $s->{ts_fn} = ($self->{ts_func_num} )?$self->{ts_func_num} :30; - $s->{ti_fn} = ($self->{ti_func_num} )?$self->{ti_func_num} :36; - $s->{tii_fn} = ($self->{tii_func_num})?$self->{tii_func_num}:37; - - $s->{s_queue} = []; # Send queue - $s->{r_buf} = undef; # Receive buffer - - $s->{t0_timer} = timer_new(\&t0_timer_run,$self,$sid); - $s->{t1_timer} = timer_new(\&t1_timer_run,$self,$sid); - $s->{t2_timer} = timer_new(\&t2_timer_run,$self,$sid); - $s->{t3_timer} = timer_new(\&t3_timer_run,$self,$sid); - - $s->{ci_timeout} = ($self->{ci_timeout} )?$self->{ci_timeout} : 5*60; # CI timeout, sec - $s->{sync_timeout} = ($self->{sync_timeout})?$self->{sync_timeout}:60*60; # Sync timeout, sec - $s->{ci_timer} = timer_new(\&ci_timer_run, $self,$sid); - $s->{sync_timer} = timer_new(\&sync_timer_run,$self,$sid); - - $s->{time_diff} = 0; # diff in time between kp and control station - - $s->{t0_timer}->add($s->{t0}); # timeout of init connection + my $self = shift; + my %h = @_; + my $sid = $h{"sid"} or croak $@; + + $self->{sids}{$sid} = (); + my $s = \%{ $self->{sids}{$sid} }; + + $s->{type} = ( $h{type} ) ? $h{type} : "SLAVE"; + $s->{event} = $h{"event"} or croak $@; + $s->{ufunc} = ( $self->{ufunc} ) ? $self->{ufunc} : "STOPDT"; + $s->{ca} = ( $self->{ca} ) ? $self->{ca} : 1; # common address of asdu + $s->{t0} = ( $self->{t0} ) ? $self->{t0} : 30; # t0 timeout, sec + $s->{t1} = ( $self->{t1} ) ? $self->{t1} : 15; # t1 timeout, sec + $s->{t2} = ( $self->{t2} ) ? $self->{t2} : 10; # t2 timeout, sec + $s->{t3} = ( $self->{t3} ) ? $self->{t3} : 20; # t3 timeout, sec + $s->{w} = ( $self->{w} ) ? $self->{w} : 8; # w + $s->{k} = ( $self->{k} ) ? $self->{k} : 12; # k + $s->{vs} = 0; # Number S sended + $s->{vr} = 0; # Number S received + $s->{as} = 0; # Number S ack by me + $s->{ar} = 0; # Number S ack by peer + + $s->{rcb} = + ( $self->{read_callback} ) ? $self->{read_callback} : \&default_read_cb; + $s->{wcb} = + ( $self->{write_callback} ) + ? $self->{write_callback} + : \&default_write_cb; + + $s->{ts_fn} = ( $self->{ts_func_num} ) ? $self->{ts_func_num} : 30; + $s->{ti_fn} = ( $self->{ti_func_num} ) ? $self->{ti_func_num} : 36; + $s->{tii_fn} = ( $self->{tii_func_num} ) ? $self->{tii_func_num} : 37; + + $s->{s_queue} = []; # Send queue + $s->{r_buf} = undef; # Receive buffer + + $s->{t0_timer} = timer_new( \&t0_timer_run, $self, $sid ); + $s->{t1_timer} = timer_new( \&t1_timer_run, $self, $sid ); + $s->{t2_timer} = timer_new( \&t2_timer_run, $self, $sid ); + $s->{t3_timer} = timer_new( \&t3_timer_run, $self, $sid ); + + $s->{ci_timeout} = + ( $self->{ci_timeout} ) ? $self->{ci_timeout} : 5 * 60; # CI timeout, sec + $s->{sync_timeout} = + ( $self->{sync_timeout} ) + ? $self->{sync_timeout} + : 60 * 60; # Sync timeout, sec + $s->{ci_timer} = timer_new( \&ci_timer_run, $self, $sid ); + $s->{sync_timer} = timer_new( \&sync_timer_run, $self, $sid ); + + $s->{time_diff} = 0; # diff in time between kp and control station + + $s->{t0_timer}->add( $s->{t0} ); # timeout of init connection } # Invoked when the client's socket becomes readable sub handle_client { - my $data; - - my $e = shift; - my $etype = shift; - my $self = shift; - my $sid = shift; - my $h = $e->fh; - my $s = \%{$self->{sids}{$sid}}; - - unless ($h->connected) { - &DEBUG(9,"client disconnected"); - $self->disconnect($sid); - return; - } - if (eof($h)) { - &DEBUG(1,"connection not readable"); - $self->disconnect($sid); - return; - } - - if (defined($self->{sids}{$sid}{r_buf})) { - $data = $s->{r_buf}; - $s->{r_buf} = undef; - } - - while (<$h>){ - $data .= $_; - } - - my ($start,$length,$bits) = unpack("C3",$data); - while (1) { - if ($start != 0x68 || length($data) < 3) { - #error in iec104 packet - &DEBUG(11,raw2hex($data)); - &DEBUG(1,"error in iec104 chunk: START: ", $start, - "; LENGTH: ", defined($length)?$length:0 , "; DATA SIZE: ", length($data)); - $self->disconnect($sid); - return; - } - # retransmission (part of data will be received in next packet) - # put data in receive buffer - if ($length > length($data)-2) { - &DEBUG(2,"receive fragmented packet, save data in buffer and wait next chunk"); - &DEBUG(2,"LENGTH: ",length($data)-2," of ", $length, " bytes"); - $s->{r_buf} = $data; - return; - } - my $curdata = substr($data,0,$length+2); - &DEBUG(11,raw2hex($curdata)); - - # Receiving packet of any type restart a T3 timer - if ($s->{t3_timer}->pending) { - $s->{t3_timer}->remove; - } - $s->{t3_timer}->add($s->{t3}); - - if (($bits & 1) == 0) { - # frame type I - $self->frame_i_recv($sid,$curdata); - } elsif (($bits & 3) == 1) { - # frame type S - $self->frame_s_recv($sid,$curdata); - } elsif (($bits & 3) == 3) { - # frame type U - $self->frame_u_recv($sid,$curdata); - } else { - &DEBUG(0,"unknown frame type"); - } - $data = substr($data,$length+2); - return unless($data); - ($start,$length,$bits) = unpack("C3",$data) - } + my $data; + + my $e = shift; + my $etype = shift; + my $self = shift; + my $sid = shift; + my $h = $e->fh; + my $s = \%{ $self->{sids}{$sid} }; + + unless ( $h->connected ) { + &DEBUG( 9, "client disconnected" ); + $self->disconnect($sid); + return; + } + if ( eof($h) ) { + &DEBUG( 1, "connection not readable" ); + $self->disconnect($sid); + return; + } + + if ( defined( $self->{sids}{$sid}{r_buf} ) ) { + $data = $s->{r_buf}; + $s->{r_buf} = undef; + } + + while (<$h>) { + $data .= $_; + } + + my ( $start, $length, $bits ) = unpack( "C3", $data ); + while (1) { + if ( $start != 0x68 || length($data) < 3 ) { + + #error in iec104 packet + &DEBUG( 11, raw2hex($data) ); + &DEBUG( + 1, "error in iec104 chunk: START: ", + $start, "; LENGTH: ", + defined($length) ? $length : 0, + "; DATA SIZE: ", + length($data) + ); + $self->disconnect($sid); + return; + } + + # retransmission (part of data will be received in next packet) + # put data in receive buffer + if ( $length > length($data) - 2 ) { + &DEBUG( 2, +"receive fragmented packet, save data in buffer and wait next chunk" + ); + &DEBUG( 2, "LENGTH: ", length($data) - 2, + " of ", $length, " bytes" ); + $s->{r_buf} = $data; + return; + } + my $curdata = substr( $data, 0, $length + 2 ); + &DEBUG( 11, raw2hex($curdata) ); + + # Receiving packet of any type restart a T3 timer + if ( $s->{t3_timer}->pending ) { + $s->{t3_timer}->remove; + } + $s->{t3_timer}->add( $s->{t3} ); + + if ( ( $bits & 1 ) == 0 ) { + + # frame type I + $self->frame_i_recv( $sid, $curdata ); + } + elsif ( ( $bits & 3 ) == 1 ) { + + # frame type S + $self->frame_s_recv( $sid, $curdata ); + } + elsif ( ( $bits & 3 ) == 3 ) { + + # frame type U + $self->frame_u_recv( $sid, $curdata ); + } + else { + &DEBUG( 0, "unknown frame type" ); + } + $data = substr( $data, $length + 2 ); + return unless ($data); + ( $start, $length, $bits ) = unpack( "C3", $data ); + } } - # public method disconnect() # close client or server connection sub disconnect { - my $self = shift; - my ($e,$h); - my $csid = undef; - - if($#_ != -1 && defined($_[0])) { - $csid = shift; - } - - foreach my $sid (keys %{$self->{sids}}) { - next if (defined($csid) && $csid ne $sid); - $e = $self->{sids}{$sid}{event}; - $h = $e->fh; - &DEBUG(1,"disconnected from ",&sid2hex($sid)); - close $h; - $e->remove; - $self->{sids}{$sid}{t0_timer}->remove; - $self->{sids}{$sid}{t1_timer}->remove; - $self->{sids}{$sid}{t2_timer}->remove; - $self->{sids}{$sid}{t3_timer}->remove; - $self->{sids}{$sid}{ci_timer}->remove; - $self->{sids}{$sid}{sync_timer}->remove; - delete $self->{sids}{$sid}; - if ($self->{type} eq "master" && $self->{persist} != 0) { - &DEBUG(11,"persist is true, reconnect..."); - $self->connect(); - } else { - &DEBUG(11,"persist is not true, dont reconnect..."); - } - } + my $self = shift; + my ( $e, $h ); + my $csid = undef; + + if ( $#_ != -1 && defined( $_[0] ) ) { + $csid = shift; + } + + foreach my $sid ( keys %{ $self->{sids} } ) { + next if ( defined($csid) && $csid ne $sid ); + $e = $self->{sids}{$sid}{event}; + $h = $e->fh; + &DEBUG( 1, "disconnected from ", &sid2hex($sid) ); + close $h; + $e->remove; + $self->{sids}{$sid}{t0_timer}->remove; + $self->{sids}{$sid}{t1_timer}->remove; + $self->{sids}{$sid}{t2_timer}->remove; + $self->{sids}{$sid}{t3_timer}->remove; + $self->{sids}{$sid}{ci_timer}->remove; + $self->{sids}{$sid}{sync_timer}->remove; + delete $self->{sids}{$sid}; + + if ( $self->{type} eq "master" && $self->{persist} != 0 ) { + &DEBUG( 11, "persist is true, reconnect..." ); + $self->connect(); + } + else { + &DEBUG( 11, "persist is not true, dont reconnect..." ); + } + } } sub reconnect { - my $e = shift; - my $etype = shift; - my $self = shift; - my $timer; - - $self->{retry_count}++; - &DEBUG(1,"reconnect to ", $self->{ip}, ":",$self->{port}, " after fail... Attempt: ",$self->{retry_count}); - $self->connect; + my $e = shift; + my $etype = shift; + my $self = shift; + my $timer; + + $self->{retry_count}++; + &DEBUG( + 1, "reconnect to ", + $self->{ip}, ":", $self->{port}, " after fail... Attempt: ", + $self->{retry_count} + ); + $self->connect; } # public method send() # send spontaneous data to all client connections (only for SLAVE) sub send { - my $self = shift; - my $ca = shift; - my %h = @_; - return if ($self->{type} ne "slave"); - foreach my $sid (keys %{$self->{sids}}) { - next if ( - ! defined($self->{sids}{$sid}) || - $self->{sids}{$sid}{ufunc} eq "STOPDT" || - $self->{sids}{$sid}{ca} != $ca - ); - if (exists $h{"TI"} && $self->{sids}{$sid}{ti_fn} != 0) { - &{$asdu_type{$self->{sids}{$sid}{ti_fn}}{write_cb}}( - $self,$sid,3,$self->{sids}{$sid}{ti_fn},$h{"TI"} - ) - } - if (exists $h{"TS"} && $self->{sids}{$sid}{ts_fn} != 0) { - &{$asdu_type{$self->{sids}{$sid}{ts_fn}}{write_cb}}( - $self,$sid,3,$self->{sids}{$sid}{ts_fn},$h{"TS"} - ) - } - if (exists $h{"TII"} && $self->{sids}{$sid}{tii_fn} != 0) { - &{$asdu_type{$self->{sids}{$sid}{tii_fn}}{write_cb}}( - $self,$sid,3,$self->{sids}{$sid}{tii_fn},$h{"TII"} - ) - } - } + my $self = shift; + my $ca = shift; + my %h = @_; + return if ( $self->{type} ne "slave" ); + foreach my $sid ( keys %{ $self->{sids} } ) { + next + if ( !defined( $self->{sids}{$sid} ) + || $self->{sids}{$sid}{ufunc} eq "STOPDT" + || $self->{sids}{$sid}{ca} != $ca ); + if ( exists $h{"TI"} && $self->{sids}{$sid}{ti_fn} != 0 ) { + &{ $asdu_type{ $self->{sids}{$sid}{ti_fn} }{write_cb} }( $self, + $sid, 3, $self->{sids}{$sid}{ti_fn}, $h{"TI"} ); + } + if ( exists $h{"TS"} && $self->{sids}{$sid}{ts_fn} != 0 ) { + &{ $asdu_type{ $self->{sids}{$sid}{ts_fn} }{write_cb} }( $self, + $sid, 3, $self->{sids}{$sid}{ts_fn}, $h{"TS"} ); + } + if ( exists $h{"TII"} && $self->{sids}{$sid}{tii_fn} != 0 ) { + &{ $asdu_type{ $self->{sids}{$sid}{tii_fn} }{write_cb} }( $self, + $sid, 3, $self->{sids}{$sid}{tii_fn}, $h{"TII"} ); + } + } } sub t0_timer_run { - my $e = shift; - my $type = shift; - my $self = shift; - my $sid = shift; - - &DEBUG(1,"sid: ",&sid2hex($sid),". t0 timer run"); - $self->disconnect($sid); + my $e = shift; + my $type = shift; + my $self = shift; + my $sid = shift; + + &DEBUG( 1, "sid: ", &sid2hex($sid), ". t0 timer run" ); + $self->disconnect($sid); } sub t1_timer_run { - my $e = shift; - my $type = shift; - my $self = shift; - my $sid = shift; - &DEBUG(1,"sid: ",&sid2hex($sid),". t1 timer run"); - $self->disconnect($sid); + my $e = shift; + my $type = shift; + my $self = shift; + my $sid = shift; + &DEBUG( 1, "sid: ", &sid2hex($sid), ". t1 timer run" ); + $self->disconnect($sid); } sub t2_timer_run { - my $e = shift; - my $type = shift; - my $self = shift; - my $sid = shift; - &DEBUG(2,"t2 timer run"); - $self->frame_s_send($sid); + my $e = shift; + my $type = shift; + my $self = shift; + my $sid = shift; + &DEBUG( 2, "t2 timer run" ); + $self->frame_s_send($sid); } sub t3_timer_run { - my $e = shift; - my $type = shift; - my $self= shift; - my $sid = shift; - my $s = \%{$self->{sids}{$sid}}; - &DEBUG(1,"sid: ",&sid2hex($sid),". t3 timer run"); - $self->frame_u_send($sid,"TESTFRACT"); - unless ($s->{t1_timer}->pending) { - $s->{t1_timer}->add($s->{t1}); - } + my $e = shift; + my $type = shift; + my $self = shift; + my $sid = shift; + my $s = \%{ $self->{sids}{$sid} }; + &DEBUG( 1, "sid: ", &sid2hex($sid), ". t3 timer run" ); + $self->frame_u_send( $sid, "TESTFRACT" ); + unless ( $s->{t1_timer}->pending ) { + $s->{t1_timer}->add( $s->{t1} ); + } } sub ci_timer_run { - my $e = shift; - my $type = shift; - my $self= shift; - my $sid = shift; - &DEBUG(1,"sid: ",&sid2hex($sid),". ci timer run"); - $self->frame_i_send($sid,pack("C2S3C2",100,1,6,$self->{sids}{$sid}{ca},0,0,20)); + my $e = shift; + my $type = shift; + my $self = shift; + my $sid = shift; + &DEBUG( 1, "sid: ", &sid2hex($sid), ". ci timer run" ); + $self->frame_i_send( $sid, + pack( "C2S3C2", 100, 1, 6, $self->{sids}{$sid}{ca}, 0, 0, 20 ) ); } sub sync_timer_run { - my $e = shift; - my $type = shift; - my $self= shift; - my $sid = shift; - &DEBUG(1,"sid: ",&sid2hex($sid),". sync timer run"); - $self->sync_time($sid); + my $e = shift; + my $type = shift; + my $self = shift; + my $sid = shift; + &DEBUG( 1, "sid: ", &sid2hex($sid), ". sync timer run" ); + $self->sync_time($sid); } # send frame type U sub frame_u_send { - my $self = shift; - my $sid = shift; - my $func = shift; - my $bits = 3; - - &DEBUG(3,"send frame type U, function ",$func, ": "); - if ($func eq "STARTDTACT") { - $bits |= 1<<2; - } elsif ($func eq "STARTDTCON") { - $bits |= 1<<3; - } elsif ($func eq "STOPDTACT") { - $bits |= 1<<4; - } elsif ($func eq "STOPDTCON") { - $bits |= 1<<5; - } elsif ($func eq "TESTFRACT") { - $bits |= 1<<6; - } elsif ($func eq "TESTFRCON") { - $bits |= 1<<7; - } - - return unless (defined($self->{sids}{$sid}{event})); - my $sock = $self->{sids}{$sid}{event}->fh; - my $data = pack("C6",0x68,4,$bits,0,0,0); - &DEBUG(3,raw2hex($data)); - print $sock $data; + my $self = shift; + my $sid = shift; + my $func = shift; + my $bits = 3; + + &DEBUG( 3, "send frame type U, function ", $func, ": " ); + if ( $func eq "STARTDTACT" ) { + $bits |= 1 << 2; + } + elsif ( $func eq "STARTDTCON" ) { + $bits |= 1 << 3; + } + elsif ( $func eq "STOPDTACT" ) { + $bits |= 1 << 4; + } + elsif ( $func eq "STOPDTCON" ) { + $bits |= 1 << 5; + } + elsif ( $func eq "TESTFRACT" ) { + $bits |= 1 << 6; + } + elsif ( $func eq "TESTFRCON" ) { + $bits |= 1 << 7; + } + + return unless ( defined( $self->{sids}{$sid}{event} ) ); + my $sock = $self->{sids}{$sid}{event}->fh; + my $data = pack( "C6", 0x68, 4, $bits, 0, 0, 0 ); + &DEBUG( 3, raw2hex($data) ); + print $sock $data; } sub frame_u_recv { - my $self = shift; - my $sid = shift; - my $data = shift; - - my ($bits) = (unpack("C3",$data))[2]; - &DEBUG(3,"received frame type U, function "); - - if ($bits & 1<<2){ - # STARTDT ACT - &DEBUG(3,"STARTDTACT"); - if ($self->{sids}{$sid}{type} ne "SLAVE") { - &DEBUG(0, "master receive STARTDT ACT"); - return -1; - } - # T0 timer removed after init - $self->{sids}{$sid}{t0_timer}->remove; - $self->{sids}{$sid}{ufunc} = "STARTDT"; - $self->frame_u_send($sid,"STARTDTCON"); - } elsif ($bits & 1<<3) { - # STARTDT CON - &DEBUG(3,"STARTDTCON"); - if ($self->{sids}{$sid}{type} ne "MASTER") { - return -1; - } - # Slave answer on init -> remove T0 timer - $self->{sids}{$sid}{t0_timer}->remove; - - $self->{sids}{$sid}{ufunc} = "STARTDT"; - - # Connection activated. - # Sync time - $self->sync_time($sid); - # Start common interogation - $self->frame_i_send($sid,pack("C2S3C2",100,1,6,$self->{sids}{$sid}{ca},0,0,20)); - - } elsif ($bits & 1<<4) { - # STOPDT ACT - &DEBUG(3,"STOPDTACT"); - if ($self->{sids}{$sid}{type} ne "SLAVE") { - return -1; - } - $self->{sids}{$sid}{ufunc} = "STOPDT"; - $self->frame_u_send($sid,"STOPDTCON"); - } elsif ($bits & 1<<5) { - # STOPDT CON - &DEBUG(3,"STOPDTCON"); - if ($self->{sids}{$sid}{type} ne "MASTER") { - return -1; - } - $self->{sids}{$sid}{ufunc} = "STOPDT"; - } elsif ($bits & 1<<6) { - # TESTFR ACT - &DEBUG(3,"TESTFRACT"); - $self->frame_u_send($sid,"TESTFRCON"); - } elsif ($bits & 1<<7) { - # TESTFR CON - &DEBUG(3,"TESTFRCON"); - $self->{sids}{$sid}{t1_timer}->remove; - } else { - &DEBUG(3,"unknown"); - &DEBUG(0, "Error in received U frame: unknown type"); - } + my $self = shift; + my $sid = shift; + my $data = shift; + + my ($bits) = ( unpack( "C3", $data ) )[2]; + &DEBUG( 3, "received frame type U, function " ); + + if ( $bits & 1 << 2 ) { + + # STARTDT ACT + &DEBUG( 3, "STARTDTACT" ); + if ( $self->{sids}{$sid}{type} ne "SLAVE" ) { + &DEBUG( 0, "master receive STARTDT ACT" ); + return -1; + } + + # T0 timer removed after init + $self->{sids}{$sid}{t0_timer}->remove; + $self->{sids}{$sid}{ufunc} = "STARTDT"; + $self->frame_u_send( $sid, "STARTDTCON" ); + } + elsif ( $bits & 1 << 3 ) { + + # STARTDT CON + &DEBUG( 3, "STARTDTCON" ); + if ( $self->{sids}{$sid}{type} ne "MASTER" ) { + return -1; + } + + # Slave answer on init -> remove T0 timer + $self->{sids}{$sid}{t0_timer}->remove; + + $self->{sids}{$sid}{ufunc} = "STARTDT"; + + # Connection activated. + # Sync time + $self->sync_time($sid); + + # Start common interogation + $self->frame_i_send( $sid, + pack( "C2S3C2", 100, 1, 6, $self->{sids}{$sid}{ca}, 0, 0, 20 ) ); + + } + elsif ( $bits & 1 << 4 ) { + + # STOPDT ACT + &DEBUG( 3, "STOPDTACT" ); + if ( $self->{sids}{$sid}{type} ne "SLAVE" ) { + return -1; + } + $self->{sids}{$sid}{ufunc} = "STOPDT"; + $self->frame_u_send( $sid, "STOPDTCON" ); + } + elsif ( $bits & 1 << 5 ) { + + # STOPDT CON + &DEBUG( 3, "STOPDTCON" ); + if ( $self->{sids}{$sid}{type} ne "MASTER" ) { + return -1; + } + $self->{sids}{$sid}{ufunc} = "STOPDT"; + } + elsif ( $bits & 1 << 6 ) { + + # TESTFR ACT + &DEBUG( 3, "TESTFRACT" ); + $self->frame_u_send( $sid, "TESTFRCON" ); + } + elsif ( $bits & 1 << 7 ) { + + # TESTFR CON + &DEBUG( 3, "TESTFRCON" ); + $self->{sids}{$sid}{t1_timer}->remove; + } + else { + &DEBUG( 3, "unknown" ); + &DEBUG( 0, "Error in received U frame: unknown type" ); + } } -# send frame type S +# send frame type S sub frame_s_send { - my $self = shift; - my $sid = shift; - if ($self->{sids}{$sid}{as} == $self->{sids}{$sid}{vr}) { - &DEBUG(2,"not sending frame type S, AS=VR (",$self->{sids}{$sid}{as},"=",$self->{sids}{$sid}{vr},")"); - return; - } - &DEBUG(3,"send frame type S(",$self->{sids}{$sid}{vr},")"); - my $sock = $self->{sids}{$sid}{event}->fh; - print $sock pack("C4S",0x68,4,1,0,$self->{sids}{$sid}{vr}<<1); - $self->{sids}{$sid}{as} = $self->{sids}{$sid}{vr}; + my $self = shift; + my $sid = shift; + if ( $self->{sids}{$sid}{as} == $self->{sids}{$sid}{vr} ) { + &DEBUG( + 2, + "not sending frame type S, AS=VR (", + $self->{sids}{$sid}{as}, + "=", $self->{sids}{$sid}{vr}, ")" + ); + return; + } + &DEBUG( 3, "send frame type S(", $self->{sids}{$sid}{vr}, ")" ); + my $sock = $self->{sids}{$sid}{event}->fh; + print $sock pack( "C4S", 0x68, 4, 1, 0, $self->{sids}{$sid}{vr} << 1 ); + $self->{sids}{$sid}{as} = $self->{sids}{$sid}{vr}; } # receive frame type S sub frame_s_recv { - my $self = shift; - my $sid = shift; - my $data = shift; - - my ($nr) = (unpack("C2S2",$data))[3]; - $nr>>=1; - if ((($self->{sids}{$sid}{vs} - $nr + 32768) % 32768) > $self->{sids}{$sid}{k}) { - &DEBUG(0, "wrong N(R) number in S-ack received: ", $nr, ". Current N(S) = ", $self->{sids}{$sid}{vs}); - $self->disconnect($sid); - return; - } - &DEBUG(3,"received frame type S(", $nr,")"); - $self->{sids}{$sid}{ar} = $nr; - $self->{sids}{$sid}{t1_timer}->remove; - - $self->flush_send_queue($sid); + my $self = shift; + my $sid = shift; + my $data = shift; + + my ($nr) = ( unpack( "C2S2", $data ) )[3]; + $nr >>= 1; + if ( ( ( $self->{sids}{$sid}{vs} - $nr + 32768 ) % 32768 ) > + $self->{sids}{$sid}{k} ) + { + &DEBUG( + 0, "wrong N(R) number in S-ack received: ", + $nr, + ". Current N(S) = ", + $self->{sids}{$sid}{vs} + ); + $self->disconnect($sid); + return; + } + &DEBUG( 3, "received frame type S(", $nr, ")" ); + $self->{sids}{$sid}{ar} = $nr; + $self->{sids}{$sid}{t1_timer}->remove; + + $self->flush_send_queue($sid); } sub flush_send_queue { - my $ret; - my $self = shift; - my $sid = shift; - -# return if ($#{$self->{sids}{$sid}{s_queue}} == -1); -# for my $i (0 .. $#{$self->{sids}{$sid}{s_queue}}) { -# $ret = $self->frame_i_send($sid,undef); -# last if ($ret != 0); -# } - while( $#{$self->{sids}{$sid}{s_queue}} != -1 ) { - last if ($self->frame_i_send($sid,undef)); - } + my $ret; + my $self = shift; + my $sid = shift; + + # return if ($#{$self->{sids}{$sid}{s_queue}} == -1); + # for my $i (0 .. $#{$self->{sids}{$sid}{s_queue}}) { + # $ret = $self->frame_i_send($sid,undef); + # last if ($ret != 0); + # } + while ( $#{ $self->{sids}{$sid}{s_queue} } != -1 ) { + last if ( $self->frame_i_send( $sid, undef ) ); + } } sub frame_i_send { - my $self = shift; - my $sid = shift; - my $asdu = shift; - - unless(exists($self->{sids}{$sid})) { - &DEBUG(0,"frame_i_send(): error, connection ",&sid2hex($sid)," already dead\n"); - for my $j (0..3) { - &DEBUG(0,join("->",(caller($j))[0,1,2,3])); - } - return -1; - } - if ($self->{sids}{$sid}{vs} == ($self->{sids}{$sid}{ar} + $self->{sids}{$sid}{k}) % 32768) { - &DEBUG(2,"reached k, no frames will be sent"); - if ($#{$self->{sids}{$sid}{s_queue}}+1 > $MAX_S_QUEUE ) { - &DEBUG(0,$self->sidinfo($sid)); - &DEBUG(0,"send queue overloaded, asdu dropped"); - $self->disconnect($sid); - return 2; - } elsif (defined($asdu)) { - push @{$self->{sids}{$sid}{s_queue}}, $asdu; - if ($self->{sids}{$sid}{type} eq "SLAVE" && $#{$self->{sids}{$sid}{s_queue}} > $MAX_S_QUEUE*0.9) { - &DEBUG(0,&sid2hex($sid),", queue increased: ",($#{$self->{sids}{$sid}{s_queue}}+1)); - } - &DEBUG(2,"asdu queued (No:",$#{$self->{sids}{$sid}{s_queue}}+1,")"); - } - return 1; - } - unless (defined($asdu)) { - $asdu = shift(@{$self->{sids}{$sid}{s_queue}}) if ($#{$self->{sids}{$sid}{s_queue}}>=0); - unless(defined($asdu)) { - &DEBUG(0,"Error: empty asdu"); - return 3; - } else { - if ($self->{sids}{$sid}{type} eq "SLAVE" && $#{$self->{sids}{$sid}{s_queue}} > $MAX_S_QUEUE*0.9) { - &DEBUG(0,&sid2hex($sid),", queue reduced: ",($#{$self->{sids}{$sid}{s_queue}}+1)); - } - &DEBUG(3,"send frame type I from queue: "); - } - } else { - &DEBUG(3,"send frame type I: "); - } - - $self->{sids}{$sid}{t1_timer}->remove; - $self->{sids}{$sid}{t1_timer}->add($self->{sids}{$sid}{t1}); - - my $ns = $self->{sids}{$sid}{vs}<<1; - my $nr = $self->{sids}{$sid}{vr}<<1; - my $sock = $self->{sids}{$sid}{event}->fh; - my $data = pack("C2S2",0x68,4+length($asdu),$ns,$nr) . $asdu; - DEBUG(3,raw2hex($data)); - print $sock $data; - - $self->{sids}{$sid}{vs} = ($self->{sids}{$sid}{vs}+1) % 32768; - return 0; + my $self = shift; + my $sid = shift; + my $asdu = shift; + + unless ( exists( $self->{sids}{$sid} ) ) { + &DEBUG( 0, "frame_i_send(): error, connection ", + &sid2hex($sid), " already dead\n" ); + for my $j ( 0 .. 3 ) { + &DEBUG( 0, join( "->", ( caller($j) )[ 0, 1, 2, 3 ] ) ); + } + return -1; + } + if ( $self->{sids}{$sid}{vs} == + ( $self->{sids}{$sid}{ar} + $self->{sids}{$sid}{k} ) % 32768 ) + { + &DEBUG( 2, "reached k, no frames will be sent" ); + if ( $#{ $self->{sids}{$sid}{s_queue} } + 1 > $MAX_S_QUEUE ) { + &DEBUG( 0, $self->sidinfo($sid) ); + &DEBUG( 0, "send queue overloaded, asdu dropped" ); + $self->disconnect($sid); + return 2; + } + elsif ( defined($asdu) ) { + push @{ $self->{sids}{$sid}{s_queue} }, $asdu; + if ( $self->{sids}{$sid}{type} eq "SLAVE" + && $#{ $self->{sids}{$sid}{s_queue} } > $MAX_S_QUEUE * 0.9 ) + { + &DEBUG( + 0, &sid2hex($sid), + ", queue increased: ", + ( $#{ $self->{sids}{$sid}{s_queue} } + 1 ) + ); + } + &DEBUG( + 2, + "asdu queued (No:", + $#{ $self->{sids}{$sid}{s_queue} } + 1, ")" + ); + } + return 1; + } + unless ( defined($asdu) ) { + $asdu = shift( @{ $self->{sids}{$sid}{s_queue} } ) + if ( $#{ $self->{sids}{$sid}{s_queue} } >= 0 ); + unless ( defined($asdu) ) { + &DEBUG( 0, "Error: empty asdu" ); + return 3; + } + else { + if ( $self->{sids}{$sid}{type} eq "SLAVE" + && $#{ $self->{sids}{$sid}{s_queue} } > $MAX_S_QUEUE * 0.9 ) + { + &DEBUG( + 0, &sid2hex($sid), + ", queue reduced: ", + ( $#{ $self->{sids}{$sid}{s_queue} } + 1 ) + ); + } + &DEBUG( 3, "send frame type I from queue: " ); + } + } + else { + &DEBUG( 3, "send frame type I: " ); + } + + $self->{sids}{$sid}{t1_timer}->remove; + $self->{sids}{$sid}{t1_timer}->add( $self->{sids}{$sid}{t1} ); + + my $ns = $self->{sids}{$sid}{vs} << 1; + my $nr = $self->{sids}{$sid}{vr} << 1; + my $sock = $self->{sids}{$sid}{event}->fh; + my $data = pack( "C2S2", 0x68, 4 + length($asdu), $ns, $nr ) . $asdu; + DEBUG( 3, raw2hex($data) ); + print $sock $data; + + $self->{sids}{$sid}{vs} = ( $self->{sids}{$sid}{vs} + 1 ) % 32768; + return 0; } sub frame_i_recv { - my $self = shift; - my $sid = shift; - my $data = shift; - DEBUG(3,"received frame type I "); - if ($self->{sids}{$sid}{ufunc} ne "STARTDT") { - DEBUG(0,"Error: no STARTDT in current connection"); - return -1; - } - - my ($ns,$nr) = (unpack("C2S2",$data))[2,3]; - $ns>>=1; - $nr>>=1; - DEBUG(3,"NS=$ns,NR=$nr"); - if ($ns != $self->{sids}{$sid}{vr}) { - &DEBUG(0, "Error: Expect N(S)=",$self->{sids}{$sid}{vr}, - ", but receive: ",$ns,". Packet lost or reordered"); - return -1; - } - if ( ($nr != $self->{sids}{$sid}{ar}) && - (($nr - $self->{sids}{$sid}{ar} + 32768) % 32768) <= $self->{sids}{$sid}{k} && - (($self->{sids}{$sid}{vs} - $nr + 32768) % 32768) <= $self->{sids}{$sid}{k} ) { - $self->{sids}{$sid}{ar} = $nr; - $self->{sids}{$sid}{t1_timer}->remove; - } - - $self->{sids}{$sid}{t2_timer}->remove(); - $self->{sids}{$sid}{t2_timer}->add($self->{sids}{$sid}{t2}); - - $self->{sids}{$sid}{vr} = ($ns+1) % 32768; - if (($self->{sids}{$sid}{vr} - $self->{sids}{$sid}{as} + 32768) % 32768 == $self->{sids}{$sid}{w}){ - $self->frame_s_send($sid); - } - - $self->parse_asdu($sid,substr($data,6)); + my $self = shift; + my $sid = shift; + my $data = shift; + DEBUG( 3, "received frame type I " ); + if ( $self->{sids}{$sid}{ufunc} ne "STARTDT" ) { + DEBUG( 0, "Error: no STARTDT in current connection" ); + return -1; + } + + my ( $ns, $nr ) = ( unpack( "C2S2", $data ) )[ 2, 3 ]; + $ns >>= 1; + $nr >>= 1; + DEBUG( 3, "NS=$ns,NR=$nr" ); + if ( $ns != $self->{sids}{$sid}{vr} ) { + &DEBUG( + 0, + "Error: Expect N(S)=", + $self->{sids}{$sid}{vr}, + ", but receive: ", + $ns, ". Packet lost or reordered" + ); + return -1; + } + if ( ( $nr != $self->{sids}{$sid}{ar} ) + && ( ( $nr - $self->{sids}{$sid}{ar} + 32768 ) % 32768 ) <= + $self->{sids}{$sid}{k} + && ( ( $self->{sids}{$sid}{vs} - $nr + 32768 ) % 32768 ) <= + $self->{sids}{$sid}{k} ) + { + $self->{sids}{$sid}{ar} = $nr; + $self->{sids}{$sid}{t1_timer}->remove; + } + + $self->{sids}{$sid}{t2_timer}->remove(); + $self->{sids}{$sid}{t2_timer}->add( $self->{sids}{$sid}{t2} ); + + $self->{sids}{$sid}{vr} = ( $ns + 1 ) % 32768; + if ( + ( $self->{sids}{$sid}{vr} - $self->{sids}{$sid}{as} + 32768 ) % 32768 == + $self->{sids}{$sid}{w} ) + { + $self->frame_s_send($sid); + } + + $self->parse_asdu( $sid, substr( $data, 6 ) ); } sub parse_asdu { - my $self = shift; - my $sid = shift; - my $asdu = shift; - - my $i = \%{$self->{sids}{$sid}{i}}; - my $kps; - - &DEBUG(12,"parse_asdu(): ",raw2hex($asdu)); - ($i->{id},$kps,$i->{cause},$i->{ca}) = unpack("C2S2",$asdu); - $i->{sq} = ($kps & 1<<7)>>7; - $i->{nobj} = $kps & 0x7F; - $i->{cause} &= 0xFF; - &DEBUG(3,"ID: ",sprintf("%02X",$i->{id}),", "); - &DEBUG(3,"SQ: ",$i->{sq},", "); - &DEBUG(3,"NObj: ",sprintf("%02X",$i->{nobj}),", "); - &DEBUG(3,"Cause: ",sprintf("%02X",$i->{cause}),", "); - &DEBUG(3,"C.Addr ASDU: ",sprintf("%02X",$i->{ca})); - - unless (exists $asdu_type{$i->{id}}) { - &DEBUG(-1,"not implemented type ",$i->{id}); - return; - } - - if ($i->{sq} == 0) { - # Numerous information objects - for my $j (1..$i->{nobj}) { - my $k = ($j-1)*(3+$asdu_type{$i->{id}}{size}); - ($i->{obj}{$j}{ioa},$i->{obj}{$j}{ioa2}) = - unpack("SC",substr($asdu,6+$k,3)); - $i->{obj}{$j}{data} = substr($asdu,6+3+$k,$asdu_type{$i->{id}}{size}); - } - } else { - # One object, numerous elements - # <{TODO}> - &DEBUG(-1,"not implemented code"); - } - &{$asdu_type{$i->{id}}{parse_cb}}($self,$sid); + my $self = shift; + my $sid = shift; + my $asdu = shift; + + my $i = \%{ $self->{sids}{$sid}{i} }; + my $kps; + + &DEBUG( 12, "parse_asdu(): ", raw2hex($asdu) ); + ( $i->{id}, $kps, $i->{cause}, $i->{ca} ) = unpack( "C2S2", $asdu ); + $i->{sq} = ( $kps & 1 << 7 ) >> 7; + $i->{nobj} = $kps & 0x7F; + $i->{cause} &= 0xFF; + &DEBUG( 3, "ID: ", sprintf( "%02X", $i->{id} ), ", " ); + &DEBUG( 3, "SQ: ", $i->{sq}, ", " ); + &DEBUG( 3, "NObj: ", sprintf( "%02X", $i->{nobj} ), ", " ); + &DEBUG( 3, "Cause: ", sprintf( "%02X", $i->{cause} ), ", " ); + &DEBUG( 3, "C.Addr ASDU: ", sprintf( "%02X", $i->{ca} ) ); + + unless ( exists $asdu_type{ $i->{id} } ) { + &DEBUG( -1, "not implemented type ", $i->{id} ); + return; + } + + if ( $i->{sq} == 0 ) { + + # Numerous information objects + for my $j ( 1 .. $i->{nobj} ) { + my $k = ( $j - 1 ) * ( 3 + $asdu_type{ $i->{id} }{size} ); + ( $i->{obj}{$j}{ioa}, $i->{obj}{$j}{ioa2} ) = + unpack( "SC", substr( $asdu, 6 + $k, 3 ) ); + $i->{obj}{$j}{data} = + substr( $asdu, 6 + 3 + $k, $asdu_type{ $i->{id} }{size} ); + } + } + else { + + # One object, numerous elements + # <{TODO}> + &DEBUG( -1, "not implemented code" ); + } + &{ $asdu_type{ $i->{id} }{parse_cb} }( $self, $sid ); } sub default_read_cb { - DEBUG(1,"default_read_cb()"); + DEBUG( 1, "default_read_cb()" ); } sub default_write_cb { - DEBUG(1,"default_write_cb()"); - return ("TS"=>{"1"=>["0",gettimeofday]}); + DEBUG( 1, "default_write_cb()" ); + return ( "TS" => { "1" => [ "0", gettimeofday ] } ); } sub parse_asdu_type_100 { - my $self = shift; - my $sid = shift; - my $s = \%{$self->{sids}{$sid}}; - my $i = \%{$s->{i}}; - if ($i->{cause} == 6 ) { - &DEBUG(0,&sid2hex($sid)," [100] activation"); - # drop outgoing queue - if ($#{$self->{sids}{$sid}{s_queue}} >= 0) { - &DEBUG(0,&sid2hex($sid), - " drop ", ($#{$self->{sids}{$sid}{s_queue}}+1)," asdu from queue because of activation"); - $self->{sids}{$sid}{s_queue} = []; - } - # send accept - $s->{ca} = $i->{ca}; - my $data = pack("C2S3C",100,1,7,$i->{ca}, - $i->{obj}{1}{ioa},$i->{obj}{1}{ioa2}) . $i->{obj}{1}{data}; - $self->frame_i_send($sid,$data); - # 1. Send whole database - my %h = &{$s->{wcb}}($self,$s->{ca}); - - if (exists $h{"TI"} && $s->{ti_fn} != 0) { - &{$asdu_type{$s->{ti_fn}}{write_cb}}( - $self,$sid,3,$s->{ti_fn},$h{"TI"} - ) - } - if (exists $h{"TS"} && $s->{ts_fn} != 0) { - &{$asdu_type{$s->{ts_fn}}{write_cb}}( - $self,$sid,3,$s->{ts_fn},$h{"TS"} - ) - } - if (exists $h{"TII"} && $s->{tii_fn} != 0) { - &{$asdu_type{$s->{tii_fn}}{write_cb}}( - $self,$sid,3,$s->{tii_fn},$h{"TII"} - ) - } - } elsif ($i->{cause} == 8) { - &DEBUG(0,&sid2hex($sid)," [100] deactivation"); - # <{TODO}> - } elsif ($i->{cause} == 7) { - &DEBUG(2,&sid2hex($sid)," [100] accept of activation"); - - $s->{t1_timer}->remove; - # Start timer Common Interogation - $s->{ci_timer}->remove; - $s->{ci_timer}->add($s->{ci_timeout}); - # <{TODO}> - } elsif ($i->{cause} == 9) { - &DEBUG(2,"[100] accept of deactivation"); - # <{TODO}> - } elsif ($i->{cause} == 10) { - &DEBUG(2,&sid2hex($sid)," [100] close activation"); - # <{TODO}> - } else { - &DEBUG(2,"[100] unknown cause: ", $i->{cause}); - } + my $self = shift; + my $sid = shift; + my $s = \%{ $self->{sids}{$sid} }; + my $i = \%{ $s->{i} }; + if ( $i->{cause} == 6 ) { + &DEBUG( 0, &sid2hex($sid), " [100] activation" ); + + # drop outgoing queue + if ( $#{ $self->{sids}{$sid}{s_queue} } >= 0 ) { + &DEBUG( + 0, &sid2hex($sid), " drop ", + ( $#{ $self->{sids}{$sid}{s_queue} } + 1 ), + " asdu from queue because of activation" + ); + $self->{sids}{$sid}{s_queue} = []; + } + + # send accept + $s->{ca} = $i->{ca}; + my $data = pack( "C2S3C", + 100, 1, 7, $i->{ca}, + $i->{obj}{1}{ioa}, + $i->{obj}{1}{ioa2} ) + . $i->{obj}{1}{data}; + $self->frame_i_send( $sid, $data ); + + # 1. Send whole database + my %h = &{ $s->{wcb} }( $self, $s->{ca} ); + + if ( exists $h{"TI"} && $s->{ti_fn} != 0 ) { + &{ $asdu_type{ $s->{ti_fn} }{write_cb} }( $self, $sid, 3, + $s->{ti_fn}, $h{"TI"} ); + } + if ( exists $h{"TS"} && $s->{ts_fn} != 0 ) { + &{ $asdu_type{ $s->{ts_fn} }{write_cb} }( $self, $sid, 3, + $s->{ts_fn}, $h{"TS"} ); + } + if ( exists $h{"TII"} && $s->{tii_fn} != 0 ) { + &{ $asdu_type{ $s->{tii_fn} }{write_cb} }( $self, $sid, 3, + $s->{tii_fn}, $h{"TII"} ); + } + } + elsif ( $i->{cause} == 8 ) { + &DEBUG( 0, &sid2hex($sid), " [100] deactivation" ); + + # <{TODO}> + } + elsif ( $i->{cause} == 7 ) { + &DEBUG( 2, &sid2hex($sid), " [100] accept of activation" ); + + $s->{t1_timer}->remove; + + # Start timer Common Interogation + $s->{ci_timer}->remove; + $s->{ci_timer}->add( $s->{ci_timeout} ); + + # <{TODO}> + } + elsif ( $i->{cause} == 9 ) { + &DEBUG( 2, "[100] accept of deactivation" ); + + # <{TODO}> + } + elsif ( $i->{cause} == 10 ) { + &DEBUG( 2, &sid2hex($sid), " [100] close activation" ); + + # <{TODO}> + } + else { + &DEBUG( 2, "[100] unknown cause: ", $i->{cause} ); + } } sub parse_asdu_type_103 { - my $self = shift; - my $sid = shift; - my $s = \%{$self->{sids}{$sid}}; - my $i = \%{$s->{i}}; - if ($i->{cause} == 6 ) { - &DEBUG(2,"[103] activation"); - # send accept and current time - $s->{ca} = $i->{ca}; - my $data = pack("C2S3C",103,1,7,$i->{ca}, - $i->{obj}{1}{ioa},$i->{obj}{1}{ioa2}) . &time_2_cp56_2a(gettimeofday); - $self->frame_i_send($sid,$data); - } elsif ($i->{cause} == 3) { - &DEBUG(2,"[103] sporadic"); - # Start timer of Sync - $s->{sync_timer}->remove; - $s->{sync_timer}->add($s->{sync_timeout}); - } elsif ($i->{cause} == 7) { - &DEBUG(2,"[103] accept of activation"); - my ($tm,$ms) = &cp56_2a_2_time($i->{obj}{1}{data}); - my $time_diff = tv_interval([$tm,$ms]); - &DEBUG(1,"CA: ",$i->{ca},"; KP unix time: ", $tm, ", Diff: ",$time_diff ); - $s->{time_diff} = $time_diff; - # Start timer of Sync - $s->{sync_timer}->remove; - $s->{sync_timer}->add($s->{sync_timeout}); - } else { - &DEBUG(2,"[103] unknown cause: ", $i->{cause}); - } + my $self = shift; + my $sid = shift; + my $s = \%{ $self->{sids}{$sid} }; + my $i = \%{ $s->{i} }; + if ( $i->{cause} == 6 ) { + &DEBUG( 2, "[103] activation" ); + + # send accept and current time + $s->{ca} = $i->{ca}; + my $data = pack( "C2S3C", + 103, 1, 7, $i->{ca}, + $i->{obj}{1}{ioa}, + $i->{obj}{1}{ioa2} ) + . &time_2_cp56_2a(gettimeofday); + $self->frame_i_send( $sid, $data ); + } + elsif ( $i->{cause} == 3 ) { + &DEBUG( 2, "[103] sporadic" ); + + # Start timer of Sync + $s->{sync_timer}->remove; + $s->{sync_timer}->add( $s->{sync_timeout} ); + } + elsif ( $i->{cause} == 7 ) { + &DEBUG( 2, "[103] accept of activation" ); + my ( $tm, $ms ) = &cp56_2a_2_time( $i->{obj}{1}{data} ); + my $time_diff = tv_interval( [ $tm, $ms ] ); + &DEBUG( 1, "CA: ", $i->{ca}, "; KP unix time: ", + $tm, ", Diff: ", $time_diff ); + $s->{time_diff} = $time_diff; + + # Start timer of Sync + $s->{sync_timer}->remove; + $s->{sync_timer}->add( $s->{sync_timeout} ); + } + else { + &DEBUG( 2, "[103] unknown cause: ", $i->{cause} ); + } } sub parse_asdu_type_0_44 { - my ($addr,$value,$tm,$ms); - my $self = shift; - my $sid = shift; - - my $s = \%{$self->{sids}{$sid}}; - my $i = \%{$s->{i}}; - my $type = $asdu_type{$i->{id}}{type}; - - my %result = ( $type=>{}); - for my $j (1..$i->{nobj}) { - if ($i->{id} == 30) { - $value = unpack("C",$i->{obj}{$j}{data}); - $value &= 1; - ($tm,$ms) = cp56_2a_2_time(substr($i->{obj}{$j}{data},1,7)); - } elsif ($i->{id} == 35) { - $value = unpack("s",$i->{obj}{$j}{data}); - ($tm,$ms) = cp56_2a_2_time(substr($i->{obj}{$j}{data},3,7)); - } elsif ($i->{id} == 36) { - $value = unpack("f",$i->{obj}{$j}{data}); - ($tm,$ms) = cp56_2a_2_time(substr($i->{obj}{$j}{data},5,7)); - } elsif ($i->{id} == 37) { - $value = unpack("L",$i->{obj}{$j}{data}); - ($tm,$ms) = cp56_2a_2_time(substr($i->{obj}{$j}{data},5,7)); - } - $tm += int($s->{time_diff}); - $ms += int(($s->{time_diff} - int($s->{time_diff}))*1000000); - if ($ms > 1000000) { - $ms -= 1000000; - $tm++; - } elsif ($ms < 0) { - $ms += 1000000; - $tm--; - } - $addr = $i->{obj}{$j}{ioa}+65536*$i->{obj}{$j}{ioa2}; - - $result{$type}->{$addr} = [$value, $tm, $ms]; - &DEBUG(8,"ioa: ",$addr, ", val: ", $value, " time: ", scalar localtime($tm), ";"); - } - &{$s->{rcb}}($self,%result); + my ( $addr, $value, $tm, $ms ); + my $self = shift; + my $sid = shift; + + my $s = \%{ $self->{sids}{$sid} }; + my $i = \%{ $s->{i} }; + my $type = $asdu_type{ $i->{id} }{type}; + + my %result = ( $type => {} ); + for my $j ( 1 .. $i->{nobj} ) { + if ( $i->{id} == 30 ) { + $value = unpack( "C", $i->{obj}{$j}{data} ); + $value &= 1; + ( $tm, $ms ) = + cp56_2a_2_time( substr( $i->{obj}{$j}{data}, 1, 7 ) ); + } + elsif ( $i->{id} == 35 ) { + $value = unpack( "s", $i->{obj}{$j}{data} ); + ( $tm, $ms ) = + cp56_2a_2_time( substr( $i->{obj}{$j}{data}, 3, 7 ) ); + } + elsif ( $i->{id} == 36 ) { + $value = unpack( "f", $i->{obj}{$j}{data} ); + ( $tm, $ms ) = + cp56_2a_2_time( substr( $i->{obj}{$j}{data}, 5, 7 ) ); + } + elsif ( $i->{id} == 37 ) { + $value = unpack( "L", $i->{obj}{$j}{data} ); + ( $tm, $ms ) = + cp56_2a_2_time( substr( $i->{obj}{$j}{data}, 5, 7 ) ); + } + $tm += int( $s->{time_diff} ); + $ms += int( ( $s->{time_diff} - int( $s->{time_diff} ) ) * 1000000 ); + if ( $ms > 1000000 ) { + $ms -= 1000000; + $tm++; + } + elsif ( $ms < 0 ) { + $ms += 1000000; + $tm--; + } + $addr = $i->{obj}{$j}{ioa} + 65536 * $i->{obj}{$j}{ioa2}; + + $result{$type}->{$addr} = [ $value, $tm, $ms ]; + &DEBUG( 8, "ioa: ", $addr, ", val: ", $value, " time: ", + scalar localtime($tm), ";" ); + } + &{ $s->{rcb} }( $self, %result ); } sub send_asdu_type_0_44 { - my ($data,$cnt); - my $self = shift; - my $sid = shift; - my $cause = shift; - my $id = shift; - my $d = shift; - my $ca = $self->{sids}{$sid}{ca}; - - &DEBUG(3,"send_asdu_type_0_44(): send ",scalar (keys %{$d})," row(s)"); - foreach my $key (keys %{$d}) { - $cnt++; - if ($id == 30) { - $data .= pack("SC2",($key%65536),int($key/65536),$d->{$key}->[0]&1) . - &time_2_cp56_2a($d->{$key}->[1],$d->{$key}->[2]); - } elsif ($id == 35) { - $data .= pack("SCSC",($key%65536),int($key/65536),$d->{$key}->[0],0) . - &time_2_cp56_2a($d->{$key}->[1],$d->{$key}->[2]); - } elsif ($id == 36) { - $data .= pack("SCfC",($key%65536),int($key/65536),$d->{$key}->[0],0) . - &time_2_cp56_2a($d->{$key}->[1],$d->{$key}->[2]); - } elsif ($id == 37) { - $data .= pack("SCLC",($key%65536),int($key/65536),$d->{$key}->[0],0) . - &time_2_cp56_2a($d->{$key}->[1],$d->{$key}->[2]); - } - &DEBUG(5,"$cnt|--> IOA:",$key,", VALUE:",$d->{$key}->[0], - ", TIME:",$d->{$key}->[1]," size of pack: ", length($data)); - if (length($data) >= $MAX_ASDU_SIZE - 6 - 3 - $asdu_type{$id}{size}) { - $self->frame_i_send($sid,pack("C2S2",$id,$cnt,$cause,$ca) . $data); - $cnt = 0; - $data = ""; - } - } - if ($data) { - $self->frame_i_send($sid,pack("C2S2",$id,$cnt,$cause,$ca) . $data); - } + my ( $data, $cnt ); + my $self = shift; + my $sid = shift; + my $cause = shift; + my $id = shift; + my $d = shift; + my $ca = $self->{sids}{$sid}{ca}; + + &DEBUG( 3, "send_asdu_type_0_44(): send ", scalar( keys %{$d} ), + " row(s)" ); + foreach my $key ( keys %{$d} ) { + $cnt++; + if ( $id == 30 ) { + $data .= pack( "SC2", + ( $key % 65536 ), + int( $key / 65536 ), + $d->{$key}->[0] & 1 ) + . &time_2_cp56_2a( $d->{$key}->[1], $d->{$key}->[2] ); + } + elsif ( $id == 35 ) { + $data .= pack( "SCSC", + ( $key % 65536 ), + int( $key / 65536 ), + $d->{$key}->[0], 0 ) + . &time_2_cp56_2a( $d->{$key}->[1], $d->{$key}->[2] ); + } + elsif ( $id == 36 ) { + $data .= pack( "SCfC", + ( $key % 65536 ), + int( $key / 65536 ), + $d->{$key}->[0], 0 ) + . &time_2_cp56_2a( $d->{$key}->[1], $d->{$key}->[2] ); + } + elsif ( $id == 37 ) { + $data .= pack( "SCLC", + ( $key % 65536 ), + int( $key / 65536 ), + $d->{$key}->[0], 0 ) + . &time_2_cp56_2a( $d->{$key}->[1], $d->{$key}->[2] ); + } + &DEBUG( + 5, "$cnt|--> IOA:", + $key, ", VALUE:", + $d->{$key}->[0], ", TIME:", + $d->{$key}->[1], " size of pack: ", + length($data) + ); + if ( length($data) >= $MAX_ASDU_SIZE - 6 - 3 - $asdu_type{$id}{size} ) { + $self->frame_i_send( $sid, + pack( "C2S2", $id, $cnt, $cause, $ca ) . $data ); + $cnt = 0; + $data = ""; + } + } + if ($data) { + $self->frame_i_send( $sid, + pack( "C2S2", $id, $cnt, $cause, $ca ) . $data ); + } } 1; @@ -1130,7 +1355,7 @@ Vladimir Lettiev, Ethecrux@gmail.comE =head1 COPYRIGHT AND LICENSE -Copyright (C) 2008 by Vladimir Lettiev +Copyright (C) 2008-2010 by Vladimir Lettiev This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.8 or,