File Coverage

blib/lib/Ekahau/Server/Test.pm
Criterion Covered Total %
statement 3 3 100.0
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 4 4 100.0


line stmt bran cond sub pod time code
1             package Ekahau::Server::Test;
2 6     6   422875 use base 'Ekahau::Server'; our $VERSION = $Ekahau::Server::VERSION;
  6         18  
  6         5236  
3             use base 'Exporter';
4              
5             # Written by Scott Gifford
6             # Copyright (C) 2004 The Regents of the University of Michigan.
7             # See the file LICENSE included with the distribution for license
8             # information.
9              
10             use warnings;
11             use strict;
12             use bytes;
13              
14             =head1 NAME
15              
16             Ekahau::Server::Test - Creates a test Ekahau server
17              
18             =head1 SYNOPSIS
19              
20             This class is used to create a "mock" Ekahau server for testing the
21             Ekahau client.
22              
23             Because this class is used only for testing, it is not documented.
24              
25             =cut
26              
27             use Ekahau::Response::Error qw(:codes);
28             use Ekahau::License;
29              
30              
31             our @EXPORT_OK = qw(static_area static_location);
32              
33             use constant DEFAULT_PASSWORD => 'Llama';
34             use constant DEFAULT_TICK => 2;
35              
36             our @devices = (
37             {
38             props => {
39             'ECLIENT.WLAN_TECHNOLOGY' => 0,
40             'ECLIENT.WLAN_MODEL' => 'Agere',
41             'ECLIENT.COMMON_INTERNALNAME' => 'Wlan_Agere.dll',
42             'NETWORK.MAC' => '00:10:C6:6A:12:3E',
43             'GROUP' => 'ECLIENT',
44             'NETWORK.DNS_NAME' => '141.212.55.129',
45             'ECLIENT.COMMON_OS_VER' => '4.21.1088',
46             'ECLIENT.COMMON_CLIENTID' => '000ea544c3f5ac51cc7e140b5d8',
47             'NETWORK.IP-ADDRESS' => '141.212.55.129',
48             'ECLIENT.COMMON_CLIENT_VER' => '3.2.198',
49             },
50             location_track => static_location({
51             accurateX => 100,
52             accurateY => 100,
53             accurateContextId => '12345',
54             accurateExpectedError => 1,
55             latestX => 100,
56             latestY => 100,
57             latestContextId => 'ctx1',
58             latestExpectedError => 1,
59             speed => 10,
60             heading => 180,
61             }),
62             area_track => static_area([
63             {
64             name => 'area51',
65             probability => '100.00',
66             contextId => '12345',
67             polygon => '100;75;150&100;75;150',
68             property1 => 'value1',
69             }]),
70             },
71             );
72            
73            
74              
75             our %contexts = (
76             12345 => {
77             name => 12345,
78             address => "building/floor1",
79             mapScale => '10.00',
80             property1 => 'value1',
81             },
82             );
83              
84             our %maps = (
85             12345 => 'Pretend this is a PNG map file',
86             23456 => 'All work and no play makes Jack a dull boy',
87             );
88            
89              
90             sub new
91             {
92             my $class = shift;
93             my(%p)=@_;
94              
95             my $self = $class->SUPER::new(@_);
96             $self->errhandler_deconstructed;
97             $self->{_devices}=$p{Devices} || \@devices;
98             $self->{_contexts}=$p{Contexts} || \%contexts;
99             $self->{_maps}=$p{Maps} || \%maps;
100             $self->{_password} = $p{Password} || DEFAULT_PASSWORD;
101             $self->{_tick} = $p{Tick} || DEFAULT_TICK;
102             if ($p{LicenseFile})
103             {
104             $self->{_license} = Ekahau::License->new(LicenseFile => $p{LicenseFile})
105             or return $self->reterr("Error processing LicenseFile '$p{LicenseFile}': ".Ekahau::License->lasterr);
106             }
107             $self->errhandler_constructed;
108             }
109              
110             sub run
111             {
112             my $self = shift;
113              
114             $self->{auth_state} = 0;
115             $self->{_rand_str} = 'blahblahblah';
116             $self->command(['HELLO',1,$self->{_rand_str}]);
117              
118             my $lasttick = time;
119             while(1)
120             {
121             my $started_waiting = time;
122             $self->{_timeout} = 1;
123             warn "Waiting for response\n"
124             if ($ENV{VERBOSE});
125             my $resp = $self->nextresponse();
126             my $now = time;
127             if (($now - $lasttick) >= $self->{_tick})
128             {
129             $self->handle_tick;
130             $lasttick = $now;
131             }
132             if (!$resp)
133             {
134             if ($self->{auth_state} < 1)
135             {
136             if ((time - $started_waiting) < $self->{_timeout})
137             {
138             $self->auth_failure(EKAHAU_ERR_AUTH_TIMEOUT);
139             $self->abort;
140             exit(0);
141             }
142             }
143             next;
144             }
145              
146              
147             if (uc $resp->{cmd} eq 'CLOSE')
148             {
149             $self->handle_close($resp);
150             return;
151             }
152             elsif (uc $resp->{cmd} eq 'HELLO')
153             {
154             $self->handle_hello($resp);
155             }
156             elsif (uc $resp->{cmd} eq 'TALK')
157             {
158             $self->handle_talk($resp);
159             }
160             elsif ($self->{auth_state} < 1)
161             {
162             warn "Not authorized for this command\n"
163             if ($ENV{VERBOSE});
164             # This is a fatal error.
165             return $self->auth_failure(EKAHAU_ERR_MALFORMED_REQUEST);
166             }
167             elsif (uc $resp->{cmd} eq 'GET_DEVICE_LIST')
168             {
169             $self->handle_devlist($resp);
170             }
171             elsif (uc $resp->{cmd} eq 'GET_DEVICE_PROPERTIES')
172             {
173             $self->handle_devprop($resp);
174             }
175             elsif (uc $resp->{cmd} eq 'GET_LOGICAL_AREAS')
176             {
177             $self->handle_getla($resp);
178             }
179             elsif (uc $resp->{cmd} eq 'GET_CONTEXT')
180             {
181             $self->handle_getctx($resp);
182             }
183             elsif (uc $resp->{cmd} eq 'GET_MAP')
184             {
185             $self->handle_getmap($resp);
186             }
187             elsif (uc $resp->{cmd} eq 'START_LOCATION_TRACK')
188             {
189             $self->handle_loctrack($resp);
190             }
191             elsif (uc $resp->{cmd} eq 'START_AREA_TRACK')
192             {
193             $self->handle_areatrack($resp);
194             }
195             elsif (uc $resp->{cmd} eq 'STOP_LOCATION_TRACK')
196             {
197             $self->handle_stoploctrack($resp);
198             }
199             elsif (uc $resp->{cmd} eq 'STOP_AREA_TRACK')
200             {
201             $self->handle_stopareatrack($resp);
202             }
203             else
204             {
205             warn "Didn't recognize command '$resp->{cmd}'\n";
206             }
207             }
208             }
209              
210             sub handle_close
211             {
212             my $self = shift;
213             my($resp)=@_;
214              
215             $self->abort();
216             }
217              
218             sub handle_hello
219             {
220             my $self = shift;
221             my($resp)=@_;
222              
223             if ($resp->{args}[0] != 1)
224             {
225             # Should do better errors.
226             die "Bad protocol version\n";
227             }
228             $self->{hello} = $resp;
229             }
230              
231             sub handle_talk
232             {
233             my $self = shift;
234             my($resp)=@_;
235             if (!$self->{hello})
236             {
237             return $self->auth_failure(EKAHAU_ERR_MALFORMED_REQUEST);
238             }
239              
240             if ($resp->{args}[0] ne 'yax' or
241             $resp->{args}[1] != 1 or
242             $resp->{args}[2] ne 'yax1' or
243             $resp->{args}[3] ne 'MD5')
244             {
245             return $self->auth_failure(EKAHAU_ERR_UNSUPPORTED_PROTOCOL);
246             }
247            
248             if ($resp->{args}[4] eq '')
249             {
250             # Anonymous Login
251             if (!$self->{hello}{params}{password} or $self->{hello}{params}{password} ne $self->{_password})
252             {
253             return $self->auth_failure(EKAHAU_ERR_AUTHENTICATION_FAILED);
254             }
255             $self->command(['TALK','yax',1,'yax1','MD5','blahblahblah'])
256             or die "Couldn't send TALK response\n";
257             $self->{auth_state} = 1;
258             }
259             else
260             {
261             # License Login
262             if (!$self->{_license})
263             {
264             return $self->auth_failure(EKAHAU_ERR_AUTHENTICATION_FAILED);
265             }
266              
267             my $digest = $self->{_license}->talk_str(HelloStr => $self->{_rand_str},
268             Password => $self->{_password});
269             if (!$digest or $digest ne $resp->{args}[4])
270             {
271             return $self->auth_failure(EKAHAU_ERR_AUTHENTICATION_FAILED);
272             }
273            
274             $digest = $self->{_license}->talk_str(HelloStr => $self->{hello}{args}[1],
275             Password => $self->{_password});
276              
277             $self->command(['TALK','yax',1,'yax1','MD5',$digest])
278             or die "Couldn't send TALK response\n";
279             $self->{auth_state} = 2;
280             }
281            
282             }
283              
284             sub auth_failure
285             {
286             my $self = shift;
287             my($reason) = @_;
288             $self->command(['FAILURE',$reason]);
289             undef;
290             }
291              
292             sub handle_devlist
293             {
294             my $self = shift;
295             my($resp)=@_;
296             $self->reply($resp,'DEVICE_LIST',{ map { ($_ => [{}]) } 1..@{$self->{_devices}}});
297             }
298              
299             sub handle_devprop
300             {
301             my $self = shift;
302             my($resp)=@_;
303             my $whichdev = $resp->{args}[0];
304            
305             if (defined($whichdev) and $whichdev =~ /^\d+$/ and (my $dev = $self->{_devices}[$whichdev-1]))
306             {
307             $self->reply($resp,['DEVICE_PROPERTIES',$whichdev],$dev->{props});
308             }
309             else
310             {
311             $self->reply($resp,['GET_DEVICE_PROPERTIES_FAILED'],
312             {errorCode => -601,
313             errorLevel => 3});
314              
315             }
316             }
317              
318             sub handle_getla
319             {
320             my $self = shift;
321             my($resp)=@_;
322              
323             $self->reply($resp,'AREALIST',{ AREA => [ values %{$self->{_contexts}} ] });
324             }
325              
326             sub handle_getctx
327             {
328             my $self = shift;
329             my($resp)=@_;
330             my $whichctx = $resp->{args}[0];
331             if (defined($whichctx) and (my $ctx = $self->{_contexts}{$whichctx}))
332             {
333             $self->reply($resp,['CONTEXT',$whichctx],$ctx);
334             }
335             else
336             {
337             $self->reply($resp,['CONTEXT_NOT_FOUND',$whichctx],{});
338             }
339             }
340              
341             sub handle_getmap
342             {
343             my $self = shift;
344             my($resp)=@_;
345              
346             my $whichmap = $resp->{args}[0];
347             if ($whichmap and $self->{_maps}{$whichmap})
348             {
349             $self->reply($resp,['MAP',$whichmap],{ type => 'png', data => \$self->{_maps}{$whichmap} });
350             }
351             else
352             {
353             $self->reply($resp,['MAP_NOT_FOUND',$whichmap],{});
354             }
355             }
356              
357             sub handle_loctrack
358             {
359             my $self = shift;
360             my($req)=@_;
361             my $dev;
362              
363             eval {
364             $dev = $req->{args}[0]
365             or die "no dev";
366             $dev =~ /^\d+$/
367             or die "bad dev";
368             my $loctrack = $self->{_devices}[$dev-1]{location_track}
369             or die "no locator";
370            
371             push(@{$self->{loctrack}},{req => $req, dev => $dev, track => $loctrack});
372             warn "Starting location tracking of '$dev'\n"
373             if ($ENV{VERBOSE});
374             };
375             if ($@)
376             {
377             $self->reply($req,['START_LOCATION_TRACK_FAILED',defined($dev)?$dev:'?'],
378             {errorCode => -600,
379             errorLevel => 2});
380             }
381            
382             }
383              
384             sub handle_stoploctrack
385             {
386             my $self = shift;
387             my($req)=@_;
388             my $dev;
389              
390             eval {
391             $dev = $req->{args}[0]
392             or die "no dev";
393             $dev =~ /^\d+$/
394             or die "bad dev";
395             my $deleted;
396            
397             foreach my $i (0..$#{$self->{loctrack}})
398             {
399             if ($self->{loctrack}[$i]{dev} == $dev)
400             {
401             # Remove that element
402             $deleted = splice(@{$self->{loctrack}},$i,1);
403             warn "Stopped location tracking of '$dev'\n"
404             if ($ENV{VERBOSE});
405             last;
406             }
407             }
408             $deleted
409             or die "no such dev";
410             };
411             if ($@)
412             {
413             $self->reply($req,'STOP_LOCATION_TRACK_FAILED',
414             {errorCode => -600,
415             errorLevel => 2});
416             }
417             else
418             {
419             $self->reply($req,['STOP_LOCATION_TRACK_OK',defined($dev)?$dev:'?'],{});
420             }
421             }
422              
423              
424             sub handle_areatrack
425             {
426             my $self = shift;
427             my($req)=@_;
428             my $dev;
429              
430             eval {
431             $dev = $req->{args}[0]
432             or die "no dev";
433             $dev =~ /^\d+$/
434             or die "bad dev";
435             my $track = $self->{_devices}[$dev-1]{area_track}
436             or die "no area tracker";
437             push(@{$self->{areatrack}},{req => $req, dev => $dev, track => $track});
438             warn "Starting area tracking of '$dev'\n"
439             if ($ENV{VERBOSE});
440             };
441             if ($@)
442             {
443             $self->reply($req,['START_AREA_TRACK_FAILED',defined($dev)?$dev:'?'],
444             {errorCode => -600,
445             errorLevel => 2});
446             }
447            
448             }
449              
450             sub handle_stopareatrack
451             {
452             my $self = shift;
453             my($req)=@_;
454             my $dev;
455              
456             eval {
457             $dev = $req->{args}[0]
458             or die "no dev";
459             $dev =~ /^\d+$/
460             or die "bad dev";
461             my $deleted;
462              
463             foreach my $i (0..$#{$self->{areatrack}})
464             {
465             if ($self->{areatrack}[$i]{dev} == $dev)
466             {
467             # Remove that element
468             $deleted = splice(@{$self->{areatrack}},$i,1);
469             warn "Stopped area tracking of '$dev'\n"
470             if ($ENV{VERBOSE});
471             last;
472             }
473             }
474             $deleted
475             or die "no such dev";
476             };
477             if ($@)
478             {
479             $self->reply($req,'STOP_AREA_TRACK_FAILED',
480             {errorCode => -600,
481             errorLevel => 2});
482             }
483             else
484             {
485             $self->reply($req,['STOP_AREA_TRACK_OK',defined($dev)?$dev:'?'],{});
486             }
487             }
488              
489             sub handle_tick
490             {
491             my $self = shift;
492              
493             foreach my $track (@{$self->{loctrack}})
494             {
495             $track->{track}->($self,$track->{dev},$track->{req});
496             }
497             foreach my $track (@{$self->{areatrack}})
498             {
499             $track->{track}->($self,$track->{dev},$track->{req});
500             }
501             }
502              
503             sub static_location
504             {
505             my($loc)=@_;
506              
507             sub {
508             my($self,$dev,$req)=@_;
509             my $now = time;
510             $self->reply($req,['LOCATION_ESTIMATE',$dev],
511             {%$loc,
512             accurateTime => $now,
513             latestTime => $now,
514             });
515             };
516             }
517              
518             sub static_area
519             {
520             my($area) = @_;
521             sub {
522             my($self,$dev,$req)=@_;
523             my $numresp = $req->{params}{'EPE.NUMBER_OF_AREAS'} || 1;
524             if ($numresp > @$area)
525             {
526             my $ta = { %{$area->[$#{$area}]} };
527             $ta->{probability} = 0;
528             foreach my $i (scalar(@$area)..$numresp)
529             {
530             push(@$area,$ta);
531             }
532             }
533              
534             $self->reply($req,['AREA_ESTIMATE',$dev],
535             {
536             AREA => [@{$area}[0..$numresp-1] ],
537             });
538             };
539             }
540              
541             package Ekahau::Server::Test::Listener;
542             use base 'Ekahau::Server::Listener';
543              
544             sub accept
545             {
546             my $self = shift;
547             my $obj = $self->SUPER::accept(@_,'Ekahau::Server::Test')
548             or return undef;
549             $obj->{_password}=$self->{_password}||Ekahau::Server::Test::DEFAULT_PASSWORD;
550             $obj;
551             }
552              
553             package Ekahau::Server::Test::Background;
554             use base 'Ekahau::Server::Test';
555              
556             use Symbol;
557             use Socket;
558              
559             sub start
560             {
561             my $class = shift;
562              
563             my $server_side = gensym;
564             my $client_side = gensym;
565              
566             socketpair($server_side, $client_side, AF_UNIX, SOCK_STREAM, PF_UNSPEC)
567             or return undef;
568              
569             my $server = $class->new(Socket => $server_side,
570             Timeout => 10,
571             @_
572             )
573             or return undef;
574              
575             if (!defined(my $fork = fork))
576             {
577             return undef;
578             die "fork error: $!\n";
579             }
580             elsif (!$fork)
581             {
582             eval {
583             # Child
584             close($client_side);
585             delete $ENV{VERBOSE};
586             $ENV{VERBOSE}=$ENV{VERBOSE_SERVER}
587             if($ENV{VERBOSE_SERVER});
588             $server->run;
589             exit(0);
590             };
591             warn $@
592             if ($@);
593             exit(-1);
594             }
595            
596             close($server_side);
597             return $client_side;
598             }
599              
600             =head1 AUTHOR
601              
602             Scott Gifford Egifford@umich.eduE, Esgifford@suspectclass.comE
603              
604             Copyright (C) 2005 The Regents of the University of Michigan.
605              
606             See the file LICENSE included with the distribution for license
607             information.
608              
609             =cut
610              
611             1;