use DXDebug;
use Filter;
use Local;
+use DXDb;
+use Time::HiRes qw(gettimeofday tv_interval);
use Carp;
use strict;
-use vars qw($me $pc11_max_age $pc11_dup_age $pc23_dup_age
- %spotdup %wwvdup $last_hour %pings %rcmds
+use vars qw($me $pc11_max_age $pc23_max_age $pc11_dup_age $pc23_dup_age
+ %spotdup %wwvdup $last_hour %pings %rcmds
%nodehops @baddx $baddxfn $pc12_dup_age
- %anndup $allowzero);
+ %anndup $allowzero $pc12_dup_lth $decode_dk0wcy);
$me = undef; # the channel id for this cluster
+$decode_dk0wcy = undef; # if set use this callsign to decode announces from the EU WWV data beacon
$pc11_max_age = 1*3600; # the maximum age for an incoming 'real-time' pc11
-$pc11_dup_age = 24*3600; # the maximum time to keep the spot dup list for
-$pc23_dup_age = 24*3600; # the maximum time to keep the wwv dup list for
+$pc23_max_age = 1*3600; # the maximum age for an incoming 'real-time' pc23
+$pc11_dup_age = 3*3600; # the maximum time to keep the spot dup list for
+$pc23_dup_age = 3*3600; # the maximum time to keep the wwv dup list for
$pc12_dup_age = 24*3600; # the maximum time to keep the ann dup list for
+$pc12_dup_lth = 60; # the length of ANN text to save for deduping
%spotdup = (); # the pc11 and 26 dup hash
%wwvdup = (); # the pc23 and 27 dup hash
%anndup = (); # the PC12 dup hash
%nodehops = (); # node specific hop control
@baddx = (); # list of illegal spotted callsigns
+
$baddxfn = "$main::data/baddx.pl";
sub init
$self->{consort} = $line; # save the connection type
$self->{here} = 1;
- # get the filters
- $self->{spotfilter} = Filter::read_in('spots', $call);
- $self->{wwvfilter} = Filter::read_in('wwv', $call);
- $self->{annfilter} = Filter::read_in('ann', $call);
+ # get the INPUT filters (these only pertain to Clusters)
+ $self->{inspotfilter} = Filter::read_in('spots', $call, 1);
+ $self->{inwwvfilter} = Filter::read_in('wwv', $call, 1);
+ $self->{inannfilter} = Filter::read_in('ann', $call, 1);
# set unbuffered and no echo
$self->send_now('B',"0");
$self->send_now('E',"0");
+ # ping neighbour node stuff
+ my $ping = $user->pingint;
+ $ping = 5*60 unless defined $ping;
+ $self->{pingint} = $ping;
+ $self->{nopings} = $user->nopings || 2;
+ $self->{pingtime} = [ ];
+ $self->{pingave} = 0;
+
# send initialisation string
- if (!$self->{outbound}) {
+ unless ($self->{outbound}) {
$self->send(pc38()) if DXNode->get_all();
$self->send(pc18());
+ $self->{lastping} = $main::systime;
+ } else {
+ # remove from outstanding connects queue
+ @main::outstanding_connects = grep {$_->{call} ne $call} @main::outstanding_connects;
+ $self->{lastping} = $main::systime + $self->pingint / 2;
}
$self->state('init');
$self->pc50_t(time);
+ # send info to all logged in thingies
+ $self->tell_login('loginn');
+
Log('DXProt', "$call connected");
}
my ($pcno) = $field[0] =~ /^PC(\d\d)/; # just get the number
return unless $pcno;
return if $pcno < 10 || $pcno > 51;
-
+
+ # dump bad protocol messages unless it is a PC29
+ if ($line =~ /\%[0-9A-F][0-9A-F]/o && $pcno != 29) {
+ dbg('chan', "CORRUPT protocol message - dumped");
+ return;
+ }
+
# local processing 1
my $pcr;
eval {
# is it for me or one of mine?
my $call = ($field[5] gt ' ') ? $field[5] : $field[2];
- if ($call eq $main::mycall || grep $_ eq $call, get_all_user_calls()) {
+ if ($call eq $main::mycall || grep $_ eq $call, DXChannel::get_all_user_calls()) {
# yes, it is
my $text = unpad($field[3]);
my $ref = DXChannel->get($call);
$ref->send("$call de $field[1]: $text") if $ref && $ref->{talk};
} else {
- route($field[2], $line); # relay it on its way
+ $self->route($field[2], $line); # relay it on its way
}
return;
}
# route 'foreign' pc26s
if ($pcno == 26) {
if ($field[7] ne $main::mycall) {
- route($field[7], $line);
+ $self->route($field[7], $line);
return;
}
}
# if this is a 'nodx' node then ignore it
- last SWITCH if grep $field[7] =~ /^$_/, @DXProt::nodx_node;
+ if (grep $field[7] =~ /^$_/, @DXProt::nodx_node) {
+ dbg('chan', "Bad DXNode, dropped");
+ return;
+ }
# convert the date to a unix date
my $d = cltounix($field[3], $field[4]);
# store it away
my $spotter = $field[6];
- $spotter =~ s/-\d+$//o; # strip off the ssid from the spotter
+ $spotter =~ s/-[\@\d]+$//o; # strip off the ssid from the spotter
# do some de-duping
my $freq = $field[1] - 0;
dbg('chan', "Bad DX spot, ignored");
return;
}
+
+ # are any of the crucial fields invalid?
+ if ($field[2] =~ /[a-z]/ || $field[6] =~ /[a-z]/ || $field[7] =~ /[a-z]/) {
+ dbg('chan', "Spot contains lower case callsigns, rejected");
+ return;
+ }
my @spot = Spot::add($freq, $field[2], $d, $text, $spotter, $field[7]);
}
if ($pcno == 12) { # announces
- # announce duplicate checking
- my $text = uc unpad($field[3]);
- my $dupkey = $field[1].$field[2].$text.$field[4].$field[6];
+ # announce duplicate checking
+ my $text = substr(uc unpad($field[3]), 0, $pc12_dup_lth);
+ my $dupkey = $field[1].$field[2].$text;
if ($anndup{$dupkey}) {
dbg('chan', "Duplicate Announce ignored\n");
return;
}
$anndup{$dupkey} = $main::systime;
- # global ann filtering
- my ($filter, $hops) = Filter::it($self->{annfilter}, @field[1..6], $self->{call} ) if $self->{annfilter};
- if ($self->{annfilter} && !$filter) {
- dbg('chan', "Rejected by filter");
- return;
- }
-
if ($field[2] eq '*' || $field[2] eq $main::mycall) {
- # strip leading and trailing stuff
- my $text = unpad($field[3]);
- my $target;
- my $to = 'To ';
- my @list;
-
- if ($field[4] eq '*') { # sysops
- $target = "SYSOP";
- @list = map { $_->priv >= 5 ? $_ : () } get_all_users();
- } elsif ($field[4] gt ' ') { # speciality list handling
- my ($name) = split /\./, $field[4];
- $target = "$name"; # put the rest in later (if bothered)
- }
-
- if ($field[6] eq '1') {
- $target = "WX";
- $to = '';
+ # global ann filtering on INPUT
+ if ($self->{inannfilter}) {
+ my ($filter, $hops) = Filter::it($self->{inannfilter}, @field[1..6], $self->{call} );
+ unless ($filter) {
+ dbg('chan', "Rejected by filter");
+ return;
+ }
}
- $target = "All" if !$target;
+
+ # send it
+ $self->send_announce($line, @field[1..6]);
- if (@list > 0) {
- broadcast_list("$to$target de $field[1]: $text", 'ann', undef, @list);
- } else {
- broadcast_users("$target de $field[1]: $text", 'ann', undef);
+ if ($decode_dk0wcy && $field[1] eq $decode_dk0wcy) {
+ my ($hour, $k, $next, $a, $r, $sfi, $alarm) = $field[3] =~ /^Aurora Beacon\s+(\d+)UTC,\s+Kiel\s+K=(\d+),.*ed\s+K=(\d+),\s+A=(\d+),\s+R=(\d+),\s+SFI=(\d+),.*larm:\s+(\w+)/;
+ $alarm = ($alarm =~ /^Y/i) ? ', Aurora in DE' : '';
+ my $wwv = Geomag::update($main::systime, $hour, $sfi, $a, $k, "R=$r, Next K=$next$alarm", $decode_dk0wcy, $field[5], $r) if $sfi && $r;
}
- Log('ann', $target, $field[1], $text);
- return if $field[2] eq $main::mycall; # it's routed to me
} else {
- route($field[2], $line);
- return; # only on a routed one
+ $self->route($field[2], $line);
}
- last SWITCH;
+ return;
}
if ($pcno == 13) {
if ($pcno == 16) { # add a user
my $node = DXCluster->get_exact($field[1]);
+ my $dxchan;
+ if (!$node && ($dxchan = DXChannel->get($field[1]))) {
+ # add it to the node table if it isn't present and it's
+ # connected locally
+ $node = DXNode->new($dxchan, $field[1], 0, 1, 5400);
+ broadcast_ak1a(pc19($dxchan, $node), $dxchan, $self) unless $dxchan->{isolate};
+
+ }
return unless $node; # ignore if havn't seen a PC19 for this one yet
return unless $node->isa('DXNode');
if ($node->dxchan != $self) {
dbg('chan', "LOOP: $field[1] came in on wrong channel");
return;
}
- my $dxchan;
if (($dxchan = DXChannel->get($field[1])) && $dxchan != $self) {
dbg('chan', "LOOP: $field[1] connected locally");
return;
}
my $i;
-
-
+
for ($i = 2; $i < $#field; $i++) {
my ($call, $confmode, $here) = $field[$i] =~ /^(\S+) (\S) (\d)/o;
next if !$call || length $call < 3 || length $call > 8;
if ($pcno == 17) { # remove a user
my $node = DXCluster->get_exact($field[2]);
+ my $dxchan;
+ if (!$node && ($dxchan = DXChannel->get($field[2]))) {
+ # add it to the node table if it isn't present and it's
+ # connected locally
+ $node = DXNode->new($dxchan, $field[2], 0, 1, 5400);
+ broadcast_ak1a(pc19($dxchan, $node), $dxchan, $self) unless $dxchan->{isolate};
+ return;
+ }
return unless $node;
return unless $node->isa('DXNode');
if ($node->dxchan != $self) {
dbg('chan', "LOOP: $field[2] came in on wrong channel");
return;
}
- my $dxchan;
if (($dxchan = DXChannel->get($field[2])) && $dxchan != $self) {
dbg('chan', "LOOP: $field[2] connected locally");
return;
# unbusy and stop and outgoing mail (ie if somehow we receive another PC19 without a disconnect)
my $mref = DXMsg::get_busy($call);
- $mref->stop_msg($self) if $mref;
+ $mref->stop_msg($call) if $mref;
# add this station to the user database, if required (don't remove SSID from nodes)
my $user = DXUser->get_current($call);
$user->put;
}
- # queue up any messages
- DXMsg::queue_msg(0) if $self->state eq 'normal';
return if $newline eq "PC19^";
# add hop count
$self->send_local_config();
$self->send(pc22());
$self->state('normal');
-
- # queue mail
- DXMsg::queue_msg(0);
-
return;
}
if ($pcno == 22) {
$self->state('normal');
-
- # queue mail
- DXMsg::queue_msg(0);
return;
}
-
+
if ($pcno == 23 || $pcno == 27) { # WWV info
# route 'foreign' pc27s
if ($pcno == 27) {
if ($field[8] ne $main::mycall) {
- route($field[8], $line);
+ $self->route($field[8], $line);
return;
}
}
my $sfi = unpad($field[3]);
my $k = unpad($field[4]);
my $i = unpad($field[5]);
+ my ($r) = $field[6] =~ /R=(\d+)/;
+ $r = 0 unless $r;
my $dupkey = "$d.$sfi$k$i";
if ($wwvdup{$dupkey}) {
dbg('chan', "Dup WWV Spot ignored\n");
return;
}
- if ($d > $main::systime + 900 || $field[2] < 0 || $field[2] > 23) {
+ if (($pcno == 23 && $d < $main::systime - $pc23_max_age) || $d > $main::systime + 1500 || $field[2] < 0 || $field[2] > 23) {
dbg('chan', "WWV Date ($field[1] $field[2]) out of range");
return;
}
$wwvdup{$dupkey} = $d;
$field[6] =~ s/-\d+$//o; # remove spotter's ssid
- my $wwv = Geomag::update($d, $field[2], $sfi, $k, $i, @field[6..8]);
+ my $wwv = Geomag::update($d, $field[2], $sfi, $k, $i, @field[6..8], $r);
- my $r;
+ my $rep;
eval {
- $r = Local::wwv($self, $field[1], $field[2], $sfi, $k, $i, @field[6..8]);
+ $rep = Local::wwv($self, $field[1], $field[2], $sfi, $k, $i, @field[6..8], $r);
};
# dbg('local', "Local::wwv2 error $@") if $@;
- return if $r;
+ return if $rep;
# DON'T be silly and send on PC27s!
return if $pcno == 27;
if ($pcno == 25) { # merge request
if ($field[1] ne $main::mycall) {
- route($field[1], $line);
+ $self->route($field[1], $line);
return;
}
if ($field[2] eq $main::mycall) {
# spots
if ($field[3] > 0) {
- my @in = reverse Spot::search(1, undef, undef, 0, $field[3]-1);
+ my @in = reverse Spot::search(1, undef, undef, 0, $field[3]);
my $in;
foreach $in (@in) {
$self->send(pc26(@{$in}[0..4], $field[2]));
if ($pcno == 49 || $field[1] eq $main::mycall) {
DXMsg::process($self, $line);
} else {
- route($field[1], $line);
+ $self->route($field[1], $line);
}
return;
}
$self->send(pc35($main::mycall, $field[2], "$main::mycall:your attempt is logged, Tut tut tut...!"));
}
} else {
- route($field[1], $line);
+ $self->route($field[1], $line);
}
return;
}
delete $rcmds{$field[2]} if !$dxchan;
}
} else {
- route($field[1], $line);
+ $self->route($field[1], $line);
}
return;
}
last SWITCH;
}
if ($pcno == 37 || $pcno == 44 || $pcno == 45 || $pcno == 46 || $pcno == 47) {
- if ($field[1] eq $main::mycall) {
- ;
- } else {
- route($field[1], $line);
- }
+ DXDb::process($self, $line);
return;
}
# it's a reply, look in the ping list for this one
my $ref = $pings{$field[2]};
if ($ref) {
- my $r = shift @$ref;
- my $dxchan = DXChannel->get($r->{call});
- $dxchan->send($dxchan->msg('pingi', $field[2], atime($main::systime), $main::systime - $r->{t})) if $dxchan;
+ my $tochan = DXChannel->get($field[2]);
+ while (@$ref) {
+ my $r = shift @$ref;
+ my $dxchan = DXChannel->get($r->{call});
+ next unless $dxchan;
+ my $t = tv_interval($r->{t}, [ gettimeofday ]);
+ if ($dxchan->is_user) {
+ my $s = sprintf "%.2f", $t;
+ my $ave = sprintf "%.2f", $tochan ? ($tochan->{pingave} || $t) : $t;
+ $dxchan->send($dxchan->msg('pingi', $field[2], $s, $ave))
+ } elsif ($dxchan->is_ak1a) {
+ if ($tochan) {
+ $tochan->{nopings} = 2; # pump up the timer
+ push @{$tochan->{pingtime}}, $t;
+ shift @{$tochan->{pingtime}} if @{$tochan->{pingtime}} > 6;
+ my $st;
+ for (@{$tochan->{pingtime}}) {
+ $st += $_;
+ }
+ $tochan->{pingave} = $st / @{$tochan->{pingtime}};
+ }
+ }
+ }
}
}
-
} else {
# route down an appropriate thingy
- route($field[1], $line);
+ $self->route($field[1], $line);
}
return;
}
# REBROADCAST!!!!
#
- if (!$self->{isolate}) {
+ unless ($self->{isolate}) {
broadcast_ak1a($line, $self); # send it to everyone but me
}
}
if ($t >= $dxchan->pc50_t + $DXProt::pc50_interval) {
$dxchan->send(pc50());
$dxchan->pc50_t($t);
+ }
+
+ # send a ping out on this channel
+ if ($dxchan->{pingint} && $t >= $dxchan->{pingint} + $dxchan->{lastping}) {
+ if ($dxchan->{nopings} <= 0) {
+ $dxchan->disconnect;
+ } else {
+ addping($main::mycall, $dxchan->call);
+ $dxchan->{nopings} -= 1;
+ $dxchan->{lastping} = $t;
+ }
}
}
{
my $self = shift;
my $call = $self->call;
+ my $nopc39 = shift;
my $ref = DXCluster->get_exact($call);
+ $self->send_now("D", DXProt::pc39($main::mycall, $self->msg('disc1', "System Op"))) unless $nopc39;
+
# unbusy and stop and outgoing mail
my $mref = DXMsg::get_busy($call);
- $mref->stop_msg($self) if $mref;
+ $mref->stop_msg($call) if $mref;
# broadcast to all other nodes that all the nodes connected to via me are gone
my @gonenodes = map { $_->dxchan == $self ? $_ : () } DXNode::get_all();
# now broadcast to all other ak1a nodes that I have gone
broadcast_ak1a(pc21($call, 'Gone.'), $self) unless $self->{isolate};
-
+
+ # send info to all logged in thingies
+ $self->tell_login('logoutn');
+
Log('DXProt', $call . " Disconnected");
$ref->del() if $ref;
}
# taking into account filtering and so on
foreach $dxchan (@dxchan) {
my $routeit;
- my ($filter, $hops) = Filter::it($dxchan->{spotfilter}, @_, $self->{call} ) if $dxchan->{spotfilter};
+ my ($filter, $hops);
+
+ if ($dxchan->{spotfilter}) {
+ ($filter, $hops) = Filter::it($dxchan->{spotfilter}, @_, $self->{call} );
+ next unless $filter;
+ }
+
if ($dxchan->is_ak1a) {
next if $dxchan == $self;
if ($hops) {
$dxchan->send($routeit) if $routeit;
} else {
$dxchan->send($routeit) unless $dxchan->{isolate} || $self->{isolate};
-
}
} elsif ($dxchan->is_user && $dxchan->{dx}) {
my $buf = Spot::formatb($_[0], $_[1], $_[2], $_[3], $_[4]);
$buf .= "\a\a" if $dxchan->{beep};
if ($dxchan->{state} eq 'prompt' || $dxchan->{state} eq 'convers') {
- $dxchan->send($buf) if !$hops || ($hops && $filter);
+ $dxchan->send($buf);
} else {
- $dxchan->delay($buf) if !$hops || ($hops && $filter);
+ $dxchan->delay($buf);
}
}
}
# taking into account filtering and so on
foreach $dxchan (@dxchan) {
my $routeit;
- my ($filter, $hops) = Filter::it($dxchan->{wwvfilter}, @_, $self->{call} ) if $dxchan->{wwvfilter};
+ my ($filter, $hops);
+
+ if ($dxchan->{spotfilter}) {
+ ($filter, $hops) = Filter::it($dxchan->{wwvfilter}, @_, $self->{call} );
+ next unless $filter;
+ }
if ($dxchan->is_ak1a) {
next if $dxchan == $self;
if ($hops) {
my $buf = "WWV de $_[6] <$_[1]>: SFI=$_[2], A=$_[3], K=$_[4], $_[5]";
$buf .= "\a\a" if $dxchan->{beep};
if ($dxchan->{state} eq 'prompt' || $dxchan->{state} eq 'convers') {
- $dxchan->send($buf) if !$hops || ($hops && $filter);
+ $dxchan->send($buf);
} else {
- $dxchan->delay($buf) if !$hops || ($hops && $filter);
+ $dxchan->delay($buf);
+ }
+ }
+ }
+}
+
+# send an announce
+sub send_announce
+{
+ my $self = shift;
+ my $line = shift;
+ my @dxchan = DXChannel->get_all();
+ my $dxchan;
+ my $text = unpad($_[2]);
+ my $target;
+ my $to = 'To ';
+
+ if ($_[3] eq '*') { # sysops
+ $target = "SYSOP";
+ } elsif ($_[3] gt ' ') { # speciality list handling
+ my ($name) = split /\./, $_[3];
+ $target = "$name"; # put the rest in later (if bothered)
+ }
+
+ if ($_[5] eq '1') {
+ $target = "WX";
+ $to = '';
+ }
+ $target = "All" if !$target;
+
+ Log('ann', $target, $_[0], $text);
+
+ # send it if it isn't the except list and isn't isolated and still has a hop count
+ # taking into account filtering and so on
+ foreach $dxchan (@dxchan) {
+ my $routeit;
+ my ($filter, $hops);
+
+ if ($dxchan->{annfilter}) {
+ ($filter, $hops) = Filter::it($dxchan->{annfilter}, @_, $self->{call} );
+ next unless $filter;
+ }
+ if ($dxchan->is_ak1a && $_[1] ne $main::mycall) { # i.e not specifically routed to me
+ next if $dxchan == $self;
+ if ($hops) {
+ $routeit = $line;
+ $routeit =~ s/\^H\d+\^\~$/\^H$hops\^\~/;
+ } else {
+ $routeit = adjust_hops($dxchan, $line); # adjust its hop count by node name
+ next unless $routeit;
+ }
+ if ($filter) {
+ $dxchan->send($routeit) if $routeit;
+ } else {
+ $dxchan->send($routeit) unless $dxchan->{isolate} || $self->{isolate};
+
+ }
+ } elsif ($dxchan->is_user && $dxchan->{ann}) {
+ next if $target eq 'SYSOP' && $dxchan->{priv} < 5;
+ my $buf = "$to$target de $_[0]: $text";
+ $buf .= "\a\a" if $dxchan->{beep};
+ if ($dxchan->{state} eq 'prompt' || $dxchan->{state} eq 'convers') {
+ $dxchan->send($buf);
+ } else {
+ $dxchan->delay($buf);
}
}
}
#
sub route
{
- my ($call, $line) = @_;
+ my ($self, $call, $line) = @_;
my $cl = DXCluster->get_exact($call);
- if ($cl) {
+ if ($cl) { # don't route it back down itself
+ if (ref $self && $call eq $self->{call}) {
+ dbg('chan', "Trying to route back to source, dropped");
+ return;
+ }
my $hops;
my $dxchan = $cl->{dxchan};
if ($dxchan) {
{
my $s = shift; # the line to be rebroadcast
my @except = @_; # to all channels EXCEPT these (dxchannel refs)
- my @dxchan = get_all_ak1a();
+ my @dxchan = DXChannel::get_all_ak1a();
my $dxchan;
# send it if it isn't the except list and isn't isolated and still has a hop count
{
my $s = shift; # the line to be rebroadcast
my @except = @_; # to all channels EXCEPT these (dxchannel refs)
- my @dxchan = get_all_ak1a();
+ my @dxchan = DXChannel::get_all_ak1a();
my $dxchan;
# send it if it isn't the except list and isn't isolated and still has a hop count
my $sort = shift; # the type of transmission
my $fref = shift; # a reference to an object to filter on
my @except = @_; # to all channels EXCEPT these (dxchannel refs)
- my @dxchan = get_all_users();
+ my @dxchan = DXChannel::get_all_users();
my $dxchan;
my @out;
}
}
-#
-# gimme all the ak1a nodes
-#
-sub get_all_ak1a
-{
- my @list = DXChannel->get_all();
- my $ref;
- my @out;
- foreach $ref (@list) {
- push @out, $ref if $ref->is_ak1a;
- }
- return @out;
-}
-
-# return a list of all users
-sub get_all_users
-{
- my @list = DXChannel->get_all();
- my $ref;
- my @out;
- foreach $ref (@list) {
- push @out, $ref if $ref->is_user;
- }
- return @out;
-}
-
-# return a list of all user callsigns
-sub get_all_user_calls
-{
- my @list = DXChannel->get_all();
- my $ref;
- my @out;
- foreach $ref (@list) {
- push @out, $ref->call if $ref->is_user;
- }
- return @out;
-}
#
# obtain the hops from the list for this callsign and pc no
sub addping
{
my ($from, $to) = @_;
- my $ref = $pings{$to};
- $ref = $pings{$to} = [] if !$ref;
+ my $ref = $pings{$to} || [];
my $r = {};
$r->{call} = $from;
- $r->{t} = $main::systime;
- route($to, pc51($to, $main::mycall, 1));
+ $r->{t} = [ gettimeofday ];
+ route(undef, $to, pc51($to, $main::mycall, 1));
push @$ref, $r;
+ $pings{$to} = $ref;
}
# add a rcmd request to the rcmd queues
$r->{call} = $from;
$r->{t} = $main::systime;
$r->{cmd} = $cmd;
- route($to, pc34($main::mycall, $to, $cmd));
+ route(undef, $to, pc34($main::mycall, $to, $cmd));
$rcmds{$to} = $r;
}
1;