File Coverage

blib/lib/Palm/BlockPack.pm
Criterion Covered Total %
statement 13 188 6.9
branch 1 144 0.6
condition 1 32 3.1
subroutine 4 10 40.0
pod 0 6 0.0
total 19 380 5.0


line stmt bran cond sub pod time code
1             #
2             # BlockPack.pm --
3             #
4             # Author : Maxime Soule
5             # Created On : Wed Dec 29 11:22:47 2004
6             # Last Modified By: Maxime Soule
7             # Last Modified On: Mon May 3 15:03:08 2010
8             # Update Count : 30
9             # Status : Unknown, Use with caution!
10             #
11             # Copyright (C) 2005, Maxime Soulé
12             # You may distribute this file under the terms of the Artistic
13             # License, as specified in the README file.
14             #
15              
16             package Palm::BlockPack;
17              
18 2     2   5373 use strict;
  2         6  
  2         87  
19              
20 2     2   11 use Carp qw(carp);
  2         4  
  2         181  
21 2     2   617 use Palm::FieldPack;
  2         5  
  2         7127  
22              
23             our $VERBOSE = 1;
24              
25             our $VERSION = '1.0';
26              
27              
28             my %SPECIAL_TYPES = (UInt8 => [ \&pack_fields_to_UInt8,
29             \&unpack_UInt8_to_fields ],
30             UInt16 => [ \&pack_fields_to_UInt16,
31             \&unpack_UInt16_to_fields ],
32             UInt32 => [ \&pack_fields_to_UInt32,
33             \&unpack_UInt32_to_fields ],
34             DateType => [ \&pack_DateType, \&unpack_DateType ],
35             TimeType => [ \&pack_TimeType, \&unpack_TimeType ],
36             double => [ \&pack_double, \&unpack_double, 8 ],
37             );
38              
39             sub pretty_str ($)
40             {
41 0     0 0 0 my $str = shift;
42 0         0 my $new = '';
43              
44 0 0       0 return $str if ref $str;
45              
46 0         0 while (length($str) > 0)
47             {
48 0         0 my $char = substr($str, 0, 1, '');
49              
50 0 0       0 if (ord($char) < 32)
51             {
52 0 0       0 if (ord($char) == 10)
53             {
54 0         0 $new .= "\n";
55             }
56             else
57             {
58 0         0 $new .= "^" . chr(ord($char) + 64);
59             }
60             }
61             else
62             {
63 0         0 $new .= $char;
64             }
65             }
66              
67 0         0 return $new;
68             }
69              
70              
71             sub __negate ($$)
72             {
73 0     0   0 my($ref, $size) = @_;
74              
75 0 0       0 my $array = ref($ref) eq 'ARRAY' ? $ref : [ $$ref ];
76              
77 0         0 foreach my $elt (@$array)
78             {
79             # Already null or negative, nothing to do...
80 0 0       0 next if $elt <= 0; # (1 << ($size - 1));
81              
82 0 0       0 if ($size == 2)
83             {
84 0         0 $elt = unpack('s', pack('S', $elt));
85             }
86             else # $size == 4
87             {
88 0         0 $elt = unpack('l', pack('L', $elt));
89             }
90             }
91              
92 0 0       0 if (ref($ref) ne 'ARRAY')
93             {
94 0         0 $$ref = $array->[0];
95             }
96             }
97              
98              
99             sub new ($@)
100             {
101 8     8 0 20 my $class = shift;
102              
103 8 50       30 die "Odd number of attributes" if @_ % 2;
104              
105 8         24 my $self = [ @_ ];
106              
107 8   33     59 return bless($self, ref($class) || $class);
108             }
109              
110              
111             sub init_block_element ($$$;$)
112             {
113 0     0 0   my($self, $ref_hash, $index, $delete_noinit) = @_;
114              
115 0           my $type = $self->[$index];
116 0           my $value = $self->[$index + 1];
117              
118             # Skip bytes
119 0 0         if ($type eq 'skip')
    0          
    0          
    0          
120             {
121             # Nothing to do here
122             }
123             # Bit field
124             elsif ($type =~ /^UInt(?:8|16|32)\z/)
125             {
126 0           foreach my $field (@$value)
127             {
128             # No reference => no init
129 0 0         if (ref $field)
    0          
130             {
131             # The name without bit_width
132 0           my($name) = split(':', $field->[0]);
133              
134 0           $ref_hash->{$name} = $field->[1];
135             }
136             elsif ($delete_noinit)
137             {
138             # The name without bit_width
139 0           my($name) = split(':', $field);
140              
141 0           delete $ref_hash->{$name};
142             }
143             }
144             }
145             elsif ($type eq 'DateType')
146             {
147             # No reference => no init
148 0 0         if (ref $value)
    0          
149             {
150             # 'date_', 'now'
151 0 0         if (@$value == 2)
152             {
153 0 0         if ($value->[1] eq 'now')
154             {
155 0           my @now = localtime(time);
156 0           $ref_hash->{"$value->[0]day"} = $now[3];
157 0           $ref_hash->{"$value->[0]month"} = $now[4] + 1;
158 0           $ref_hash->{"$value->[0]year"} = $now[5] + 1900;
159             }
160             else
161             {
162 0           $ref_hash->{"$value->[0]day"} = $value->[1];
163 0           $ref_hash->{"$value->[0]month"} = $value->[1];
164 0           $ref_hash->{"$value->[0]year"} = $value->[1];
165             }
166             }
167             # 'date_', day, month, year
168             else
169             {
170 0           $ref_hash->{"$value->[0]day"} = $value->[1];
171 0           $ref_hash->{"$value->[0]month"} = $value->[2];
172 0           $ref_hash->{"$value->[0]year"} = $value->[3];
173             }
174             }
175             elsif ($delete_noinit)
176             {
177 0           delete @$ref_hash{"${value}day",
178             "${value}month",
179             "${value}year"};
180             }
181             }
182             elsif ($type eq 'TimeType')
183             {
184             # No reference => no init
185 0 0         if (ref $value)
    0          
186             {
187             # 'time_', 'now'
188 0 0         if (@$value == 2)
189             {
190 0 0         if ($value->[1] eq 'now')
191             {
192 0           my @now = localtime(time);
193 0           $ref_hash->{"$value->[0]hour"} = $now[2];
194 0           $ref_hash->{"$value->[0]min"} = $now[1];
195             }
196             else
197             {
198 0           $ref_hash->{"$value->[0]hour"} = $value->[1];
199 0           $ref_hash->{"$value->[0]min"} = $value->[1];
200             }
201             }
202             # 'time_', hour, min
203             else
204             {
205 0           $ref_hash->{"$value->[0]hour"} = $value->[1];
206 0           $ref_hash->{"$value->[0]min"} = $value->[2];
207             }
208             }
209             elsif ($delete_noinit)
210             {
211 0           delete @$ref_hash{"${value}hour", "${value}min"};
212             }
213             }
214             # Other types
215             else
216             {
217             # No reference => no init
218 0 0         if (ref $value)
    0          
219             {
220             # Liste de nombres
221 0 0         if ($type =~ /^\[(?:-?[Nn]|[Cc])[1-9]\d*\]\z/)
222             {
223 0           $ref_hash->{$value->[0]} = [ @$value[1 .. $#$value] ];
224             }
225             else
226             {
227 0           $ref_hash->{$value->[0]} = $value->[1];
228             }
229             }
230             elsif ($delete_noinit)
231             {
232 0           delete $ref_hash->{$value};
233             }
234             }
235             }
236              
237              
238             #
239             # Initialise le hash passé avec les valeurs par défaut
240             sub init_block ($$;$)
241             {
242 0     0 0   my($self, $ref_hash, $delete_noinit) = @_;
243 0           my $index;
244              
245 0           for (my $index = 0; $index < @$self; $index += 2)
246             {
247 0           $self->init_block_element($ref_hash, $index, $delete_noinit);
248             }
249             }
250              
251              
252             sub pack_block ($$)
253             {
254 0     0 0   my($self, $ref_hash) = @_;
255 0           my $index;
256              
257 0           my $pack = '';
258              
259 0           for (my $index = 0; $index < @$self; $index += 2)
260             {
261 0           my $type = $self->[$index];
262 0           my $value = $self->[$index + 1];
263              
264             # Skip bytes
265 0 0         if ($type eq 'skip')
    0          
266             {
267 0           my($num, $byte);
268              
269 0 0         if (ref $value)
270             {
271 0           ($num, $byte) = @$value;
272             }
273             else
274             {
275 0           $num = $value;
276 0           $byte = "\0";
277             }
278              
279 0           $pack .= $byte x $num;
280             }
281             # Bit field
282             elsif ($type =~ /^UInt(?:8|16|32)\z/)
283             {
284 0 0         $pack .=
285             $SPECIAL_TYPES{$type}[0]->($ref_hash,
286 0           map {ref($_) ? $_->[0] : $_} @$value);
287             }
288             else
289             {
290 0 0         my $field_name = ref($value) ? $value->[0] : $value;
291              
292             # DateType || TimeType
293 0 0         if ($type =~ /^(?:Date|Time)Type\z/)
    0          
294             {
295 0           $pack .= $SPECIAL_TYPES{$type}[0]->($ref_hash, $field_name);
296             }
297             # Liste de nombres
298             elsif ($type =~ /^\[((?:-?[Nn]|[Cc])([1-9]\d*))\]\z/)
299             {
300 0           my($pack_type, $num) = ($1, $2);
301              
302 0           $pack_type =~ s/^-//; # Negative case deleted here...
303              
304 0   0       my $ref_list = $ref_hash->{$field_name} || [];
305              
306 0 0         if (@$ref_list < $num)
307             {
308 0           push(@$ref_list, (0) x (@{$ref_list} - $num));
  0            
309             }
310              
311 0           $pack .= pack($pack_type, @$ref_list);
312             }
313             else
314             {
315 0           my $field_value = $ref_hash->{$field_name};
316              
317 0 0         unless (defined $field_value)
318             {
319 0 0 0       if ($type =~ /^(?:-?[Nn]|[Cc])\z/
    0          
320             or exists $SPECIAL_TYPES{$type})
321             {
322 0           $field_value = 0;
323             }
324             elsif ($type =~ /^Z(\*|\d+)\z/)
325             {
326 0           $field_value = '';
327             }
328             else
329             {
330 0           die "Unknown Perl pack type";
331             }
332             }
333              
334             # Special type
335 0 0         if (exists $SPECIAL_TYPES{$type})
336             {
337 0           $pack .= $SPECIAL_TYPES{$type}[0]->($field_value);
338             }
339             # Perl pack type
340             else
341             {
342 0           $type =~ s/^-//; # Negative case deleted here...
343              
344 0           $pack .= pack($type, $field_value);
345             }
346             }
347             }
348             }
349              
350 0           return $pack;
351             }
352              
353              
354             sub unpack_block ($$;$$)
355             {
356 0     0 0   my($self, $pack, $ref_hash, $no_nonempty_alert) = @_;
357              
358 0 0         $ref_hash = {} unless defined $ref_hash;
359              
360 0 0         my $ref_pack = ref($pack) ? $pack : \$pack;
361              
362 0           for (my $index = 0; $index < @$self; $index += 2)
363             {
364 0           my $type = $self->[$index];
365 0           my $value = $self->[$index + 1];
366 0           my $out_of_data = 0;
367              
368 0 0         if ($type eq 'skip')
    0          
369             {
370 0 0         my $size = ref($value) ? $value->[0] : $value;
371              
372 0 0         if (length($$ref_pack) < $size)
373             {
374 0 0 0       if ($VERBOSE and not $no_nonempty_alert)
375             {
376 0           carp("unpack_block: out of data, can't skip"
377             . " (only " . length($$ref_pack)
378             . " byte(s) available: \""
379             . pretty_str($$ref_pack)
380             . "\")");
381             }
382             }
383             else
384             {
385 0           substr($$ref_pack, 0, $size) = '';
386             }
387             }
388             # Bit field
389             elsif ($type =~ /^UInt(8|16|32)\z/)
390             {
391 0           my $size = $1 / 8;
392              
393 0 0         if (length($$ref_pack) < $size)
394             {
395 0 0 0       if ($VERBOSE and not $no_nonempty_alert)
396             {
397 0           carp("unpack_block: ",
398             "out of data, can't unpack field $type"
399             . " (only " . length($$ref_pack)
400             . " byte(s) available: \""
401             . pretty_str($$ref_pack)
402             . "\")");
403             }
404 0           $out_of_data = 1;
405             }
406             else
407             {
408 0 0         $SPECIAL_TYPES{$type}[1]->($ref_hash,
409             substr($$ref_pack, 0, $size, ''),
410 0           map { ref($_) ? $_->[0] : $_ }
411             @$value);
412             }
413             }
414             else
415             {
416 0 0         my $field_name = ref($value) ? $value->[0] : $value;
417              
418             # DateType || TimeType
419 0 0         if ($type =~ /^(?:Date|Time)Type\z/)
    0          
    0          
420             {
421 0 0         if (length($$ref_pack) < 2)
422             {
423 0 0 0       if ($VERBOSE and not $no_nonempty_alert)
424             {
425 0           carp("unpack_block: out of data, can't unpack $type"
426             . " (only " . length($$ref_pack)
427             . " byte(s) available: \""
428             . pretty_str($$ref_pack)
429             . "\")");
430             }
431 0           $out_of_data = 1;
432             }
433             else
434             {
435 0           $SPECIAL_TYPES{$type}[1]->($ref_hash,
436             substr($$ref_pack, 0, 2, ''),
437             $field_name);
438             }
439             }
440             # Special type
441             elsif (exists $SPECIAL_TYPES{$type})
442             {
443 0 0         if (length($$ref_pack) < $SPECIAL_TYPES{$type}[2])
444             {
445 0 0 0       if ($VERBOSE and not $no_nonempty_alert)
446             {
447 0           carp("unpack_block: out of data, can't unpack $type"
448             . " (only " . length($$ref_pack)
449             . " byte(s) available: \""
450             . pretty_str($$ref_pack)
451             . "\")");
452             }
453 0           $out_of_data = 1;
454             }
455             else
456             {
457 0           $ref_hash->{$field_name} = $SPECIAL_TYPES{$type}[1]
458             ->(substr($$ref_pack, 0, $SPECIAL_TYPES{$type}[2],''));
459             }
460             }
461             # Liste de nombres
462             elsif ($type =~ /^\[((-?[Nn]|[Cc])([1-9]\d*))\]\z/)
463             {
464 0           my($pack_type, $pack_one, $num) = ($1, $2, $3);
465              
466 0           my $neg = 0;
467 0 0         if (substr($pack_type, 0, 1) eq '-')
468             {
469 0           substr($pack_type, 0, 1) = '';
470 0           substr($pack_one, 0, 1) = '';
471 0           $neg = 1;
472             }
473              
474 0           my $size;
475 0 0         if ($pack_one eq 'N')
    0          
476             {
477 0           $size = 4;
478             }
479             elsif ($pack_one eq 'n')
480             {
481 0           $size = 2;
482             }
483             else # $pack_one eq 'C' or $pack_one eq 'c'
484             {
485 0           $size = 1;
486             }
487              
488 0 0         if (length($$ref_pack) < $size * $num)
489             {
490 0 0 0       if ($VERBOSE and not $no_nonempty_alert)
491             {
492 0           carp("unpack_block: out of data, can't unpack $type"
493             . " (only " . length($$ref_pack)
494             . " byte(s) available: \""
495             . pretty_str($$ref_pack)
496             . "\")");
497             }
498 0           $out_of_data = 1;
499             }
500             else
501             {
502 0           $ref_hash->{$field_name}
503             = [ unpack($pack_type,
504             substr($$ref_pack, 0, $size * $num, '')) ];
505              
506             # 16 bits or 32 bits value is signed...
507 0 0         __negate($ref_hash->{$field_name}, $size) if $neg;
508             }
509             }
510             # Perl pack type
511             else
512             {
513 0           my($size, $min_size);
514              
515 0           my $neg = 0;
516              
517 0 0         if ($type =~ s/^(-?)N\z/N/)
    0          
    0          
    0          
518             {
519 0           $neg = $1;
520 0           $size = $min_size = 4;
521             }
522             elsif ($type =~ s/^(-?)n\z/n/)
523             {
524 0           $neg = $1;
525 0           $size = $min_size = 2;
526             }
527             elsif ($type =~ /^[Cc]\z/)
528             {
529 0           $size = $min_size = 1;
530             }
531             elsif ($type =~ /^Z(\*|\d+)\z/)
532             {
533 0 0         if ($1 eq '*')
534             {
535 0           $min_size = 1; # juste \0
536 0           $size = undef;
537             }
538             else
539             {
540 0           $size = $min_size = $1;
541             }
542             }
543             else
544             {
545 0           die "Unknown Perl unpack type";
546             }
547              
548 0 0         if (length($$ref_pack) < $min_size)
549             {
550 0 0 0       if ($VERBOSE and not $no_nonempty_alert)
551             {
552 0           carp("unpack_block: out of data, can't unpack $type"
553             . " (only " . length($$ref_pack)
554             . " byte(s) available: \""
555             . pretty_str($$ref_pack)
556             . "\")");
557             }
558 0           $out_of_data = 1;
559             }
560             else
561             {
562 0 0         if (defined $size)
563             {
564 0           $ref_hash->{$field_name}
565             = unpack($type, substr($$ref_pack, 0, $size, ''));
566              
567             # 16 bits or 32 bits value is signed...
568 0 0         __negate(\$ref_hash->{$field_name}, $size) if $neg;
569             }
570             # On ne pouvait pas connaître la taille avant le unpack
571             else
572             {
573 0           $ref_hash->{$field_name} = unpack($type, $$ref_pack);
574              
575 0           substr($$ref_pack, 0,
576             length($ref_hash->{$field_name}) + 1)
577             = ''; # Longueur avec le \0
578             }
579             }
580             }
581             }
582              
583             # Cas de out of data, il faut initialiser à la valeur par défaut
584 0 0         $self->init_block_element($ref_hash, $index) if $out_of_data;
585             }
586              
587 0 0 0       if ($VERBOSE and not $no_nonempty_alert and length($$ref_pack) > 0)
      0        
588             {
589 0           carp("unpack_block: ", length($$ref_pack), " bytes of data remain: \""
590             . pretty_str($$ref_pack)
591             . "\"");
592             }
593              
594 0           return $ref_hash;
595             }
596              
597             1;
598             __END__