File Coverage

blib/lib/Protocol/DBus/Marshal.pm
Criterion Covered Total %
statement 152 164 92.6
branch 45 54 83.3
condition 9 16 56.2
subroutine 16 19 84.2
pod 0 6 0.0
total 222 259 85.7


line stmt bran cond sub pod time code
1             package Protocol::DBus::Marshal;
2              
3 8     8   99750 use strict;
  8         24  
  8         195  
4 8     8   34 use warnings;
  8         11  
  8         149  
5              
6 8     8   2666 use Protocol::DBus::Pack ();
  8         30  
  8         164  
7 8     8   2729 use Protocol::DBus::Signature ();
  8         18  
  8         13541  
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 37     37 0 17549 local $_ENDIAN_PACK = '<';
26 37         93 local @_MARSHAL_FDS;
27 37         97 return( _marshal(@_[0, 1]), \@_MARSHAL_FDS );
28             }
29              
30             # buf, buf offset, sig
31             sub unmarshal_le {
32 41     41 0 43800 local $_ENDIAN_PACK = '<';
33 41         129 return _unmarshal(@_);
34             }
35              
36             sub marshal_be {
37 4     4 0 3342 local $_ENDIAN_PACK = '>';
38 4         8 local @_MARSHAL_FDS;
39 4         9 return( _marshal(@_[0, 1]), \@_MARSHAL_FDS );
40             }
41              
42             sub unmarshal_be {
43 1     1 0 6872 local $_ENDIAN_PACK = '>';
44 1         3 return _unmarshal(@_);
45             }
46              
47             #----------------------------------------------------------------------
48              
49             sub _marshal {
50 231     231   379 my ($sig, $data, $buf_sr, $_data_are_not_list) = @_;
51              
52 231   100     438 $buf_sr ||= \do { my $v = q<> };
  41         167  
53              
54 231         398 my @scts = Protocol::DBus::Signature::split($sig);
55              
56 231         412 for my $si ( 0 .. $#scts ) {
57 296         374 my $sct = $scts[$si];
58              
59 296 100       428 my $datum = $_data_are_not_list ? $data : $data->[$si];
60              
61 296 50       428 if (!defined $datum) {
62 0         0 die "Undefined datum (SCT=“$sct”)!";
63             }
64              
65             # Arrays
66 296 100       637 if (index($sct, 'a') == 0) {
    100          
    100          
67 12         36 _marshal_array( $sct, $datum, $buf_sr);
68             }
69              
70             # Structs are given as arrays.
71             elsif (index($sct, '(') == 0) {
72 11         24 Protocol::DBus::Pack::align_str($$buf_sr, 8);
73              
74 11         14 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 47         150 _marshal( g => $datum->[0], $buf_sr, 1 );
82 47         87 _marshal( $datum->[0], $datum->[1], $buf_sr, 1 );
83             }
84              
85             # Anything else is a basic type.
86             else {
87 226 100       464 if ($sct eq 'h') {
    100          
    100          
88 3         5 my $fd = fileno($datum);
89 3 50       6 die "fileno($datum) returned undef!" if !defined $fd;
90              
91 3         6 my ($idx) = grep { $_MARSHAL_FDS[$_] == $fd } 0 .. $#_MARSHAL_FDS;
  3         7  
92              
93 3 100       7 if (!defined $idx) {
94 2         11 $idx = @_MARSHAL_FDS;
95 2         2 push @_MARSHAL_FDS, $fd;
96             }
97              
98 3         5 $datum = $idx;
99             }
100             elsif ($sct eq 'o') {
101 11 50       115 $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 41         87 utf8::encode($datum);
107             }
108              
109 226         553 Protocol::DBus::Pack::align_str($$buf_sr, Protocol::DBus::Pack::ALIGNMENT()->{$sct});
110              
111 226         319 my ($pack) = _get_pack_template($sct);
112              
113 226         386 $pack = "($pack)$_ENDIAN_PACK";
114 226         686 $$buf_sr .= pack( $pack, $datum );
115             }
116             }
117              
118 231         446 return $buf_sr;
119             }
120              
121             sub _marshal_array {
122 12     12   26 my ($sct, $data, $buf_sr) = @_;
123              
124 12         35 Protocol::DBus::Pack::align_str($$buf_sr, 4);
125              
126             # We’ll fill this in with the length below.
127 12         25 $$buf_sr .= "\0\0\0\0";
128              
129 12         17 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 12         17 my $compensate_align8;
134              
135 12         20 substr($sct, 0, 1, q<>); # chop off the leading “a”
136              
137 12 100       33 if ($array_start % 8) {
138 3         3 $compensate_align8 = (0 == index($sct, '('));
139 3   100     11 $compensate_align8 ||= (0 == index($sct, '{'));
140 3   50     8 $compensate_align8 ||= ((Protocol::DBus::Pack::ALIGNMENT()->{$sct} || 0) == 8);
      66        
141             }
142              
143             # DICT_ENTRY arrays are given as plain Perl hashes
144 12 100       33 if (0 == index($sct, '{')) {
145 9         17 my $key_sig = substr($sct, 1, 1);
146 9         21 my $value_sig = substr($sct, 2, -1);
147              
148 9 100       71 for my $key ( $DICT_CANONICAL ? (sort keys %$data) : keys %$data ) {
149 38         88 Protocol::DBus::Pack::align_str($$buf_sr, 8);
150 38         121 _marshal($key_sig, $key, $buf_sr, 1);
151 38         71 _marshal( $value_sig, $data->{$key}, $buf_sr, 1);
152             }
153             }
154              
155             # Any other array is given as an array.
156             else {
157 3         6 for my $item ( @$data ) {
158 9         26 _marshal($sct, $item, $buf_sr, 1);
159             }
160             }
161              
162 12         30 my $array_len = length($$buf_sr) - $array_start;
163 12 100       28 $array_len -= 4 if $compensate_align8;
164              
165 12         42 substr( $$buf_sr, $array_start - 4, 4, pack("L$_ENDIAN_PACK", $array_len) );
166             }
167              
168             #----------------------------------------------------------------------
169              
170             sub _unmarshal {
171 78     78   138 my ($buf_sr, $buf_offset, $sig) = @_;
172              
173 78         109 my @items;
174              
175 78         97 my $buf_start = $buf_offset;
176 78         95 my $sig_offset = 0;
177              
178 78         153 while ($sig_offset < length($sig)) {
179 278         459 my $next_sct_len = Protocol::DBus::Signature::get_sct_length($sig, $sig_offset);
180              
181 278         570 my ($item, $item_length) = _unmarshal_sct(
182             $buf_sr,
183             $buf_offset,
184             substr( $sig, $sig_offset, $next_sct_len ),
185             );
186              
187 278         520 push @items, $item;
188              
189 278         319 $buf_offset += $item_length;
190 278         465 $sig_offset += $next_sct_len;
191             }
192              
193 78         209 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 1872     1872   2661 my ($buf_sr, $buf_offset, $sct_sig) = @_;
208              
209 1872         2041 my $buf_start = $buf_offset;
210              
211 1872 100       4019 if (substr($sct_sig, 0, 1) eq 'a') {
    100          
    100          
212 116         236 Protocol::DBus::Pack::align($buf_offset, 4);
213              
214 116         249 my $array_len = unpack "\@$buf_offset L$_ENDIAN_PACK", $$buf_sr;
215 116         161 $buf_offset += 4; #uint32 length
216              
217 116         125 my $obj;
218              
219             # We parse arrays of DICT_ENTRY into a hash.
220 116 100       191 if (substr($sct_sig, 1, 1) eq '{') {
221              
222             # The key is always a basic type, so just one letter.
223 52         89 my $key_type = substr($sct_sig, 2, 1);
224              
225             # The value can be any SCT.
226 52         109 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 52         114 Protocol::DBus::Pack::align($buf_offset, 8);
232              
233 52         118 $obj = _unmarshal_to_hashref($buf_sr, $buf_offset, $array_len, $key_type, $value_type);
234 52         76 $buf_offset += $array_len;
235             }
236              
237             # Anything else we parse normally.
238             else {
239 64         102 my $array_sig = substr( $sct_sig, 1, Protocol::DBus::Signature::get_sct_length($sct_sig, 1) );
240              
241 64         80 my @array_items;
242 64         109 $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         74 my $array_end = $buf_offset + $array_len;
248              
249 64         92 while ($buf_offset < $array_end) {
250 838         1149 my ($item, $item_length) = _unmarshal_sct($buf_sr, $buf_offset, $array_sig);
251              
252 838         974 $buf_offset += $item_length;
253              
254 838         1503 push @array_items, $item;
255             }
256             }
257              
258 116         214 return ($obj, $buf_offset - $buf_start);
259             }
260             elsif (substr($sct_sig, 0, 1) eq '(') {
261 36         63 return _unmarshal_struct(@_);
262             }
263             elsif (substr($sct_sig, 0, 1) eq 'v') {
264 193         294 return _unmarshal_variant(@_);
265             }
266              
267 1527         1986 my ($pack_tmpl, $is_string) = _get_pack_template($sct_sig);
268              
269 1527         3037 Protocol::DBus::Pack::align($buf_offset, Protocol::DBus::Pack::ALIGNMENT()->{$sct_sig});
270              
271 1527         3354 my $val = unpack("\@$buf_offset ($pack_tmpl)$_ENDIAN_PACK", $$buf_sr);
272              
273 1527         1858 my $strlen;
274              
275 1527 50 33     2886 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 428         463 $strlen = length $val;
283 428         604 utf8::decode($val);
284             }
285              
286 1527 100       3290 return ($val, $buf_offset - $buf_start + Protocol::DBus::Pack::WIDTH()->{$sct_sig} + ($is_string ? $strlen : 0));
287             }
288              
289             sub _unmarshal_variant {
290 193     193   256 my ($buf_sr, $buf_offset) = @_;
291              
292 193         201 my $buf_start = $buf_offset;
293              
294 193         317 my ($sig, $len) = _unmarshal_sct( $buf_sr, $buf_offset, 'g' );
295              
296 193 50       439 die sprintf("No sig ($len bytes?) from “%s”?", substr($$buf_sr, $buf_offset)) if !length $sig;
297              
298 193         208 $buf_offset += $len;
299              
300 193         311 (my $val, $len) = _unmarshal_sct( $buf_sr, $buf_offset, $sig );
301              
302             return(
303 193 50       473 $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 1753     1753   2145 my ($sct_sig) = @_;
310              
311 1753         1924 my ($is_string, $pack_tmpl);
312 1753 100       2517 if ( $pack_tmpl = Protocol::DBus::Pack::STRING()->{$sct_sig} ) {
313 533         593 $is_string = 1;
314             }
315             else {
316 1220 50       1899 $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 1220         1313 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 1753         2660 return ($pack_tmpl, $is_string);
331             }
332              
333             sub _unmarshal_to_hashref {
334 52     52   87 my ($buf_sr, $buf_offset, $array_len, $key_type, $value_type) = @_;
335              
336 52         62 my %items;
337 52         117 my $obj = bless \%items, 'Protocol::DBus::Type::Dict';
338              
339             # NB: We already align()ed this.
340              
341 52         72 my $end_offset = $buf_offset + $array_len;
342              
343 52         83 while ($buf_offset < $end_offset) {
344 185         355 Protocol::DBus::Pack::align($buf_offset, 8);
345              
346 185         327 my ($key, $len_in_buf) = _unmarshal_sct($buf_sr, $buf_offset, $key_type);
347              
348 185         240 $buf_offset += $len_in_buf;
349              
350 185         281 (my $val, $len_in_buf) = _unmarshal_sct($buf_sr, $buf_offset, $value_type);
351              
352 185         217 $buf_offset += $len_in_buf;
353              
354 185         471 $items{$key} = $val;
355             }
356              
357             # We don’t need to return the length.
358 52         117 return $obj;
359             }
360              
361             sub _unmarshal_struct {
362 36     36   49 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         51 substr( $sct_sig, 0, 1, q<> );
367              
368 36         44 my $buf_start = $buf_offset;
369              
370 36         69 Protocol::DBus::Pack::align($buf_offset, 8);
371              
372 36         79 my ($items_ar, $len) = _unmarshal($buf_sr, $buf_offset, $sct_sig);
373 36         68 bless $items_ar, 'Protocol::DBus::Type::Struct';
374              
375 36         74 return ($items_ar, ($buf_offset - $buf_start) + $len);
376             }
377              
378             sub _add_uint32_variant_length {
379 0     0     my ($buf_sr, $buf_offset_sr) = @_;
380              
381 0           Protocol::DBus::Pack::align( $$buf_offset_sr, 4 );
382              
383 0 0         my $array_len = unpack(
384             "\@$$buf_offset_sr " . ($_ENDIAN_PACK eq '<' ? 'V' : 'N'),
385             $$buf_sr,
386             );
387              
388 0           $$buf_offset_sr += 4;
389 0           $$buf_offset_sr += $array_len;
390              
391 0           return;
392             }
393              
394             1;