+# stick a broadcast on the delayed queue (but only up to 20 items)
+sub delay
+{
+ my $self = shift;
+ my $s = shift;
+
+ $self->{delayed} = [] unless $self->{delayed};
+ push @{$self->{delayed}}, $s;
+ if (@{$self->{delayed}} >= 20) {
+ shift @{$self->{delayed}}; # lose oldest one
+ }
+}
+
+# change the state of the channel - lots of scope for debugging here :-)
+sub state
+{
+ my $self = shift;
+ if (@_) {
+ $self->{oldstate} = $self->{state};
+ $self->{state} = shift;
+ $self->{func} = '' unless defined $self->{func};
+ dbg("$self->{call} channel func $self->{func} state $self->{oldstate} -> $self->{state}\n") if isdbg('state');
+
+ # if there is any queued up broadcasts then splurge them out here
+ if ($self->{delayed} && ($self->{state} eq 'prompt' || $self->{state} eq 'talk')) {
+ $self->send (@{$self->{delayed}});
+ delete $self->{delayed};
+ }
+ }
+ return $self->{state};
+}
+
+# disconnect this channel
+sub disconnect
+{
+ my $self = shift;
+ my $user = $self->{user};
+
+ # remove outstanding pings
+ delete $pings{$self->{call}};
+
+ $user->close() if defined $user;
+ $self->{conn}->disconnect;
+ $self->del();
+}
+
+#
+# just close all the socket connections down without any fiddling about, cleaning, being
+# nice to other processes and otherwise telling them what is going on.
+#
+# This is for the benefit of forked processes to prepare for starting new programs, they
+# don't want or need all this baggage.
+#
+
+sub closeall
+{
+ my $ref;
+ foreach $ref (values %channels) {
+ $ref->{conn}->disconnect() if $ref->{conn};
+ }
+}
+
+#
+# Tell all the users that we have come in or out (if they want to know)
+#
+sub tell_login
+{
+ my ($self, $m) = @_;
+
+ # send info to all logged in thingies
+ my @dxchan = get_all_users();
+ my $dxchan;
+ foreach $dxchan (@dxchan) {
+ next if $dxchan == $self;
+ next if $dxchan->{call} eq $main::mycall;
+ $dxchan->send($dxchan->msg($m, $self->{call})) if $dxchan->{logininfo};
+ }
+}
+
+# various access routines
+
+#
+# return a list of valid elements
+#
+
+sub fields
+{
+ return keys(%valid);
+}
+
+#
+# return a prompt for a field
+#
+
+sub field_prompt
+{
+ my ($self, $ele) = @_;
+ return $valid{$ele};
+}
+
+# take a standard input message and decode it into its standard parts
+sub decode_input
+{
+ my $dxchan = shift;
+ my $data = shift;
+ my ($sort, $call, $line) = $data =~ /^([A-Z])([A-Z0-9\-]{3,9})\|(.*)$/;
+
+ my $chcall = (ref $dxchan) ? $dxchan->call : "UN.KNOWN";
+
+ # the above regexp must work
+ unless (defined $sort && defined $call && defined $line) {
+# $data =~ s/([\x00-\x1f\x7f-\xff])/uc sprintf("%%%02x",ord($1))/eg;
+ dbg("DUFF Line on $chcall: $data");
+ return ();
+ }
+
+ if(ref($dxchan) && $call ne $chcall) {
+ dbg("DUFF Line come in for $call on wrong channel $chcall");
+ return();
+ }
+
+ return ($sort, $call, $line);
+}
+
+sub rspfcheck
+{
+ my ($self, $flag, $node, $user) = @_;
+ my $nref = Route::Node::get($node);
+ my $dxchan = $nref->dxchan if $nref;
+ if ($nref && $dxchan) {
+ if ($dxchan == $self) {
+ return 1 unless $user;
+ return 1 if $user eq $node;
+ my @users = $nref->users;
+ return 1 if @users == 0 || grep $user eq $_, @users;
+ dbg("RSPF: $user not on $node") if isdbg('chanerr');
+ } else {
+ dbg("RSPF: Shortest path for $node is " . $nref->dxchan->{call}) if isdbg('chanerr');
+ }
+ } else {
+ return 1 if $flag;
+ dbg("RSPF: required $node not found" ) if isdbg('chanerr');
+ }
+ return 0;
+}
+
+# broadcast a message to all clusters taking into account isolation
+# [except those mentioned after buffer]
+sub broadcast_nodes
+{
+ my $s = shift; # the line to be rebroadcast
+ my @except = @_; # to all channels EXCEPT these (dxchannel refs)
+ my @dxchan = DXChannel::get_all_nodes();
+ my $dxchan;
+
+ # send it if it isn't the except list and isn't isolated and still has a hop count
+ foreach $dxchan (@dxchan) {
+ next if grep $dxchan == $_, @except;
+ next if $dxchan == $main::me;
+
+ my $routeit = $dxchan->can('adjust_hops') ? $dxchan->adjust_hops($s) : $s; # adjust its hop count by node name
+
+ $dxchan->send($routeit) unless $dxchan->{isolate} || !$routeit;
+ }
+}
+
+# broadcast a message to all clusters ignoring isolation
+# [except those mentioned after buffer]
+sub broadcast_all_nodes
+{
+ my $s = shift; # the line to be rebroadcast
+ my @except = @_; # to all channels EXCEPT these (dxchannel refs)
+ my @dxchan = DXChannel::get_all_nodes();
+ my $dxchan;
+
+ # send it if it isn't the except list and isn't isolated and still has a hop count
+ foreach $dxchan (@dxchan) {
+ next if grep $dxchan == $_, @except;
+ next if $dxchan == $main::me;
+
+ my $routeit = $dxchan->can('adjust_hops') ? $dxchan->adjust_hops($s) : $s; # adjust its hop count by node name
+ $dxchan->send($routeit);
+ }
+}
+
+# broadcast to all users
+# storing the spot or whatever until it is in a state to receive it
+sub broadcast_users
+{
+ my $s = shift; # the line to be rebroadcast
+ 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 = DXChannel::get_all_users();
+ my $dxchan;
+ my @out;
+
+ foreach $dxchan (@dxchan) {
+ next if grep $dxchan == $_, @except;
+ push @out, $dxchan;
+ }
+ broadcast_list($s, $sort, $fref, @out);
+}
+
+
+# broadcast to a list of users
+sub broadcast_list
+{
+ my $s = shift;
+ my $sort = shift;
+ my $fref = shift;
+ my $dxchan;
+
+ foreach $dxchan (@_) {
+ my $filter = 1;
+ next if $dxchan == $main::me;
+
+ if ($sort eq 'dx') {
+ next unless $dxchan->{dx};
+ ($filter) = $dxchan->{spotsfilter}->it(@{$fref}) if ref $fref;
+ next unless $filter;
+ }
+ next if $sort eq 'ann' && !$dxchan->{ann} && $s !~ /^To\s+LOCAL\s+de\s+(?:$main::myalias|$main::mycall)/i;
+ next if $sort eq 'wwv' && !$dxchan->{wwv};
+ next if $sort eq 'wcy' && !$dxchan->{wcy};
+ next if $sort eq 'wx' && !$dxchan->{wx};
+
+ $s =~ s/\a//og unless $dxchan->{beep};
+
+ if ($dxchan->{state} eq 'prompt' || $dxchan->{state} eq 'talk') {
+ $dxchan->send($s);
+ } else {
+ $dxchan->delay($s);
+ }
+ }
+}
+
+sub handlepingreply
+{
+ my ($self, $from) = @_;
+
+ my $ref = $pings{$from};
+ if ($ref) {
+ my $tochan = DXChannel->get($from);
+ 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', $from, $s, $ave))
+ } elsif ($dxchan->is_node) {
+ if ($tochan) {
+ my $nopings = $tochan->user->nopings || 2;
+ push @{$tochan->{pingtime}}, $t;
+ shift @{$tochan->{pingtime}} if @{$tochan->{pingtime}} > 6;
+
+ # cope with a missed ping, this means you must set the pingint large enough
+ if ($t > $tochan->{pingint} && $t < 2 * $tochan->{pingint} ) {
+ $t -= $tochan->{pingint};
+ }
+
+ # calc smoothed RTT a la TCP
+ if (@{$tochan->{pingtime}} == 1) {
+ $tochan->{pingave} = $t;
+ } else {
+ $tochan->{pingave} = $tochan->{pingave} + (($t - $tochan->{pingave}) / 6);
+ }
+ $tochan->{nopings} = $nopings; # pump up the timer
+ }
+ }
+ }
+ }
+}
+
+#no strict;
+sub AUTOLOAD
+{
+ my $self = shift;
+ no strict;
+ my $name = $AUTOLOAD;
+ return if $name =~ /::DESTROY$/;
+ $name =~ s/^.*:://o;
+
+ confess "Non-existant field '$AUTOLOAD'" if !$valid{$name};
+
+ # this clever line of code creates a subroutine which takes over from autoload
+ # from OO Perl - Conway
+ *$AUTOLOAD = sub {@_ > 1 ? $_[0]->{$name} = $_[1] : $_[0]->{$name}};
+ &$AUTOLOAD($self, @_);
+# *{$AUTOLOAD} = sub {@_ > 1 ? $_[0]->{$name} = $_[1] : $_[0]->{$name}} ;
+# @_ ? $self->{$name} = shift : $self->{$name} ;
+}
+
+