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   88207 use strict;
  8         18  
  8         190  
4 8     8   33 use warnings;
  8         14  
  8         146  
5              
6 8     8   2535 use Protocol::DBus::Pack ();
  8         27  
  8         155  
7 8     8   2605 use Protocol::DBus::Signature ();
  8         17  
  8         13068  
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 15276 local $_ENDIAN_PACK = '<';
26 37         76 local @_MARSHAL_FDS;
27 37         83 return( _marshal(@_[0, 1]), \@_MARSHAL_FDS );
28             }
29              
30             # buf, buf offset, sig
31             sub unmarshal_le {
32 41     41 0 42058 local $_ENDIAN_PACK = '<';
33 41         125 return _unmarshal(@_);
34             }
35              
36             sub marshal_be {
37 4     4 0 2941 local $_ENDIAN_PACK = '>';
38 4         7 local @_MARSHAL_FDS;
39 4         9 return( _marshal(@_[0, 1]), \@_MARSHAL_FDS );
40             }
41              
42             sub unmarshal_be {
43 1     1 0 6544 local $_ENDIAN_PACK = '>';
44 1         5 return _unmarshal(@_);
45             }
46              
47             #----------------------------------------------------------------------
48              
49             sub _marshal {
50 231     231   350 my ($sig, $data, $buf_sr, $_data_are_not_list) = @_;
51              
52 231   100     391 $buf_sr ||= \do { my $v = q<> };
  41         157  
53              
54 231         360 my @scts = Protocol::DBus::Signature::split($sig);
55              
56 231         387 for my $si ( 0 .. $#scts ) {
57 296         346 my $sct = $scts[$si];
58              
59 296 100       417 my $datum = $_data_are_not_list ? $data : $data->[$si];
60              
61 296 50       405 if (!defined $datum) {
62 0         0 die "Undefined datum (SCT=“$sct”)!";
63             }
64              
65             # Arrays
66 296 100       600 if (index($sct, 'a') == 0) {
    100          
    100          
67 12         29 _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         13 my $struct_sig = substr($sig, 1, -1);
75              
76 11         18 _marshal( $struct_sig, $datum, $buf_sr );
77             }
78              
79             # Variants are given as two-member arrays.
80             elsif ($sct eq 'v') {
81 47         122 _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       477 if ($sct eq 'h') {
    100          
    100          
88 3         5 my $fd = fileno($datum);
89 3 50       5 die "fileno($datum) returned undef!" if !defined $fd;
90              
91 3         7 my ($idx) = grep { $_MARSHAL_FDS[$_] == $fd } 0 .. $#_MARSHAL_FDS;
  3         5  
92              
93 3 100       7 if (!defined $idx) {
94 2         3 $idx = @_MARSHAL_FDS;
95 2         3 push @_MARSHAL_FDS, $fd;
96             }
97              
98 3         4 $datum = $idx;
99             }
100             elsif ($sct eq 'o') {
101 11 50       72 $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         75 utf8::encode($datum);
107             }
108              
109 226         520 Protocol::DBus::Pack::align_str($$buf_sr, Protocol::DBus::Pack::ALIGNMENT()->{$sct});
110              
111 226         330 my ($pack) = _get_pack_template($sct);
112              
113 226         344 $pack = "($pack)$_ENDIAN_PACK";
114 226         619 $$buf_sr .= pack( $pack, $datum );
115             }
116             }
117              
118 231         415 return $buf_sr;
119             }
120              
121             sub _marshal_array {
122 12     12   29 my ($sct, $data, $buf_sr) = @_;
123              
124 12         31 Protocol::DBus::Pack::align_str($$buf_sr, 4);
125              
126             # We’ll fill this in with the length below.
127 12         22 $$buf_sr .= "\0\0\0\0";
128              
129 12         21 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         18 my $compensate_align8;
134              
135 12         22 substr($sct, 0, 1, q<>); # chop off the leading “a”
136              
137 12 100       34 if ($array_start % 8) {
138 3         5 $compensate_align8 = (0 == index($sct, '('));
139 3   100     11 $compensate_align8 ||= (0 == index($sct, '{'));
140 3   50     9 $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       31 if (0 == index($sct, '{')) {
145 9         21 my $key_sig = substr($sct, 1, 1);
146 9         18 my $value_sig = substr($sct, 2, -1);
147              
148 9 100       73 for my $key ( $DICT_CANONICAL ? (sort keys %$data) : keys %$data ) {
149 38         84 Protocol::DBus::Pack::align_str($$buf_sr, 8);
150 38         115 _marshal($key_sig, $key, $buf_sr, 1);
151 38         65 _marshal( $value_sig, $data->{$key}, $buf_sr, 1);
152             }
153             }
154              
155             # Any other array is given as an array.
156             else {
157 3         5 for my $item ( @$data ) {
158 9         25 _marshal($sct, $item, $buf_sr, 1);
159             }
160             }
161              
162 12         23 my $array_len = length($$buf_sr) - $array_start;
163 12 100       45 $array_len -= 4 if $compensate_align8;
164              
165 12         50 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         88 my @items;
174              
175 78         86 my $buf_start = $buf_offset;
176 78         84 my $sig_offset = 0;
177              
178 78         133 while ($sig_offset < length($sig)) {
179 278         446 my $next_sct_len = Protocol::DBus::Signature::get_sct_length($sig, $sig_offset);
180              
181 278         549 my ($item, $item_length) = _unmarshal_sct(
182             $buf_sr,
183             $buf_offset,
184             substr( $sig, $sig_offset, $next_sct_len ),
185             );
186              
187 278         495 push @items, $item;
188              
189 278         301 $buf_offset += $item_length;
190 278         401 $sig_offset += $next_sct_len;
191             }
192              
193 78         197 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   2401 my ($buf_sr, $buf_offset, $sct_sig) = @_;
208              
209 1872         1783 my $buf_start = $buf_offset;
210              
211 1872 100       3499 if (substr($sct_sig, 0, 1) eq 'a') {
    100          
    100          
212 116         216 Protocol::DBus::Pack::align($buf_offset, 4);
213              
214 116         233 my $array_len = unpack "\@$buf_offset L$_ENDIAN_PACK", $$buf_sr;
215 116         142 $buf_offset += 4; #uint32 length
216              
217 116         109 my $obj;
218              
219             # We parse arrays of DICT_ENTRY into a hash.
220 116 100       177 if (substr($sct_sig, 1, 1) eq '{') {
221              
222             # The key is always a basic type, so just one letter.
223 52         76 my $key_type = substr($sct_sig, 2, 1);
224              
225             # The value can be any SCT.
226 52         95 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         95 Protocol::DBus::Pack::align($buf_offset, 8);
232              
233 52         85 $obj = _unmarshal_to_hashref($buf_sr, $buf_offset, $array_len, $key_type, $value_type);
234 52         65 $buf_offset += $array_len;
235             }
236              
237             # Anything else we parse normally.
238             else {
239 64         100 my $array_sig = substr( $sct_sig, 1, Protocol::DBus::Signature::get_sct_length($sct_sig, 1) );
240              
241 64         67 my @array_items;
242 64         93 $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         71 my $array_end = $buf_offset + $array_len;
248              
249 64         90 while ($buf_offset < $array_end) {
250 838         936 my ($item, $item_length) = _unmarshal_sct($buf_sr, $buf_offset, $array_sig);
251              
252 838         811 $buf_offset += $item_length;
253              
254 838         1247 push @array_items, $item;
255             }
256             }
257              
258 116         181 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 193         281 return _unmarshal_variant(@_);
265             }
266              
267 1527         1739 my ($pack_tmpl, $is_string) = _get_pack_template($sct_sig);
268              
269 1527         2725 Protocol::DBus::Pack::align($buf_offset, Protocol::DBus::Pack::ALIGNMENT()->{$sct_sig});
270              
271 1527         2979 my $val = unpack("\@$buf_offset ($pack_tmpl)$_ENDIAN_PACK", $$buf_sr);
272              
273 1527         1574 my $strlen;
274              
275 1527 50 33     2552 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         397 $strlen = length $val;
283 428         573 utf8::decode($val);
284             }
285              
286 1527 100       2851 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   224 my ($buf_sr, $buf_offset) = @_;
291              
292 193         187 my $buf_start = $buf_offset;
293              
294 193         296 my ($sig, $len) = _unmarshal_sct( $buf_sr, $buf_offset, 'g' );
295              
296 193 50       465 die sprintf("No sig ($len bytes?) from “%s”?", substr($$buf_sr, $buf_offset)) if !length $sig;
297              
298 193         187 $buf_offset += $len;
299              
300 193         295 (my $val, $len) = _unmarshal_sct( $buf_sr, $buf_offset, $sig );
301              
302             return(
303 193 50       424 $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   1888 my ($sct_sig) = @_;
310              
311 1753         1675 my ($is_string, $pack_tmpl);
312 1753 100       2220 if ( $pack_tmpl = Protocol::DBus::Pack::STRING()->{$sct_sig} ) {
313 533         538 $is_string = 1;
314             }
315             else {
316 1220 50       1657 $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         1126 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         2370 return ($pack_tmpl, $is_string);
331             }
332              
333             sub _unmarshal_to_hashref {
334 52     52   82 my ($buf_sr, $buf_offset, $array_len, $key_type, $value_type) = @_;
335              
336 52         56 my %items;
337 52         114 my $obj = bless \%items, 'Protocol::DBus::Type::Dict';
338              
339             # NB: We already align()ed this.
340              
341 52         67 my $end_offset = $buf_offset + $array_len;
342              
343 52         95 while ($buf_offset < $end_offset) {
344 185         362 Protocol::DBus::Pack::align($buf_offset, 8);
345              
346 185         297 my ($key, $len_in_buf) = _unmarshal_sct($buf_sr, $buf_offset, $key_type);
347              
348 185         215 $buf_offset += $len_in_buf;
349              
350 185         235 (my $val, $len_in_buf) = _unmarshal_sct($buf_sr, $buf_offset, $value_type);
351              
352 185         215 $buf_offset += $len_in_buf;
353              
354 185         410 $items{$key} = $val;
355             }
356              
357             # We don’t need to return the length.
358 52         109 return $obj;
359             }
360              
361             sub _unmarshal_struct {
362 36     36   50 my ($buf_sr, $buf_offset, $sct_sig) = @_;
363              
364             # Remove “()” and just parse as a series of types.
365 36         43 chop $sct_sig;
366 36         44 substr( $sct_sig, 0, 1, q<> );
367              
368 36         38 my $buf_start = $buf_offset;
369              
370 36         71 Protocol::DBus::Pack::align($buf_offset, 8);
371              
372 36         76 my ($items_ar, $len) = _unmarshal($buf_sr, $buf_offset, $sct_sig);
373 36         60 bless $items_ar, 'Protocol::DBus::Type::Struct';
374              
375 36         62 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;