File Coverage

blib/lib/Protocol/DBus/Marshal.pm
Criterion Covered Total %
statement 159 177 89.8
branch 48 58 82.7
condition 10 13 76.9
subroutine 17 22 77.2
pod 0 8 0.0
total 234 278 84.1


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