--- CBOR-XS/XS.pm 2013/10/28 21:28:14 1.9 +++ CBOR-XS/XS.pm 2016/11/25 23:37:27 1.64 @@ -28,23 +28,31 @@ =head1 DESCRIPTION -WARNING! This module is very new, and not very well tested (that's up to -you to do). Furthermore, details of the implementation might change freely -before version 1.0. And lastly, the object serialisation protocol depends -on a pending IANA assignment, and until that assignment is official, this -implementation is not interoperable with other implementations (even -future versions of this module) until the assignment is done. - -You are still invited to try out CBOR, and this module. - This module converts Perl data structures to the Concise Binary Object Representation (CBOR) and vice versa. CBOR is a fast binary serialisation -format that aims to use a superset of the JSON data model, i.e. when you -can represent something in JSON, you should be able to represent it in -CBOR. - -In short, CBOR is a faster and very compact binary alternative to JSON, -with the added ability of supporting serialisation of Perl objects. +format that aims to use an (almost) superset of the JSON data model, i.e. +when you can represent something useful in JSON, you should be able to +represent it in CBOR. + +In short, CBOR is a faster and quite compact binary alternative to JSON, +with the added ability of supporting serialisation of Perl objects. (JSON +often compresses better than CBOR though, so if you plan to compress the +data later and speed is less important you might want to compare both +formats first). + +To give you a general idea about speed, with texts in the megabyte range, +C usually encodes roughly twice as fast as L or +L and decodes about 15%-30% faster than those. The shorter the +data, the worse L performs in comparison. + +Regarding compactness, C-encoded data structures are usually +about 20% smaller than the same data encoded as (compact) JSON or +L. + +In addition to the core CBOR data format, this module implements a +number of extensions, to support cyclic and shared data structures +(see C and C), string deduplication (see +C) and scalar references (always enabled). The primary goal of this module is to be I and the secondary goal is to be I. To reach the latter goal it was written in C. @@ -58,7 +66,7 @@ use common::sense; -our $VERSION = 0.05; +our $VERSION = 1.51; our @ISA = qw(Exporter); our @EXPORT = qw(encode_cbor decode_cbor); @@ -105,7 +113,6 @@ The mutators for flags all return the CBOR object again and thus calls can be chained: -#TODO my $cbor = CBOR::XS->new->encode ({a => [1,2]}); =item $cbor = $cbor->max_depth ([$maximum_nesting_depth]) @@ -149,6 +156,209 @@ See SECURITY CONSIDERATIONS, below, for more info on why this is useful. +=item $cbor = $cbor->allow_unknown ([$enable]) + +=item $enabled = $cbor->get_allow_unknown + +If C<$enable> is true (or missing), then C will I throw an +exception when it encounters values it cannot represent in CBOR (for +example, filehandles) but instead will encode a CBOR C value. + +If C<$enable> is false (the default), then C will throw an +exception when it encounters anything it cannot encode as CBOR. + +This option does not affect C in any way, and it is recommended to +leave it off unless you know your communications partner. + +=item $cbor = $cbor->allow_sharing ([$enable]) + +=item $enabled = $cbor->get_allow_sharing + +If C<$enable> is true (or missing), then C will not double-encode +values that have been referenced before (e.g. when the same object, such +as an array, is referenced multiple times), but instead will emit a +reference to the earlier value. + +This means that such values will only be encoded once, and will not result +in a deep cloning of the value on decode, in decoders supporting the value +sharing extension. This also makes it possible to encode cyclic data +structures (which need C to be enabled to be decoded by this +module). + +It is recommended to leave it off unless you know your +communication partner supports the value sharing extensions to CBOR +(L), as without decoder support, the +resulting data structure might be unusable. + +Detecting shared values incurs a runtime overhead when values are encoded +that have a reference counter large than one, and might unnecessarily +increase the encoded size, as potentially shared values are encode as +shareable whether or not they are actually shared. + +At the moment, only targets of references can be shared (e.g. scalars, +arrays or hashes pointed to by a reference). Weirder constructs, such as +an array with multiple "copies" of the I string, which are hard but +not impossible to create in Perl, are not supported (this is the same as +with L). + +If C<$enable> is false (the default), then C will encode shared +data structures repeatedly, unsharing them in the process. Cyclic data +structures cannot be encoded in this mode. + +This option does not affect C in any way - shared values and +references will always be decoded properly if present. + +=item $cbor = $cbor->allow_cycles ([$enable]) + +=item $enabled = $cbor->get_allow_cycles + +If C<$enable> is true (or missing), then C will happily decode +self-referential (cyclic) data structures. By default these will not be +decoded, as they need manual cleanup to avoid memory leaks, so code that +isn't prepared for this will not leak memory. + +If C<$enable> is false (the default), then C will throw an error +when it encounters a self-referential/cyclic data structure. + +FUTURE DIRECTION: the motivation behind this option is to avoid I +cycles - future versions of this module might chose to decode cyclic data +structures using weak references when this option is off, instead of +throwing an error. + +This option does not affect C in any way - shared values and +references will always be encoded properly if present. + +=item $cbor = $cbor->pack_strings ([$enable]) + +=item $enabled = $cbor->get_pack_strings + +If C<$enable> is true (or missing), then C will try not to encode +the same string twice, but will instead encode a reference to the string +instead. Depending on your data format, this can save a lot of space, but +also results in a very large runtime overhead (expect encoding times to be +2-4 times as high as without). + +It is recommended to leave it off unless you know your +communications partner supports the stringref extension to CBOR +(L), as without decoder support, the +resulting data structure might not be usable. + +If C<$enable> is false (the default), then C will encode strings +the standard CBOR way. + +This option does not affect C in any way - string references will +always be decoded properly if present. + +=item $cbor = $cbor->text_keys ([$enable]) + +=item $enabled = $cbor->get_text_keys + +If C<$enabled> is true (or missing), then C will encode all +perl hash keys as CBOR text strings/UTF-8 string, upgrading them as needed. + +If C<$enable> is false (the default), then C will encode hash keys +normally - upgraded perl strings (strings internally encoded as UTF-8) as +CBOR text strings, and downgraded perl strings as CBOR byte strings. + +This option does not affect C in any way. + +This option is useful for interoperability with CBOR decoders that don't +treat byte strings as a form of text. It is especially useful as Perl +gives very little control over hash keys. + +Enabling this option can be slow, as all downgraded hash keys that are +encoded need to be scanned and converted to UTF-8. + +=item $cbor = $cbor->text_strings ([$enable]) + +=item $enabled = $cbor->get_text_strings + +This option works similar to C, above, but works on all strings +(including hash keys), so C has no further effect after +enabling C. + +If C<$enabled> is true (or missing), then C will encode all perl +strings as CBOR text strings/UTF-8 strings, upgrading them as needed. + +If C<$enable> is false (the default), then C will encode strings +normally (but see C) - upgraded perl strings (strings +internally encoded as UTF-8) as CBOR text strings, and downgraded perl +strings as CBOR byte strings. + +This option does not affect C in any way. + +This option has similar advantages and disadvantages as C. In +addition, this option effectively removes the ability to encode byte +strings, which might break some C and C methods that rely +on this, such as bignum encoding, so this option is mainly useful for very +simple data. + +=item $cbor = $cbor->validate_utf8 ([$enable]) + +=item $enabled = $cbor->get_validate_utf8 + +If C<$enable> is true (or missing), then C will validate that +elements (text strings) containing UTF-8 data in fact contain valid UTF-8 +data (instead of blindly accepting it). This validation obviously takes +extra time during decoding. + +The concept of "valid UTF-8" used is perl's concept, which is a superset +of the official UTF-8. + +If C<$enable> is false (the default), then C will blindly accept +UTF-8 data, marking them as valid UTF-8 in the resulting data structure +regardless of whether that's true or not. + +Perl isn't too happy about corrupted UTF-8 in strings, but should +generally not crash or do similarly evil things. Extensions might be not +so forgiving, so it's recommended to turn on this setting if you receive +untrusted CBOR. + +This option does not affect C in any way - strings that are +supposedly valid UTF-8 will simply be dumped into the resulting CBOR +string without checking whether that is, in fact, true or not. + +=item $cbor = $cbor->filter ([$cb->($tag, $value)]) + +=item $cb_or_undef = $cbor->get_filter + +Sets or replaces the tagged value decoding filter (when C<$cb> is +specified) or clears the filter (if no argument or C is provided). + +The filter callback is called only during decoding, when a non-enforced +tagged value has been decoded (see L for a +list of enforced tags). For specific tags, it's often better to provide a +default converter using the C<%CBOR::XS::FILTER> hash (see below). + +The first argument is the numerical tag, the second is the (decoded) value +that has been tagged. + +The filter function should return either exactly one value, which will +replace the tagged value in the decoded data structure, or no values, +which will result in default handling, which currently means the decoder +creates a C object to hold the tag and the value. + +When the filter is cleared (the default state), the default filter +function, C, is used. This function simply looks +up the tag in the C<%CBOR::XS::FILTER> hash. If an entry exists it must be +a code reference that is called with tag and value, and is responsible for +decoding the value. If no entry exists, it returns no values. + +Example: decode all tags not handled internally into C +objects, with no other special handling (useful when working with +potentially "unsafe" CBOR data). + + CBOR::XS->new->filter (sub { })->decode ($cbor_data); + +Example: provide a global filter for tag 1347375694, converting the value +into some string form. + + $CBOR::XS::FILTER{1347375694} = sub { + my ($tag, $value); + + "tag 1347375694 value $value" + }; + =item $cbor_data = $cbor->encode ($perl_scalar) Converts the given Perl data structure (a scalar value) to its CBOR @@ -174,6 +384,70 @@ =back +=head2 INCREMENTAL PARSING + +In some cases, there is the need for incremental parsing of JSON +texts. While this module always has to keep both CBOR text and resulting +Perl data structure in memory at one time, it does allow you to parse a +CBOR stream incrementally, using a similar to using "decode_prefix" to see +if a full CBOR object is available, but is much more efficient. + +It basically works by parsing as much of a CBOR string as possible - if +the CBOR data is not complete yet, the pasrer will remember where it was, +to be able to restart when more data has been accumulated. Once enough +data is available to either decode a complete CBOR value or raise an +error, a real decode will be attempted. + +A typical use case would be a network protocol that consists of sending +and receiving CBOR-encoded messages. The solution that works with CBOR and +about anything else is by prepending a length to every CBOR value, so the +receiver knows how many octets to read. More compact (and slightly slower) +would be to just send CBOR values back-to-back, as C knows where +a CBOR value ends, and doesn't need an explicit length. + +The following methods help with this: + +=over 4 + +=item @decoded = $cbor->incr_parse ($buffer) + +This method attempts to decode exactly one CBOR value from the beginning +of the given C<$buffer>. The value is removed from the C<$buffer> on +success. When C<$buffer> doesn't contain a complete value yet, it returns +nothing. Finally, when the C<$buffer> doesn't start with something +that could ever be a valid CBOR value, it raises an exception, just as +C would. In the latter case the decoder state is undefined and +must be reset before being able to parse further. + +This method modifies the C<$buffer> in place. When no CBOR value can be +decoded, the decoder stores the current string offset. On the next call, +continues decoding at the place where it stopped before. For this to make +sense, the C<$buffer> must begin with the same octets as on previous +unsuccessful calls. + +You can call this method in scalar context, in which case it either +returns a decoded value or C. This makes it impossible to +distinguish between CBOR null values (which decode to C) and an +unsuccessful decode, which is often acceptable. + +=item @decoded = $cbor->incr_parse_multiple ($buffer) + +Same as C, but attempts to decode as many CBOR values as +possible in one go, instead of at most one. Calls to C and +C can be interleaved. + +=item $cbor->incr_reset + +Resets the incremental decoder. This throws away any saved state, so that +subsequent calls to C or C start to parse +a new CBOR value from the beginning of the C<$buffer> again. + +This method can be called at any time, but it I be called if you want +to change your C<$buffer> or there was a decoding error and you want to +reuse the C<$cbor> object for future incremental parsings. + +=back + =head1 MAPPING @@ -198,7 +472,7 @@ =item byte strings -Byte strings will become octet strings in Perl (the byte values 0..255 +Byte strings will become octet strings in Perl (the Byte values 0..255 will simply become characters of the same value in Perl). =item UTF-8 strings @@ -226,24 +500,12 @@ C<1> and C<0> (for true and false) or to throw an exception on access (for error). See the L manpage for details. -=item CBOR tag 256 (perl object) - -The tag value C<256> (TODO: pending iana registration) will be used -to deserialise a Perl object serialised with C. See "OBJECT -SERIALISATION", below, for details. - -=item CBOR tag 55799 (magic header) - -The tag 55799 is ignored (this tag implements the magic header). - -=item other CBOR tags +=item tagged values -Tagged items consists of a numeric tag and another CBOR value. Tags not -handled internally are currently converted into a L -object, which is simply a blessed array reference consisting of the -numeric tag value followed by the (decoded) CBOR value. +Tagged items consists of a numeric tag and another CBOR value. -In the future, support for user-supplied conversions might get added. +See L and the description of C<< ->filter >> +for details on which tags are handled how. =item anything else @@ -256,8 +518,8 @@ =head2 PERL -> CBOR The mapping from Perl to CBOR is slightly more difficult, as Perl is a -truly typeless language, so we can only guess which CBOR type is meant by -a Perl value. +typeless language. That means this module can only guess which CBOR type +is meant by a perl value. =over 4 @@ -265,7 +527,7 @@ Perl hash references become CBOR maps. As there is no inherent ordering in hash keys (or CBOR maps), they will usually be encoded in a pseudo-random -order. +order. This order can be different each time a hash is encoded. Currently, tied hashes will use the indefinite-length format, while normal hashes will use the fixed-length format. @@ -276,15 +538,19 @@ =item other references -Other unblessed references are generally not allowed and will cause an -exception to be thrown, except for references to the integers C<0> and -C<1>, which get turned into false and true in CBOR. +Other unblessed references will be represented using +the indirection tag extension (tag value C<22098>, +L). CBOR decoders are guaranteed +to be able to decode these values somehow, by either "doing the right +thing", decoding into a generic tagged object, simply ignoring the tag, or +something else. =item CBOR::XS::Tagged objects Objects of this type must be arrays consisting of a single C<[tag, value]> -pair. The (numerical) tag will be encoded as a CBOR tag, the value will be -encoded as appropriate for the value. +pair. The (numerical) tag will be encoded as a CBOR tag, the value will +be encoded as appropriate for the value. You must use C to +create such objects. =item Types::Serialiser::true, Types::Serialiser::false, Types::Serialiser::error @@ -295,11 +561,11 @@ =item other blessed objects Other blessed objects are serialised via C or C. See -"OBJECT SERIALISATION", below, for details. +L for specific classes handled by this +module, and L for generic object serialisation. =item simple scalars -TODO Simple Perl scalars (any scalar that is not a reference) are the most difficult objects to encode: CBOR::XS will encode undefined scalars as CBOR null values, scalars that have last been used in a string context @@ -310,7 +576,7 @@ encode_cbor [-3.0e17] # yields [-3e+17] my $value = 5; encode_cbor [$value] # yields [5] - # used as string, so dump as string + # used as string, so dump as string (either byte or text) print $value; encode_cbor [$value] # yields ["5"] @@ -324,6 +590,17 @@ $x .= ""; # another, more awkward way to stringify print $x; # perl does it for you, too, quite often +You can force whether a string is encoded as byte or text string by using +C and C (if C is disabled): + + utf8::upgrade $x; # encode $x as text string + utf8::downgrade $x; # encode $x as byte string + +Perl doesn't define what operations up- and downgrade strings, so if the +difference between byte and text is important, you should up- or downgrade +your string as late as possible before encoding. You can also force the +use of CBOR text strings by using C or C. + You can force the type to be a CBOR number by numifying it: my $x = "3"; # some variable containing a string @@ -345,10 +622,16 @@ =head2 OBJECT SERIALISATION +This module implements both a CBOR-specific and the generic +L object serialisation protocol. The following +subsections explain both methods. + +=head3 ENCODING + This module knows two way to serialise a Perl object: The CBOR-specific way, and the generic way. -Whenever the encoder encounters a Perl object that it cnanot serialise +Whenever the encoder encounters a Perl object that it cannot serialise directly (most of them), it will first look up the C method on it. @@ -364,11 +647,18 @@ more). These will be encoded as CBOR perl object, together with the classname. +These methods I change the data structure that is being +serialised. Failure to comply to this can result in memory corruption - +and worse. + If an object supports neither C nor C, encoding will fail with an error. -Objects encoded via C cannot be automatically decoded, but -objects encoded via C can be decoded using the following protocol: +=head3 DECODING + +Objects encoded via C cannot (normally) be automatically decoded, +but objects encoded via C can be decoded using the following +protocol: When an encoded CBOR perl object is encountered by the decoder, it will look up the C method, by using the stored classname, and will fail @@ -378,7 +668,7 @@ as first argument, the constant string C as second argument, and all values returned by C as remaining arguments. -=head4 EXAMPLES +=head3 EXAMPLES Here is an example C method: @@ -399,7 +689,7 @@ my ($self) = @_; my $uri = "$self"; # stringify uri utf8::upgrade $uri; # make sure it will be encoded as UTF-8 string - CBOR::XS::tagged 32, "$_[0]" + CBOR::XS::tag 32, "$_[0]" } This will encode URIs as a UTF-8 string with tag 32, which indicates an @@ -420,7 +710,6 @@ sub URI::THAW { my ($class, $serialiser, $uri) = @_; - $class->new ($uri) } @@ -446,14 +735,234 @@ There is no way to distinguish CBOR from other formats programmatically. To make it easier to distinguish CBOR from other formats, the CBOR specification has a special "magic string" that can be -prepended to any CBOR string without changing it's meaning. +prepended to any CBOR string without changing its meaning. This string is available as C<$CBOR::XS::MAGIC>. This module does not -prepend this string tot he CBOR data it generates, but it will ignroe it +prepend this string to the CBOR data it generates, but it will ignore it if present, so users can prepend this string as a "file type" indicator as required. +=head1 THE CBOR::XS::Tagged CLASS + +CBOR has the concept of tagged values - any CBOR value can be tagged with +a numeric 64 bit number, which are centrally administered. + +C handles a few tags internally when en- or decoding. You can +also create tags yourself by encoding C objects, and the +decoder will create C objects itself when it hits an +unknown tag. + +These objects are simply blessed array references - the first member of +the array being the numerical tag, the second being the value. + +You can interact with C objects in the following ways: + +=over 4 + +=item $tagged = CBOR::XS::tag $tag, $value + +This function(!) creates a new C object using the given +C<$tag> (0..2**64-1) to tag the given C<$value> (which can be any Perl +value that can be encoded in CBOR, including serialisable Perl objects and +C objects). + +=item $tagged->[0] + +=item $tagged->[0] = $new_tag + +=item $tag = $tagged->tag + +=item $new_tag = $tagged->tag ($new_tag) + +Access/mutate the tag. + +=item $tagged->[1] + +=item $tagged->[1] = $new_value + +=item $value = $tagged->value + +=item $new_value = $tagged->value ($new_value) + +Access/mutate the tagged value. + +=back + +=cut + +sub tag($$) { + bless [@_], CBOR::XS::Tagged::; +} + +sub CBOR::XS::Tagged::tag { + $_[0][0] = $_[1] if $#_; + $_[0][0] +} + +sub CBOR::XS::Tagged::value { + $_[0][1] = $_[1] if $#_; + $_[0][1] +} + +=head2 EXAMPLES + +Here are some examples of C uses to tag objects. + +You can look up CBOR tag value and emanings in the IANA registry at +L. + +Prepend a magic header (C<$CBOR::XS::MAGIC>): + + my $cbor = encode_cbor CBOR::XS::tag 55799, $value; + # same as: + my $cbor = $CBOR::XS::MAGIC . encode_cbor $value; + +Serialise some URIs and a regex in an array: + + my $cbor = encode_cbor [ + (CBOR::XS::tag 32, "http://www.nethype.de/"), + (CBOR::XS::tag 32, "http://software.schmorp.de/"), + (CBOR::XS::tag 35, "^[Pp][Ee][Rr][lL]\$"), + ]; + +Wrap CBOR data in CBOR: + + my $cbor_cbor = encode_cbor + CBOR::XS::tag 24, + encode_cbor [1, 2, 3]; + +=head1 TAG HANDLING AND EXTENSIONS + +This section describes how this module handles specific tagged values +and extensions. If a tag is not mentioned here and no additional filters +are provided for it, then the default handling applies (creating a +CBOR::XS::Tagged object on decoding, and only encoding the tag when +explicitly requested). + +Tags not handled specifically are currently converted into a +L object, which is simply a blessed array reference +consisting of the numeric tag value followed by the (decoded) CBOR value. + +Future versions of this module reserve the right to special case +additional tags (such as base64url). + +=head2 ENFORCED TAGS + +These tags are always handled when decoding, and their handling cannot be +overridden by the user. + +=over 4 + +=item 26 (perl-object, L) + +These tags are automatically created (and decoded) for serialisable +objects using the C methods (the L object +serialisation protocol). See L for details. + +=item 28, 29 (shareable, sharedref, L) + +These tags are automatically decoded when encountered (and they do not +result in a cyclic data structure, see C), resulting in +shared values in the decoded object. They are only encoded, however, when +C is enabled. + +Not all shared values can be successfully decoded: values that reference +themselves will I decode as C (this is not the same +as a reference pointing to itself, which will be represented as a value +that contains an indirect reference to itself - these will be decoded +properly). + +Note that considerably more shared value data structures can be decoded +than will be encoded - currently, only values pointed to by references +will be shared, others will not. While non-reference shared values can be +generated in Perl with some effort, they were considered too unimportant +to be supported in the encoder. The decoder, however, will decode these +values as shared values. + +=item 256, 25 (stringref-namespace, stringref, L) + +These tags are automatically decoded when encountered. They are only +encoded, however, when C is enabled. + +=item 22098 (indirection, L) + +This tag is automatically generated when a reference are encountered (with +the exception of hash and array references). It is converted to a reference +when decoding. + +=item 55799 (self-describe CBOR, RFC 7049) + +This value is not generated on encoding (unless explicitly requested by +the user), and is simply ignored when decoding. + +=back + +=head2 NON-ENFORCED TAGS + +These tags have default filters provided when decoding. Their handling can +be overridden by changing the C<%CBOR::XS::FILTER> entry for the tag, or by +providing a custom C callback when decoding. + +When they result in decoding into a specific Perl class, the module +usually provides a corresponding C method as well. + +When any of these need to load additional modules that are not part of the +perl core distribution (e.g. L), it is (currently) up to the user to +provide these modules. The decoding usually fails with an exception if the +required module cannot be loaded. + +=over 4 + +=item 0, 1 (date/time string, seconds since the epoch) + +These tags are decoded into L objects. The corresponding +C method always encodes into tag 1 values currently. + +The L API is generally surprisingly bad, and fractional +seconds are only accidentally kept intact, so watch out. On the plus side, +the module comes with perl since 5.10, which has to count for something. + +=item 2, 3 (positive/negative bignum) + +These tags are decoded into L objects. The corresponding +C method encodes "small" bigints into normal CBOR +integers, and others into positive/negative CBOR bignums. + +=item 4, 5, 264, 265 (decimal fraction/bigfloat) + +Both decimal fractions and bigfloats are decoded into L +objects. The corresponding C method I +encodes into a decimal fraction (either tag 4 or 264). + +NaN and infinities are not encoded properly, as they cannot be represented +in CBOR. + +See L for more info. + +=item 30 (rational numbers) + +These tags are decoded into L objects. The corresponding +C method encodes rational numbers with denominator +C<1> via their numerator only, i.e., they become normal integers or +C. + +See L for more info. + +=item 21, 22, 23 (expected later JSON conversion) + +CBOR::XS is not a CBOR-to-JSON converter, and will simply ignore these +tags. + +=item 32 (URI) + +These objects decode into L objects. The corresponding +C method again results in a CBOR URI value. + +=back + +=cut + =head1 CBOR and JSON CBOR is supposed to implement a superset of the JSON data model, and is, @@ -478,7 +987,12 @@ any buffer overflows. Obviously, this module should ensure that and I am trying hard on making that true, but you never know. -Second, you need to avoid resource-starving attacks. That means you should +Second, CBOR::XS supports object serialisation - decoding CBOR can cause +calls to I C method in I package that exists in your +process (that is, CBOR::XS will not try to load modules, but any existing +C method or function can be called, so they all have to be secure). + +Third, you need to avoid resource-starving attacks. That means you should limit the size of CBOR data you accept, or make sure then when your resources run out, that's just fine (e.g. by using a separate process that can crash safely). The size of a CBOR string in octets is usually a good @@ -487,7 +1001,7 @@ too late when you already have it in memory, so you might want to check the size before you accept the string. -Third, CBOR::XS recurses using the C stack when decoding objects and +Fourth, CBOR::XS recurses using the C stack when decoding objects and arrays. The C stack is a limited resource: for instance, on my amd64 machine with 8MB of stack size I can decode around 180k nested arrays but only 14k nested CBOR objects (due to perl itself recursing deeply on croak @@ -504,6 +1018,39 @@ information you might want to make sure that exceptions thrown by CBOR::XS will not end up in front of untrusted eyes. + +=head1 BIGNUM SECURITY CONSIDERATIONS + +CBOR::XS provides a C method for both L and +L that tries to encode the number in the simplest possible +way, that is, either a CBOR integer, a CBOR bigint/decimal fraction (tag +4) or an arbitrary-exponent decimal fraction (tag 264). Rational numbers +(L, tag 30) can also contain bignums as members. + +CBOR::XS will also understand base-2 bigfloat or arbitrary-exponent +bigfloats (tags 5 and 265), but it will never generate these on its own. + +Using the built-in L support, encoding and decoding +decimal fractions is generally fast. Decoding bigints can be slow for very +big numbers (tens of thousands of digits, something that could potentially +be caught by limiting the size of CBOR texts), and decoding bigfloats or +arbitrary-exponent bigfloats can be I slow (minutes, decades) +for large exponents (roughly 40 bit and longer). + +Additionally, L can take advantage of other bignum +libraries, such as L, which cannot handle big floats with large +exponents, and might simply abort or crash your program, due to their code +quality. + +This can be a concern if you want to parse untrusted CBOR. If it is, you +might want to disable decoding of tag 2 (bigint) and 3 (negative bigint) +types. You should also disable types 5 and 265, as these can be slow even +without bigints. + +Disabling bigints will also partially or fully disable types that rely on +them, e.g. rational numbers that use bignums. + + =head1 CBOR IMPLEMENTATION NOTES This section contains some random implementation notes. They do not @@ -523,6 +1070,16 @@ Strict mode and canonical mode are not implemented. +=head1 LIMITATIONS ON PERLS WITHOUT 64-BIT INTEGER SUPPORT + +On perls that were built without 64 bit integer support (these are rare +nowadays, even on 32 bit architectures, as all major Perl distributions +are built with 64 bit integer support), support for any kind of 64 bit +integer in CBOR is very limited - most likely, these 64 bit values will +be truncated, corrupted, or otherwise not decoded correctly. This also +includes string, array and map sizes that are stored as 64 bit integers. + + =head1 THREADS This module is I guaranteed to be thread safe and there are no @@ -544,6 +1101,134 @@ =cut +# clumsy hv_store-in-perl +sub _hv_store { + $_[0]{$_[1]} = $_[2]; +} + +our %FILTER = ( + 0 => sub { # rfc4287 datetime, utf-8 + require Time::Piece; + # Time::Piece::Strptime uses the "incredibly flexible date parsing routine" + # from FreeBSD, which can't parse ISO 8601, RFC3339, RFC4287 or much of anything + # else either. Whats incredibe over standard strptime totally escapes me. + # doesn't do fractional times, either. sigh. + # In fact, it's all a lie, it uses whatever strptime it wants, and of course, + # they are all incompatible. The openbsd one simply ignores %z (but according to the + # docs, it would be much more incredibly flexible indeed. If it worked, that is.). + scalar eval { + my $s = $_[1]; + + $s =~ s/Z$/+00:00/; + $s =~ s/(\.[0-9]+)?([+-][0-9][0-9]):([0-9][0-9])$// + or die; + + my $b = $1 - ($2 * 60 + $3) * 60; # fractional part + offset. hopefully + my $d = Time::Piece->strptime ($s, "%Y-%m-%dT%H:%M:%S"); + + Time::Piece::gmtime ($d->epoch + $b) + } || die "corrupted CBOR date/time string ($_[0])"; + }, + + 1 => sub { # seconds since the epoch, possibly fractional + require Time::Piece; + scalar Time::Piece::gmtime (pop) + }, + + 2 => sub { # pos bigint + require Math::BigInt; + Math::BigInt->new ("0x" . unpack "H*", pop) + }, + + 3 => sub { # neg bigint + require Math::BigInt; + -Math::BigInt->new ("0x" . unpack "H*", pop) + }, + + 4 => sub { # decimal fraction, array + require Math::BigFloat; + Math::BigFloat->new ($_[1][1] . "E" . $_[1][0]) + }, + + 264 => sub { # decimal fraction with arbitrary exponent + require Math::BigFloat; + Math::BigFloat->new ($_[1][1] . "E" . $_[1][0]) + }, + + 5 => sub { # bigfloat, array + require Math::BigFloat; + scalar Math::BigFloat->new ($_[1][1]) * Math::BigFloat->new (2)->bpow ($_[1][0]) + }, + + 265 => sub { # bigfloat with arbitrary exponent + require Math::BigFloat; + scalar Math::BigFloat->new ($_[1][1]) * Math::BigFloat->new (2)->bpow ($_[1][0]) + }, + + 30 => sub { # rational number + require Math::BigRat; + Math::BigRat->new ("$_[1][0]/$_[1][1]") # separate parameters only work in recent versons + }, + + 21 => sub { pop }, # expected conversion to base64url encoding + 22 => sub { pop }, # expected conversion to base64 encoding + 23 => sub { pop }, # expected conversion to base16 encoding + + # 24 # embedded cbor, byte string + + 32 => sub { + require URI; + URI->new (pop) + }, + + # 33 # base64url rfc4648, utf-8 + # 34 # base64 rfc46484, utf-8 + # 35 # regex pcre/ecma262, utf-8 + # 36 # mime message rfc2045, utf-8 +); + +sub CBOR::XS::default_filter { + &{ $FILTER{$_[0]} or return } +} + +sub URI::TO_CBOR { + my $uri = $_[0]->as_string; + utf8::upgrade $uri; + tag 32, $uri +} + +sub Math::BigInt::TO_CBOR { + if (-2147483648 <= $_[0] && $_[0] <= 2147483647) { + $_[0]->numify + } else { + my $hex = substr $_[0]->as_hex, 2; + $hex = "0$hex" if 1 & length $hex; # sigh + tag $_[0] >= 0 ? 2 : 3, pack "H*", $hex + } +} + +sub Math::BigFloat::TO_CBOR { + my ($m, $e) = $_[0]->parts; + + -9223372036854775808 <= $e && $e <= 18446744073709551615 + ? tag 4, [$e->numify, $m] + : tag 264, [$e, $m] +} + +sub Math::BigRat::TO_CBOR { + my ($n, $d) = $_[0]->parts; + + # older versions of BigRat need *1, as they not always return numbers + + $d*1 == 1 + ? $n*1 + : tag 30, [$n*1, $d*1] +} + +sub Time::Piece::TO_CBOR { + tag 1, 0 + $_[0]->epoch +} + XSLoader::load "CBOR::XS", $VERSION; =head1 SEE ALSO