File Coverage

blib/lib/Protocol/DBus/Marshal.pm
Criterion Covered Total %
statement 170 190 89.4
branch 55 68 80.8
condition 9 16 56.2
subroutine 17 22 77.2
pod 0 8 0.0
total 251 304 82.5


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