add DXCIDR, fix version no tracking
[spider.git] / perl / Route / Node.pm
1 #
2 # Node routing routines
3 #
4 # Copyright (c) 2001 Dirk Koopman G1TLH
5 #
6 #
7 #
8
9 package Route::Node;
10
11 use DXDebug;
12 use Route;
13 use Route::User;
14 use DXUtil;
15
16 use strict;
17
18 use vars qw(%list %valid @ISA $max $filterdef $obscount);
19 @ISA = qw(Route);
20
21 %valid = (
22                   K => '9,Seen on PC92K,yesno',
23                   nodes => '0,Nodes,parray',
24                   users => '0,Users,parray',
25                   usercount => '0,User Count',
26                   version => '0,Version',
27                   build => '0,Build',
28                   handle_xml => '0,Using XML,yesno',
29                   lastmsg => '0,Last Route Msg,atime',
30                   lastid => '0,Last Route MsgID',
31                   do_pc9x => '0,Uses pc9x,yesno',
32                   via_pc92 => '0,In via pc92?,yesno',
33                   obscount => '0,Obscount',
34                   last_PC92C => '9,Last PC92C',
35                   PC92C_dxchan => '9,PC92C hops,phash',
36 );
37
38 $filterdef = $Route::filterdef;
39 %list = ();
40 $max = 0;
41 $obscount = 3;
42
43 sub count
44 {
45         my $n = scalar (keys %list);
46         $max = $n if $n > $max;
47         return $n;
48 }
49
50 sub max
51 {
52         count();
53         return $max;
54 }
55
56 #
57 # this routine handles the possible adding of an entry in the routing
58 # table. It will only add an entry if it is new. It may have all sorts of
59 # other side effects which may include fixing up other links.
60 #
61 # It will return a node object if (and only if) it is a completely new
62 # object with that callsign. The upper layers are expected to do something
63 # sensible with this!
64 #
65 # called as $parent->add(call, dxchan, version, flags)
66 #
67
68 sub add
69 {
70         my $parent = shift;
71         my $call = uc shift;
72         confess "Route::add trying to add $call to myself" if $call eq $parent->{call};
73         my $self = get($call);
74         if ($self) {
75                 $self->_addparent($parent);
76                 $parent->_addnode($self);
77                 return undef;
78         }
79         $self = $parent->new($call, @_);
80         $parent->_addnode($self);
81         dbg("CLUSTER: node $call added") if isdbg('cluster');
82         return $self;
83 }
84
85 #
86 # this routine is the opposite of 'add' above.
87 #
88 # It will return an object if (and only if) this 'del' will remove
89 # this object completely
90 #
91
92 sub del
93 {
94         my $self = shift;
95         my $pref = shift;
96
97         # delete parent from this call's parent list
98         $pref->_delnode($self);
99     $self->_delparent($pref);
100         my @nodes;
101         my $ncall = $self->{call};
102
103         # is this the last connection, I have no parents anymore?
104         unless (@{$self->{parent}}) {
105                 foreach my $rcall (@{$self->{nodes}}) {
106                         next if grep $rcall eq $_, @_;
107                         my $r = Route::Node::get($rcall);
108                         push @nodes, $r->del($self, $ncall, @_) if $r;
109                 }
110                 $self->_del_users;
111                 delete $list{$ncall};
112                 push @nodes, $self;
113                 dbg("CLUSTER: node $ncall deleted") if isdbg('cluster');
114         }
115         return @nodes;
116 }
117
118 # this deletes this node completely by grabbing the parents
119 # and deleting me from them, then deleting me from all the
120 # dependent nodes.
121 sub delete
122 {
123         my $self = shift;
124         my @out;
125         my $ncall = $self->{call};
126
127         # get rid of users and parents
128         $self->_del_users;
129         if (@{$self->{parent}}) {
130                 foreach my $call (@{$self->{parent}}) {
131                         my $parent = Route::Node::get($call);
132                         push @out, $parent->del($self) if $parent;
133                 }
134         }
135         # get rid of my nodes
136         push @out, $self->del_nodes;
137         # this only happens if we a orphan with no parents
138         if ($list{$ncall}) {
139                 push @out, $self;
140                 delete $list{$ncall};
141         }
142         return @out;
143 }
144
145 sub del_nodes
146 {
147         my $parent = shift;
148         my @out;
149         foreach my $rcall (@{$parent->{nodes}}) {
150                 my $r = get($rcall);
151                 push @out, $r->del($parent, $parent->{call}, @_) if $r;
152         }
153         return @out;
154 }
155
156 sub _del_users
157 {
158         my $self = shift;
159         for (@{$self->{users}}) {
160                 my $ref = Route::User::get($_);
161                 $ref->del($self) if $ref;
162         }
163         $self->{users} = [];
164 }
165
166 # add a user to this node
167 sub add_user
168 {
169         my $self = shift;
170         my $ucall = shift;
171         my $here = shift;
172         my $ip = shift;
173
174         confess "Trying to add NULL User call to routing tables" unless $ucall;
175
176         my $uref = Route::User::get($ucall);
177         my @out;
178         if ($uref) {
179                 @out = $uref->addparent($self);
180         } else {
181                 $uref = Route::User->new($ucall, $self->{call}, $here, $ip);
182                 @out = $uref;
183         }
184         $self->_adduser($uref);
185         $self->{usercount} = scalar @{$self->{users}};
186
187         return @out;
188 }
189
190 # delete a user from this node
191 sub del_user
192 {
193         my $self = shift;
194         my $ref = shift;
195         my @out;
196
197         if ($ref) {
198                 @out = $self->_deluser($ref);
199                 $ref->del($self);
200         } else {
201                 confess "tried to delete non-existant $ref->{call} from $self->{call}";
202         }
203         $self->{usercount} = scalar @{$self->{users}};
204         return @out;
205 }
206
207 # is a user on this node
208 sub is_user
209 {
210         my $self = shift;
211         my $call = shift;
212         return scalar grep {$_ eq $call} @{$self->{users}};
213 }
214
215 sub usercount
216 {
217         my $self = shift;
218         if (@_ && @{$self->{users}} == 0) {
219                 $self->{usercount} = shift;
220         }
221         return $self->{usercount};
222 }
223
224 sub users
225 {
226         my $self = shift;
227         return @{$self->{users}};
228 }
229
230 sub nodes
231 {
232         my $self = shift;
233         return @{$self->{nodes}};
234 }
235
236 sub rnodes
237 {
238         my $self = shift;
239         my @out;
240         foreach my $call (@{$self->{nodes}}) {
241                 next if grep $call eq $_, @_;
242                 push @out, $call;
243                 my $r = get($call);
244                 push @out, $r->rnodes($call, @_) if $r;
245         }
246         return @out;
247 }
248
249 # this takes in a list of node and user calls (not references) from
250 # a config type update for a node and returns
251 # the differences as lists of things that have gone away
252 # and things that have been added.
253 sub calc_config_changes
254 {
255         my $self = shift;
256         my %nodes = map {$_ => 1} @{$self->{nodes}};
257         my %users = map {$_ => 1} @{$self->{users}};
258         my $cnodes = shift;
259         my $cusers = shift;
260         if (isdbg('route')) {
261                 dbg("ROUTE: start calc_config_changes");
262                 dbg("ROUTE: incoming nodes on $self->{call}: " . join(',', sort @$cnodes));
263                 dbg("ROUTE: incoming users on $self->{call}: " . join(',', sort @$cusers));
264                 dbg("ROUTE: existing nodes on $self->{call}: " . join(',', sort keys %nodes));
265                 dbg("ROUTE: existing users on $self->{call}: " . join(',', sort keys %users));
266         }
267         my (@dnodes, @dusers, @nnodes, @nusers);
268         push @nnodes, map {my @r = $nodes{$_} ? () : $_; delete $nodes{$_}; @r} @$cnodes;
269         push @dnodes, keys %nodes;
270         push @nusers, map {my @r = $users{$_} ? () : $_; delete $users{$_}; @r} @$cusers;
271         push @dusers, keys %users;
272         if (isdbg('route')) {
273                 dbg("ROUTE: deleted nodes on $self->{call}: " . join(',', sort @dnodes));
274                 dbg("ROUTE: deleted users on $self->{call}: " . join(',', sort @dusers));
275                 dbg("ROUTE: added nodes on $self->{call}: " . join(',', sort  @nnodes));
276                 dbg("ROUTE: added users on $self->{call}: " . join(',', sort @nusers));
277                 dbg("ROUTE: end calc_config_changes");
278         }
279         return (\@dnodes, \@dusers, \@nnodes, \@nusers);
280 }
281
282
283 sub new
284 {
285         my $pkg = shift;
286         my $call = uc shift;
287
288         confess "already have $call in $pkg" if $list{$call};
289
290         my $self = $pkg->SUPER::new($call);
291         $self->{parent} = ref $pkg ? [ $pkg->{call} ] : [ ];
292         $self->{version} = shift || 5401;
293         $self->{flags} = shift || Route::here(1);
294         $self->{users} = [];
295         $self->{nodes} = [];
296         $self->{PC92C_dxchan} = {};
297         my $ip = shift;
298         $self->{ip} = $ip if defined $ip;
299         $self->reset_obs;                       # by definition
300
301         $list{$call} = $self;
302
303         return $self;
304 }
305
306 sub get
307 {
308         my $call = shift;
309         $call = shift if ref $call;
310         my $ref = $list{uc $call};
311         dbg("ROUTE: Failed to get Node $call" ) if !$ref && isdbg('routerr');
312         return $ref;
313 }
314
315 sub get_all
316 {
317         return values %list;
318 }
319
320 sub _addparent
321 {
322         my $self = shift;
323     return $self->_addlist('parent', @_);
324 }
325
326 sub _delparent
327 {
328         my $self = shift;
329     return $self->_dellist('parent', @_);
330 }
331
332
333 sub _addnode
334 {
335         my $self = shift;
336     return $self->_addlist('nodes', @_);
337 }
338
339 sub _delnode
340 {
341         my $self = shift;
342     return $self->_dellist('nodes', @_);
343 }
344
345
346 sub _adduser
347 {
348         my $self = shift;
349     return $self->_addlist('users', @_);
350 }
351
352 sub _deluser
353 {
354         my $self = shift;
355     return $self->_dellist('users', @_);
356 }
357
358 sub dec_obs
359 {
360         my $self = shift;
361         $self->{obscount}--;
362         return $self->{obscount};
363 }
364
365 sub reset_obs
366 {
367         my $self = shift;
368         $self->{obscount} = $obscount;
369 }
370
371 sub measure_pc9x_t
372 {
373         my $parent = shift;
374         my $t = shift;
375         my $lastid = $parent->{lastid};
376         if ($lastid) {
377                 return ($t < $lastid) ? $t+86400-$lastid : $t - $lastid;
378         } else {
379                 return 86400;
380         }
381 }
382
383 sub PC92C_dxchan
384 {
385         my $parent = shift;
386         my $call = shift;
387         my $hops = shift;
388         if ($call && $hops) {
389                 $hops =~ s/^H//;
390                 $parent->{PC92C_dxchan}->{$call} = $hops;
391                 return;
392         }
393         return (%{$parent->{PC92C_dxchan}});
394 }
395
396 sub DESTROY
397 {
398         my $self = shift;
399         my $pkg = ref $self;
400         my $call = $self->{call} || "Unknown";
401
402         dbg("ROUTE: destroying $pkg with $call") if isdbg('routelow');
403 }
404
405 #
406 # generic AUTOLOAD for accessors
407 #
408
409 sub AUTOLOAD
410 {
411         no strict;
412         my $name = $AUTOLOAD;
413         return if $name =~ /::DESTROY$/;
414         $name =~ s/^.*:://o;
415
416         confess "Non-existant field '$AUTOLOAD'" unless $valid{$name} || $Route::valid{$name};
417
418         # this clever line of code creates a subroutine which takes over from autoload
419         # from OO Perl - Conway
420         *$AUTOLOAD = sub {$_[0]->{$name} = $_[1] if @_ > 1; return $_[0]->{$name}};
421         goto &$AUTOLOAD;
422 }
423
424 1;
425