4 # This is still part of the DXProt package
5 # It has just been split off for convenience
28 use Time::HiRes qw(gettimeofday tv_interval);
36 use vars qw($VERSIONH $BRANCHH);
37 $VERSIONH = sprintf( "%d.%03d", q$Revision$ =~ /:\s+(\d+)\.(\d+)/ );
38 $BRANCHH = sprintf( "%d.%03d", q$Revision$ =~ /:\s+\d+\.\d+\.(\d+)\.(\d+)/ || (0,0));
39 $main::build += $VERSIONH;
40 $main::branch += $BRANCHH;
42 use vars qw($pc11_max_age $pc23_max_age $last_pc50 $eph_restime $eph_info_restime $eph_pc34_restime
43 $last_hour $last10 %eph %pings %rcmds $ann_to_talk $pc19_version
44 $pingint $obscount %pc19list $chatdupeage $investigation_int
45 %nodehops $baddx $badspotter $badnode $censorpc $rspfcheck $use_newroute
46 $allowzero $decode_dk0wcy $send_opernam @checklist);
48 # incoming talk commands
57 return if $rspfcheck and !$self->rspfcheck(0, $_[6], $_[1]);
59 # will we allow it at all?
62 if (@bad = BadWords::check($_[3])) {
63 dbg("PCPROT: Bad words: @bad, dropped") if isdbg('chanerr');
68 # is it for me or one of mine?
69 my ($from, $to, $via, $call, $dxchan);
78 # if this is a 'nodx' node then ignore it
79 if ($badnode->in($_[6]) || ($via && $badnode->in($via))) {
80 dbg("PCPROT: Bad Node, dropped") if isdbg('chanerr');
84 # if this is a 'bad spotter' user then ignore it
87 if ($badspotter->in($nossid)) {
88 dbg("PCPROT: Bad Spotter, dropped") if isdbg('chanerr');
92 # if we are converting announces to talk is it a dup?
94 if (AnnTalk::is_talk_candidate($from, $_[3]) && AnnTalk::dup($from, $to, $_[3])) {
95 dbg("DXPROT: Dupe talk from announce, dropped") if isdbg('chanerr');
100 # it is here and logged on
101 $dxchan = DXChannel->get($main::myalias) if $to eq $main::mycall;
102 $dxchan = DXChannel->get($to) unless $dxchan;
103 if ($dxchan && $dxchan->is_user) {
105 $dxchan->talk($from, $to, $via, $_[3]);
109 # is it elsewhere, visible on the cluster via the to address?
110 # note: this discards the via unless the to address is on
113 if ($ref = Route::get($to)) {
114 $vref = Route::Node::get($via) if $via;
115 $vref = undef unless $vref && grep $to eq $_, $vref->users;
116 $ref->bestdxchan->talk($from, $to, $vref ? $via : undef, $_[3], $_[6]);
120 # not visible here, send a message of condolence
122 $ref = Route::get($from);
123 $vref = $ref = Route::Node::get($_[6]) unless $ref;
125 $dxchan = $ref->bestdxchan;
126 $dxchan->talk($main::mycall, $from, $vref ? $vref->call : undef, $dxchan->msg('talknh', $to) );
138 # route 'foreign' pc26s
140 if ($_[7] ne $main::mycall) {
141 $self->route($_[7], $line);
147 # return if $rspfcheck and !$self->rspfcheck(1, $_[7], $_[6]);
149 # if this is a 'nodx' node then ignore it
150 if ($badnode->in($_[7])) {
151 dbg("PCPROT: Bad Node, dropped") if isdbg('chanerr');
155 # if this is a 'bad spotter' user then ignore it
157 $nossid =~ s/-\d+$//;
158 if ($badspotter->in($nossid)) {
159 dbg("PCPROT: Bad Spotter, dropped") if isdbg('chanerr');
163 # convert the date to a unix date
164 my $d = cltounix($_[3], $_[4]);
165 # bang out (and don't pass on) if date is invalid or the spot is too old (or too young)
166 if (!$d || ($pcno == 11 && ($d < $main::systime - $pc11_max_age || $d > $main::systime + 900))) {
167 dbg("PCPROT: Spot ignored, invalid date or out of range ($_[3] $_[4])\n") if isdbg('chanerr');
172 if ($baddx->in($_[2]) || BadWords::check($_[2]) || $_[2] =~ /COCK/) {
173 dbg("PCPROT: Bad DX spot, ignored") if isdbg('chanerr');
178 $_[5] =~ s/^\s+//; # take any leading blanks off
179 $_[2] = unpad($_[2]); # take off leading and trailing blanks from spotted callsign
180 if ($_[2] =~ /BUST\w*$/) {
181 dbg("PCPROT: useless 'BUSTED' spot") if isdbg('chanerr');
186 if (@bad = BadWords::check($_[5])) {
187 dbg("PCPROT: Bad words: @bad, dropped") if isdbg('chanerr');
193 my @spot = Spot::prepare($_[1], $_[2], $d, $_[5], $_[6], $_[7]);
194 # global spot filtering on INPUT
195 if ($self->{inspotsfilter}) {
196 my ($filter, $hops) = $self->{inspotsfilter}->it(@spot);
198 dbg("PCPROT: Rejected by input spot filter") if isdbg('chanerr');
203 # this goes after the input filtering, but before the add
204 # so that if it is input filtered, it isn't added to the dup
205 # list. This allows it to come in from a "legitimate" source
206 if (Spot::dup($_[1], $_[2], $d, $_[5], $_[6])) {
207 dbg("PCPROT: Duplicate Spot ignored\n") if isdbg('chanerr');
215 # @spot at this point contains:-
216 # freq, spotted call, time, text, spotter, spotted cc, spotters cc, orig node
217 # then spotted itu, spotted cq, spotters itu, spotters cq
218 # you should be able to route on any of these
221 # fix up qra locators of known users
222 my $user = DXUser->get_current($spot[4]);
224 my $qra = $user->qra;
225 unless ($qra && is_qra($qra)) {
226 my $lat = $user->lat;
227 my $long = $user->long;
228 if (defined $lat && defined $long) {
229 $user->qra(DXBearing::lltoqra($lat, $long));
234 # send a remote command to a distant cluster if it is visible and there is no
235 # qra locator and we havn't done it for a month.
237 unless ($user->qra) {
239 my $to = $user->homenode;
240 my $last = $user->lastoper || 0;
241 if ($send_opernam && $to && $to ne $main::mycall && $main::systime > $last + $DXUser::lastoperinterval && ($node = Route::Node::get($to)) ) {
242 my $cmd = "forward/opernam $spot[4]";
243 # send the rcmd but we aren't interested in the replies...
244 my $dxchan = $node->bestdxchan;
245 if ($dxchan && $dxchan->is_clx) {
246 route(undef, $to, pc84($main::mycall, $to, $main::mycall, $cmd));
248 route(undef, $to, pc34($main::mycall, $to, $cmd));
252 $node = Route::Node::get($to);
254 $dxchan = $node->bestdxchan;
255 if ($dxchan && $dxchan->is_clx) {
256 route(undef, $to, pc84($main::mycall, $to, $main::mycall, $cmd));
258 route(undef, $to, pc34($main::mycall, $to, $cmd));
262 $user->lastoper($main::systime);
271 $r = Local::spot($self, @spot);
273 # dbg("Local::spot1 error $@") if isdbg('local') if $@;
276 # DON'T be silly and send on PC26s!
277 return if $pcno == 26;
279 # send out the filtered spots
280 send_dx_spot($self, $line, @spot) if @spot;
291 # return if $rspfcheck and !$self->rspfcheck(1, $_[5], $_[1]);
293 # announce duplicate checking
294 $_[3] =~ s/^\s+//; # remove leading blanks
298 if (@bad = BadWords::check($_[3])) {
299 dbg("PCPROT: Bad words: @bad, dropped") if isdbg('chanerr');
304 # if this is a 'nodx' node then ignore it
305 if ($badnode->in($_[5])) {
306 dbg("PCPROT: Bad Node, dropped") if isdbg('chanerr');
310 # if this is a 'bad spotter' user then ignore it
312 $nossid =~ s/-\d+$//;
313 if ($badspotter->in($nossid)) {
314 dbg("PCPROT: Bad Spotter, dropped") if isdbg('chanerr');
320 if ((($dxchan = DXChannel->get($_[2])) && $dxchan->is_user) || $_[4] =~ /^[\#\w.]+$/){
321 $self->send_chat($line, @_[1..6]);
322 } elsif ($_[2] eq '*' || $_[2] eq $main::mycall) {
324 # ignore something that looks like a chat line coming in with sysop
325 # flag - this is a kludge...
326 if ($_[3] =~ /^\#\d+ / && $_[4] eq '*') {
327 dbg('PCPROT: Probable chat rewrite, dropped') if isdbg('chanerr');
331 # here's a bit of fun, convert incoming ann with a callsign in the first word
332 # or one saying 'to <call>' to a talk if we can route to the recipient
334 my $call = AnnTalk::is_talk_candidate($_[1], $_[3]);
336 my $ref = Route::get($call);
338 $dxchan = $ref->bestdxchan;
339 $dxchan->talk($_[1], $call, undef, $_[3], $_[5]) if $dxchan != $self;
346 $self->send_announce($line, @_[1..6]);
348 $self->route($_[2], $line);
364 my $newline = "PC16^";
366 # do I want users from this channel?
367 unless ($self->user->wantpc16) {
368 dbg("PCPROT: don't send users to $self->{call}") if isdbg('chanerr');
373 if ($ncall eq $main::mycall) {
374 dbg("PCPROT: trying to alter config on this node from outside!") if isdbg('chanerr');
378 # do we believe this call?
379 unless ($ncall eq $self->{call} || $self->is_believed($ncall)) {
380 if (my $ivp = Investigate::get($ncall, $self->{call})) {
381 $ivp->store_pcxx($pcno,$line,$origin,@_);
383 dbg("PCPROT: We don't believe $ncall on $self->{call}");
387 my $node = Route::Node::get($ncall);
389 dbg("PCPROT: Node $ncall not in config") if isdbg('chanerr');
393 # dedupe only that which we potentially process
394 if (eph_dup($line)) {
395 dbg("PCPROT: dup PC16 detected") if isdbg('chanerr');
401 for ($i = 2; $i < $#_; $i++) {
402 my ($call, $conf, $here) = $_[$i] =~ /^(\S+) (\S) (\d)/o;
403 next unless $call && $conf && defined $here && is_callsign($call);
404 next if $call eq $main::mycall;
406 eph_del_regex("^PC17\\^$call\\^$ncall");
408 $conf = $conf eq '*';
410 # reject this if we think it is a node already
411 my $r = Route::Node::get($call);
412 my $u = DXUser->get_current($call) unless $r;
413 if ($r || ($u && $u->is_node)) {
414 dbg("PCPROT: $call is a node") if isdbg('chanerr');
418 $r = Route::User::get($call) || Route::User->new($call);
421 $node->lastseen($main::systime);
425 # add this station to the user database, if required
426 $call =~ s/-\d+$//o; # remove ssid for users
427 my $user = DXUser->get_current($call);
428 $user = DXUser->new($call) if !$user;
429 $user->homenode($node->call) if !$user->homenode;
430 $user->node($node->call);
431 $user->lastin($main::systime) unless DXChannel->get($call);
434 $self->process_pc59($pcno, 'A', hexstamp(), Route::Node::get($self->{call}),
435 $node, undef, @rout);
449 eph_del_regex("^PC16\\^$ncall.*$ucall");
451 # do I want users from this channel?
452 unless ($self->user->wantpc16) {
453 dbg("PCPROT: don't send users to $self->{call}") if isdbg('chanerr');
456 if ($ncall eq $main::mycall) {
457 dbg("PCPROT: trying to alter config on this node from outside!") if isdbg('chanerr');
461 # do we believe this call?
462 unless ($ncall eq $self->{call} || $self->is_believed($ncall)) {
463 if (my $ivp = Investigate::get($ncall, $self->{call})) {
464 $ivp->store_pcxx($pcno,$line,$origin,@_);
466 dbg("PCPROT: We don't believe $ncall on $self->{call}");
470 my $uref = Route::User::get($ucall);
472 dbg("PCPROT: Route::User $ucall not in config") if isdbg('chanerr');
474 my $node = Route::Node::get($ncall);
476 dbg("PCPROT: Route::Node $ncall not in config") if isdbg('chanerr');
479 return unless $node && $uref;
482 if ($self->in_filter_route($node)) {
483 if (eph_dup($line)) {
484 dbg("PCPROT: dup PC17 detected") if isdbg('chanerr');
491 $self->process_pc59($pcno, 'D', hexstamp(), Route::Node::get($self->{call}), $node, undef, $uref);
501 $self->state('init');
503 # record the type and version offered
504 if ($_[1] =~ /DXSpider Version: (\d+\.\d+) Build: (\d+\.\d+)/) {
505 $self->version(53 + $1);
506 $self->user->version(53 + $1);
507 $self->build(0 + $2);
508 $self->user->build(0 + $2);
509 unless ($self->is_spider) {
510 $self->user->sort('S');
515 $self->version(50.0);
516 $self->version($_[2] / 100) if $_[2] && $_[2] =~ /^\d+$/;
517 $self->user->version($self->version);
519 $self->newroute( $_[1] =~ /\!NRt/ );
521 # first clear out any nodes on this dxchannel
522 my $node = Route::Node::get($self->{call}) ;
524 foreach my $n ($node->nodes) {
525 next if $n eq $main::mycall;
526 next if $n eq $self->{call};
527 my $nref = Route::Node::get($n);
528 push @rout, $nref if $nref;
530 $self->process_pc59(21, 'D', hexstamp(), $main::routeroot, $node, undef, @rout) if @rout;
532 # send the new config
533 $self->send_local_config();
537 # incoming cluster list
547 my $parent = Route::Node::get($self->{call});
549 dbg("DXPROT: my parent $self->{call} has disappeared");
554 if (eph_dup($line)) {
555 dbg("PCPROT: dup PC19 detected") if isdbg('chanerr');
561 for ($i = 1; $i < $#_-1; $i += 4) {
563 my $call = uc $_[$i+1];
566 next unless defined $here && defined $conf && is_callsign($call);
568 # check for sane parameters
569 # $ver = 5000 if $ver eq '0000';
570 next if $ver < 5000; # only works with version 5 software
571 next if length $call < 3; # min 3 letter callsigns
572 next if $call eq $main::mycall;
574 # add this station to the user database, if required (don't remove SSID from nodes)
575 my $user = DXUser->get_current($call);
577 $user = DXUser->new($call);
579 $user->priv(1); # I have relented and defaulted nodes
581 $user->homenode($call);
584 $user->wantroutepc19(1) unless defined $user->wantroutepc19;
585 $user->lastin($main::systime) unless DXChannel->get($call);
588 # do we believe this call?
589 unless ($call eq $self->{call} || $self->is_believed($call)) {
590 my $pt = $user->lastping || 0;
591 if ($pt+$investigation_int < $main::systime && !Investigate::get($call, $self->{call})) {
592 my $ivp = Investigate->new($call, $self->{call});
595 $ivp->store_pcxx($pcno,$line,$origin,'PC19',$here,$call,$conf,$ver,$_[-1]);
597 dbg("PCPROT: We don't believe $call on $self->{call}");
601 eph_del_regex("^PC(?:21\\^$call|17\\^[^\\^]+\\^$call)");
603 my $r = Route::Node::get($call) || Route::Node->new($call);
607 $r->lastseen($main::systime);
609 if ($self->in_filter_route($r)) {
613 # unbusy and stop and outgoing mail (ie if somehow we receive another PC19 without a disconnect)
614 my $mref = DXMsg::get_busy($call);
615 $mref->stop_msg($call) if $mref;
618 # unshift in the route::node for this interface if isn't present
620 unshift @rout, $parent unless $rout[0]->call ne $self->{call};
621 $self->process_pc59($pcno, 'A', hexstamp(), Route::Node::get($self->{call}), $parent, undef, @rout);
625 # send local configuration
632 $self->send_local_config();
634 $self->state('normal');
635 $self->{lastping} = 0;
638 # delete a cluster from the list
647 return if $call eq $main::mycall; # don't allow malicious buggers to disconnect me (or ignore loops)!
649 unless ($call eq $self->{call} || $self->is_believed($call)) {
650 if (my $ivp = Investigate::get($call, $self->{call})) {
651 $ivp->store_pcxx($pcno,$line,$origin,@_);
653 dbg("PCPROT: We don't believe $call on $self->{call}");
657 eph_del_regex("^PC1[679].*$call");
659 # if I get a PC21 from the same callsign as self then treat it
660 # as a PC39: I have gone away
661 if ($call eq $self->call) {
662 $self->disconnect(1);
668 my $parent = Route::Node::get($self->{call});
670 dbg("DXPROT: my parent $self->{call} has disappeared");
676 my $node = Route::Node::get($call);
678 return if eph_dup($line);
680 $node->lastseen($main::systime);
683 return unless $self->in_filter_route($node);
687 $self->process_pc59($pcno, 'D', hexstamp(), Route::Node::get($self->{call}), $parent, undef, @rout);
697 $self->state('normal');
698 $self->{lastping} = 0;
709 # route foreign' pc27s
711 if ($_[8] ne $main::mycall) {
712 $self->route($_[8], $line);
717 # only do a rspf check on PC23 (not 27)
719 return if $rspfcheck and !$self->rspfcheck(1, $_[8], $_[7])
723 my $d = cltounix($_[1], sprintf("%02d18Z", $_[2]));
724 my $sfi = unpad($_[3]);
725 my $k = unpad($_[4]);
726 my $i = unpad($_[5]);
727 my ($r) = $_[6] =~ /R=(\d+)/;
729 if (($pcno == 23 && $d < $main::systime - $pc23_max_age) || $d > $main::systime + 1500 || $_[2] < 0 || $_[2] > 23) {
730 dbg("PCPROT: WWV Date ($_[1] $_[2]) out of range") if isdbg('chanerr');
733 if (Geomag::dup($d,$sfi,$k,$i,$_[6])) {
734 dbg("PCPROT: Dup WWV Spot ignored\n") if isdbg('chanerr');
737 $_[7] =~ s/-\d+$//o; # remove spotter's ssid
739 my $wwv = Geomag::update($d, $_[2], $sfi, $k, $i, @_[6..8], $r);
743 $rep = Local::wwv($self, $_[1], $_[2], $sfi, $k, $i, @_[6..8], $r);
745 # dbg("Local::wwv2 error $@") if isdbg('local') if $@;
748 # DON'T be silly and send on PC27s!
749 return if $pcno == 27;
751 # broadcast to the eager world
752 send_wwv_spot($self, $line, $d, $_[2], $sfi, $k, $i, @_[6..8]);
764 $nref = Route::Node::get($call);
765 $uref = Route::User::get($call);
766 return unless $nref || $uref; # if we don't know where they are, it's pointless sending it on
768 if (eph_dup($line)) {
769 dbg("PCPROT: Dup PC24 ignored\n") if isdbg('chanerr');
773 $nref->here($_[2]) if $nref;
774 $uref->here($_[2]) if $uref;
775 my $ref = $nref || $uref;
776 return unless $self->in_filter_route($ref);
778 $self->route_pc24($origin, $line, $ref, $_[3]);
788 if ($_[1] ne $main::mycall) {
789 $self->route($_[1], $line);
792 if ($_[2] eq $main::mycall) {
793 dbg("PCPROT: Trying to merge to myself, ignored") if isdbg('chanerr');
797 Log('DXProt', "Merge request for $_[3] spots and $_[4] WWV from $_[2]");
801 my @in = reverse Spot::search(1, undef, undef, 0, $_[3]);
804 $self->send(pc26(@{$in}[0..4], $_[2]));
810 my @in = reverse Geomag::search(0, $_[4], time, 1);
813 $self->send(pc27(@{$in}[0..5], $_[2]));
818 sub handle_26 {goto &handle_11}
819 sub handle_27 {goto &handle_23}
828 if ($_[1] eq $main::mycall) {
830 my $sub = "DXMsg::handle_$pcno";
833 $self->route($_[1], $line) unless $self->is_clx;
837 sub handle_29 {goto &handle_28}
838 sub handle_30 {goto &handle_28}
839 sub handle_31 {goto &handle_28}
840 sub handle_32 {goto &handle_28}
841 sub handle_33 {goto &handle_28}
849 if (eph_dup($line, $eph_pc34_restime)) {
850 dbg("PCPROT: dupe PC34, ignored") if isdbg('chanerr');
852 $self->process_rcmd($_[1], $_[2], $_[2], $_[3]);
856 # remote command replies
863 eph_del_regex("^PC35\\^$_[2]\\^$_[1]\\^");
864 $self->process_rcmd_reply($_[1], $_[2], $_[1], $_[3]);
867 sub handle_36 {goto &handle_34}
876 if ($_[1] eq $main::mycall) {
878 my $sub = "DXDb::handle_$pcno";
881 $self->route($_[1], $line) unless $self->is_clx;
885 # node connected list from neighbour
894 # incoming disconnect
901 if ($_[1] eq $self->{call}) {
902 $self->disconnect(1);
904 dbg("PCPROT: came in on wrong channel") if isdbg('chanerr');
908 sub handle_40 {goto &handle_28}
920 $l =~ s/[\x00-\x20\x7f-\xff]+//g; # remove all funny characters and spaces for dup checking
921 if (eph_dup($l, $eph_info_restime)) {
922 dbg("PCPROT: dup PC41, ignored") if isdbg('chanerr');
926 # input filter if required
927 # my $ref = Route::get($call) || Route->new($call);
928 # return unless $self->in_filter_route($ref);
930 if ($_[3] eq $_[2] || $_[3] =~ /^\s*$/) {
931 dbg('PCPROT: invalid value') if isdbg('chanerr');
935 # add this station to the user database, if required
936 my $user = DXUser->get_current($call);
937 $user = DXUser->new($call) unless $user;
941 } elsif ($_[2] == 2) {
943 } elsif ($_[2] == 3) {
944 if (is_latlong($_[3])) {
945 my ($lat, $long) = DXBearing::stoll($_[3]);
948 $user->qra(DXBearing::lltoqra($lat, $long));
950 dbg('PCPROT: not a valid lat/long') if isdbg('chanerr');
953 } elsif ($_[2] == 4) {
954 $user->homenode($_[3]);
955 } elsif ($_[2] == 5) {
956 if (is_qra(uc $_[3])) {
957 my ($lat, $long) = DXBearing::qratoll(uc $_[3]);
960 $user->qra(uc $_[3]);
962 dbg('PCPROT: not a valid QRA locator') if isdbg('chanerr');
966 $user->lastoper($main::systime); # to cut down on excessive for/opers being generated
969 unless ($self->{isolate}) {
970 DXChannel::broadcast_nodes($line, $self); # send it to everyone but me
973 # perhaps this IS what we want after all
974 # $self->route_pc41($ref, $call, $_[2], $_[3], $_[4]);
977 sub handle_42 {goto &handle_28}
981 sub handle_44 {goto &handle_37}
982 sub handle_45 {goto &handle_37}
983 sub handle_46 {goto &handle_37}
984 sub handle_47 {goto &handle_37}
985 sub handle_48 {goto &handle_37}
987 # message and database
995 if (eph_dup($line)) {
996 dbg("PCPROT: Dup PC49 ignored\n") if isdbg('chanerr');
1000 if ($_[1] eq $main::mycall) {
1001 DXMsg::handle_49($self, @_);
1003 $self->route($_[1], $line) unless $self->is_clx;
1007 # keep alive/user list
1016 my $node = Route::Node::get($call);
1018 return unless $node->call eq $self->{call};
1019 $node->usercount($_[2]);
1021 # input filter if required
1022 return unless $self->in_filter_route($node);
1024 $self->route_pc50($origin, $line, $node, $_[2], $_[3]) unless eph_dup($line);
1028 # incoming ping requests/answers
1041 if ($to eq $main::mycall) {
1043 $self->send(pc51($from, $to, '0'));
1045 # it's a reply, look in the ping list for this one
1046 my $ref = $pings{$from};
1048 my $tochan = DXChannel->get($from);
1050 my $r = shift @$ref;
1051 my $dxchan = DXChannel->get($r->{call});
1052 next unless $dxchan;
1053 my $t = tv_interval($r->{t}, [ gettimeofday ]);
1054 if ($dxchan->is_user) {
1055 my $s = sprintf "%.2f", $t;
1056 my $ave = sprintf "%.2f", $tochan ? ($tochan->{pingave} || $t) : $t;
1057 $dxchan->send($dxchan->msg('pingi', $from, $s, $ave))
1058 } elsif ($dxchan->is_node) {
1060 my $nopings = $tochan->user->nopings || $obscount;
1061 push @{$tochan->{pingtime}}, $t;
1062 shift @{$tochan->{pingtime}} if @{$tochan->{pingtime}} > 6;
1064 # cope with a missed ping, this means you must set the pingint large enough
1065 if ($t > $tochan->{pingint} && $t < 2 * $tochan->{pingint} ) {
1066 $t -= $tochan->{pingint};
1069 # calc smoothed RTT a la TCP
1070 if (@{$tochan->{pingtime}} == 1) {
1071 $tochan->{pingave} = $t;
1073 $tochan->{pingave} = $tochan->{pingave} + (($t - $tochan->{pingave}) / 6);
1075 my $rref = Route::Node::get($tochan->{call});
1076 $rref->pingtime($tochan->{pingave}) if $rref;
1077 $tochan->{nopings} = $nopings; # pump up the timer
1078 if (my $ivp = Investigate::get($from, $self->{call})) {
1081 } elsif (my $rref = Route::Node::get($r->{call})) {
1082 if (defined $rref->pingtime) {
1083 $rref->pingtime($rref->pingtime + (($t - $rref->pingtime) / 6));
1085 $rref->pingtime($t);
1087 if (my $ivp = Investigate::get($from, $self->{call})) {
1096 if (eph_dup($line)) {
1097 dbg("PCPROT: dup PC51 detected") if isdbg('chanerr');
1100 # route down an appropriate thingy
1101 $self->route($to, $line);
1106 # New style routing handler
1114 my ($sort, $hexstamp, $ncall) = @_[1,2,3];
1115 if ($ncall eq $main::mycall) {
1116 dbg("PCPROT: ignoring PC59 for me") if isdbg('chan');
1120 # do this once for filtering with a throwaway routing entry if a new node
1121 my $fnode = Route::Node::get($ncall) || Route::new($ncall);
1122 return unless $self->in_filter_route($fnode);
1124 return if eph_dup($line);
1126 # mark myself as NewRoute if I get a PC59
1127 $self->{newroute} = 1 if $ncall eq $self->{call};
1129 # now do it properly for actions
1130 my $node = Route::Node::get($ncall) || Route::Node->new($ncall);
1133 # create the user, if required
1134 my $user = DXUser->get_current($ncall);
1136 $user = DXUser->new($ncall);
1138 $user->priv(1); # I have relented and defaulted nodes
1140 $user->homenode($ncall);
1141 $user->node($ncall);
1145 # find each of the entries (or create new ones)
1147 for my $ent (@_[4..$#_]) {
1148 next if $ent =~ /^H\d+$/;
1150 my $ref = $node->dec_pc59($ent);
1152 my $user = DXUser->get_current($ref->{call});
1154 $user = DXUser->new($ref->{call});
1155 $user->homenode($ncall);
1156 $user->node($ncall);
1157 if ($ref->isa('Route::Node')) {
1158 $user->priv(1); # I have relented and defaulted nodes
1167 dbg("DXPROT: unknown entity type '$ent'") if isdbg('chan');
1173 $self->process_pc59($pcno, $sort, $hexstamp, Route::Node::get($self->{call}), $node, $line, @refs);
1177 # dunno but route it
1185 if ($call ne $main::mycall) {
1186 $self->route($call, $line);
1200 my $d = cltounix($call, sprintf("%02d18Z", $_[2]));
1201 if (($pcno == 23 && $d < $main::systime - $pc23_max_age) || $d > $main::systime + 1500 || $_[2] < 0 || $_[2] > 23) {
1202 dbg("PCPROT: WCY Date ($call $_[2]) out of range") if isdbg('chanerr');
1205 @_ = map { unpad($_) } @_;
1207 dbg("PCPROT: Dup WCY Spot ignored\n") if isdbg('chanerr');
1211 my $wcy = WCY::update($d, @_[2..12]);
1215 $rep = Local::wcy($self, @_[1..12]);
1217 # dbg("Local::wcy error $@") if isdbg('local') if $@;
1220 # broadcast to the eager world
1221 send_wcy_spot($self, $line, $d, @_[2..12]);
1224 # remote commands (incoming)
1231 $self->process_rcmd($_[1], $_[2], $_[3], $_[4]);
1234 # remote command replies
1241 $self->process_rcmd_reply($_[1], $_[2], $_[3], $_[4]);
1244 # if get here then rebroadcast the thing with its Hop count decremented (if
1245 # there is one). If it has a hop count and it decrements to zero then don't
1248 # NOTE - don't arrive here UNLESS YOU WANT this lump of protocol to be
1259 if (eph_dup($line)) {
1260 dbg("PCPROT: Ephemeral dup, dropped") if isdbg('chanerr');
1262 unless ($self->{isolate}) {
1263 DXChannel::broadcast_nodes($line, $self) if $line =~ /\^H\d+\^?~?$/; # send it to everyone but me