Document the public surface
This commit is contained in:
parent
7f90884528
commit
d7fcebe694
3 changed files with 21 additions and 32 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
/// A pure Dart zstd decompressor.
|
/// A pure Dart zstd decompressor.
|
||||||
///
|
///
|
||||||
/// Decompression only, and only the subset of RFC 8878 that the elevation
|
/// Decompression only, one RFC 8878 frame per call, on native platforms. It
|
||||||
/// containers this was written for use. See the README for what is left out.
|
/// refuses dictionaries and skippable frames; see the README for the rest.
|
||||||
library;
|
library;
|
||||||
|
|
||||||
export 'src/decoder.dart' show ZstdDecoder, zstdDecode;
|
export 'src/decoder.dart' show ZstdDecoder, zstdDecode;
|
||||||
|
|
|
||||||
|
|
@ -13,33 +13,17 @@ import 'xxhash.dart';
|
||||||
/// One instance is not safe to share between isolates, and a frame must be
|
/// One instance is not safe to share between isolates, and a frame must be
|
||||||
/// decoded to completion before the next one starts.
|
/// decoded to completion before the next one starts.
|
||||||
class ZstdDecoder {
|
class ZstdDecoder {
|
||||||
|
/// Creates a decoder that refuses any frame larger than [maxOutputSize].
|
||||||
ZstdDecoder({this.maxOutputSize = defaultMaxOutputSize});
|
ZstdDecoder({this.maxOutputSize = defaultMaxOutputSize});
|
||||||
|
|
||||||
|
/// The four bytes every zstd frame starts with, read little-endian.
|
||||||
static const int magic = 0xFD2FB528;
|
static const int magic = 0xFD2FB528;
|
||||||
|
|
||||||
/// The largest output this decoder will allocate for one frame.
|
/// The largest output a decoder allocates for one frame unless told
|
||||||
|
/// otherwise, since a frame header can claim up to 2^64 bytes.
|
||||||
///
|
///
|
||||||
/// `Frame_Content_Size` is up to a 64-bit number taken straight from the
|
/// The top-level [zstdDecode] always uses it; raising it takes a
|
||||||
/// input and it used to size the output buffer unchecked, so a sixteen-byte
|
/// [ZstdDecoder] of one's own.
|
||||||
/// frame could ask for four exbibytes. It is reachable by accident as well
|
|
||||||
/// as by design: one bit flipped in a real tile's frame descriptor moves
|
|
||||||
/// the content-size flag from 2 to 3, eight bytes of compressed data are
|
|
||||||
/// read as the size, and the eighth lands on the sign bit. That is a
|
|
||||||
/// `RangeError` for a negative length, or an `OutOfMemoryError` for a large
|
|
||||||
/// positive one, and both are `Error` rather than `Exception`, so a caller
|
|
||||||
/// writing `on ZstdException` does not catch either and the isolate dies.
|
|
||||||
///
|
|
||||||
/// RFC 8878 section 3.1.1.1.2 lets a decoder refuse a frame that asks for
|
|
||||||
/// more memory than it is willing to give, which is the job of the window
|
|
||||||
/// descriptor this package skips. Allocating the output whole is why it can
|
|
||||||
/// skip it, and this is the bound that replaces it.
|
|
||||||
///
|
|
||||||
/// A caller that knows the exact size should pass [decode]'s `expectedSize`
|
|
||||||
/// as well, and gets an exact check *in addition to* this ceiling rather
|
|
||||||
/// than instead of it: a frame declaring 70 MiB is refused here before the
|
|
||||||
/// two are ever compared. Raising it takes a `ZstdDecoder` of one's own,
|
|
||||||
/// since the top-level [zstdDecode] does not offer it. The `.hgtz` reader
|
|
||||||
/// knows its size: a block is `height * width * 2`.
|
|
||||||
static const int defaultMaxOutputSize = 64 * 1024 * 1024;
|
static const int defaultMaxOutputSize = 64 * 1024 * 1024;
|
||||||
|
|
||||||
/// The ceiling this instance enforces. See [defaultMaxOutputSize].
|
/// The ceiling this instance enforces. See [defaultMaxOutputSize].
|
||||||
|
|
@ -74,10 +58,12 @@ class ZstdDecoder {
|
||||||
int _literalAt = 0;
|
int _literalAt = 0;
|
||||||
int _literalEnd = 0;
|
int _literalEnd = 0;
|
||||||
|
|
||||||
/// Decodes one frame.
|
/// Decodes exactly one frame, which must be the whole of [src], into a new
|
||||||
|
/// list.
|
||||||
///
|
///
|
||||||
/// [expectedSize] is only needed when the frame header omits its content
|
/// [expectedSize] is required when the frame omits its content size, and
|
||||||
/// size, which the containers this was written for never do.
|
/// otherwise must match it exactly. Throws a [ZstdException] for any frame
|
||||||
|
/// that is malformed, too large, fails its checksum, or uses a dictionary.
|
||||||
Uint8List decode(Uint8List src, {int? expectedSize}) {
|
Uint8List decode(Uint8List src, {int? expectedSize}) {
|
||||||
if (src.length < 6) {
|
if (src.length < 6) {
|
||||||
throw const ZstdException('too short to be a zstd frame');
|
throw const ZstdException('too short to be a zstd frame');
|
||||||
|
|
@ -151,9 +137,7 @@ class ZstdDecoder {
|
||||||
_repeat2 = 4;
|
_repeat2 = 4;
|
||||||
_repeat3 = 8;
|
_repeat3 = 8;
|
||||||
|
|
||||||
// Only when the frame carries one. Hashing every frame would cost the
|
// Only when the frame carries one, so a frame without pays nothing.
|
||||||
// .hgtz reader throughput it has no use for, since pyzstd writes no
|
|
||||||
// checksum and the container's own SHA-256 is verified on download.
|
|
||||||
final hash = checksum == 1 ? (_hash..reset()) : null;
|
final hash = checksum == 1 ? (_hash..reset()) : null;
|
||||||
|
|
||||||
var written = 0;
|
var written = 0;
|
||||||
|
|
@ -674,7 +658,7 @@ class ZstdDecoder {
|
||||||
|
|
||||||
/// Decodes one zstd frame into a new list.
|
/// Decodes one zstd frame into a new list.
|
||||||
///
|
///
|
||||||
/// Allocates a decoder per call. Decoding many frames, which is what reading
|
/// Allocates a decoder per call, so decoding many frames should hold a
|
||||||
/// a tile does, should hold a [ZstdDecoder] instead.
|
/// [ZstdDecoder] instead. Throws as [ZstdDecoder.decode] does.
|
||||||
Uint8List zstdDecode(Uint8List src, {int? expectedSize}) =>
|
Uint8List zstdDecode(Uint8List src, {int? expectedSize}) =>
|
||||||
ZstdDecoder().decode(src, expectedSize: expectedSize);
|
ZstdDecoder().decode(src, expectedSize: expectedSize);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
/// Thrown when a frame is malformed, or uses a feature this decoder omits.
|
/// Thrown when a frame is malformed, or uses a feature this decoder omits.
|
||||||
|
///
|
||||||
|
/// Every refusal from this package is one of these, so `on ZstdException` is
|
||||||
|
/// enough to survive untrusted input.
|
||||||
class ZstdException implements Exception {
|
class ZstdException implements Exception {
|
||||||
|
/// Creates an exception carrying [message].
|
||||||
const ZstdException(this.message);
|
const ZstdException(this.message);
|
||||||
|
|
||||||
|
/// What was wrong with the frame, for a log rather than for matching on.
|
||||||
final String message;
|
final String message;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue