d75dd46c7d3e21b05ce21133be0cafa699328fe8
[spider.git] / perl / DXProt.pm
1 #!/usr/bin/perl
2 #
3 # This module impliments the protocal mode for a dx cluster
4 #
5 # Copyright (c) 1998 Dirk Koopman G1TLH
6 #
7 # $Id$
8
9
10 package DXProt;
11
12 @ISA = qw(DXChannel);
13
14 use DXUtil;
15 use DXChannel;
16 use DXUser;
17 use DXM;
18 use DXCluster;
19 use DXProtVars;
20 use DXCommandmode;
21 use Spot;
22 use Date::Parse;
23 use DXProtout;
24
25 use strict;
26 use vars qw($me);
27
28 $me = undef;            # the channel id for this cluster
29
30 sub init
31 {
32   my $user = DXUser->get($main::mycall);
33   $me = DXProt->new($main::mycall, undef, $user); 
34 #  $me->{sort} = 'M';    # M for me
35 }
36
37 #
38 # obtain a new connection this is derived from dxchannel
39 #
40
41 sub new 
42 {
43   my $self = DXChannel::alloc(@_);
44   $self->{sort} = 'A';   # in absence of how to find out what sort of an object I am
45   return $self;
46 }
47
48 # this is how a pc connection starts (for an incoming connection)
49 # issue a PC38 followed by a PC18, then wait for a PC20 (remembering
50 # all the crap that comes between).
51 sub start
52 {
53   my ($self, $line) = shift;
54   my $call = $self->call;
55   
56   # remember type of connection
57   $self->{consort} = $line;
58
59   # set unbuffered
60   $self->send_now('B',"0");
61   
62   # send initialisation string
63   $self->send(pc38()) if DXNode->get_all();
64   $self->send(pc18());
65   $self->state('normal');
66   $self->pc50_t(time);
67 }
68
69 #
70 # This is the normal pcxx despatcher
71 #
72 sub normal
73 {
74   my ($self, $line) = @_;
75   my @field = split /[\^\~]/, $line;
76   
77   # ignore any lines that don't start with PC
78   return if !$field[0] =~ /^PC/;
79
80   # process PC frames
81   my ($pcno) = $field[0] =~ /^PC(\d\d)/;          # just get the number
82   return if $pcno < 10 || $pcno > 51;
83   
84   SWITCH: {
85     if ($pcno == 10) {             # incoming talk
86
87       # is it for me or one of mine?
88           my $call = ($field[5] gt ' ') ? $field[5] : $field[2];
89           if ($call eq $main::mycall || grep $_ eq $call, get_all_user_calls()) {
90             
91                 # yes, it is
92                 my $text = unpad($field[3]);
93                 my $ref = DXChannel->get($call);
94                 $ref->send("$call de $field[1]: $text") if $ref;
95           } else {
96             route($field[2], $line);       # relay it on its way
97           }
98           return;
99         }
100         
101     if ($pcno == 11) {             # dx spot
102
103       # if this is a 'nodx' node then ignore it
104           last SWITCH if grep $field[7] =~ /^$_/,  @DXProt::nodx_node;
105           
106       # convert the date to a unix date
107           my $date = $field[3];
108           my $time = $field[4];
109           $date =~ s/^\s*(\d+)-(\w\w\w)-(19\d\d)$/$1 $2 $3/;
110           $time =~ s/^(\d\d)(\d\d)Z$/$1:$2 +0000/;
111           my $d = str2time("$date $time");
112           return if !$d;               # bang out (and don't pass on) if date is invalid
113           
114           # strip off the leading & trailing spaces from the comment
115           my $text = unpad($field[5]);
116           
117           # store it away
118           Spot::add($field[1], $field[2], $d, $text, $field[6]);
119           
120           # format and broadcast it to users
121           my $spotter = $field[6];
122           $spotter =~ s/^(\w+)-\d+/$1/;    # strip off the ssid from the spotter
123       $spotter .= ':';                # add a colon
124           
125           # send orf to the users
126           my $buf = sprintf "DX de %-7.7s %13.13s %-12.12s %-30.30s %5.5s\a\a", $spotter, $field[1], $field[2], $text, $field[4];
127       broadcast_users($buf);
128           
129           last SWITCH;
130         }
131         
132     if ($pcno == 12) {             # announces
133         
134           if ($field[2] eq '*' || $field[2] eq $main::mycall) {
135
136         # strip leading and trailing stuff
137             my $text = unpad($field[3]);
138                 my $target = "To Sysops" if $field[4] eq '*';
139                 $target = "WX" if $field[6];
140                 $target = "To All" if !$target;
141                 broadcast_users("$target de $field[1]: $text"); 
142                 
143                 return if $field[2] eq $main::mycall;   # it's routed to me
144           } else {
145             route($field[2], $line);
146                 return;                     # only on a routed one
147           }
148           
149           last SWITCH;
150         }
151         
152     if ($pcno == 13) {last SWITCH;}
153     if ($pcno == 14) {last SWITCH;}
154     if ($pcno == 15) {last SWITCH;}
155         
156     if ($pcno == 16) {              # add a user
157           my $node = DXCluster->get($field[1]);
158           last SWITCH if !$node;        # ignore if havn't seen a PC19 for this one yet
159           my $i;
160           
161           for ($i = 2; $i < $#field; $i++) {
162             my ($call, $confmode, $here) = $field[$i] =~ /^(\w+) (-) (\d)/o;
163                 next if length $call < 3;
164                 next if !$confmode;
165         $call =~ s/^(\w+)-\d+/$1/;        # remove ssid
166                 next if DXCluster->get($call);    # we already have this (loop?)
167                 
168                 $confmode = $confmode eq '*';
169                 DXNodeuser->new($self, $node, $call, $confmode, $here);
170           }
171           last SWITCH;
172         }
173         
174     if ($pcno == 17) {              # remove a user
175           my $ref = DXCluster->get($field[1]);
176           $ref->del() if $ref;
177           last SWITCH;
178         }
179         
180     if ($pcno == 18) {              # link request
181         
182       # send our nodes
183           my $hops = get_hops(19);
184           $self->send($me->pc19(get_all_ak1a()));
185           
186       # get all the local users and send them out
187           $self->send($me->pc16(get_all_users()));
188           $self->send(pc20());
189           last SWITCH;
190         }
191         
192     if ($pcno == 19) {               # incoming cluster list
193       my $i;
194           for ($i = 1; $i < $#field-1; $i += 4) {
195             my $here = $field[$i];
196             my $call = $field[$i+1];
197                 my $confmode = $field[$i+2] eq '*';
198                 my $ver = $field[$i+3];
199                 
200                 # now check the call over
201                 next if DXCluster->get($call);   # we already have this
202                 
203                 # check for sane parameters
204                 next if $ver < 5000;             # only works with version 5 software
205                 next if length $call < 3;        # min 3 letter callsigns
206         DXNode->new($self, $call, $confmode, $here, $ver);
207           }
208           last SWITCH;
209         }
210         
211     if ($pcno == 20) {              # send local configuration
212
213       # send our nodes
214           my $hops = get_hops(19);
215           $self->send($me->pc19(get_all_ak1a()));
216           
217       # get all the local users and send them out
218           $self->send($me->pc16(get_all_users()));
219           $self->send(pc22());
220           return;
221         }
222         
223     if ($pcno == 21) {             # delete a cluster from the list
224           my $ref = DXCluster->get($field[1]);
225           $ref->del() if $ref;
226           last SWITCH;
227         }
228         
229     if ($pcno == 22) {last SWITCH;}
230     if ($pcno == 23) {last SWITCH;}
231     if ($pcno == 24) {last SWITCH;}
232     if ($pcno == 25) {last SWITCH;}
233     if ($pcno == 26) {last SWITCH;}
234     if ($pcno == 27) {last SWITCH;}
235     if ($pcno == 28) {last SWITCH;}
236     if ($pcno == 29) {last SWITCH;}
237     if ($pcno == 30) {last SWITCH;}
238     if ($pcno == 31) {last SWITCH;}
239     if ($pcno == 32) {last SWITCH;}
240     if ($pcno == 33) {last SWITCH;}
241     if ($pcno == 34) {last SWITCH;}
242     if ($pcno == 35) {last SWITCH;}
243     if ($pcno == 36) {last SWITCH;}
244     if ($pcno == 37) {last SWITCH;}
245     if ($pcno == 38) {last SWITCH;}
246     if ($pcno == 39) {last SWITCH;}
247     if ($pcno == 40) {last SWITCH;}
248     if ($pcno == 41) {last SWITCH;}
249     if ($pcno == 42) {last SWITCH;}
250     if ($pcno == 43) {last SWITCH;}
251     if ($pcno == 44) {last SWITCH;}
252     if ($pcno == 45) {last SWITCH;}
253     if ($pcno == 46) {last SWITCH;}
254     if ($pcno == 47) {last SWITCH;}
255     if ($pcno == 48) {last SWITCH;}
256     if ($pcno == 49) {last SWITCH;}
257         
258     if ($pcno == 50) {              # keep alive/user list
259           my $ref = DXCluster->get($field[1]);
260           $ref->update_users($field[2]) if $ref;
261           last SWITCH;
262         }
263         
264     if ($pcno == 51) {              # incoming ping requests/answers
265           
266           # is it for us?
267           if ($field[1] eq $main::mycall) {
268             my $flag = $field[3];
269             $flag ^= 1;
270             $self->send($self->pc51($field[2], $field[1], $flag));
271           } else {
272             # route down an appropriate thingy
273                 route($field[1], $line);
274           }
275           return;
276         }
277   }
278   
279   # if get here then rebroadcast the thing with its Hop count decremented (if
280   # there is one). If it has a hop count and it decrements to zero then don't
281   # rebroadcast it.
282   #
283   # NOTE - don't arrive here UNLESS YOU WANT this lump of protocol to be
284   #        REBROADCAST!!!!
285   #
286   
287   my $hopfield = pop @field;
288   push @field, $hopfield; 
289   
290   my $hops;
291   if (($hops) = $hopfield =~ /H(\d+)\^\~?$/o) {
292         my $newhops = $hops - 1;
293         if ($newhops > 0) {
294           $line =~ s/\^H$hops(\^\~?)$/\^H$newhops$1/;       # change the hop count
295           broadcast_ak1a($line, $self);             # send it to everyone but me
296         }
297   }
298 }
299
300 #
301 # This is called from inside the main cluster processing loop and is used
302 # for despatching commands that are doing some long processing job
303 #
304 sub process
305 {
306   my $t = time;
307   my @chan = DXChannel->get_all();
308   my $chan;
309   
310   foreach $chan (@chan) {
311     next if !$chan->is_ak1a();
312
313     # send a pc50 out on this channel
314     if ($t >= $chan->pc50_t + $DXProt::pc50_interval) {
315       $chan->send(pc50());
316           $chan->pc50_t($t);
317         }
318   }
319 }
320
321 #
322 # finish up a pc context
323 #
324 sub finish
325 {
326   my $self = shift;
327   broadcast_ak1a($self->pc21('Gone.'));
328   my $ref = DXCluster->get($self->call);
329   $ref->del() if $ref;
330 }
331
332 #
333 # some active measures
334 #
335
336 #
337 # route a message down an appropriate interface for a callsign
338 #
339 # is called route(to, pcline);
340 #
341 sub route
342 {
343   my ($call, $line) = @_;
344   my $cl = DXCluster->get($call);
345   if ($cl) {
346     my $dxchan = $cl->{dxchan};
347     $cl->send($line) if $dxchan;
348   }
349 }
350
351 # broadcast a message to all clusters [except those mentioned after buffer]
352 sub broadcast_ak1a
353 {
354   my $s = shift;                  # the line to be rebroadcast
355   my @except = @_;                # to all channels EXCEPT these (dxchannel refs)
356   my @chan = get_all_ak1a();
357   my $chan;
358   
359   foreach $chan (@chan) {
360          $chan->send($s) if !grep $chan, @except;              # send it if it isn't the except list
361   }
362 }
363
364 # broadcast to all users
365 sub broadcast_users
366 {
367   my $s = shift;                  # the line to be rebroadcast
368   my @except = @_;                # to all channels EXCEPT these (dxchannel refs)
369   my @chan = get_all_users();
370   my $chan;
371   
372   foreach $chan (@chan) {
373          $chan->send($s) if !grep $chan, @except;              # send it if it isn't the except list
374   }
375 }
376
377 #
378 # gimme all the ak1a nodes
379 #
380 sub get_all_ak1a
381 {
382   my @list = DXChannel->get_all();
383   my $ref;
384   my @out;
385   foreach $ref (@list) {
386     push @out, $ref if $ref->is_ak1a;
387   }
388   return @out;
389 }
390
391 # return a list of all users
392 sub get_all_users
393 {
394   my @list = DXChannel->get_all();
395   my $ref;
396   my @out;
397   foreach $ref (@list) {
398     push @out, $ref if $ref->is_user;
399   }
400   return @out;
401 }
402
403 # return a list of all user callsigns
404 sub get_all_user_calls
405 {
406   my @list = DXChannel->get_all();
407   my $ref;
408   my @out;
409   foreach $ref (@list) {
410     push @out, $ref->call if $ref->is_user;
411   }
412   return @out;
413 }
414
415 #
416 # obtain the hops from the list for this callsign and pc no 
417 #
418
419 sub get_hops
420 {
421   my ($pcno) = @_;
422   my $hops = $DXProt::hopcount{$pcno};
423   $hops = $DXProt::def_hopcount if !$hops;
424   return "H$hops";       
425 }
426
427 # remove leading and trailing spaces from an input string
428 sub unpad
429 {
430   my $s = shift;
431   $s =~ s/^\s+|\s+$//;
432   return $s;
433 }
434 1;
435 __END__