--- CBOR-XS/XS.pm 2013/10/27 22:35:15 1.7 +++ CBOR-XS/XS.pm 2014/10/25 06:36:34 1.42 @@ -28,18 +28,31 @@ =head1 DESCRIPTION -WARNING! THIS IS A PRE-ALPHA RELEASE! IT WILL CRASH, CORRUPT YOUR DATA -AND EAT YOUR CHILDREN! (Actually, apart from being untested and a bit -feature-limited, it might already be useful). - 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. - -This makes it a faster and more compact binary alternative to JSON, with -the added ability of supporting serialising 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. @@ -53,7 +66,7 @@ use common::sense; -our $VERSION = 0.03; +our $VERSION = 1.26; our @ISA = qw(Exporter); our @EXPORT = qw(encode_cbor decode_cbor); @@ -100,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]) @@ -144,6 +156,165 @@ 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 ne 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->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 thats 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 @@ -169,6 +340,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 caled 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 @@ -193,7 +428,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 @@ -221,24 +456,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) +=item tagged values -The tag 55799 is ignored (this tag implements the magic header). +Tagged items consists of a numeric tag and another CBOR value. -=item other CBOR tags - -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. - -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 @@ -251,8 +474,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 @@ -260,7 +483,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 hahs is encoded. Currently, tied hashes will use the indefinite-length format, while normal hashes will use the fixed-length format. @@ -271,15 +494,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 @@ -290,11 +517,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 @@ -305,7 +532,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"] @@ -319,6 +546,16 @@ $x .= ""; # another, more awkward way to stringify print $x; # perl does it for you, too, quite often +You can force whether a string ie encoded as byte or text string by using +C and C): + + 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 force the type to be a CBOR number by numifying it: my $x = "3"; # some variable containing a string @@ -340,10 +577,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. @@ -359,11 +602,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 @@ -373,7 +623,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: @@ -394,7 +644,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 @@ -441,14 +691,267 @@ 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 +overriden 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 refernces). 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 overriden 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 (decimal fraction/bigfloat) + +Both decimal fractions and bigfloats are decoded into L +objects. The corresponding C method I +encodes into a decimal fraction. + +CBOR cannot represent bigfloats with I large exponents - conversion +of such big float objects is undefined. + +Also, NaN and infinities are not encoded properly. + +=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 + +our %FILTER = ( + # 0 # rfc4287 datetime, utf-8 + # 1 # unix timestamp, any + + 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]) + }, + + 5 => sub { # bigfloat, array + require Math::BigFloat; + scalar Math::BigFloat->new ($_[1][1])->blsft ($_[1][0], 2) + }, + + 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 +); + + =head1 CBOR and JSON CBOR is supposed to implement a superset of the JSON data model, and is, @@ -518,6 +1021,15 @@ 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), 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 @@ -539,6 +1051,101 @@ =cut +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 incomptible. 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]) + }, + + 5 => sub { # bigfloat, array + require Math::BigFloat; + scalar Math::BigFloat->new ($_[1][1])->blsft ($_[1][0], 2) + }, + + 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 ($_[0] >= -2147483648 && $_[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; + tag 4, [$e->numify, $m] +} + +sub Time::Piece::TO_CBOR { + tag 1, 0 + $_[0]->epoch +} + XSLoader::load "CBOR::XS", $VERSION; =head1 SEE ALSO