File Coverage

blib/lib/Finnigan/Decoder.pm
Criterion Covered Total %
statement 128 227 56.3
branch 32 94 34.0
condition 3 12 25.0
subroutine 11 16 68.7
pod 11 11 100.0
total 185 360 51.3


line stmt bran cond sub pod time code
1             package Finnigan::Decoder;
2              
3 2     2   49 use 5.010000;
  2         7  
  2         81  
4 2     2   12 use strict;
  2         4  
  2         64  
5 2     2   9 use warnings FATAL => qw( all );
  2         4  
  2         88  
6             our $VERSION = 0.0206;
7              
8 2     2   3089 use Encode qw//;
  2         25191  
  2         102  
9 2     2   17 use Carp qw/confess/;
  2         3  
  2         6148  
10              
11             sub read {
12 2024     2024 1 4722 my ($class, $stream, $fields, $any_arg) = @_;
13 2024         5214 my $self = {size => 0};
14              
15 2024         4402 bless $self, $class;
16 2024         3971 $self->decode($stream, $fields, $any_arg);
17 2024         6111 return $self;
18             }
19              
20             sub iterate_object {
21 28     28 1 71 my ($self, $stream, $count, $name, $class, $any_arg) = @_;
22              
23 28         48 my $addr = tell $stream;
24              
25 28         41 my $current_element_number = keys(%{$self->{data}}) + 1;
  28         60  
26              
27 28 50       89 confess qq(key "$name" already exists) if $self->{data}->{$name};
28              
29 28         38 my $size = 0;
30 28         52 foreach my $i ( 1 .. $count ) {
31 1791         5637 my $value = $class->decode($stream, $any_arg);
32 1791         3446 $size += $value->{size};
33 1791         1921 push @{$self->{data}->{$name}->{value}}, $value;
  1791         5540  
34             }
35              
36 28         93 $self->{data}->{$name}->{seq} = $current_element_number;
37 28         151 $self->{data}->{$name}->{addr} = $addr,
38             $self->{data}->{$name}->{size} = $size,
39             $self->{data}->{$name}->{type} = "$class\[\]",
40              
41             $self->{size} += $size;
42 28         48 $self->{current_element_number}++;
43              
44 28         85 return $self;
45             }
46              
47             sub iterate_scalar {
48 584     584 1 1010 my ($self, $stream, $count, $name, $desc) = @_;
49 584         760 my ($template, $type) = @$desc;
50              
51 584         826 my $addr = my $current_addr = tell $stream;
52              
53 584         568 my $current_element_number = keys(%{$self->{data}}) + 1;
  584         1467  
54              
55 584 50       1467 confess qq(key "$name" already exists) if $self->{data}->{$name};
56              
57 584         646 my $size = 0;
58 584         573 my ($rec, $nbytes);
59 0         0 my ($i, $bytes_to_read);
60              
61 584 100       885 if ( $template eq 'varstr' ) {
62 1 50       4 if ( $type eq 'PascalStringWin32' ) {
63 1         5 for ($i = 1; $i <= $count; $i++) {
64             # read the prefix counter into $nchars
65 2         27 my $bytes_to_read = 4;
66 2         4 $nbytes = CORE::read $stream, $rec, $bytes_to_read;
67 2 50       5 $nbytes == $bytes_to_read
68             or die "could not read all $bytes_to_read bytes of the prefix counter in $name at $current_addr";
69 2         5 my $nchars = unpack "V", $rec;
70              
71             # read the 2-byte characters
72 2         15 $bytes_to_read = 2*$nchars;
73 2         6 $nbytes = CORE::read $stream, $rec, $bytes_to_read;
74 2 50       19 $nbytes == $bytes_to_read
75             or die "could not read all $nchars 2-byte characters of $name at $current_addr";
76 2         3 $nbytes += 4; # total string length
77              
78 2         4 $size += $nbytes;
79 2         3 $current_addr += $nbytes;
80              
81 2         2 push @{$self->{data}->{$name}->{value}}, Encode::decode('UTF-16LE', (pack "C*", unpack "U0C*", $rec));
  2         16  
82             }
83             }
84             else {
85 0         0 confess "unknown varstr type $type";
86             }
87             }
88             else {
89 583         941 my $template_length = length(pack($template,()));
90 583 50       958 if ( substr($template, 0, 3) eq 'U0C' ) {
91 0         0 foreach $i ( 1 .. $count ) {
92 0         0 $nbytes = CORE::read $stream, $rec, $template_length;
93 0 0       0 $nbytes == $template_length
94             or die "could not read all $template_length bytes of $name at $current_addr";
95              
96 0         0 $size += $nbytes;
97 0         0 $current_addr += $nbytes;
98              
99 0         0 push @{$self->{data}->{$name}->{value}}, pack ( "C*", unpack $template, $rec );
  0         0  
100             }
101             }
102             else {
103 583         921 foreach $i ( 1 .. $count ) {
104 4243         6781 $nbytes = CORE::read $stream, $rec, $template_length;
105 4243 50       7391 $nbytes == $template_length
106             or die "could not read all $template_length bytes of $name at $current_addr";
107              
108 4243         4309 $size += $nbytes;
109 4243         3956 $current_addr += $nbytes;
110              
111 4243         3961 push @{$self->{data}->{$name}->{value}}, unpack ( $template, $rec );
  4243         13060  
112             }
113             }
114             }
115              
116 584         1475 $self->{data}->{$name}->{seq} = $current_element_number;
117 584         2103 $self->{data}->{$name}->{addr} = $addr,
118             $self->{data}->{$name}->{size} = $size,
119             $self->{data}->{$name}->{type} = $type,
120              
121             $self->{size} += $size;
122 584         719 $self->{current_element_number}++;
123              
124 584         1357 return $self;
125             }
126              
127             sub decode {
128 2114     2114 1 4657 my ($self, $stream, $fields, $any_arg) = @_;
129 2114         2344 my ( $rec, $nbytes );
130              
131 2114         2818 my $current_addr = tell $stream;
132 2114   100     8096 $self->{addr} ||= $current_addr; # assign the address only if called
133             # the first time (because decoding
134             # can be done in multiple chunks)
135              
136 2114         2226 my $current_element_number = keys %{$self->{data}};
  2114         5654  
137              
138 2114         2412 my $value;
139 2114         4846 foreach my $i ( 0 .. @$fields/2 - 1 ) {
140 14793         22281 my $name = $fields->[2*$i];
141 14793 100       30042 unless ( $fields->[2*$i+1] ) {
142             # it is a spacer in the human-readable generic record
143 536         2606 $self->{data}->{$name} = {
144             seq => $current_element_number + $i,
145             addr => $current_addr,
146             size => 0,
147             type => 'spacer',
148             value => '',
149             };
150              
151 536         841 $self->{current_element_number} = $i;
152 536         830 next;
153             }
154              
155 14257         15022 my ($template, $type) = @{$fields->[2*$i+1]};
  14257         25919  
156              
157 14257 50       36104 confess qq(key "$name" already exists) if $self->{data}->{$name};
158              
159 14257 100       45311 if ( $template eq 'object' ) {
    100          
    100          
    100          
160 93         423 $value = $type->decode($stream, $any_arg);
161 93         812 $nbytes = $value->{size};
162             }
163             elsif ( $template eq 'varstr' ) {
164 655 50       1096 if ( $type eq 'PascalStringWin32' ) {
165             # read the prefix counter into $nchars
166 655         743 my $bytes_to_read = 4;
167 655         1262 $nbytes = CORE::read $stream, $rec, $bytes_to_read;
168 655 50       1287 $nbytes == $bytes_to_read
169             or die "could not read all $bytes_to_read bytes of the prefix counter in $name at $current_addr";
170 655         1051 my $nchars = unpack "V", $rec;
171              
172             # read the 2-byte characters
173 655         877 $bytes_to_read = 2*$nchars;
174 655         1082 $nbytes = CORE::read $stream, $rec, $bytes_to_read;
175 655 50       1226 $nbytes == $bytes_to_read
176             or die "could not read all $nchars 2-byte characters of $name at $current_addr";
177 655         1096 $rec =~ s/\xb0/\*/; # remove the degree sign
178             #print(STDERR (ord > 127 ? sprintf("<%02X>", ord) : $_)) for(split //, $rec); print STDERR "\n";
179 655         4879 $value = Encode::decode('UTF-16LE', (pack "C*", unpack "U0C*", $rec));
180 655         19357 $nbytes += 4;
181             }
182             else {
183 0         0 confess "unknown varstr type $type";
184             }
185             }
186             elsif ( $template eq 'string' ) {
187 208 100       531 if ( substr($type, 0, 6) eq 'ASCIIZ' ) {
    50          
    0          
188 66         136 (undef, my $bytes_to_read) = split ":", $type;
189 66         153 $nbytes = CORE::read $stream, $rec, $bytes_to_read;
190 66 50       124 $nbytes == $bytes_to_read
191             or die "could not read all $bytes_to_read bytes of the string in $name at $current_addr";
192 66         129 $value = unpack "Z*", $rec;
193             }
194             elsif ( substr($type, 0, 9) eq 'UTF-16-LE' ) {
195 142         379 (undef, my $bytes_to_read) = split ":", $type;
196 142         372 $nbytes = CORE::read $stream, $rec, $bytes_to_read;
197 142 50       306 $nbytes == $bytes_to_read
198             or die "could not read all $bytes_to_read bytes of the string in $name at $current_addr";
199 142         1008 ($value) = split /\0/, Encode::decode('UTF-16LE', (pack "C*", unpack "U0C*", $rec)); # decode and truncate at 0
200             }
201             elsif ( substr($type, 0, 9) eq 'UTF-16-BE' ) {
202 0         0 confess "UTF-16-BE not implemented";
203             }
204             else {
205 0         0 confess "unknown string type";
206             }
207             }
208             elsif ( $template eq 'windows_time' ) {
209 14         18 my $bytes_to_read = 8;
210 14         28 $nbytes = CORE::read $stream, $rec, $bytes_to_read;
211 14 50       28 $nbytes == $bytes_to_read
212             or die "could not read all $bytes_to_read bytes of $name at $current_addr";
213 14         27 my ($w1, $w2) = unpack "VV", $rec;
214 14         156 $value = scalar gmtime (($w2 * 4294967296 + $w1) / 10000000 - 11644473600); # Windows timestamp is 100s of ns since Jan 1 1601
215             }
216             else {
217 13287         22972 my $bytes_to_read = length(pack($template,()));
218 13287         21618 $nbytes = CORE::read $stream, $rec, $bytes_to_read;
219 13287 50       25959 $nbytes == $bytes_to_read
220             or die "could not read all $bytes_to_read bytes of $name at $current_addr";
221              
222 13287 100       23435 if ( substr($template, 0, 3) eq 'U0C' ) {
223 29         620 $value = pack "C*", unpack $template, $rec;
224             }
225             else {
226 13258         22805 $value = unpack $template, $rec;
227             }
228             }
229              
230 14257         78639 $self->{data}->{$name} = {
231             seq => $current_element_number + $i,
232             addr => $current_addr,
233             size => $nbytes,
234             type => $type,
235             value => $value,
236             };
237              
238 14257         20020 $current_addr = tell $stream;
239 14257         18620 $self->{size} += $nbytes;
240 14257         28512 $self->{current_element_number} = $i;
241             }
242              
243 2114         4412 return $self;
244             }
245              
246             sub size {
247 10     10 1 87 shift->{size};
248             }
249              
250             sub data {
251 12     12 1 1043 shift->{data};
252             }
253              
254             sub addr {
255 0     0 1   shift->{addr};
256             }
257              
258             sub item {
259 0     0 1   my ($self, $key) = @_;
260 0           $self->{data}->{$key};
261             }
262              
263             sub values {
264 0     0 1   my ($self) = @_;
265 0           return {map { $_ => $self->{data}->{$_}->{value} } keys %{$self->{data}}};
  0            
  0            
266             }
267              
268             sub dump {
269 0     0 1   my ( $self, %arg ) = @_;
270              
271 0           my $addr = $self->{addr};
272              
273 0 0         $arg{header}++ unless exists $arg{header}; # print the header by default
274              
275 0           my @keys = sort {
276 0           $self->data->{$a}->{seq} <=> $self->data->{$b}->{seq}
277 0           } keys %{$self->{data}};
278              
279 0 0         if ( $arg{filter} ) {
280 0           my %filter = map {$_ => 1} @{$arg{filter}};
  0            
  0            
281 0           @keys = grep {$filter{$_}} @keys;
  0            
282             }
283              
284 0 0 0       if ($arg{style} and $arg{style} eq 'html') {
    0 0        
285 0           say ""; " if $arg{header}; " " " " " " "
286 0 0         say "
offset size type key value
287 0           foreach my $key ( @keys ) {
288 0 0         my $offset = $arg{relative} ? $self->item($key)->{addr} - $addr : $self->item($key)->{addr};
289 0           my $value = $self->item($key)->{value};
290 0           my $type = $self->item($key)->{type};
291 0           $type =~ s/^Finnigan:://;
292 0 0         if ( ref $value eq 'ARRAY' ) {
293 0           $value = join ", ", map {"$_"} @$value;
  0            
294             }
295 0 0         if ( $type eq 'RawBytes' ) {
296 0           my $len = length($value);
297 0           my @list = unpack('C*', substr($value, 0, 16));
298 0           $_ = sprintf "%2.2x", $_ for @list;
299 0           $value = join(" ", @list);
300 0 0         $value .= " ..." if $len > 16;
301             }
302 0           say "
303             . " " . $offset . "
304             . " " . $self->item($key)->{size} . "
305             . " " . $type . "
306             . " " . $key . "
307             . " $value
308             . "
309             ;
310             }
311 0           say "
";
312             }
313             elsif ($arg{style} and $arg{style} eq 'wiki') {
314 0 0         say "|| " . join(" || ", qw/offset size type key value/) . " ||" if $arg{header};
315 0           foreach my $key ( @keys ) {
316 0 0         my $offset = $arg{relative} ? $self->item($key)->{addr} - $addr : $self->item($key)->{addr};
317 0           my $value = $self->item($key)->{value};
318 0           my $type = $self->item($key)->{type};
319 0           $type =~ s/^Finnigan:://;
320 0           $type =~ s/\[\]/\`[]\`/;
321 0 0 0       if ($self->item($key)->{type} eq 'UTF16LE'
322             and substr($value, 0, 2) eq "\x00\x00") {
323 0           $value =~ s/\x00/00 /g;
324 0 0         if (length($value) > 20) {
325 0           $value = substr($value, 0, 30) . "...";
326             }
327             }
328 0 0         if ( ref $value eq 'ARRAY' ) {
329 0           $value = join ", ", map {"$_"} @$value;
  0            
330             }
331 0 0         if ( $type eq 'RawBytes' ) {
332 0           my $len = length($value);
333 0           my @list = unpack('C*', substr($value, 0, 16));
334 0           $_ = sprintf "%2.2x", $_ for @list;
335 0           $value = join(" ", @list);
336 0 0         $value .= " ..." if $len > 16;
337             }
338 0           say "|| " . join(" || ",
339             $offset,
340             $self->item($key)->{size},
341             $type,
342             "\`$key\`",
343             "\`$value\`"
344             ). " ||";
345             }
346             }
347             else {
348 0           foreach my $key ( @keys ) {
349 0 0         my $offset = $arg{relative} ? $self->item($key)->{addr} - $addr : $self->item($key)->{addr};
350 0           my $value = $self->item($key)->{value};
351 0           my $type = $self->item($key)->{type};
352 0           $type =~ s/^Finnigan:://;
353 0 0         if ( ref $value eq 'ARRAY' ) {
354 0           $value = join ", ", map {"$_"} @$value;
  0            
355             }
356 0 0         if ( $type eq 'RawBytes' ) {
357 0           my $len = length($value);
358 0           my @list = unpack('C*', substr($value, 0, 16));
359 0           $_ = sprintf "%2.2x", $_ for @list;
360 0           $value = join(" ", @list);
361 0 0         $value .= " ..." if $len > 16;
362             }
363 0           say join("\t",
364             $offset,
365             $self->item($key)->{size},
366             $type,
367             $key,
368             "$value"
369             );
370             }
371             }
372             }
373              
374             sub purge_unused_data {
375 0     0 1   my $self = shift;
376 0           delete $self->{current_element_number};
377 0           delete $self->{addr};
378 0           delete $self->{size};
379 0           foreach my $key (keys %{$self->{data}}) {
  0            
380 0 0         if ( substr($key, 0, 4) eq 'unkn' ) {
381 0           delete $self->{data}->{$key};
382             }
383             else {
384 0           delete $self->{data}->{$key}->{type};
385 0           delete $self->{data}->{$key}->{addr};
386 0           delete $self->{data}->{$key}->{seq};
387 0           delete $self->{data}->{$key}->{size};
388             }
389             }
390 0           return $self;
391             }
392              
393             1;
394             __END__