File Coverage

blib/lib/Ekahau/Response.pm
Criterion Covered Total %
statement 48 143 33.5
branch 0 32 0.0
condition 0 6 0.0
subroutine 16 26 61.5
pod 9 10 90.0
total 73 217 33.6


line stmt bran cond sub pod time code
1             package Ekahau::Response;
2 6     6   33 use Ekahau::Base; our $VERSION=Ekahau::Base::VERSION;
  6         14  
  6         558  
3              
4             # Written by Scott Gifford
5             # Copyright (C) 2004 The Regents of the University of Michigan.
6             # See the file LICENSE included with the distribution for license
7             # information.
8              
9 6     6   519 use strict;
  6         12  
  6         237  
10 6     6   34 use warnings;
  6         98  
  6         228  
11 6     6   33 use bytes;
  6         13  
  6         46  
12              
13             =head1 NAME
14              
15             Ekahau::Response - Response from an Ekahau server
16              
17             =head1 SYNOPSIS
18              
19             Provides a straightforward encapsulation of the response objects
20             returned by the Ekahau Positioning Engine. This is the base class for
21             the specific responses; in general you will want to use one of them.
22              
23             =head1 DESCRIPTION
24              
25             This class takes care of parsing responses from the Ekahau server into
26             their individual components.
27              
28             The responses returned by Ekahau are a sort of half-assed XML. They
29             look superficially like XML, but not enough that they can be parsed by
30             an XML parser. Instead, this module takes a simplistic approach to
31             parsing them. The exact rules for parsing aren't clear from the
32             documentation, so this parsing may not be correct in all
33             circumstances, but we have not observed any misparsing with the
34             current code.
35              
36             The response is parsed into the hash reference that comprises this
37             object. It's parsed like this:
38              
39             <#tag cmd args[0] args[1] ...
40             params{key1}=value1
41             params{key2}=value2
42             SEPARATOR1
43             params{SEPERATOR1}[0]{key3}=value3
44             params{SEPERATOR1}[0]{key4}=value4
45             SEPARATOR1
46             params{SEPERATOR1}[1]{key3}=value3
47             params{SEPERATOR1}[1]{key4}=value4
48             >
49              
50             Here are some examples. First,
51              
52             <#LOC LOCATION_ESTIMATE 4
53             accurateX=1427.09
54             accurateY=2141.73
55             accurateTime=1117138297067
56             accurateContextId=22940
57             latestX=1429.14
58             latestY=2140.60
59             latestTime=1117138301067
60             latestContextId=22940
61             speed=0.09101
62             heading=5.78188
63             >
64              
65              
66             parses to:
67              
68             {
69             'tag' => 'LOC',
70             'cmd' => 'LOCATION_ESTIMATE',
71             'args' => [ '4' ],
72             'params' => {
73             'latestX' => '1429.14',
74             'accurateY' => '2141.73',
75             'heading' => '5.78188',
76             'accurateTime' => '1117138297067',
77             'latestY' => '2140.60',
78             'accurateContextId' => '22940',
79             'speed' => '0.09101',
80             'accurateX' => '1427.09',
81             'latestTime' => '1117138301067',
82             'latestContextId' => '22940'
83             },
84             }
85              
86             Second,
87              
88             <#AREA AREA_ESTIMATE 4
89             AREA
90             name=2425
91             probability=1.000
92             contextId=22940
93             polygon=1396;1396;1554;1554;1394;1396&1964;2200;2200;1964;1964;1964;
94             AREA
95             name=2431
96             probability=0.000
97             contextId=22940
98             polygon=1560;1559;1682;1680;1804;1804;1559;1560&1968;2197;2196;2153;2154;1966;1966;1968;
99             >
100              
101             parses to:
102              
103             {
104             'tag' => 'AREA',
105             'cmd' => 'AREA_ESTIMATE',
106             'args' => [ '4' ],
107             'params' => {
108             'AREA' => [
109             {
110             'contextId' => '22940',
111             'probability' => '1.000',
112             'name' => '2425',
113             'polygon' => '1396;1396;1554;1554;1394;1396&1964;2200;2200;1964;1964;1964;'
114             }, {
115             'contextId' => '22940',
116             'probability' => '0.000',
117             'name' => '2431',
118             'polygon' => '1560;1559;1682;1680;1804;1804;1559;1560&1968;2197;2196;2153;2154;1966;1966;1968;'
119             }
120             ]
121             }
122             }
123              
124             Finally, this response:
125              
126             <#2376 DEVICE_LIST
127             1
128             2
129             3
130             >
131              
132             parses to this:
133              
134             {
135             'tag' => '2376',
136             'cmd' => 'DEVICE_LIST',
137             'args' => [],
138             'params' => {
139             '3' => [ {} ],
140             '2' => [ {} ],
141             '1' => [ {} ],
142             }
143             }
144              
145             =cut
146              
147 6     6   6865 use Ekahau::Response::DeviceList;
  6         16  
  6         187  
148 6     6   4305 use Ekahau::Response::DeviceProperties;
  6         15  
  6         244  
149 6     6   22047 use Ekahau::Response::Error;
  6         18  
  6         750  
150 6     6   14780 use Ekahau::Response::LocationEstimate;
  6         17  
  6         273  
151 6     6   3732 use Ekahau::Response::LocationContext;
  6         16  
  6         337  
152 6     6   3957 use Ekahau::Response::AreaEstimate;
  6         19  
  6         253  
153 6     6   39 use Ekahau::Response::AreaList;
  6         13  
  6         185  
154 6     6   4175 use Ekahau::Response::StopLocationTrackOK;
  6         18  
  6         5391  
155 6     6   5703 use Ekahau::Response::StopAreaTrackOK;
  6         19  
  6         236  
156 6     6   7574 use Ekahau::Response::MapImage;
  6         23  
  6         288  
157              
158 6     6   40 use constant RESPONSEBASE => 'Ekahau::Response::';
  6         16  
  6         2639  
159              
160             our %CMDCLASS = (
161             DEVICE_LIST => RESPONSEBASE.'DeviceList',
162             DEVICE_PROPERTIES => RESPONSEBASE.'DeviceProperties',
163             LOCATION_ESTIMATE => RESPONSEBASE.'LocationEstimate',
164             CONTEXT => RESPONSEBASE.'LocationContext',
165             AREA_ESTIMATE => RESPONSEBASE.'AreaEstimate',
166             STOP_LOCATION_TRACK_OK => RESPONSEBASE.'StopLocationTrackOK',
167             STOP_AREA_TRACK_OK => RESPONSEBASE.'StopAreaTrackOK',
168             AREALIST => RESPONSEBASE.'AreaList',
169             MAP => RESPONSEBASE.'MapImage',
170             );
171 6     6   36 use constant ERROR_CLASS => 'Ekahau::Response::Error';
  6         12  
  6         25358  
172              
173             =head2 Constructors
174              
175             =head3 new ( %params )
176              
177             Creates a new empty object. The only parameter recognized is C,
178             which sets the tag property for the response.
179              
180             =cut
181              
182             sub new
183             {
184 0     0 1   my $class = shift;
185 0           my(%p) = @_;
186 0           my $self = {};
187              
188 0 0         if ($p{tag})
189             {
190 0           $self->{tag} = $p{tag};
191             }
192 0           bless $self, $class;
193             }
194              
195             # Internal method
196             sub init
197 0     0 0   {
198             # Do nothing
199             }
200              
201             =head3 parsenew ( $response_str )
202              
203             Parse a response string into an object. The results are undefined if
204             C<$response_str> is not a valid Ekahau response.
205              
206             =cut
207              
208             sub parsenew
209             {
210 0     0 1   my $class = shift;
211 0           my $self = $class->new();
212 0           $self->parse(@_);
213 0 0         warn "parsenew: cmd is '$self->{cmd}'\n"
214             if ($ENV{VERBOSE});
215 0 0         if (my $newclass = $CMDCLASS{$self->{cmd}})
    0          
216             {
217 0           bless $self,$newclass;
218 0           $self->init;
219             }
220             elsif ($self->{cmd} =~ /^(?:MALFORMED_REQUEST|.*_NOT_FOUND|FAILURE|.*_FAILED|.*_PROBLEM)/)
221             {
222 0           bless $self,ERROR_CLASS;
223 0           $self->init;
224             }
225 0           $self;
226             }
227              
228             =head2 Methods
229              
230             =head3 parse ( $response_str )
231              
232             Populate the fields of an object with the ones in C<$response_str>,
233             overwriting any existing values. The results are undefined if
234             C<$response_str> is not a valid Ekahau response.
235              
236             =cut
237              
238             sub parse
239             {
240 0     0 1   my $self = shift;
241 0           my($r) = @_;
242 0           my $data;
243              
244 0           $r =~ s/^\s+//;
245 0           $r =~ s/\s+$//;
246 0           $self->{string} = $r;
247            
248             # Look for a tag
249 0 0 0       if ($r =~ s/^\s*<(\#\w*?\s)?\s*// and $1)
250             {
251             # Preserve taintedness with substr(X,0,0)
252 0           chop($self->{tag} = substr($1,1).substr($self->{string},0,0))
253             }
254             # Does this contain sized data?
255 0 0         if ($r =~ /\x0asize=(\d+).*?\x0adata=/sg)
256             {
257 0           my $data_len = $1;
258 0           my $data_pos = pos($r);
259 0           $data = substr($r,$data_pos,$data_len,'');
260             # Remove the "data="
261 0           substr($r,-5,5,'');
262             }
263              
264             # Remove trailing angle bracket and whitespace
265 0           $r =~ s/\s*>\s*$//;
266              
267             # Split the response into lines
268 0           my @lines = split(/(?
269             # This probably doesn't handle quoting correctly
270 0           my @firstline = split(' ',shift @lines);
271              
272             # The first line
273 0           $self->{cmd} = shift @firstline;
274 0           $self->{args} = [map { s/^\"//; s/\"$//; $_ } @firstline];
  0            
  0            
  0            
275 0           $self->{params}={};
276              
277             # Are there any arguments that are really parameters?
278 0           foreach my $i (0..$#{$self->{args}})
  0            
279             {
280 0 0         if ($self->{args}[$i] =~ /^(\w+)=(.*)$/)
281             {
282             # Use substr to keep taintedness
283 0           $self->{params}{$1}=$2.substr($self->{args}[$i],0,0);
284             # Remove argument
285 0           splice(@{$self->{args}},$i,1,());
  0            
286             }
287             }
288             # Parameters on other lines
289 0           my $datahash = $self->{params};
290 0           foreach my $l (@lines)
291             {
292 0           my $keep_taintedness = substr($l,0,0);
293 0           $l =~ s/\\(<|>|\x0d\x0a)/$1/g;
294 0 0         if ($l =~ /^([\w.\-]+?)=(.*)$/)
295             {
296             # Using this as a hash key will implictly untaint it,
297             # so require that values be "word characters".
298 0           my $val = $2.$keep_taintedness;
299 0           $datahash->{$1}=$val;
300             }
301             else
302             {
303 0           $datahash = {};
304 0           push(@{$self->{params}{$l}},$datahash);
  0            
305             }
306             }
307 0 0         if (defined($data))
308             {
309 0           $self->{params}{data}=$data;
310             }
311             # Special case: copy area up to main parameters, for backwards
312             # compatibility.
313 0 0 0       if ($self->{params}{AREA} and $self->{params}{AREA}[0])
314             {
315 0           while(my($k,$v)=each(%{$self->{params}{AREA}[0]}))
  0            
316             {
317 0           $self->{params}{$k} = $v;
318             }
319             }
320            
321 0           $self;
322             }
323              
324             =head3 get_props ( @prop_names )
325              
326             Returns a hash containing the values for the list of properties in
327             C<@prop_names>. If C<@prop_names> is empty, all properties will be
328             returned.
329              
330             =cut
331              
332             sub get_props
333             {
334 0     0 1   my $self = shift;
335 0 0         if (!@_) { @_ = keys %{$self->{params}} };
  0            
  0            
336            
337 0           return map { $_ => $self->{params}{$_} } @_;
  0            
338             }
339              
340             =head3 get_prop ( $prop_name )
341              
342             Returns the value for one of this object's properties, specified by
343             C<$prop_name>. If no property named C<$prop_name> exists, C is
344             returned.
345              
346             =cut
347              
348             sub get_prop
349             {
350 0     0 1   my $self = shift;
351 0           my $prop = $_[0];
352              
353 0           return $self->{params}{($prop)};
354             }
355              
356              
357             =head3 error ( )
358              
359             Returns true if this response is an L object,
360             else returns false.
361              
362             =cut
363              
364             sub error
365             {
366 0     0 1   0;
367             }
368              
369             =head3 eventname ( )
370              
371             Returns the name of this event in the same format used by
372             L.
373              
374             =cut
375              
376             sub eventname
377             {
378 0     0 1   my $self = shift;
379 0 0         $self->error ? 'ERROR' : uc $self->{cmd};
380             }
381              
382             =head3 type ( )
383              
384             Returns the string I, to identify the type of this object,
385             and that no more specific information is available.
386              
387             =cut
388              
389             sub type
390             {
391 0     0 1   'Response';
392             }
393              
394             =head3 tostring ( )
395              
396             Return a string representation of the object. This is reconstructed
397             from the object's properties, and so may not be identical to the
398             string which was parsed to create it.
399              
400             =cut
401              
402             sub tostring
403             {
404 0     0 1   my $self = shift;
405            
406 0           my $str = "<";
407 0 0         if (defined($self->{tag})) { $str .= "#$self->{tag} " };
  0            
408 0           $str .= $self->{cmd};
409 0 0         if (@{$self->{args}})
  0            
410             {
411 0           $str .= " ".join(" ",@{$self->{args}});
  0            
412             }
413 0 0         if ($self->{params})
414             {
415 0           $str .= "\x0d\x0a";
416            
417 0           foreach my $var (keys %{$self->{params}})
  0            
418             {
419 0           my $val = $self->{params}{$var};
420 0 0         if (defined($val))
421             {
422 0           $val =~ s/(<|>|\x0d\x0a)/\\$1/g;
423 0           $str .= "$var=$val\x0d\x0a";
424             }
425             else
426             {
427 0           $str .= "$var\x0d\x0a";
428             }
429             }
430             }
431 0           $str .= ">\x0d\x0a";
432 0           return $str;
433             }
434              
435             1;
436              
437             =head1 AUTHOR
438              
439             Scott Gifford Egifford@umich.eduE, Esgifford@suspectclass.comE
440              
441             Copyright (C) 2005 The Regents of the University of Michigan.
442              
443             See the file LICENSE included with the distribution for license
444             information.
445              
446              
447             =head1 SEE ALSO
448              
449             L, L,
450             L, L,
451             L,
452             L,
453             L, L,
454             L,
455             L, L.
456              
457             =cut
458              
459             1;