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