File Coverage

blib/lib/Protocol/DBus/Marshal.pm
Criterion Covered Total %
statement 159 176 90.3
branch 47 56 83.9
condition 8 10 80.0
subroutine 17 22 77.2
pod 0 8 0.0
total 231 272 84.9


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