New improved route finding algorithm
[spider.git] / perl / DXProtHandle.pm
index f7f8d0742eb8ecb3ac9090c33be3787ffe8f4c9c..b3f03222034ea6c05bae6c8013f7e94e0236710d 100644 (file)
@@ -2,7 +2,7 @@
 #
 # This module impliments the handlers for the protocal mode for a dx cluster
 #
-# Copyright (c) 1998-2006 Dirk Koopman G1TLH
+# Copyright (c) 1998-2007 Dirk Koopman G1TLH
 #
 #
 #
@@ -32,8 +32,6 @@ use DXHash;
 use Route;
 use Route::Node;
 use Script;
-use RouteDB;
-
 
 use strict;
 
@@ -43,17 +41,30 @@ use vars qw($pc11_max_age $pc23_max_age $last_pc50 $eph_restime $eph_info_restim
                        $investigation_int $pc19_version $myprot_version
                        %nodehops $baddx $badspotter $badnode $censorpc
                        $allowzero $decode_dk0wcy $send_opernam @checklist
-                       $eph_pc15_restime $pc9x_past_age $pc9x_future_age
+                       $eph_pc15_restime $pc9x_past_age $pc9x_dupe_age
                        $pc10_dupe_age $pc92_slug_changes $last_pc92_slug
-                       $pc92Ain $pc92Cin $pc92Din $pc92Kin
+                       $pc92Ain $pc92Cin $pc92Din $pc92Kin $pc9x_time_tolerance
+                       $pc92filterdef
                   );
 
-$pc9x_past_age = 62*60;                        # maximum age in the past of a px9x (a config record might be the only
-                                                               # thing a node might send - once an hour)
-$pc9x_future_age = 2*3600;             # maximum age in the future ditto
+$pc9x_dupe_age = 60;                   # catch loops of circular (usually) D records
 $pc10_dupe_age = 45;                   # just something to catch duplicate PC10->PC93 conversions
 $pc92_slug_changes = 60;               # slug any changes going outward for this long
 $last_pc92_slug = 0;                   # the last time we sent out any delayed add or del PC92s
+$pc9x_time_tolerance = 15*60;  # the time on a pc9x is allowed to be out by this amount
+$pc9x_past_age = (122*60)+             # maximum age in the past of a px9x (a config record might be the only
+       $pc9x_time_tolerance;           # thing a node might send - once an hour and we allow an extra hour for luck)
+                                # this is actually the partition between "yesterday" and "today" but old.
+
+$pc92filterdef = bless ([
+                         # tag, sort, field, priv, special parser
+                         ['call', 'c', 0],
+                         ['by', 'c', 0],
+                         ['dxcc', 'nc', 1],
+                         ['itu', 'ni', 2],
+                         ['zone', 'nz', 3],
+                        ], 'Filter::Cmd');
+
 
 # incoming talk commands
 sub handle_10
@@ -109,10 +120,6 @@ sub handle_10
                }
        }
 
-       # remember a route to this node and also the node on which this user is
-       RouteDB::update($_[6], $self->{call});
-#      RouteDB::update($to, $_[6]);
-
        # convert this to a PC93, coming from mycall with origin set and process it as such
        $main::me->normal(pc93($to, $from, $via, $_[3], $_[6]));
 }
@@ -190,10 +197,6 @@ sub handle_11
                }
        }
 
-       # remember a route
-#      RouteDB::update($_[7], $self->{call});
-#      RouteDB::update($_[6], $_[7]);
-
        my @spot = Spot::prepare($_[1], $_[2], $d, $_[5], $nossid, $_[7]);
        # global spot filtering on INPUT
        if ($self->{inspotsfilter}) {
@@ -223,7 +226,7 @@ sub handle_11
        #
 
        # fix up qra locators of known users
-       my $user = DXUser->get_current($spot[4]);
+       my $user = DXUser::get_current($spot[4]);
        if ($user) {
                my $qra = $user->qra;
                unless ($qra && is_qra($qra)) {
@@ -270,12 +273,13 @@ sub handle_11
        }
 
        # local processing
-       my $r;
-       eval {
-               $r = Local::spot($self, @spot);
-       };
-       #                       dbg("Local::spot1 error $@") if isdbg('local') if $@;
-       return if $r;
+       if (defined &Local::spot) {
+               my $r;
+               eval {
+                       $r = Local::spot($self, @spot);
+               };
+               return if $r;
+       }
 
        # DON'T be silly and send on PC26s!
        return if $pcno == 26;
@@ -324,10 +328,6 @@ sub handle_12
                $self->send_chat(0, $line, @_[1..6]);
        } elsif ($_[2] eq '*' || $_[2] eq $main::mycall) {
 
-               # remember a route
-#              RouteDB::update($_[5], $self->{call});
-#              RouteDB::update($_[1], $_[5]);
-
                # ignore something that looks like a chat line coming in with sysop
                # flag - this is a kludge...
                if ($_[3] =~ /^\#\d+ / && $_[4] eq '*') {
@@ -354,6 +354,15 @@ sub handle_12
        } else {
                $self->route($_[2], $line);
        }
+
+       # local processing
+       if (defined &Local::ann) {
+               my $r;
+               eval {
+                       $r = Local::ann($self, $line, @_[1..6]);
+               };
+               return if $r;
+       }
 }
 
 sub handle_15
@@ -399,7 +408,6 @@ sub handle_16
 
        my $h;
        $h = 1 if DXChannel::get($ncall);
-       RouteDB::update($ncall, $self->{call}, $h);
        if ($h && $self->{call} ne $ncall) {
                dbg("PCPROT: trying to update a local node, ignored") if isdbg('chanerr');
                return;
@@ -450,7 +458,7 @@ sub handle_16
 
                # reject this if we think it is a node already
                my $r = Route::Node::get($call);
-               my $u = DXUser->get_current($call) unless $r;
+               my $u = DXUser::get_current($call) unless $r;
                if ($r || ($u && $u->is_node)) {
                        dbg("PCPROT: $call is a node") if isdbg('chanerr');
                        next;
@@ -472,7 +480,7 @@ sub handle_16
                }
 
                # add this station to the user database, if required
-               my $user = DXUser->get_current($ncall);
+               my $user = DXUser::get_current($ncall);
                $user = DXUser->new($call) unless $user;
                $user->homenode($parent->call) if !$user->homenode;
                $user->node($parent->call);
@@ -519,8 +527,6 @@ sub handle_17
                return;
        }
 
-       RouteDB::delete($ncall, $self->{call});
-
        my $uref = Route::User::get($ucall);
        unless ($uref) {
                dbg("PCPROT: Route::User $ucall not in config") if isdbg('chanerr');
@@ -555,7 +561,7 @@ sub handle_17
        $parent->del_user($uref);
 
        # send info to all logged in thingies
-       my $user = DXUser->get_current($ncall);
+       my $user = DXUser::get_current($ncall);
        $self->tell_login('logoutu', "$ncall: $ucall") if $user && $user->is_local_node;
        $self->tell_buddies('logoutb', $ucall, $ncall);
 
@@ -625,7 +631,7 @@ sub check_add_node
        my $call = shift;
 
        # add this station to the user database, if required (don't remove SSID from nodes)
-       my $user = DXUser->get_current($call);
+       my $user = DXUser::get_current($call);
        if (!$user) {
                $user = DXUser->new($call);
                $user->priv(1);         # I have relented and defaulted nodes
@@ -714,7 +720,6 @@ sub handle_19
 #                      next;
 #              }
 
-               RouteDB::update($call, $self->{call}, $dxchan ? 1 : undef);
 
                unless ($h) {
                        if ($parent->via_pc92) {
@@ -820,8 +825,6 @@ sub handle_21
        # we don't need any isolation code here, because we will never
        # act on a PC21 with self->call in it.
 
-       RouteDB::delete($call, $self->{call});
-
        my $parent = Route::Node::get($self->{call});
        unless ($parent) {
                dbg("PCPROT: my parent $self->{call} has disappeared");
@@ -934,12 +937,13 @@ sub handle_23
        # note this only takes the first one it gets
        Geomag::update($d, $_[2], $sfi, $k, $i, @_[6..8], $r);
 
-       my $rep;
-       eval {
-               $rep = Local::wwv($self, $_[1], $_[2], $sfi, $k, $i, @_[6..8], $r);
-       };
-       #                       dbg("Local::wwv2 error $@") if isdbg('local') if $@;
-       return if $rep;
+       if (defined &Local::wwv) {
+               my $rep;
+               eval {
+                       $rep = Local::wwv($self, $_[1], $_[2], $sfi, $k, $i, @_[6..8], $r);
+               };
+               return if $rep;
+       }
 
        # DON'T be silly and send on PC27s!
        return if $pcno == 27;
@@ -1128,7 +1132,7 @@ sub handle_41
        }
 
        # add this station to the user database, if required
-       my $user = DXUser->get_current($call);
+       my $user = DXUser::get_current($call);
        $user = DXUser->new($call) unless $user;
 
        if ($sort == 1) {
@@ -1217,13 +1221,12 @@ sub handle_50
 
        my $call = $_[1];
 
-       RouteDB::update($call, $self->{call});
-
        my $node = Route::Node::get($call);
        if ($node) {
                return unless $node->call eq $self->{call};
                $node->usercount($_[2]) unless $node->users;
                $node->reset_obs;
+               $node->PC92C_dxchan($self->call, $_[-1]);
 
                # input filter if required
 #              return unless $self->in_filter_route($node);
@@ -1255,9 +1258,6 @@ sub handle_51
                        DXXml::Ping::handle_ping_reply($self, $from);
                }
        } else {
-
-               RouteDB::update($from, $self->{call});
-
                if (eph_dup($line)) {
                        return;
                }
@@ -1304,12 +1304,13 @@ sub handle_73
 
        my $wcy = WCY::update($d, @_[2..12]);
 
-       my $rep;
-       eval {
-               $rep = Local::wcy($self, @_[1..12]);
-       };
-       # dbg("Local::wcy error $@") if isdbg('local') if $@;
-       return if $rep;
+       if (defined &Local::wcy) {
+               my $rep;
+               eval {
+                       $rep = Local::wcy($self, @_[1..12]);
+               };
+               return if $rep;
+       }
 
        # broadcast to the eager world
        send_wcy_spot($self, $line, $d, @_[2..12]);
@@ -1390,6 +1391,9 @@ sub _add_thingy
 {
        my $parent = shift;
        my $s = shift;
+       my $dxchan = shift;
+       my $hops = shift;
+
        my ($call, $is_node, $is_extnode, $here, $version, $build) = @$s;
        my @rout;
 
@@ -1397,6 +1401,8 @@ sub _add_thingy
                if ($is_node) {
                        dbg("ROUTE: added node $call to " . $parent->call) if isdbg('routelow');
                        @rout = $parent->add($call, $version, Route::here($here));
+                       my $r = Route::Node::get($call);
+                       $r->PC92C_dxchan($dxchan->call, $hops) if $r;
                } else {
                        dbg("ROUTE: added user $call to " . $parent->call) if isdbg('routelow');
                        @rout = $parent->add_user($call, Route::here($here));
@@ -1478,6 +1484,21 @@ sub check_pc9x_t
        my $pc = shift;
        my $create = shift;
 
+       # check that the time is between 0 >= $t < 86400
+       unless ($t >= 0 && $t < 86400) {
+               dbg("PCPROT: time invalid t: $t, ignored") if isdbg('chanerr');
+               return undef;
+       }
+
+       # check that the time of this pc9x is within tolerance (default 15 mins either way)
+       my $now = $main::systime - $main::systime_daystart ;
+       my $diff = abs($now - $t);
+       unless ($diff < $pc9x_time_tolerance || 86400 - $diff < $pc9x_time_tolerance) {
+               my $c = ref $call ? $call->call : $call;
+               dbg("PC9XERR: $c time out of range t: $t now: $now diff: $diff > $pc9x_time_tolerance, ignored") if isdbg('chan');
+               return undef;
+       }
+
        my $parent = ref $call ? $call : Route::Node::get($call);
        if ($parent) {
                # we only do this for external calls whose routing table
@@ -1489,26 +1510,46 @@ sub check_pc9x_t
                        my $lastid = $parent->lastid;
                        if (defined $lastid) {
                                if ($t < $lastid) {
+                                       # note that this is where we determine whether this pc9x has come in yesterday
+                                       # but is still greater (modulo 86400) than the lastid or is simply an old
+                                       # duplicate sentence. To determine this we need to do some module 86400
+                                       # arithmetic. High numbers mean that this is an old duplicate sentence,
+                                       # low numbers that it is a new sentence.
+                                       #
+                                       # Typically you will see yesterday being taken on $t = 84, $lastid = 86235
+                                       # and old dupes with $t = 234, $lastid = 256 (which give answers 249 and
+                                       # 86378 respectively in the calculation below).
+                                       #
                                        if ($t+86400-$lastid > $pc9x_past_age) {
-                                               dbg("PCPROT: dup id on $t <= lastid $lastid, ignored") if isdbg('chanerr');
-                                               return;
+                                               dbg("PCPROT: dup id on $t <= lastid $lastid, ignored") if isdbg('chanerr') || isdbg('pc92dedupe');
+                                               return undef;
                                        }
                                } elsif ($t == $lastid) {
-                                       dbg("PCPROT: dup id on $t == lastid $lastid, ignored") if isdbg('chanerr');
-                                       return;
+                                       dbg("PCPROT: dup id on $t == lastid $lastid, ignored") if isdbg('chanerr') || isdbg('pc92dedupe');
+                                       return undef;
                                } else {
-                                       # $t > $lastid, check that the timestamp offered isn't too far away from 'now'
-                                       if ($t-$lastid > $pc9x_future_age ) {
-                                               dbg("PCPROT: id $t too far in the future of lastid $lastid, ignored") if isdbg('chanerr');
-                                               return;
+                                       # check that if we have a low number in lastid that yesterday's numbers
+                                       # (likely in the 85000+ area) don't override them, thus causing flip flopping
+                                       if ($lastid+86400-$t < $pc9x_past_age) {
+                                               dbg("PCPROT: dup id on $t in yesterday, lastid $lastid, ignored") if isdbg('chanerr') || isdbg('pc92dedupe');
+                                               return undef;
                                        }
                                }
                        }
                }
        } elsif ($create) {
                $parent = Route::Node->new($call);
+       } else {
+               dbg("PCPROT: $call does not exist, ignored") if isdbg('pc92dedupe');
+               return undef;
        }
-       $parent->lastid($t) if $parent;
+       if (isdbg('pc92dedupe')) {
+               my $exists = exists $parent->{lastid}; # naughty, naughty :-)
+               my $val = $parent->{lastid};
+               my $s = $exists ? (defined $val ? $val : 'exists/undef') : 'undef';
+               dbg("PCPROT: $call pc92 id lastid $s -> $t");
+       }
+       $parent->lastid($t);
 
        return $parent;
 }
@@ -1519,6 +1560,7 @@ sub pc92_handle_first_slot
        my $slot = shift;
        my $parent = shift;
        my $t = shift;
+       my $hops = shift;
        my $oparent = $parent;
 
        my @radd;
@@ -1543,7 +1585,7 @@ sub pc92_handle_first_slot
                        # from the true parent node for this external before we get one for the this node
                        unless ($parent = Route::Node::get($call)) {
                                if ($is_extnode && $oparent) {
-                                       @radd = _add_thingy($oparent, $slot);
+                                       @radd = _add_thingy($oparent, $slot, $self, $hops);
                                        $parent = $radd[0];
                                } else {
                                        dbg("PCPROT: no previous C or A for this external node received, ignored") if isdbg('chanerr');
@@ -1552,7 +1594,7 @@ sub pc92_handle_first_slot
                        }
                        $parent = check_pc9x_t($call, $t, 92) || return;
                        $parent->via_pc92(1);
-                       $parent->PC92C_dxchan($self->{call});
+                       $parent->PC92C_dxchan($self->{call}, $hops);
                }
        } else {
                dbg("PCPROT: must be \$mycall or external node as first entry, ignored") if isdbg('chanerr');
@@ -1561,7 +1603,7 @@ sub pc92_handle_first_slot
        $parent->here(Route::here($here));
        $parent->version($version || $pc19_version) if $version;
        $parent->build($build) if $build;
-       $parent->PC92C_dxchan($self->{call}) unless $self->{call} eq $parent->call;
+       $parent->PC92C_dxchan($self->{call}, $hops) unless $self->{call} eq $parent->call;
        return ($parent, @radd);
 }
 
@@ -1578,6 +1620,12 @@ sub handle_92
        my $pcall = $_[1];
        my $t = $_[2];
        my $sort = $_[3];
+       my $hops = $_[-1];
+
+       # this catches loops of A/Ds
+#      if (eph_dup($line, $pc9x_dupe_age)) {
+#              return;
+#      }
 
        if ($pcall eq $main::mycall) {
                dbg("PCPROT: looped back, ignored") if isdbg('chanerr');
@@ -1602,7 +1650,9 @@ sub handle_92
                return;
        }
 
-       my $parent = check_pc9x_t($pcall, $t, 92, 1) || return;
+       # don't create routing entries for D records that don't already exist
+       # this is what causes all those PC92 loops!
+       my $parent = check_pc9x_t($pcall, $t, 92, $sort ne 'D') || return;
        my $oparent = $parent;
 
        $parent->do_pc9x(1);
@@ -1652,21 +1702,24 @@ sub handle_92
                }
 
        } elsif ($sort eq 'K') {
-               $pc92Kin += length $line if $sort eq 'K';
+               $pc92Kin += length $line;
 
                # remember the last channel we arrived on
-               $parent->PC92C_dxchan($self->{call}) unless $self->{call} eq $parent->call;
+               $parent->PC92C_dxchan($self->{call}, $hops) unless $self->{call} eq $parent->call;
 
                my @ent = _decode_pc92_call($_[4]);
 
                if (@ent) {
                        my $add;
 
-                       ($parent, $add) = $self->pc92_handle_first_slot(\@ent, $parent, $t);
+                       ($parent, $add) = $self->pc92_handle_first_slot(\@ent, $parent, $t, $hops);
                        return unless $parent; # dupe
 
                        push @radd, $add if $add;
                        $parent->reset_obs;
+                       $parent->version($ent[4]) if $ent[4];
+                       $parent->build($ent[5]) if $ent[5];
+
                        dbg("ROUTE: reset obscount on $parent->{call} now " . $parent->obscount) if isdbg('obscount');
                }
        } elsif ($sort eq 'A' || $sort eq 'D' || $sort eq 'C') {
@@ -1676,7 +1729,7 @@ sub handle_92
                $pc92Din += length $line if $sort eq 'D';
 
                # remember the last channel we arrived on
-               $parent->PC92C_dxchan($self->{call}) unless $self->{call} eq $parent->call;
+               $parent->PC92C_dxchan($self->{call}, $hops) unless $self->{call} eq $parent->call;
 
                # this is the main route section
                # here is where all the routes are created and destroyed
@@ -1695,7 +1748,7 @@ sub handle_92
                        # that needs to be done.
                        my $add;
 
-                       ($parent, $add) = $self->pc92_handle_first_slot($ent[0], $parent, $t);
+                       ($parent, $add) = $self->pc92_handle_first_slot($ent[0], $parent, $t, $hops);
                        return unless $parent; # dupe
 
                        shift @ent;
@@ -1715,7 +1768,7 @@ sub handle_92
 
                if ($sort eq 'A') {
                        for (@nent) {
-                               push @radd, _add_thingy($parent, $_);
+                               push @radd, _add_thingy($parent, $_, $self, $hops);
                        }
                } elsif ($sort eq 'D') {
                        for (@nent) {
@@ -1748,7 +1801,7 @@ sub handle_92
                        foreach my $r (@nent) {
                                my $call = $r->[0];
                                if ($call) {
-                                       push @radd,_add_thingy($parent, $r) if grep $call eq $_, (@$nnodes, @$nusers);
+                                       push @radd,_add_thingy($parent, $r, $self, $hops) if grep $call eq $_, (@$nnodes, @$nusers);
                                }
                        }
                        # del users here
@@ -1834,6 +1887,12 @@ sub handle_93
                return;
        }
 
+       # ignore PC93 coming in from outside this node with a target of local
+       if ($to eq 'LOCAL' && $self != $main::me) {
+               dbg("PCPROT: incoming LOCAL chat not from local node, ignored") if isdbg('chanerr');
+               return;
+       }
+
        # if it is routeable then then treat it like a talk
        my $ref = Route::get($to);
        if ($ref) {
@@ -1881,7 +1940,9 @@ sub handle_93
                # chat messages to non-pc9x nodes
                $self->send_chat(1, pc12($from, $text, undef, $to, undef, $pcall), $from, '*', $text, $to, $pcall, '0');
        }
-       $self->broadcast_route_pc9x($pcall, undef, $line, 0);
+
+       # broadcast this chat sentence everywhere unless it is targetted to 'LOCAL'
+       $self->broadcast_route_pc9x($pcall, undef, $line, 0) unless $to eq 'LOCAL' || $via eq 'LOCAL';
 }
 
 # if get here then rebroadcast the thing with its Hop count decremented (if