File Coverage

blib/lib/Protocol/DBus/Marshal.pm
Criterion Covered Total %
statement 150 195 76.9
branch 45 72 62.5
condition 9 16 56.2
subroutine 15 21 71.4
pod 0 6 0.0
total 219 310 70.6


line stmt bran cond sub pod time code
1             package Protocol::DBus::Marshal;
2              
3 6     6   111416 use strict;
  6         16  
  6         128  
4 6     6   23 use warnings;
  6         8  
  6         119  
5              
6 6     6   2011 use Protocol::DBus::Pack ();
  6         29  
  6         102  
7 6     6   2016 use Protocol::DBus::Signature ();
  6         12  
  6         10199  
8              
9             our $_ENDIAN_PACK;
10              
11             # Set this to get actual Perl filehandles in the
12             # message body. XXX FIXME This is a very hacky way to do it!
13             our $FILEHANDLES;
14              
15             # XXX FIXME Hackety-hack …
16             our $PRESERVE_VARIANT_SIGNATURES;
17              
18             # for testing
19             our $DICT_CANONICAL;
20              
21             our @_MARSHAL_FDS;
22              
23             # sig, data (array ref)
24             sub marshal_le {
25 32     32 0 12720 local $_ENDIAN_PACK = '<';
26 32         56 local @_MARSHAL_FDS;
27 32         74 return( _marshal(@_[0, 1]), \@_MARSHAL_FDS );
28             }
29              
30             # buf, buf offset, sig
31             sub unmarshal_le {
32 33     33 0 46760 local $_ENDIAN_PACK = '<';
33 33         107 return _unmarshal(@_);
34             }
35              
36             sub marshal_be {
37 0     0 0 0 local $_ENDIAN_PACK = '>';
38 0         0 local @_MARSHAL_FDS;
39 0         0 return( _marshal(@_[0, 1]), \@_MARSHAL_FDS );
40             }
41              
42             sub unmarshal_be {
43 0     0 0 0 local $_ENDIAN_PACK = '>';
44 0         0 return _unmarshal(@_);
45             }
46              
47             #----------------------------------------------------------------------
48              
49             sub _marshal {
50 166     166   334 my ($sig, $data, $buf_sr, $_data_are_not_list) = @_;
51              
52 166   100     260 $buf_sr ||= \do { my $v = q<> };
  32         107  
53              
54 166         249 my @scts = Protocol::DBus::Signature::split($sig);
55              
56 166         300 for my $si ( 0 .. $#scts ) {
57 213         244 my $sct = $scts[$si];
58              
59 213 100       271 my $datum = $_data_are_not_list ? $data : $data->[$si];
60              
61 213 50       290 if (!defined $datum) {
62 0         0 die "Undefined datum (SCT=“$sct”)!";
63             }
64              
65             # Arrays
66 213 100       412 if (index($sct, 'a') == 0) {
    100          
    100          
67 9         21 _marshal_array( $sct, $datum, $buf_sr);
68             }
69              
70             # Structs are given as arrays.
71             elsif (index($sct, '(') == 0) {
72 11         23 Protocol::DBus::Pack::align_str($$buf_sr, 8);
73              
74 11         18 my $struct_sig = substr($sig, 1, -1);
75              
76 11         19 _marshal( $struct_sig, $datum, $buf_sr );
77             }
78              
79             # Variants are given as two-member arrays.
80             elsif ($sct eq 'v') {
81 33         93 _marshal( g => $datum->[0], $buf_sr, 1 );
82 33         52 _marshal( $datum->[0], $datum->[1], $buf_sr, 1 );
83             }
84              
85             # Anything else is a basic type.
86             else {
87 160 100       296 if ($sct eq 'h') {
    100          
    100          
88 3         6 my $fd = fileno($datum);
89 3 50       7 die "fileno($datum) returned undef!" if !defined $fd;
90              
91 3         7 my ($idx) = grep { $_MARSHAL_FDS[$_] == $fd } 0 .. $#_MARSHAL_FDS;
  3         7  
92              
93 3 100       6 if (!defined $idx) {
94 2         3 $idx = @_MARSHAL_FDS;
95 2         4 push @_MARSHAL_FDS, $fd;
96             }
97              
98 3         4 $datum = $idx;
99             }
100             elsif ($sct eq 'o') {
101 9 50       83 $datum =~ m<\A/(?:[A-Za-z0-9_]+(?:/[A-Za-z0-9_]+)*)?\z> or do {
102 0         0 die "Invalid object path: “$datum”";
103             };
104             }
105             elsif ($sct eq 's') {
106 30         64 utf8::encode($datum);
107             }
108              
109 160         380 Protocol::DBus::Pack::align_str($$buf_sr, Protocol::DBus::Pack::ALIGNMENT()->{$sct});
110              
111 160         210 my ($pack) = _get_pack_template($sct);
112              
113 160         236 $pack = "($pack)$_ENDIAN_PACK";
114 160         436 $$buf_sr .= pack( $pack, $datum );
115             }
116             }
117              
118 166         287 return $buf_sr;
119             }
120              
121             sub _marshal_array {
122 9     9   21 my ($sct, $data, $buf_sr) = @_;
123              
124 9         22 Protocol::DBus::Pack::align_str($$buf_sr, 4);
125              
126             # We’ll fill this in with the length below.
127 9         18 $$buf_sr .= "\0\0\0\0";
128              
129 9         15 my $array_start = length $$buf_sr;
130              
131             # Per the spec, array lengths do NOT include alignment bytes
132             # after the length. This only affects 8-byte-aligned types.
133 9         10 my $compensate_align8;
134              
135 9         16 substr($sct, 0, 1, q<>); # chop off the leading “a”
136              
137 9 100       29 if ($array_start % 8) {
138 3         7 $compensate_align8 = (0 == index($sct, '('));
139 3   100     10 $compensate_align8 ||= (0 == index($sct, '{'));
140 3   50     11 $compensate_align8 ||= ((Protocol::DBus::Pack::ALIGNMENT()->{$sct} || 0) == 8);
      66        
141             }
142              
143             # DICT_ENTRY arrays are given as plain Perl hashes
144 9 100       29 if (0 == index($sct, '{')) {
145 6         15 my $key_sig = substr($sct, 1, 1);
146 6         17 my $value_sig = substr($sct, 2, -1);
147              
148 6 100       39 for my $key ( $DICT_CANONICAL ? (sort keys %$data) : keys %$data ) {
149 24         52 Protocol::DBus::Pack::align_str($$buf_sr, 8);
150 24         65 _marshal($key_sig, $key, $buf_sr, 1);
151 24         39 _marshal( $value_sig, $data->{$key}, $buf_sr, 1);
152             }
153             }
154              
155             # Any other array is given as an array.
156             else {
157 3         8 for my $item ( @$data ) {
158 9         29 _marshal($sct, $item, $buf_sr, 1);
159             }
160             }
161              
162 9         20 my $array_len = length($$buf_sr) - $array_start;
163 9 100       22 $array_len -= 4 if $compensate_align8;
164              
165 9         42 substr( $$buf_sr, $array_start - 4, 4, pack("L$_ENDIAN_PACK", $array_len) );
166             }
167              
168             #----------------------------------------------------------------------
169              
170             sub _unmarshal {
171 69     69   111 my ($buf_sr, $buf_offset, $sig) = @_;
172              
173 69         86 my @items;
174              
175 69         77 my $buf_start = $buf_offset;
176 69         82 my $sig_offset = 0;
177              
178 69         152 while ($sig_offset < length($sig)) {
179 233         424 my $next_sct_len = Protocol::DBus::Signature::get_sct_length($sig, $sig_offset);
180              
181 233         471 my ($item, $item_length) = _unmarshal_sct(
182             $buf_sr,
183             $buf_offset,
184             substr( $sig, $sig_offset, $next_sct_len ),
185             );
186              
187 233         421 push @items, $item;
188              
189 233         263 $buf_offset += $item_length;
190 233         354 $sig_offset += $next_sct_len;
191             }
192              
193 69         208 return (\@items, $buf_offset - $buf_start);
194             }
195              
196             sub unmarshal_sct_le {
197 0     0 0 0 return _unmarshal_sct(@_);
198             }
199              
200             sub unmarshal_sct_be {
201 0     0 0 0 return _unmarshal_sct(@_);
202             }
203              
204             # SCT = “single complete type”.
205             # Returns the value plus its marshaled length.
206             sub _unmarshal_sct {
207 1715     1715   2116 my ($buf_sr, $buf_offset, $sct_sig) = @_;
208              
209 1715         1672 my $buf_start = $buf_offset;
210              
211 1715 100       3305 if (substr($sct_sig, 0, 1) eq 'a') {
    100          
    100          
212 110         232 Protocol::DBus::Pack::align($buf_offset, 4);
213              
214 110         232 my $array_len = unpack "\@$buf_offset L$_ENDIAN_PACK", $$buf_sr;
215 110         130 $buf_offset += 4; #uint32 length
216              
217 110         101 my $obj;
218              
219             # We parse arrays of DICT_ENTRY into a hash.
220 110 100       172 if (substr($sct_sig, 1, 1) eq '{') {
221              
222             # The key is always a basic type, so just one letter.
223 46         56 my $key_type = substr($sct_sig, 2, 1);
224              
225             # The value can be any SCT.
226 46         70 my $value_type = substr( $sct_sig, 3, Protocol::DBus::Signature::get_sct_length($sct_sig, 3) );
227              
228             # Do this here rather than in
229             # _unmarshal_to_hashref() to avoid
230             # the creation of an intermediate length.
231 46         83 Protocol::DBus::Pack::align($buf_offset, 8);
232              
233 46         67 $obj = _unmarshal_to_hashref($buf_sr, $buf_offset, $array_len, $key_type, $value_type);
234 46         58 $buf_offset += $array_len;
235             }
236              
237             # Anything else we parse normally.
238             else {
239 64         108 my $array_sig = substr( $sct_sig, 1, Protocol::DBus::Signature::get_sct_length($sct_sig, 1) );
240              
241 64         68 my @array_items;
242 64         103 $obj = bless \@array_items, 'Protocol::DBus::Type::Array';
243              
244             # If the array contents are 8-byte-aligned, then the array will
245             # actually be 4 bytes longer than this. But it doesn’t appear we
246             # need to care about that since _unmarshal_sct() accounts for that.
247 64         73 my $array_end = $buf_offset + $array_len;
248              
249 64         93 while ($buf_offset < $array_end) {
250 838         977 my ($item, $item_length) = _unmarshal_sct($buf_sr, $buf_offset, $array_sig);
251              
252 838         827 $buf_offset += $item_length;
253              
254 838         1248 push @array_items, $item;
255             }
256             }
257              
258 110         178 return ($obj, $buf_offset - $buf_start);
259             }
260             elsif (substr($sct_sig, 0, 1) eq '(') {
261 36         54 return _unmarshal_struct(@_);
262             }
263             elsif (substr($sct_sig, 0, 1) eq 'v') {
264 165         283 return _unmarshal_variant(@_);
265             }
266              
267 1404         1729 my ($pack_tmpl, $is_string) = _get_pack_template($sct_sig);
268              
269 1404         2575 Protocol::DBus::Pack::align($buf_offset, Protocol::DBus::Pack::ALIGNMENT()->{$sct_sig});
270              
271 1404         2729 my $val = unpack("\@$buf_offset ($pack_tmpl)$_ENDIAN_PACK", $$buf_sr);
272              
273 1404         1407 my $strlen;
274              
275 1404 50 33     2422 if ($FILEHANDLES && $sct_sig eq 'h') {
    100          
276 0   0     0 $val = $FILEHANDLES->[$val] || do {
277             warn "UNIX_FD ($val) received that doesn’t refer to a received file descriptor!\n";
278             $val;
279             };
280             }
281             elsif ($is_string) {
282 372         418 $strlen = length $val;
283 372         502 utf8::decode($val);
284             }
285              
286 1404 100       3166 return ($val, $buf_offset - $buf_start + Protocol::DBus::Pack::WIDTH()->{$sct_sig} + ($is_string ? $strlen : 0));
287             }
288              
289             sub _unmarshal_variant {
290 165     165   191 my ($buf_sr, $buf_offset) = @_;
291              
292 165         164 my $buf_start = $buf_offset;
293              
294 165         210 my ($sig, $len) = _unmarshal_sct( $buf_sr, $buf_offset, 'g' );
295              
296 165 50       242 die sprintf("No sig ($len bytes?) from “%s”?", substr($$buf_sr, $buf_offset)) if !length $sig;
297              
298 165         196 $buf_offset += $len;
299              
300 165         293 (my $val, $len) = _unmarshal_sct( $buf_sr, $buf_offset, $sig );
301              
302             return(
303 165 50       360 $PRESERVE_VARIANT_SIGNATURES ? bless( [ $sig => $val ], 'Protocol::DBus::Type::Variant' ) : $val,
304             $len + $buf_offset - $buf_start,
305             );
306             }
307              
308             sub _get_pack_template {
309 1564     1564   1690 my ($sct_sig) = @_;
310              
311 1564         1524 my ($is_string, $pack_tmpl);
312 1564 100       2039 if ( $pack_tmpl = Protocol::DBus::Pack::STRING()->{$sct_sig} ) {
313 448         506 $is_string = 1;
314             }
315             else {
316 1116 50       1561 $pack_tmpl = Protocol::DBus::Pack::NUMERIC()->{$sct_sig} or do {
317 0         0 die "No basic type template for type “$sct_sig”!";
318             };
319              
320 1116         1319 if (!Protocol::DBus::Pack::CAN_64()) {
321             if ($pack_tmpl eq 'q') {
322             $pack_tmpl = ( $_ENDIAN_PACK eq '>' ) ? 'x4 l' : 'l x4';
323             }
324             elsif ($pack_tmpl eq 'Q') {
325             $pack_tmpl = ( $_ENDIAN_PACK eq '>' ) ? 'x4 L' : 'L x4';
326             }
327             }
328             }
329              
330 1564         2134 return ($pack_tmpl, $is_string);
331             }
332              
333             sub _unmarshal_to_hashref {
334 46     46   138 my ($buf_sr, $buf_offset, $array_len, $key_type, $value_type) = @_;
335              
336 46         46 my %items;
337 46         90 my $obj = bless \%items, 'Protocol::DBus::Type::Dict';
338              
339             # NB: We already align()ed this.
340              
341 46         56 my $end_offset = $buf_offset + $array_len;
342              
343 46         70 while ($buf_offset < $end_offset) {
344 157         274 Protocol::DBus::Pack::align($buf_offset, 8);
345              
346 157         228 my ($key, $len_in_buf) = _unmarshal_sct($buf_sr, $buf_offset, $key_type);
347              
348 157         180 $buf_offset += $len_in_buf;
349              
350 157         216 (my $val, $len_in_buf) = _unmarshal_sct($buf_sr, $buf_offset, $value_type);
351              
352 157         169 $buf_offset += $len_in_buf;
353              
354 157         336 $items{$key} = $val;
355             }
356              
357             # We don’t need to return the length.
358 46         75 return $obj;
359             }
360              
361             sub _unmarshal_struct {
362 36     36   53 my ($buf_sr, $buf_offset, $sct_sig) = @_;
363              
364             # Remove “()” and just parse as a series of types.
365 36         46 chop $sct_sig;
366 36         49 substr( $sct_sig, 0, 1, q<> );
367              
368 36         35 my $buf_start = $buf_offset;
369              
370 36         62 Protocol::DBus::Pack::align($buf_offset, 8);
371              
372 36         73 my ($items_ar, $len) = _unmarshal($buf_sr, $buf_offset, $sct_sig);
373 36         61 bless $items_ar, 'Protocol::DBus::Type::Struct';
374              
375 36         71 return ($items_ar, ($buf_offset - $buf_start) + $len);
376             }
377              
378             sub _buffer_length_satisfies_signature {
379 0     0     my ($buf, $buf_offset, $sig) = @_;
380              
381 0           my $sig_offset = 0;
382              
383 0           while ($buf_offset <= length($buf)) {
384              
385             # We’re good if this passes because it means the buffer is longer
386             # than the passed-in signature needs it to be.
387 0 0         return (1, $buf_offset) if $sig_offset == length($sig);
388              
389 0           my $sct_length = Protocol::DBus::Signature::get_sct_length($sig, $sig_offset);
390              
391 0           my $next_sct = substr(
392             $sig,
393             $sig_offset,
394             $sct_length,
395             );
396              
397 0           $sig_offset += $sct_length;
398              
399 0 0         if ($next_sct eq 'v') {
    0          
    0          
    0          
400 0           my ($variant_sig, $len) = _unmarshal_sct($buf, $buf_offset, 'g');
401 0           $buf_offset += $len;
402              
403             # This has to recurse and preserve the offset.
404 0           my ($ok, $new_offset) = _buffer_length_satisfies_signature( $buf, $buf_offset, $variant_sig );
405 0 0         return 0 if !$ok;
406 0           $buf_offset = $new_offset;
407             }
408              
409             # signatures
410             elsif ($next_sct eq 'g') {
411             # 2 for the length byte and the trailing NUL
412 0           $buf_offset += 2 + unpack( "\@$buf_offset C", $buf )
413             }
414              
415             # strings and object paths
416             elsif ( Protocol::DBus::Pack::STRING()->{$next_sct} ) {
417 0           _add_uint32_variant_length(\$buf, \$buf_offset);
418 0           $buf_offset++; #trailing NUL
419             }
420              
421             # numerics
422             elsif ( my $width = Protocol::DBus::Pack::WIDTH()->{$next_sct} ) {
423 0           $buf_offset += $width;
424             }
425              
426             else {
427 0           my $char0 = substr($next_sct, 0, 1);
428              
429 0 0         if ($char0 eq 'a') {
    0          
430 0           _add_uint32_variant_length(\$buf, \$buf_offset);
431             }
432             elsif ($char0 eq '(') {
433 0           Protocol::DBus::Pack::align( $buf_offset, 8 );
434              
435 0           my ($ok, $new_offset) = _buffer_length_satisfies_signature( $buf, $buf_offset, substr($next_sct, 1, -1) );
436 0 0         return 0 if !$ok;
437 0           $buf_offset = $new_offset;
438             }
439             else {
440 0           die "unrecognized SCT: “$next_sct”";
441             }
442             }
443             }
444              
445 0           return 0;
446             }
447              
448             sub _add_uint32_variant_length {
449 0     0     my ($buf_sr, $buf_offset_sr) = @_;
450 6     6   1982 use Data::Dumper;
  6         20453  
  6         762  
451 0           $Data::Dumper::Useqq = 1;
452 0           print STDERR Dumper @_;
453              
454 0           Protocol::DBus::Pack::align( $$buf_offset_sr, 4 );
455              
456 0 0         my $array_len = unpack(
457             "\@$$buf_offset_sr " . ($_ENDIAN_PACK eq '<' ? 'V' : 'N'),
458             $$buf_sr,
459             );
460              
461 0           $$buf_offset_sr += 4;
462 0           $$buf_offset_sr += $array_len;
463              
464 0           return;
465             }
466              
467             1;