File Coverage

blib/lib/Protocol/DBus/Marshal.pm
Criterion Covered Total %
statement 163 180 90.5
branch 47 56 83.9
condition 8 10 80.0
subroutine 18 23 78.2
pod 0 8 0.0
total 236 277 85.2


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