File Coverage

blib/lib/CBOR/PP/Decode.pm
Criterion Covered Total %
statement 119 161 73.9
branch 85 120 70.8
condition 4 6 66.6
subroutine 7 7 100.0
pod 1 1 100.0
total 216 295 73.2


line stmt bran cond sub pod time code
1             package CBOR::PP::Decode;
2              
3 5     5   29 use strict;
  5         9  
  5         129  
4 5     5   21 use warnings;
  5         6  
  5         306  
5              
6             =encoding utf-8
7              
8             =head1 NAME
9              
10             CBOR::PP::Decode
11              
12             =head1 SYNOPSIS
13              
14             my $perlvar = CBOR::PP::Decode::decode($binary);
15              
16             =head1 DESCRIPTION
17              
18             This implements a basic CBOR decoder in pure Perl.
19              
20             =head1 MAPPING CBOR TO PERL
21              
22             =over
23              
24             =item * All tags are ignored. (This could be iterated on later.)
25              
26             =item * Indefinite-length objects are supported, but streamed parsing
27             is not; the data structure must be complete to be decoded.
28              
29             =item * Binary and UTF-8 strings are decoded as Perl represents them.
30             Note that UTF-8 string decoding is a bit slower.
31              
32             =item * null, undefined, true, and false become undef, undef,
33             Types::Serialiser::true(), and Types::Serialiser::false(), respectively.
34             (NB: undefined is deserialized as an error object in L,
35             which doesn’t seem to make sense.)
36              
37             =back
38              
39             =head1 TODO
40              
41             =over
42              
43             =item * Add tag decode support via callbacks.
44              
45             =item * Make it faster by removing some of the internal buffer copying.
46              
47             =back
48              
49             =head1 AUTHOR
50              
51             L (FELIPE)
52              
53             =head1 LICENSE
54              
55             This code is licensed under the same license as Perl itself.
56              
57             =cut
58              
59             #----------------------------------------------------------------------
60              
61             =head1 METHODS
62              
63             =head2 $value = decode( $CBOR_BYTESTRING )
64              
65             Returns a Perl value that represents the serialized CBOR string.
66              
67             =cut
68              
69             my ($byte1, $offset, $lead3bits);
70              
71             my $len;
72              
73             # This ensures that pieces of indefinite-length strings
74             # are all of the same type.
75             our $_lead3_must_be;
76              
77             use constant {
78 5         6703 _LEAD3_UINT => 0,
79             _LEAD3_NEGINT => 1 << 5,
80             _LEAD3_BINSTR => 2 << 5,
81             _LEAD3_UTF8STR => 3 << 5,
82             _LEAD3_ARRAY => 4 << 5,
83             _LEAD3_HASH => 5 << 5,
84             _LEAD3_TAG => 6 << 5,
85 5     5   22 };
  5         8  
86              
87             # CBOR is much simpler to create than it is to parse!
88              
89             # TODO: Optimize by removing the buffer duplication.
90              
91             sub decode {
92 403     403 1 24931 $offset = 0;
93              
94 403         519 for ($_[0]) {
95 403         586 $byte1 = ord( substr( $_, $offset, 1 ) );
96 403         459 $lead3bits = 0xe0 & $byte1;
97              
98 403 50 66     651 die "Improper lead3 ($lead3bits) within streamed $_lead3_must_be!" if $_lead3_must_be && $lead3bits != $_lead3_must_be;
99              
100             #use Text::Control;
101             #print Text::Control::to_hex($_) . $/;
102              
103 403 100       854 if ($lead3bits == _LEAD3_UINT()) {
    100          
    100          
    100          
    100          
    100          
    100          
104 203 100       405 return (1, ord) if $byte1 < 0x18;
105              
106 27 100       77 return (2, unpack('x C', $_)) if $byte1 == 0x18;
107              
108 13 100       31 return (3, unpack('x n', $_)) if $byte1 == 0x19;
109              
110 11 100       36 return (5, unpack('x N', $_)) if $byte1 == 0x1a;
111              
112 6         35 return (9, unpack('x Q>', $_));
113             }
114              
115             elsif ($lead3bits == _LEAD3_NEGINT()) {
116 5 100       16 return (1, 0x1f - ord()) if $byte1 < 0x38;
117              
118 2 100       10 return (2, -unpack( 'x C', $_) - 1) if $byte1 == 0x38;
119              
120 1 50       5 return (3, -unpack( 'x n', $_) - 1) if $byte1 == 0x39;
121              
122 0 0       0 return (5, -unpack( 'x N', $_) - 1) if $byte1 == 0x3a;
123              
124 0         0 return (9, -unpack( 'x Q>', $_) - 1);
125             }
126              
127             elsif ($lead3bits == _LEAD3_BINSTR()) {
128 59         69 my $hdrlen;
129              
130 59 100       108 if ($byte1 < 0x58) {
    50          
    50          
    50          
    50          
    50          
131 58         67 $len = $byte1 - 0x40;
132 58         66 $hdrlen = 1;
133             }
134             elsif ($byte1 == 0x58) {
135 0         0 $len = unpack 'x C', $_;
136 0         0 $hdrlen = 2;
137             }
138             elsif ($byte1 == 0x59) {
139 0         0 $len = unpack 'x n', $_;
140 0         0 $hdrlen = 3;
141             }
142             elsif ($byte1 == 0x5a) {
143 0         0 $len = unpack 'x N', $_;
144 0         0 $hdrlen = 5;
145             }
146             elsif ($byte1 == 0x5b) {
147 0         0 $len = unpack 'x Q>', $_;
148 0         0 $hdrlen = 9;
149             }
150             elsif ($byte1 == 0x5f) {
151 1         3 return _stringstream();
152             }
153             else {
154 0         0 die "Invalid lead byte: $byte1";
155             }
156              
157 58         211 return ($hdrlen + $len, substr( $_, $hdrlen, $len ));
158             }
159              
160             elsif ($lead3bits == _LEAD3_UTF8STR()) {
161 37         39 my $hdrlen;
162              
163 37 100       72 if ($byte1 < 0x78) {
    50          
    50          
    50          
    50          
    50          
164 36         42 $len = $byte1 - 0x60;
165 36         38 $hdrlen = 1;
166             }
167             elsif ($byte1 == 0x78) {
168 0         0 $len = unpack 'x C', $_;
169 0         0 $hdrlen = 2;
170             }
171             elsif ($byte1 == 0x79) {
172 0         0 $len = unpack 'x n', $_;
173 0         0 $hdrlen = 3;
174             }
175             elsif ($byte1 == 0x7a) {
176 0         0 $len = unpack 'x N', $_;
177 0         0 $hdrlen = 5;
178             }
179             elsif ($byte1 == 0x7b) {
180 0         0 $len = unpack 'x Q>', $_;
181 0         0 $hdrlen = 9;
182             }
183             elsif ($byte1 == 0x7f) {
184 1         3 return _stringstream();
185             }
186             else {
187 0         0 die "Invalid lead byte: $byte1";
188             }
189              
190 36         47 my $v = substr( $_, $hdrlen, $len );
191 36         97 utf8::decode($v);
192              
193 36         81 return ($hdrlen + $len, $v);
194             }
195              
196             elsif ($lead3bits == _LEAD3_ARRAY()) {
197 48         50 my $total;
198              
199 48 100       76 if ($byte1 < 0x98) {
    100          
    50          
    50          
    50          
    50          
200 37         39 $len = $byte1 - 0x80;
201 37         39 $total = 1;
202             }
203             elsif ($byte1 == 0x98) {
204 3         8 $len = unpack 'x C', $_;
205 3         5 $total = 2;
206             }
207             elsif ($byte1 == 0x99) {
208 0         0 $len = unpack 'x n', $_;
209 0         0 $total = 3;
210             }
211             elsif ($byte1 == 0x9a) {
212 0         0 $len = unpack 'x N', $_;
213 0         0 $total = 5;
214             }
215             elsif ($byte1 == 0x9b) {
216 0         0 $len = unpack 'x Q>', $_;
217 0         0 $total = 9;
218             }
219             elsif ($byte1 == 0x9f) {
220 8         16 return _arraystream();
221             }
222             else {
223 0         0 die "Invalid lead byte: $byte1";
224             }
225              
226             # pre-fill the array
227 40         83 my @val = (undef) x $len;
228              
229 40         42 my $cur_len;
230              
231 40         61 for my $i ( 0 .. ($len - 1) ) {
232 153         239 ($cur_len, $val[$i]) = decode( substr( $_, $total ) );
233 153         226 $total += $cur_len;
234             }
235              
236 40         113 return( $total, \@val );
237             }
238              
239             elsif ($lead3bits == _LEAD3_HASH()) {
240 16         20 my ($len, $total);
241              
242 16 100       39 if ($byte1 < 0xb8) {
    50          
    50          
    50          
    50          
    50          
243 13         15 $len = $byte1 - 0xa0;
244 13         16 $total = 1;
245             }
246             elsif ($byte1 == 0xb8) {
247 0         0 $len = unpack 'x C', $_;
248 0         0 $total = 2;
249             }
250             elsif ($byte1 == 0xb9) {
251 0         0 $len = unpack 'x n', $_;
252 0         0 $total = 3;
253             }
254             elsif ($byte1 == 0xba) {
255 0         0 $len = unpack 'x N', $_;
256 0         0 $total = 5;
257             }
258             elsif ($byte1 == 0xbb) {
259 0         0 $len = unpack 'x Q>', $_;
260 0         0 $total = 9;
261             }
262             elsif ($byte1 == 0xbf) {
263 3         7 return _hashstream();
264             }
265             else {
266 0         0 die "Invalid lead byte: $byte1";
267             }
268              
269 13         17 my %val;
270              
271             my $cur_len;
272              
273 13         23 while ( $len > 0 ) {
274 25         45 ($cur_len, my $key) = decode( substr( $_, $total ) );
275 25         35 $total += $cur_len;
276              
277 25         42 ( $cur_len, $val{$key} ) = decode( substr( $_, $total ) );
278 25         36 $total += $cur_len;
279              
280 25         39 $len--;
281             }
282              
283 13         45 return( $total, \%val );
284             }
285              
286             # tags … just ignore for now
287             elsif ($lead3bits == _LEAD3_TAG()) {
288 6         8 my $taglen;
289              
290 6 100       13 if ($byte1 < 0xd8) {
    50          
    0          
    0          
    0          
291 4         6 $taglen = 1;
292             }
293             elsif ($byte1 == 0xd8) {
294 2         3 $taglen = 2;
295             }
296             elsif ($byte1 == 0xd9) {
297 0         0 $taglen = 3;
298             }
299             elsif ($byte1 == 0xda) {
300 0         0 $taglen = 5;
301             }
302             elsif ($byte1 == 0xdb) {
303 0         0 $taglen = 9;
304             }
305             else {
306 0         0 die "Invalid lead byte: $byte1";
307             }
308              
309 6         16 my @ret = decode( substr( $_, $taglen ) );
310 6         20 return( $taglen + $ret[0], $ret[1] );
311             }
312              
313             # floats, true, false, null, undefined
314             else {
315 29 100 66     125 if ($byte1 == 0xf4) {
    100          
    100          
    100          
    100          
    50          
316 2         13 require Types::Serialiser;
317 2         6 return ( 1, Types::Serialiser::false() );
318             }
319             elsif ($byte1 == 0xf5) {
320 4         25 require Types::Serialiser;
321 4         13 return ( 1, Types::Serialiser::true() );
322             }
323             elsif ($byte1 == 0xf6 || $byte1 == 0xf7) {
324 2         37 return (1, undef);
325             }
326             elsif ($byte1 == 0xf9) {
327              
328             # Adapted from the Python code in RFC 7049 appendix D:
329 11         26 my $half = unpack 'x n', $_;
330 11         23 my $valu = (($half & 0x7fff) << 13) | (($half & 0x8000) << 16);
331              
332 11 100       20 if (($half & 0x7c00) != 0x7c00) {
333 8         68 return( unpack('f>', pack('N', $valu)) * (2**112) );
334             }
335              
336 3         23 return ( 3, unpack('f>', pack('N', $valu | 0x7f800000)) );
337             }
338             elsif ($byte1 == 0xfa) {
339 4         12 return ( 5, unpack( 'x f>', $_ ) );
340             }
341             elsif ($byte1 == 0xfb) {
342 6         20 return ( 5, unpack( 'x d>', $_ ) );
343             }
344              
345 0         0 die sprintf('can’t decode special value: %v.02x', $_);
346             }
347             }
348             }
349              
350             sub _stringstream {
351 2     2   22 my $full = q<>;
352              
353 2         5 my $i = 1;
354              
355 2         4 local $_lead3_must_be = $lead3bits;
356              
357 2         3 while (1) {
358 6 50       11 die 'Incomplete indefinite-length string!' if $i >= length();
359              
360 6 100       13 if ("\xff" eq substr( $_, $i, 1 )) {
361 2         3 $i++;
362 2         3 last;
363             }
364              
365 4         8 my ($len, $chunk) = decode( substr( $_, $i ) );
366              
367 4         8 $full .= $chunk;
368 4         5 $i += $len;
369             }
370              
371 2 100       9 utf8::decode($full) if $lead3bits == _LEAD3_UTF8STR();
372              
373 2         5 return ($i, $full);
374             }
375              
376             sub _arraystream {
377 8     8   9 my @full;
378              
379 8         9 my $i = 1;
380              
381 8         8 while (1) {
382 47 50       62 die 'Incomplete indefinite-length array!' if $i >= length();
383              
384 47 100       79 if ("\xff" eq substr( $_, $i, 1 )) {
385 8         8 $i++;
386 8         10 last;
387             }
388              
389 39         68 my ($len, $chunk) = decode( substr( $_, $i ) );
390              
391 39         55 push @full, $chunk;
392 39         43 $i += $len;
393             }
394              
395 8         18 return ($i, \@full);
396             }
397              
398             sub _hashstream {
399 3     3   5 my %full;
400              
401 3         3 my $i = 1;
402              
403 3         5 while (1) {
404 8 50       13 die 'Incomplete indefinite-length map!' if $i >= length();
405              
406 8 100       15 if ("\xff" eq substr( $_, $i, 1 )) {
407 3         5 $i++;
408 3         5 last;
409             }
410              
411 5         10 my ($len, $key) = decode( substr( $_, $i ) );
412 5         8 $i += $len;
413              
414 5 50       9 if ( "\xff" eq substr( $_, $i, 1 ) ) {
415 0         0 die "Odd number of elements in map! (Last key: “$key”)";
416             }
417              
418 5         11 ($len, $full{$key}) = decode( substr( $_, $i ) );
419 5         11 $i += $len;
420             }
421              
422 3         7 return ($i, \%full);
423             }
424              
425             1;