c877436b7ba0b96941708accfcdb3ccc25299ee1
[spider.git] / perl / DXXml.pm
1 #
2 # XML handler
3 #
4 # $Id$
5 #
6 # Copyright (c) Dirk Koopman, G1TLH
7 #
8
9 use strict;
10
11 package DXXml;
12 use IsoTime;
13
14 use DXProt;
15 use DXDebug;
16 use DXLog;
17 use DXUtil;
18 use DXXml::Ping;
19 use DXXml::Dx;
20 use DXXml::IM;
21
22 use vars qw($VERSION $BRANCH $xs $id $max_old_age $max_future_age);
23 $VERSION = sprintf( "%d.%03d", q$Revision$ =~ /(\d+)\.(\d+)/ );
24 $BRANCH = sprintf( "%d.%03d", q$Revision$ =~ /\d+\.\d+\.(\d+)\.(\d+)/  || (0,0));
25 $main::build += $VERSION;
26 $main::branch += $BRANCH;
27
28 $xs = undef;                                    # the XML::Simple parser instance
29 $id = 0;                                                # the next ID to be used
30 $max_old_age = 3600;                    # how old a sentence we will accept
31 $max_future_age = 900;                  # how far into the future we will accept
32
33 # generate a new XML sentence structure 
34 sub new
35 {
36         my $pkg = shift;
37         my $class = ref $pkg || $pkg;
38         return bless{@_}, $class;
39 }
40
41 #
42 # note that this a function not a method
43 #
44 sub init
45 {
46         return unless $main::do_xml;
47         
48         eval { require XML::Simple };
49         eval { require XML::Parser } unless $@;
50         if ($@) {
51                 LogDbg('err', "do_xml was set to 1 and the XML routines failed to load ($@)");
52                 $main::do_xml = 0;
53         } else {
54                 $XML::Simple::PREFERRED_PARSER = 'XML::Parser';
55                 import XML::Simple;
56                 $DXProt::handle_xml = 1;
57                 $xs = new XML::Simple(Cache=>[]);
58         }
59         undef $@;
60 }
61
62 #
63 # note that this a function not a method
64 #
65 sub normal
66 {
67         my $dxchan = shift;
68         my $line = shift;
69
70         unless ($main::do_xml) {
71                 dbg("xml not enabled, IGNORED") if isdbg('chanerr');
72                 return;
73         }
74         
75         my ($rootname) = $line =~ '<(\w+) ';
76         my $pkg = "DXXml::" . ucfirst lc "$rootname";
77
78         unless (defined *{"${pkg}::"} && $pkg->can('handle_input')) {
79                 dbg("xml sentence $rootname not recognised, IGNORED") if isdbg('chanerr');
80                 return;
81         }
82                 
83         my $xref;
84         unless ($xref = $pkg->decode_xml($dxchan, $line))  {
85                 dbg("invalid XML ($@), IGNORED") if isdbg('chanerr');
86                 undef $@;
87                 return;
88         }
89
90         # do some basic checks
91         my ($o, $t, $id);
92         unless (exists $xref->{o} && is_callsign($o = $xref->{o})) {
93                 dbg("Invalid origin, not a callsign") if isdbg('chanerr');
94                 return;
95         }
96         unless (exists $xref->{t} && ($t = IsoTime::unixtime($xref->{t}))) {
97                 dbg("Invalid, non-existant or zero time") if isdbg('chanerr');
98                 return;
99         }
100         unless ($t < $main::systime - $max_old_age || $t > $main::systime + $max_future_age) {
101                 dbg("Too old or too far in the future") if isdbg('chanerr');
102                 return;
103         }
104         unless (exists $xref->{id} && ($id = $xref->{id}) >= 0 && $id <= 9999) {
105                 dbg("Invalid or non-existant id") if isdbg('chanerr');
106                 return;
107         }
108
109         # mark the handle as accepting xml (but only if they 
110         # have at least one right)
111         $dxchan->handle_xml(1);
112
113         # now check that we have not seen this before 
114         # this is based on the tuple (o (origin), t (time, normalised to time_t), id)
115         $xref->{'-timet'} = $t;
116         return if DXDupe::check("xml,$o,$t,$id");
117                 
118         $xref = bless $xref, $pkg;
119         $xref->{'-xml'} = $line; 
120         $xref->handle_input($dxchan);
121 }
122
123 #
124 # note that this a function not a method
125 #
126
127 my $last10;
128 my $last_hour;
129
130 sub process
131 {
132         my $t = time;
133         my @dxchan = DXChannel::get_all();
134         my $dxchan;
135
136         foreach $dxchan (@dxchan) {
137                 next unless $dxchan->is_node;
138                 next unless $dxchan->handle_xml;
139                 next if $dxchan == $main::me;
140
141                 # send a ping out on this channel
142                 if ($dxchan->{pingint} && $t >= $dxchan->{pingint} + $dxchan->{lastping}) {
143                         if ($dxchan->{nopings} <= 0) {
144                                 $dxchan->disconnect;
145                         } else {
146                                 DXXml::Ping::add($main::me, $dxchan->call);
147                                 $dxchan->{nopings} -= 1;
148                                 $dxchan->{lastping} = $t;
149                                 $dxchan->{lastping} += $dxchan->{pingint} / 2 unless @{$dxchan->{pingtime}};
150                         }
151                 }
152         }
153
154
155         # every ten seconds
156         if (!$last10 || $t - $last10 >= 10) {   
157                 $last10 = $t;
158         }
159
160         # every hour
161         if (!$last_hour || $main::systime - 3600 > $last_hour) {
162                 $last_hour = $main::systime;
163         }
164
165 }
166
167 sub decode_xml
168 {
169         my $pkg = shift;
170         my $dxchan = shift;
171         my $line = shift;
172
173         my $xref;
174         eval {$xref = $xs->XMLin($line)};
175         return $xref;
176 }
177
178 sub nextid
179 {
180         my $r = $id++;
181         $id = 0 if $id > 999;
182         return $r;
183 }
184
185 sub toxml
186 {
187         my $self = shift;
188
189         unless (exists $self->{'-xml'}) {
190                 $self->{o} ||= $main::mycall;
191                 $self->{t} ||= IsoTime::dayminsec();
192                 $self->{id} ||= nextid();
193                 
194                 my ($name) = (ref $self) =~ /::(\w+)$/;
195                 $self->{'-xml'} = $xs->XMLout($self, RootName =>lc $name, NumericEscape=>1);
196         }
197         return $self->{'-xml'};
198 }
199
200 sub route
201 {
202         my $self = shift;
203         my $fromdxchan = shift;
204         my $to = shift;
205         my $via = $to || $self->{'-via'} || $self->{to};
206
207         unless ($via) {
208                 dbg("XML: no route specified (" . $self->toxml . ")") if isdbg('chanerr');
209                 return;
210         }
211         if (ref $fromdxchan && $via && $fromdxchan->call eq $via) {
212                 dbg("XML: Trying to route back to source (" . $self->toxml . ")") if isdbg('chanerr');
213                 return;
214         }
215
216         # always send it down the local interface if available
217         my $dxchan = DXChannel::get($via);
218         if ($dxchan) {
219                 dbg("route: $via -> $dxchan->{call} direct" ) if isdbg('route');
220         } else {
221                 my $cl = Route::get($via);
222                 $dxchan = $cl->dxchan if $cl;
223                 dbg("route: $via -> $dxchan->{call} using normal route" ) if isdbg('route');
224         }
225
226         # try the backstop method
227         unless ($dxchan) {
228                 my $rcall = RouteDB::get($via);
229                 if ($rcall) {
230                         $dxchan = DXChannel::get($rcall);
231                         dbg("route: $via -> $rcall using RouteDB" ) if isdbg('route') && $dxchan;
232                 }
233         }
234         
235         unless ($dxchan) {
236                 dbg("XML: no route available to $via") if isdbg('chanerr');
237                 return;
238         }
239
240         if ($fromdxchan->call eq $via) {
241                 dbg("XML: Trying to route back to source (" . $self->toxml . ")") if isdbg('chanerr');
242                 return;
243         }
244
245         if ($dxchan == $main::me) {
246                 dbg("XML: Trying to route to me (" . $self->toxml . ")") if isdbg('chanerr');
247                 return;
248         }
249
250         if ($dxchan->handle_xml) {
251                 $dxchan->send($self->toxml);
252         } else {
253                 $self->{o} ||= $main::mycall;
254                 $self->{id} ||= nextid();
255                 $self->{'-timet'} ||= $main::systime;
256                 $dxchan->send($self->topcxx);
257         }
258 }
259
260 sub has_xml
261 {
262         return exists $_[0]->{'-xml'};
263 }
264
265 sub has_pcxx
266 {
267         return exists $_[0]->{'-pcxx'};
268 }
269
270 sub has_cmd
271 {
272         return exists $_[0]->{'-cmd'};
273 }
274
275 1;