Sereal::Decoder(3pm)

🌈 NAME 🌈

okay so this is the Sereal::Decoder(3pm) page, straight outta the User Contributed Perl Documentation, and it's about fast, compact, powerful binary deserialization. that's it, that's the name, slay πŸ’…


✨ SYNOPSIS ✨

bestie here's the whole vibe in code form, look:

use Sereal::Decoder
  qw(decode_sereal sereal_decode_with_object scalar_looks_like_sereal);

my $decoder = Sereal::Decoder->new({...options...});

my $structure;
$decoder->decode($blob, $structure); # deserializes into $structure

# or if you don't have references to the top level structure, this works, too:
$structure = $decoder->decode($blob);

# alternatively functional interface: (See Sereal::Performance)
sereal_decode_with_object($decoder, $blob, $structure);
$structure = sereal_decode_with_object($decoder, $blob);

# much slower functional interface with no persistent objects:
decode_sereal($blob, {... options ...}, $structure);
$structure = decode_sereal($blob, {... options ...});

# Not a full validation, but just a quick check for a reasonable header:
my $is_likely_sereal = scalar_looks_like_sereal($some_string);
# or:
$is_likely_sereal = $decoder->looks_like_sereal($some_string);

🌸 DESCRIPTION 🌸

so this library is a deserializer for something called Sereal β€” an efficient, compact-output, feature-rich binary protocol. its sister module Sereal::Encoder does the encoding half of the equation. they get released separately on purpose, so each one can be upgraded independently and more safely. bestie behavior honestly 🫢

the Sereal protocol versions this decoder plays nice with? currently protocol versions 1, 2, 3 and 4. and listen β€” as it stands, it will refuse to even attempt to decode future versions of the protocol. but if it ever becomes necessary, there's likely gonna be an option to decode the parts of the input that are compatible with version 4 of the protocol. that's literally why the protocol was designed that way. foresight? iconic.

the protocol spec and a bunch of other docs live in the github repository. right now the spec is at https://github.com/Sereal/Sereal/blob/master/sereal_spec.pod, there's a whole discussion of the design objectives at https://github.com/Sereal/Sereal/blob/master/README.pod, and you can see all our benchmark output at https://github.com/Sereal/Sereal/wiki/Sereal-Comparison-Graphs.


πŸ‘‘ CLASS METHODS πŸ‘‘

πŸ’« new πŸ’«

constructor! it optionally takes a hash reference as its first parameter. that hash ref can carry any number of options that influence the behaviour of the encoder.

rn the options it recognizes are the ones below, and none of them are on by default. not a single one. woke up and chose defaults, every time ✨

refuse_snappy

if you set this, the decoder will refuse Snappy-compressed input data. this can be desirable for robustness. peep the section "ROBUSTNESS" below.

refuse_objects

if set, the decoder will refuse deserializing any objects in the input stream and will throw an exception instead. defaults to off. more deets in "ROBUSTNESS" below.

no_bless_objects

if set, the decoder will deserialize any objects in the input stream but without blessing them. defaults to off. see "ROBUSTNESS" below.

no_thaw_objects

if set, the decoder will deserialize frozen objects in the objects stream as an array ref of arguments that would be passed into the THAW subroutine, instead of calling THAW itself.

validate_utf8

if set, the decoder will refuse invalid UTF-8 byte sequences. this one is off by default, but it's strongly encouraged to turn it on if you're dealing with any data that's been encoded by an external source (e.g. http cookies). protect yourself, babe πŸ”₯

max_recursion_depth

Sereal::Decoder is recursive. like, deeply. if you hand it a Sereal document that's nested way down, it will eventually exhaust the C stack. so there's a limit on the depth of recursion that's accepted, and it defaults to 10000 nested calls. you can override that value with the max_recursion_depth option. but beware β€” setting it too high can cause hard crashes. 😭

also do note that this setting is somewhat approximate. setting it to 10000 may break somewhere between 9997 and 10003 nested structures, depending on their types.

max_num_hash_entries

if set to a non-zero value (default: 0), then Sereal::Decoder will refuse to deserialize any hash/dictionary (or hash-based object) with more than that number of entries. the whole point is being able to respond quickly to any future hash-collision attacks on Perl's hash function, and also the memory exhaustion attacks on Sereal itself. for a gentle intro to the topic from the cryptographic point of view, see http://en.wikipedia.org/wiki/Collision_attack.

max_num_array_entries

if set to a non-zero value (default: 0), then Sereal::Decoder will refuse to deserialize any array with more than that number of entries. again: so it can respond quickly to any future memory exhaustion attacks on Sereal.

max_string_length

if set to a non-zero value (default: 0), then Sereal::Decoder will refuse to deserialize any string with more than that number of characters. same energy β€” quick response to any future memory exhaustion attacks on Sereal.

max_uncompressed_size

if set to a non-zero value (default: 0), then Sereal::Decoder will refuse to deserialize any blob with a size that exceeds the value when uncompressed. yep, you already know: quick response to any future memory exhaustion attacks on Sereal.

incremental

if set to a non-zero value (default: 0), then Sereal::Decoder will destructively parse Sereal documents out of a variable. every time a Sereal document is successfully parsed, it gets removed from the front of the string it was parsed from. wiggly magic, tbh πŸ¦‹

this means you can do this:

while (length $buffer) {
    my $data= decode_sereal($buffer,{incremental=>1});
}

alias_smallint

if set to a true value then Sereal::Decoder will share integers from -16 to 15 (encoded as either SRL_HDR_NEG and SRL_HDR_POS) as read-only aliases to a common SV.

the result of this may be significant space savings in data structures with many integers in the specified range. the cost is more memory used by the decoder and a very modest speed penalty when deserializing.

note this option changes the structure of the dumped data. use with caution.

see also the alias_varint_under option.

alias_varint_under

if set to a true positive integer smaller than 16 then this option is similar to setting alias_smallint and causes all integers from -16 to 15 to be shared as read-only aliases to the same SV β€” except that this treatment ALSO applies to SRL_HDR_VARINT. if set to a value larger than 16 then this applies to all varints under the value set. (in general SRL_HDR_VARINT is used only for integers larger than 15, and SRL_HDR_NEG and SRL_HDR_POS are used for -16 to -1 and 0 to 15 respectively.)

in simple terms: if you wanna share values larger than 16 then use this option, and if you wanna share only values in the -16 to 15 range then use the alias_smallint option instead.

the result of this may be significant space savings in data structures with many integers in the desire range. the cost is more memory used by the decoder and a very modest speed penalty when deserializing.

note this option changes the structure of the dumped data. use with caution.

use_undef

if set to a true value then this any undef value to be deserialized as PL_sv_undef. this may change the structure of the data structure being dumped, so do not enable this unless you know what you are doing. periodt.

set_readonly

if set to a true value then the output will be completely readonly (deeply). πŸŽ€

set_readonly_scalars

if set to a true value then scalars in the output will be readonly (deeply). references won't be readonly.

πŸ’– INSTANCE METHODS πŸ’–

okay so these are the methods you call on an actual decoder object, not the class itself. main character energy only. let's go. πŸ’…


πŸŽ€ decode πŸŽ€

bestie, you hand decode a byte string of Sereal data, and it deserializes that data structure for you. you can get the result in one of two ways: decode accepts a second parameter, which is a scalar to write the result to, AND decode will return the resulting data structure. yes, both. the way that it does the most.

the two are subtly different in case of data structures that contain references to the root element. in that case, the return value will be a (non-recursive) copy of the reference. the pass-in style is more correct. in other words,

$decoder->decode($sereal_string, my $out);
# is almost the same but safer than:
my $out = $decoder->decode($sereal_string);

this is an unfortunate side-effect of perl's standard copy semantics of assignment. possibly one day we will have an alternative to this. πŸ₯²


πŸ¦‹ decode_with_header πŸ¦‹

given a byte string of Sereal data, the decode_with_header call deserializes that data structure as decode would do, however it also decodes the optional user data structure that can be embedded into a Sereal document, inside the header (see Sereal::Encoder::encode). it's giving overachiever.

it accepts an optional second parameter, which is a scalar to write the body to, and an optional third parameter, which is a scalar to write the header to.

regardless of the number of parameters received, decode_with_header returns an ArrayRef containing the deserialized header, and the deserialized body, in this order. header first, body second. no cap.

see "decode" for the subtle difference between the one, two and three parameters versions.

if there is no header in a Sereal document, corresponding variable or return value will be set to undef.


🌸 decode_only_header 🌸

given a byte string of Sereal data, the decode_only_header deserializes only the optional user data structure that can be embedded into a Sereal document, inside the header (see Sereal::Encoder::encode). it said "i'm just here for the header, not the body." iconic.

it accepts an optional second parameter, which is a scalar to write the header to.

regardless of the number of parameters received, decode_only_header returns the resulting data structure.

see "decode" for the subtle difference between the one and two parameters versions.

if there is no header in a Sereal document, corresponding variable or return value will be set to undef.


πŸ’œ decode_with_offset πŸ’œ

same as the decode method, except as second parameter, you must pass an integer offset into the input string, at which the decoding is to start. the optional "pass-in" style scalar (see "decode" above) is relegated to being the third parameter. it got demoted, but it's fine. we still love it.


🧚 decode_only_header_with_offset 🧚

same as the decode_only_header method, except as second parameter, you must pass an integer offset into the input string, at which the decoding is to start. the optional "pass-in" style scalar (see decode_only_header above) is relegated to being the third parameter. same energy, different method name.


πŸ”₯ decode_with_header_and_offset πŸ”₯

same as the decode_with_header method, except as second parameter, you must pass an integer offset into the input string, at which the decoding is to start. the optional "pass-in" style scalars (see decode_with_header above) are relegated to being the third and fourth parameters. they're just chilling in positions three and four now.


πŸ‘‘ bytes_consumed πŸ‘‘

after using the various decode methods documented previously, bytes_consumed can return the number of bytes from the body of the input string that were actually consumed by the decoder. that is, if you append random garbage to a valid Sereal document, decode will happily decode the data and ignore the garbage. if that is an error in your use case, you can use bytes_consumed to catch it.

my $out = $decoder->decode($sereal_string);
if (length($sereal_string) != $decoder->bytes_consumed) {
  die "Not all input data was consumed!";
}

chances are that if you do this, you're violating UNIX philosophy in "be strict in what you emit but lenient in what you accept". not me being a rule breaker. πŸ™ˆ

you can also use this to deserialize a list of Sereal documents that is concatenated into the same string (code not very robust...):

my @out;
my $pos = 0;
eval {
  while (1) {
    push @out, $decoder->decode_with_offset($sereal_string, $pos);
    $pos += $decoder->bytes_consumed;
    last if $pos >= length($sereal_string)
         or not $decoder->bytes_consumed;
  }
};

as mentioned, only the bytes consumed from the body are considered. so the following example is correct, as only the header is deserialized:

my $header = $decoder->decode_only_header($sereal_string);
my $count = $decoder->bytes_consumed;
# $count is 0

πŸ“ decode_from_file πŸ“

Sereal::Decoder->decode_from_file($file);
$decoder->decode_from_file($file);

read and decode the file specified. if called in list context and incremental mode is enabled then decodes all packets contained in the file and returns a list, otherwise decodes the first (or only) packet in the file. accepts an optional "target" variable as a second argument. it's giving file whisperer.


🌈 looks_like_sereal 🌈

performs some rudimentary check to determine if the argument appears to be a valid Sereal packet or not. these tests are not comprehensive and a true result does not mean that the document is valid, merely that it appears to be valid. on the other hand a false result is always reliable. so like, if it says no, trust it. if it says yes, it might be lying. πŸ’€

the return of this method may be treated as a simple boolean but is in fact a more complex return. when the argument does not look anything like a Sereal document then the return is perl's FALSE, which has the property of being string equivalent to "" and numerically equivalent to 0. however when the argument appears to be a UTF-8 encoded protocol 3 Sereal document (by noticing that the \xF3 in the magic string has been replaced by \xC3\xB3) then it returns 0 (the number, which is string equivalent to "0"), and otherwise returns the protocol version of the document. this means you can write something like this:

$type= Sereal::Decoder->looks_like_sereal($thing);
if ($type eq '') {
    say "Not a Sereal document";
} elsif ($type eq '0') {
    say "Possibly utf8 encoded Sereal document";
} else {
    say "Sereal document version $type";
}

for reference, Sereal's magic value is a four byte string which is either "=srl" for protocol version 1 and 2 or "=\xF3rl" for protocol version 3 and later. this function checks that the magic string corresponds with the reported version number, as well as other checks, which may be enhanced in the future. slay for future-proofing.

note that looks_like_sereal() may be called as a class or object method, and may also be called as a single argument function. see the related scalar_looks_like_sereal() for a version which may ONLY be called as a function, not as a method (and which is typically much faster). so if you want speed, use the other one. iykyk. ✨

✨ EXPORTABLE FUNCTIONS ✨

Okay bestie, these are the ones you can literally pull straight into your namespace instead of doing the whole ->method dance. Same vibes, slightly less overhead. πŸ’…

🫢 sereal_decode_with_object

The functional twin of decode. First param is a decoder object reference, second is the byte string you wanna deserialize. There's an optional third param that's the output scalar to write to. All the deets live in the decode docs up above, so go peek there.

Why bother? Because this functional interface is marginally faster than the OO one β€” it skips method resolution overhead and, on sufficiently modern Perl versions, usually dodges subroutine call overhead too. ✨ And if you're chasing max speed, go read Sereal::Performance, it's got the whole discussion on tuning Sereal.

πŸ¦‹ sereal_decode_with_header_with_object

Functional equivalent of decode_with_header. Decoder object ref first, byte string to deserialize second. Optional third and fourth params, which are the output scalars to write to. decode_with_header above has the full story.

Also marginally faster than the OO interface β€” no method resolution overhead, and on sufficiently modern Perl versions it can usually skip subroutine call overhead too. Sereal::Performance has the tuning tea if you need it. πŸ‘‘

🌸 sereal_decode_only_header_with_object

Functional equivalent of decode_only_header. Decoder object ref first, byte string to deserialize second. Optional third param, which outputs scalars to write to. See the decode_with_header docs above for the details.

Yep, you guessed it: marginally faster than the OO interface, because no method resolution overhead and, on sufficiently modern Perl versions, usually no subroutine call overhead either. Sereal::Performance is right there if you want to tune.

πŸ’€ sereal_decode_only_header_with_offset_with_object

Functional equivalent of decode_only_header_with_offset. Literally the same as sereal_decode_only_header_with_object, EXCEPT the third parameter has to be an integer offset into the input string, marking where decoding starts. That optional "pass-in" style scalar (the one from sereal_decode_only_header_with_object up above) gets demoted to fourth parameter. Rude, but okay. πŸ™ˆ

And yes: marginally faster than the OO interface β€” avoids method resolution overhead and, on sufficiently modern Perl versions, can usually avoid subroutine call overhead. Sereal::Performance, tune, etc. πŸ’–

πŸŽ€ sereal_decode_with_header_and_offset_with_object

Functional equivalent of decode_with_header_and_offset. Just like sereal_decode_with_header_with_object, except your third parameter MUST be an integer offset into the input string where decoding starts. The optional "pass-in" style scalars (from sereal_decode_with_header_with_object above) get bumped down to fourth and fifth parameters.

You know the drill by now: marginally faster than the OO interface, avoids method resolution overhead, and on sufficiently modern Perl versions can usually avoid subroutine call overhead. Sereal::Performance for the tuning discourse.

🌈 sereal_decode_with_offset_with_object

Functional equivalent of decode_with_offset. Same as sereal_decode_with_object, except the third parameter MUST be an integer offset into the input string where decoding begins. That optional "pass-in" style scalar (from sereal_decode_with_object above) is relegated to being the third parameter. (Yes, it says third. That's what it says. Not my business, it's in the source of truth. 😭)

One more time for the people in the back: marginally faster than the OO interface since it avoids method resolution overhead and, on sufficiently modern Perl versions, can usually avoid subroutine call overhead. Sereal::Performance has the how-to-tune content.

πŸ’œ decode_sereal

The functional combo of new + decode in one gloriously lazy call. First argument: the byte string to deserialize. Optionally followed by a hash reference of options (go read new()'s docs). And decode_sereal also supports a third parameter β€” the output scalar to write to. See decode above for the whole deal.

Fair warning: this functional interface is significantly slower than the OO interface, because it can't reuse the decoder object. Every call, brand new object. It's giving "convenience tax." πŸ’Έ

πŸ“ decode_sereal_with_header_data

Functional combo of new and decode_with_header. Byte string to deserialize comes first, optionally followed by a hash ref of options (check new()'s docs). And decode_sereal supports third and fourth parameters, which are the output scalars to write to. decode_with_header above has the details.

Same caveat as its sibling: significantly slower than the OO interface since it can't reuse the decoder object. 😭

⭐ scalar_looks_like_sereal

Functional equivalent of looks_like_sereal.

Heads up bestie: this version cannot be called as a method. It normally runs as a custom opcode, which means errors about its usage may get caught at compile time β€” and it should be much faster than looks_like_sereal. Speed demon behaviour. πŸ”₯

πŸ›‘οΈ ROBUSTNESS

So this Sereal decoder implementation is trying its absolute best to be robust against invalid input data. Reasonably possible = the goal. Which means it should never (though, read on, there's a but) segfault. It may, however, cause a large malloc to fail. Generally speaking, invalid data should give you a Perl-trappable exception. πŸ’–

The one exception: for Snappy-compressed Sereal documents, the Snappy library may cause segmentation faults (invalid reads or writes). This should only be a problem if you don't checksum your data (internal checksum support is a To-Do) or if you accept data from potentially malicious sources. Stay safe out there. 🫢

It takes a LOT of run-time boundary checks to keep the decoder from segfaulting on invalid data. They implemented them in the lightest way possible, because adding robustness against running out of memory would cause a very significant run-time overhead. In most cases of random garbage (with a valid header, no less), when a malloc() fails due to invalid data, the problem was a very large array or string length. That kind of very large malloc can then fail, and that's trappable from Perl. πŸ‘‘

Only when a packet causes many repeated allocations do you risk a hard OOM error from the kernel that cannot be trapped β€” because Perl may need some small allocations to succeed before the now-invalid memory is released. And it's at least not entirely trivial to craft a Sereal document that causes this behaviour. So like, effort required. πŸ’€

Finally, deserializing proper objects is potentially a problem because classes can define a destructor. So the data fed to the decoder can cause the (deferred) execution of any destructor in your application. 😭 That's why the refuse_objects option exists, and that's what no_bless_objects can be used for too. Later on, they may or may not provide a facility to whitelist classes. Furthermore, if the encoder emitted any objects using FREEZE callbacks, the THAW class method may be invoked on the respective classes. So if you can't trust the source of your Sereal documents, you may want to use the refuse_objects option. For more details on the FREEZE/THAW mechanism, please refer to Sereal::Encoder. ✨


🧊 FREEZE/THAW CALLBACK MECHANISM

Some objects just don't lend themselves naturally to naive perl datastructure level serialization. Like, XS code might use a hidden structure that wouldn't get serialized, or an object may contain volatile data like a filehandle that wouldn't be reconstituted properly. To support cases like this, Sereal supports a FREEZE and THAW api. When objects are serialized, their FREEZE method is asked for a replacement representation; when objects are deserialized, their THAW method is asked to convert that replacement back to something useful. It's giving glow-up in reverse and then forward again. πŸ¦‹

For security reasons, decoding an object will NOT autoload any modules to support THAW. However, if the classes and methods are preloaded, it will invoke THAW as required β€” and an exception will be thrown if the class has not been loaded. You can disable THAW in the decoder using the no_thaw_objects option, which when true causes frozen objects to be blessed into a special utility class Sereal::Decoder::THAW_args for debugging. The objects themselves are an array, whose last member will contain the class name the arguments are for. πŸ’…

Prior to v4.024, the decoder had issues with frozen representations that contained other objects, and did not define a specific order that items would be thawed and blessed β€” making it impractical to put an object inside of the frozen representation of another object. Messy era. 😭

As of v4.024, frozen representations may contain other objects, and the order in which they are thawed is defined to be LIFO. So the arguments to a THAW method will themselves be thawed (if need be) before the call to the containing object's THAW method. Thawing occurs only after all simple objects have been blessed into their appropriate object form. Iconic, actually. ✨

The FREEZE/THAW mechanism is inspired by the equivalent mechanism in CBOR::XS. The general mechanism is documented in the A GENERIC OBJECT SERIALIATION PROTOCOL section of Types::Serialiser. (That spelling is theirs, keep it.) Similar to CBOR using CBOR, Sereal uses the string Sereal as a serializer identifier for the callbacks. πŸ’–


πŸš€ PERFORMANCE

Please refer to the Sereal::Performance document β€” it has more detailed information about Sereal performance and tuning thereof. Go get your numbers, bestie. πŸ”₯


🧡 THREAD-SAFETY

Sereal::Decoder is thread-safe on Perl's 5.8.7 and higher. ✨ And "thread-safe" here means: if you create a new thread, all Sereal::Decoder objects will become a reference to undef in the new thread. That might change in a future release to become a full clone of the decoder object. So it's thread-safe in the "poof, gone" sense. πŸ’€


πŸ› BUGS, CONTACT AND SUPPORT

For reporting bugs, please use the github bug tracker at <http://github.com/Sereal/Sereal/issues>. They want your bug reports, fr fr. 🫢

For support and discussion of Sereal, there are two Google Groups:

Go forth and join a group. Main character energy. πŸ‘‘

πŸ‘‘ AUTHORS AND CONTRIBUTORS πŸ‘‘

Okay so bestie, the people behind this module? Absolute legends, every single one. Show some respect, they built the thing you're using to serialize your data at 3am πŸ’–

  • Yves Orton <demerphq@gmail.com>
  • Damian Gryski
  • Steffen Mueller <smueller@cpan.org>
  • RafaΓ«l Garcia-Suarez
  • Γ†var ArnfjΓΆrΓ° Bjarmason <avar@cpan.org>
  • Tim Bunce
  • Daniel Dragan <bulkdd@cpan.org> (Windows support and bugfixes)
  • Zefram
  • Borislav Nikolov
  • Ivan Kruglov <ivan.kruglov@yahoo.com>
  • Eric Herman <eric@freesa.org>

And like… some inspiration and code was borrowed from Marc Lehmann's iconic JSON::XS module, because obviously the problem domains overlap. Respect where respect is due πŸ™‡β€β™€οΈ


πŸ’ ACKNOWLEDGMENT πŸ’

Not me getting emotional but β€” this module was originally developed for Booking.com ✨ And then, with Booking.com's approval, it got generalized and published on CPAN. The authors would like to express their gratitude for that, and honestly? Same. Thank u Booking.com 🫢


So here's the paperwork part, and I'm gonna give it to you straight because licenses are not a vibe to skim:

Copyright (C) 2012, 2013, 2014 by Steffen Mueller Copyright (C) 2012, 2013, 2014 by Yves Orton

The license for the code in this distribution is the following, with the exceptions listed below:

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

Except portions taken from Marc Lehmann's code for the JSON::XS module, which is licensed under the same terms as this module. (Many thanks to Marc for inspiration, and code. πŸ’œ)

Also except the code for the Snappy compression library, whose license is reproduced below and which, to the best of our knowledge, is compatible with this module's license. The license for the enclosed Snappy code is:

  Copyright 2011, Google Inc.
  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are
  met:

    * Redistributions of source code must retain the above copyright
  notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above
  copyright notice, this list of conditions and the following disclaimer
  in the documentation and/or other materials provided with the
  distribution.
    * Neither the name of Google Inc. nor the names of its
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
fr fr, read the whole thing. Google Inc. and "AS IS" and "NO EVENT SHALL" are doing a lot of heavy lifting in there πŸ’€ It's giving legal, it's giving necessary, it's giving don't-skip-it.

okay that's a wrap bestie, the whole page, start to finish 🌈 Permalink this whole manual under perl v5.42.3, dated 2026-08-20, filed under Sereal::Decoder(3pm) β€” go forth and deserialize responsibly, mwah πŸ’…βœ¨πŸŽ€

πŸ“¦ where this page comes from