File Coverage

blib/lib/CBOR/PP/Encode.pm
Criterion Covered Total %
statement 55 71 77.4
branch 39 68 57.3
condition 8 9 88.8
subroutine 7 7 100.0
pod 2 2 100.0
total 111 157 70.7


line stmt bran cond sub pod time code
1             package CBOR::PP::Encode;
2              
3 6     6   42 use strict;
  6         13  
  6         162  
4 6     6   30 use warnings;
  6         12  
  6         197  
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 L encoder
19             in pure Perl.
20              
21             =head1 MAPPING PERL TO CBOR
22              
23             =over
24              
25             =item * Scalars that look like unsigned integers are encoded as such.
26             UTF-8 strings and strings that fit 7-bit ASCII (including floats and
27             negatives) are encoded as text. Any other scalar is encoded as binary.
28              
29             Note that there is no “right way” to determine whether an arbitrary
30             Perl (non-reference) scalar should be encoded as a string or as a number.
31             The above seems a reasonable enough approach.
32              
33             =item * UTF8-flagged strings are encoded as text; others are encoded as
34             binary. This is a “best-guess” merely; Perl’s UTF8 flag doesn’t reliably
35             indicate whether a given string is a text or a byte string.
36              
37             =item * undef, Types::Serialiser::true(), and Types::Serialiser::false()
38             are encoded as null, true, and false, respectively.
39              
40             =item * There is no support for streamed (i.e., indefinite-length)
41             objects.
42              
43             =item * There is no Perl value that maps to CBOR’s undefined value.
44              
45             =back
46              
47             =head1 TODO
48              
49             =over
50              
51             =item * Add canonicalization support.
52              
53             =item * Optimize as may be feasible.
54              
55             =back
56              
57             =head1 AUTHOR
58              
59             L (FELIPE)
60              
61             =head1 LICENSE
62              
63             This code is licensed under the same license as Perl itself.
64              
65             =cut
66              
67             #----------------------------------------------------------------------
68              
69 6     6   2265 use CBOR::PP::X;
  6         18  
  6         178  
70 6     6   2165 use CBOR::PP::Tagged;
  6         14  
  6         466  
71              
72             #----------------------------------------------------------------------
73              
74             =head1 FUNCTIONS
75              
76             =head2 $obj = tag( $NUMBER, $VALUE )
77              
78             Returns an object that represents a value and its CBOR tag number.
79             For example, to encode a date/time string, you could do:
80              
81             my $tagged = tag(0, '2013-03-21T20:04:00Z')
82              
83             C recognizes objects that this function returns and
84             turns them into tagged CBOR values.
85              
86             =cut
87              
88             sub tag {
89 1     1 1 603 return CBOR::PP::Tagged->new(@_);
90             }
91              
92             #----------------------------------------------------------------------
93              
94             =head1 METHODS
95              
96             =head2 $cbor = encode( $VALUE, \%OPTS )
97              
98             Returns a CBOR string that represents the passed $VALUE.
99              
100             For now this is only called as a static method but may eventually
101             be an instance method as well, for example, to define options like
102             canonicalization.
103              
104             =cut
105              
106             my ($numkeys);
107              
108             our $_depth = 0;
109              
110             # Avoid tripping Perl’s warning:
111 6     6   42 use constant _MAX_RECURSION => 98;
  6         22  
  6         5119  
112              
113             sub encode {
114              
115             # There’s a lot of ugliness in here for the sake of speed.
116             # For example, ideally each major type would have its own function,
117             # but we realize significant savings by putting everything into
118             # one big function.
119              
120 479     479 1 42384 local $_depth = $_depth + 1;
121 479 100       880 die CBOR::PP::X->create('Recursion', sprintf("Refuse to encode() more than %d times at once!", _MAX_RECURSION())) if $_depth > _MAX_RECURSION();
122              
123 477         773 for ($_[0]) {
124 477 100       1056 if (!ref) {
    100          
    100          
    100          
    50          
125              
126             # undef => null
127 230 100       408 return "\xf6" if !defined;
128              
129             # empty string
130 228 50       462 return utf8::is_utf8($_) ? "\x60" : "\x40" if !length;
    100          
131              
132             # unsigned int
133 225 100 100     905 if (!$_ || (!tr<0-9><>c && 0 != rindex($_, 0, 0))) {
      100        
134 158 100       593 return chr $_ if ($_ < 24);
135              
136 24 100       113 return pack('CC', 0x18, $_) if $_ < 0x100;
137              
138 12 100       39 return pack('Cn', 0x19, $_) if ($_ < 0x10000);
139              
140 10 100       42 return pack('CN', 0x1a, $_) if ($_ <= 0xffffffff);
141              
142 6         46 return pack('C Q>', 0x1b, $_);
143             }
144              
145             # negative int
146             # elsif ( 0 == rindex($_, '-', 0) && (substr($_, 1) !~ tr<0-9><>c) ) {
147             # return chr( 0x20 - $_ ) if ($_ > -25);
148             #
149             # return pack( 'CC', 0x38, -$_ ) if $_ >= -0x100;
150             #
151             # return pack( 'Cv', 0x39, -$_ ) if $_ >= -0x10000;
152             #
153             # return pack( 'CV', 0x3a, -$_ ) if $_ >= -0x100000000;
154             #
155             # return pack( 'C Q>', 0x3b, -$_ );
156             # }
157              
158 67 100       162 if (utf8::is_utf8($_)) {
159              
160             # Perl doesn’t seem to have a way to pack() a
161             # a UTF-8 string directly to bytes???
162 6         20 utf8::encode(my $bytes = $_);
163              
164 6 50       58 return pack('Ca*', 0x60 + length($bytes), $bytes) if (length() < 24);
165              
166 0 0       0 return pack('CCa*', 0x78, length($bytes), $bytes) if (length() < 0x100);
167              
168 0 0       0 return pack('Cna*', 0x79, length($bytes), $bytes) if (length() < 0x10000);
169              
170 0 0       0 return pack('CNa*', 0x7a, length($bytes), $bytes) if (length() <= 0xffffffff);
171              
172 0         0 return pack('C Q> a*', 0x7b, length($bytes), $bytes);
173             }
174             else {
175 61 50       380 return pack('Ca*', 0x40 + length, $_) if (length() < 24);
176              
177 0 0       0 return pack('CCa*', 0x58, length, $_) if (length() < 0x100);
178              
179 0 0       0 return pack('Cna*', 0x59, length, $_) if (length() < 0x10000);
180              
181 0 0       0 return pack('CNa*', 0x5a, length, $_) if (length() <= 0xffffffff);
182              
183 0         0 return pack('C Q> a*', 0x5b, length, $_);
184             }
185             }
186             elsif (ref eq 'ARRAY') {
187 227         271 my $hdr;
188              
189 227 100       364 if (@$_ < 24) {
    50          
    0          
    0          
190 224         362 $hdr = chr( 0x80 + @$_ );
191             }
192             elsif (@$_ < 0x100) {
193 3         13 $hdr = pack( 'CC', 0x98, 0 + @$_ );
194             }
195             elsif (@$_ < 0x10000) {
196 0         0 $hdr = pack( 'Cn', 0x99, 0 + @$_ );
197             }
198             elsif (@$_ <= 0xffffffff) {
199 0         0 $hdr = pack( 'CN', 0x9a, 0 + @$_ );
200             }
201             else {
202 0         0 $hdr = pack( 'C Q>', 0x9b, 0 + @$_ );
203             }
204              
205 227         414 return join( q<>, $hdr, map { encode($_, $_[1]) } @$_ );
  328         1463  
206             }
207             elsif (ref eq 'HASH') {
208 14         26 my $hdr;
209              
210 14         31 $numkeys = keys %$_;
211              
212 14 50       26 if ($numkeys < 24) {
    0          
    0          
    0          
213 14         30 $hdr = chr( 0xa0 + $numkeys );
214             }
215             elsif ($numkeys < 0x100) {
216 0         0 $hdr = pack( 'CC', 0xb8, $numkeys );
217             }
218             elsif ($numkeys < 0x10000) {
219 0         0 $hdr = pack( 'Cn', 0xb9, $numkeys );
220             }
221             elsif ($numkeys <= 0xffffffff) {
222 0         0 $hdr = pack( 'CN', 0xba, $numkeys );
223             }
224             else {
225 0         0 $hdr = pack( 'C Q>', 0xbb, $numkeys );
226             }
227              
228 14 100 66     42 if ($_[1] && $_[1]->{'canonical'}) {
229 2         3 my $hr = $_;
230              
231 2 50       11 my @keys = sort { (length($a) <=> length($b)) || ($a cmp $b) } keys %$_;
  8         23  
232 2         4 return join( q<>, $hdr, map { encode($_), encode($hr->{$_}, $_[1]) } @keys );
  8         14  
233             }
234             else {
235 12         49 return join( q<>, $hdr, map { encode($_, $_[1]) } %$_ );
  34         90  
236             }
237             }
238             elsif (ref()->isa('JSON::PP::Boolean')) {
239 5 100       52 return $_ ? "\xf5" : "\xf4";
240             }
241             elsif (ref()->isa('CBOR::PP::Tagged')) {
242 1         13 my $numstr = encode( $_->[0] );
243              
244 1         6 substr($numstr, 0, 1) &= "\x1f"; # zero out the first three bits
245 1         4 substr($numstr, 0, 1) |= "\xc0"; # now assign the first three
246              
247 1         5 return( $numstr . encode( $_->[1], $_[1] ) );
248             }
249              
250 0           die "Can’t encode “$_” as CBOR!";
251             }
252             }
253              
254             1;