///|
/// Output storage for the one-shot decoder. Unlike the general-purpose core
/// `Buffer`, this exposes the fixed backing array needed for DEFLATE's
/// overlapping back-reference copies.
priv struct DecodeOutput {
mut storage : FixedArray[Byte]
mut length : Int
fixed_capacity : Bool
}
///|
fn DecodeOutput::DecodeOutput(size_hint : Int) -> DecodeOutput {
{
storage: FixedArray::make(size_hint.max(1), b'\x00'),
length: 0,
fixed_capacity: false,
}
}
///|
/// Use caller-owned storage for a one-shot decode. Capacity exhaustion becomes
/// the existing public `OutputLimitExceeded` failure instead of reallocating.
fn DecodeOutput::into(storage : FixedArray[Byte]) -> DecodeOutput {
{ storage, length: 0, fixed_capacity: true, }
}
///|
/// Byte counts use signed 32-bit Int on every backend.
const MAX_DECODE_OUTPUT_SIZE : Int = 0x7FFFFFFF
///|
/// Both counts are non-negative. Check the remaining range before adding so
/// overflow cannot bypass the capacity check or reach an array write.
fn checked_decode_output_length(
length : Int,
additional : Int,
) -> Int raise InflateError {
guard additional <= MAX_DECODE_OUTPUT_SIZE - length else {
raise InflateError(OutputLimitExceeded, "DEFLATE output size overflow")
}
length + additional
}
///|
/// Retain geometric growth when doubling fits; otherwise allocate only the
/// required size. Keeping this arithmetic separate permits allocation-free
/// tests at the integer limit.
fn grown_decode_output_capacity(current : Int, required : Int) -> Int {
if current <= MAX_DECODE_OUTPUT_SIZE / 2 {
(current * 2).max(required)
} else {
required
}
}
///|
fn DecodeOutput::ensure_capacity(
self : DecodeOutput,
additional : Int,
) -> Unit raise InflateError {
let required = checked_decode_output_length(self.length, additional)
guard required > self.storage.length() else { return }
guard !self.fixed_capacity else {
raise InflateError(
OutputLimitExceeded,
"decoded output exceeds output buffer",
)
}
let capacity = grown_decode_output_capacity(self.storage.length(), required)
self.storage = FixedArray::make_and_blit(
self.storage,
allocate_len=capacity,
init=b'\x00',
len=self.length,
)
}
///|
fn DecodeOutput::append_byte(
self : DecodeOutput,
byte : Byte,
) -> Unit raise InflateError {
self.ensure_capacity(1)
self.storage[self.length] = byte
self.length = self.length + 1
}
///|
fn DecodeOutput::append_bytes(
self : DecodeOutput,
source : Bytes,
source_offset : Int,
length : Int,
) -> Unit raise InflateError {
guard length > 0 else { return }
self.ensure_capacity(length)
self.storage.blit_from_bytes(self.length, source, source_offset, length)
self.length = self.length + length
}
///|
/// Check capacity, then append a match without writing beyond its exact length.
/// The one-shot driver splits matches at cancellation boundaries.
fn DecodeOutput::append_repeated_suffix(
self : DecodeOutput,
distance : Int,
length : Int,
) -> Unit raise InflateError {
guard length > 0 else { return }
self.ensure_capacity(length)
let start = self.length
let storage = self.storage
if length <= 8 {
// Keep the common short-text match in the caller to avoid an extra
// helper call. Forward byte copies also handle overlapping history.
for i in 0..= 16 keeps them disjoint.
/// The final vector may rewrite the preceding output bytes, but ends exactly
/// at the match boundary and only reads history that has already been produced.
#cfg(any(target="native", target="wasm"))
fn copy_match_bytes(
storage : FixedArray[Byte],
start : Int,
distance : Int,
length : Int,
) -> Unit {
if distance >= 16 && length >= 16 {
for offset = 0; offset + 16 <= length; offset = offset + 16 {
@v128.v128_store(
storage,
start + offset,
@v128.v128_load(storage, start + offset - distance),
)
}
if (length & 15) != 0 {
let offset = length - 16
@v128.v128_store(
storage,
start + offset,
@v128.v128_load(storage, start + offset - distance),
)
}
} else {
copy_match_bytes_scalar(storage, start, distance, length)
}
}
///|
#cfg(not(any(target="native", target="wasm")))
fn copy_match_bytes(
storage : FixedArray[Byte],
start : Int,
distance : Int,
length : Int,
) -> Unit {
copy_match_bytes_scalar(storage, start, distance, length)
}
///|
/// Longer overlapping matches double their generated prefix. This is also
/// the fallback on targets without native SIMD; the caller handles short matches.
fn copy_match_bytes_scalar(
storage : FixedArray[Byte],
start : Int,
distance : Int,
length : Int,
) -> Unit {
let first = distance.min(length)
FixedArray::unsafe_blit(storage, start, storage, start - distance, first)
let mut copied = first
while copied < length {
let chunk = copied.min(length - copied)
FixedArray::unsafe_blit(storage, start + copied, storage, start, chunk)
copied = copied + chunk
}
}
///|
fn DecodeOutput::to_bytes(self : DecodeOutput) -> Bytes {
Bytes::from_array(self.storage[:self.length])
}
///|
/// In-memory DEFLATE decoder. Reads from a `Bytes` and appends to `out`.
priv struct MemDecoder {
mut input : Bytes
mut pos : Int // next input byte
mut bitbuf : UInt // LSB-first bit accumulator
mut bit_count : Int // valid bits in `bitbuf`
mut out : DecodeOutput
mut cancelled : () -> Bool // polled every `CANCEL_GRANULARITY` produced bytes
mut next_cancel : Int // poll `cancelled` once `out` reaches this length
dyn_litlen : HuffmanDecoder // literal/length (also reused for the code-length tree)
dyn_dist : HuffmanDecoder // distance
clbits : FixedArray[Int] // decoded literal+distance code lengths
codebits : FixedArray[Int] // code-length code lengths
}
///|
const CANCEL_GRANULARITY : Int = 4096
///|
/// A DEFLATE Huffman code is at most 15 bits. The one-shot decoder can keep a
/// 24-bit reservoir because it is allowed to consume trailing bytes after the
/// final block, as documented by `inflate_all`'s prefix semantics.
const HUFFMAN_FAST_BITS : Int = 15
///|
const HUFFMAN_FAST_REFILL_BITS : Int = 24
///|
/// Output chunk size for the streaming one-shot drivers: each `step` call
/// writes into this scratch, so cancellation is polled at this granularity.
const INFLATE_SCRATCH_SIZE : Int = 8192
///|
fn MemDecoder::MemDecoder(input : Bytes, cancelled : () -> Bool) -> MemDecoder {
{
input,
pos: 0,
bitbuf: 0,
bit_count: 0,
out: DecodeOutput(input.length()),
cancelled,
next_cancel: 0,
dyn_litlen: HuffmanDecoder(),
dyn_dist: HuffmanDecoder(),
clbits: FixedArray::make(MAX_NUM_LIT + MAX_NUM_DIST, 0),
codebits: FixedArray::make(NUM_CODES, 0),
}
}
///|
/// Reinitialize an in-memory decoder over a new stream while retaining its
/// Huffman and dynamic-header workspace. `output` becomes the complete
/// back-reference history, so no secondary output allocation is needed.
fn MemDecoder::reset_into(
self : MemDecoder,
input : Bytes,
output : FixedArray[Byte],
cancelled : () -> Bool,
) -> Unit {
self.input = input
self.pos = 0
self.bitbuf = 0
self.bit_count = 0
self.out = DecodeOutput::into(output)
self.cancelled = cancelled
self.next_cancel = 0
}
///|
fn MemDecoder::check_cancel(self : MemDecoder) -> Unit raise InflateError {
guard self.out.length >= self.next_cancel else { return }
if (self.cancelled)() {
raise InflateError(Cancelled, "cancelled by caller")
}
self.next_cancel = self.out.length + CANCEL_GRANULARITY
}
///|
#inline
fn MemDecoder::push(self : MemDecoder, b : Byte) -> Unit raise InflateError {
self.out.append_byte(b)
self.check_cancel()
}
///|
fn MemDecoder::pull_byte(self : MemDecoder) -> Bool {
if self.pos >= self.input.length() {
return false
}
self.bitbuf = self.bitbuf | (self.input[self.pos].to_uint() << self.bit_count)
self.bit_count = self.bit_count + 8
self.pos = self.pos + 1
true
}
///|
fn MemDecoder::need(self : MemDecoder, n : Int) -> Unit raise InflateError {
while self.bit_count < n {
if !self.pull_byte() {
raise InflateError(Truncated, "unexpected end of input")
}
}
}
///|
fn MemDecoder::read_bits(self : MemDecoder, n : Int) -> Int raise InflateError {
self.need(n)
let v = (self.bitbuf & ((1U << n) - 1)).reinterpret_as_int()
self.bitbuf = self.bitbuf >> n
self.bit_count = self.bit_count - n
v
}
///|
fn MemDecoder::huff_sym(
self : MemDecoder,
h : HuffmanDecoder,
) -> Int raise InflateError {
let mut n = h.min
for ;; {
while self.bit_count < n {
if !self.pull_byte() {
raise InflateError(Truncated, "unexpected end of input")
}
}
let mut chunk = h.chunks[(self.bitbuf & 0x1FF).reinterpret_as_int()]
n = (chunk & 0xF).reinterpret_as_int()
if n > HUFFMAN_CHUNK_BITS {
let link = ((self.bitbuf >> HUFFMAN_CHUNK_BITS) & h.link_mask).reinterpret_as_int()
chunk = h.links[(chunk >> HUFFMAN_VALUE_SHIFT).reinterpret_as_int() + link]
n = (chunk & 0xF).reinterpret_as_int()
}
if n <= self.bit_count {
if n == 0 {
raise InflateError(Corrupt, "corrupt: bad Huffman code")
}
self.bitbuf = self.bitbuf >> n
self.bit_count = self.bit_count - n
return (chunk >> HUFFMAN_VALUE_SHIFT).reinterpret_as_int()
}
}
}
///|
/// Refill the one-shot bit reservoir in two-byte groups when possible. This is
/// intentionally private to `MemDecoder`: a streaming inflater must not read
/// past the current symbol because those bytes can be a container trailer.
#inline
fn MemDecoder::refill_huffman_fast(self : MemDecoder) -> Unit {
while self.bit_count < HUFFMAN_FAST_REFILL_BITS &&
self.pos < self.input.length() {
if self.bit_count <= HUFFMAN_FAST_BITS && self.pos + 1 < self.input.length() {
let pair = self.input[self.pos].to_uint() |
(self.input[self.pos + 1].to_uint() << 8)
self.bitbuf = self.bitbuf | (pair << self.bit_count)
self.bit_count = self.bit_count + 16
self.pos = self.pos + 2
} else {
self.bitbuf = self.bitbuf |
(self.input[self.pos].to_uint() << self.bit_count)
self.bit_count = self.bit_count + 8
self.pos = self.pos + 1
}
}
}
///|
/// Decode a data-block symbol using the prefilled fast reservoir when possible.
/// Unlike the dynamic-header path, this loop is the dominant one-shot decode
/// cost, so it keeps a wide reservoir and only falls back near physical EOF.
#inline
fn MemDecoder::huff_sym_data(
self : MemDecoder,
h : HuffmanDecoder,
) -> Int raise InflateError {
self.refill_huffman_fast()
if self.bit_count < HUFFMAN_FAST_BITS {
return self.huff_sym(h)
}
let mut chunk = h.chunks[(self.bitbuf & 0x1FF).reinterpret_as_int()]
let mut n = (chunk & 0xF).reinterpret_as_int()
if n > HUFFMAN_CHUNK_BITS {
let link = ((self.bitbuf >> HUFFMAN_CHUNK_BITS) & h.link_mask).reinterpret_as_int()
chunk = h.links[(chunk >> HUFFMAN_VALUE_SHIFT).reinterpret_as_int() + link]
n = (chunk & 0xF).reinterpret_as_int()
}
guard n != 0 else { raise InflateError(Corrupt, "corrupt: bad Huffman code") }
self.bitbuf = self.bitbuf >> n
self.bit_count = self.bit_count - n
(chunk >> HUFFMAN_VALUE_SHIFT).reinterpret_as_int()
}
///|
/// Decode and copy one length/distance pair. Keeping the uncommon match path
/// separate lets `decode_block` keep its literal fast loop branch-predictable.
#inline
fn MemDecoder::decode_match(
self : MemDecoder,
code : Int,
hd : HuffmanDecoder?,
) -> Unit raise InflateError {
guard code < 286 else {
raise InflateError(Corrupt, "corrupt: invalid literal/length code")
}
let length_info = length_code_info_table[code - 257]
let length = (length_info >> 3) + self.read_bits(length_info & 0x7)
let dsym = if hd is Some(h) {
self.huff_sym_data(h)
} else {
fixed_distance_symbol(self.read_bits(5))
}
guard dsym.reinterpret_as_uint() < MAX_NUM_DIST.reinterpret_as_uint() else {
raise InflateError(Corrupt, "corrupt: invalid distance code")
}
let distance_info = distance_code_info_table[dsym]
// `read_bits(0)` reads nothing and returns 0, so the base serves as the
// distance when the code carries no extra bits.
let dist = (distance_info >> 4) + self.read_bits(distance_info & 0xF)
let available_history = self.out.length
guard available_history >= WINDOW_SIZE ||
back_reference_distance_is_valid(dist, available_history) else {
raise InflateError(Corrupt, "corrupt: distance too far back")
}
// Copy a complete match here when capacity and the cancellation interval
// permit it. Subtract before comparing so the output bound cannot overflow.
// Short text matches use forward byte copies, including overlapping history.
let out = self.out
let start = out.length
if length <= out.storage.length() - start && length < self.next_cancel - start {
let storage = out.storage
if length <= 16 {
for i in 0.. Unit raise InflateError {
for ;; {
let v = self.huff_sym_data(hl)
guard v >= 256 else {
self.push(v.to_byte())
// Most DEFLATE streams contain literal runs. Decode one extra symbol
// before returning to the match/EOB dispatch to amortize that branch.
let next = self.huff_sym_data(hl)
guard next >= 256 else {
self.push(next.to_byte())
continue
}
guard next != 256 else { return }
self.decode_match(next, hd)
continue
}
guard v != 256 else { return }
self.decode_match(v, hd)
}
}
///|
fn MemDecoder::copy_match(
self : MemDecoder,
distance : Int,
length : Int,
) -> Unit raise InflateError {
let mut remaining = length
while remaining > 0 {
// Keep the public cancellation cadence while copying in large chunks.
let until_cancel = self.next_cancel - self.out.length
let chunk = remaining.min(until_cancel)
self.out.append_repeated_suffix(distance, chunk)
remaining = remaining - chunk
self.check_cancel()
}
}
///|
fn MemDecoder::copy_stored(
self : MemDecoder,
length : Int,
) -> Unit raise InflateError {
let mut remaining = length
while remaining > 0 {
// Preserve `inflate_all`'s cancellation cadence while copying each chunk
// through the core bulk-copy primitive.
let until_cancel = self.next_cancel - self.out.length
let chunk = remaining.min(until_cancel)
self.out.append_bytes(self.input, self.pos, chunk)
self.pos = self.pos + chunk
remaining = remaining - chunk
self.check_cancel()
}
}
///|
fn MemDecoder::stored_block(self : MemDecoder) -> Unit raise InflateError {
// The fast Huffman reader can have prefetched whole bytes beyond the current
// block header. Byte alignment discards only the remaining partial byte; put
// those unread whole bytes back so LEN/NLEN starts at the correct input byte.
self.pos = self.pos - (self.bit_count >> 3)
self.bitbuf = 0
self.bit_count = 0
if self.pos + 4 > self.input.length() {
raise InflateError(Truncated, "unexpected end of input")
}
let len = self.input[self.pos].to_int() |
(self.input[self.pos + 1].to_int() << 8)
let nlen = self.input[self.pos + 2].to_int() |
(self.input[self.pos + 3].to_int() << 8)
self.pos = self.pos + 4
validate_stored_length(len, nlen)
if self.pos + len > self.input.length() {
raise InflateError(Truncated, "unexpected end of input")
}
self.copy_stored(len)
}
///|
fn MemDecoder::read_dynamic(self : MemDecoder) -> Unit raise InflateError {
let (nlit, ndist) = decode_dynamic_header(
n => self.read_bits(n),
() => self.huff_sym(self.dyn_litlen),
self.dyn_litlen,
self.codebits,
self.clbits,
)
guard self.dyn_litlen.initialize(self.clbits[0:nlit]) else {
raise InflateError(Corrupt, "corrupt: bad literal/length tree")
}
guard self.dyn_dist.initialize(self.clbits[nlit:nlit + ndist]) else {
raise InflateError(Corrupt, "corrupt: bad distance tree")
}
}
///|
fn MemDecoder::run(self : MemDecoder) -> Unit raise InflateError {
self.check_cancel()
for ;; {
self.need(3)
let bfinal = (self.bitbuf & 1) == 1
let btype = ((self.bitbuf >> 1) & 3).reinterpret_as_int()
self.bitbuf = self.bitbuf >> 3
self.bit_count = self.bit_count - 3
match btype {
0 => self.stored_block()
1 => self.decode_block(fixed_huffman_decoder, None)
2 => {
self.read_dynamic()
self.decode_block(self.dyn_litlen, Some(self.dyn_dist))
}
_ => raise InflateError(Corrupt, "corrupt: reserved block type")
}
if bfinal {
break
}
}
}
///|
/// Decompress a raw DEFLATE stream held entirely in memory. Bytes after the
/// final block are ignored; use `inflate_exact` when an exact framing boundary
/// is required. The output grows without bound; for untrusted input use
/// `inflate_all_limited` or drive `Inflater` with caller-sized output buffers.
/// If supplied, `cancelled` is polled at entry and then every 4096 produced
/// bytes; returning `true` raises `InflateError(Cancelled, _)`.
pub fn inflate_all(
input : Bytes,
cancelled? : () -> Bool = () => false,
) -> Bytes raise InflateError {
let d = MemDecoder(input, cancelled)
d.run()
d.out.to_bytes()
}
///|
/// A reusable, whole-buffer raw DEFLATE decompressor. It decodes directly into
/// caller-owned storage while retaining Huffman and dynamic-header workspace
/// across streams.
pub struct Decompressor {
priv decoder : MemDecoder
}
///|
/// Create a reusable whole-buffer decompressor.
pub fn Decompressor::Decompressor() -> Decompressor {
{ decoder: MemDecoder(b"", () => false), }
}
///|
/// Decompress `input` into `output`, returning the number of bytes written.
/// The complete output must fit in `output`; otherwise this raises
/// `InflateError(OutputLimitExceeded, _)`. As with `inflate_all`, bytes after
/// the final DEFLATE block are ignored. Reuse one `Decompressor` and output
/// buffer to avoid per-stream decoder and result-buffer allocation.
pub fn Decompressor::decompress_into(
self : Decompressor,
input : Bytes,
output : FixedArray[Byte],
cancelled? : () -> Bool = () => false,
) -> Int raise InflateError {
self.decoder.reset_into(input, output, cancelled)
self.decoder.run()
self.decoder.out.length
}
///|
/// One-shot convenience form of `Decompressor::decompress_into`. It creates a
/// decompressor for this call; use `Decompressor` when decoding many streams.
pub fn inflate_into(
input : Bytes,
output : FixedArray[Byte],
cancelled? : () -> Bool = () => false,
) -> Int raise InflateError {
Decompressor().decompress_into(input, output, cancelled~)
}
///|
/// Drive the streaming inflater over an in-memory input. If `exact` is true,
/// reject bytes after the final DEFLATE block. If `max_output` is present, one
/// fixed scratch buffer may be filled past that boundary internally, but no
/// oversized result is returned. `cancelled` is polled at entry and once per
/// step (every 8192 produced bytes); returning `true` raises
/// `InflateError(Cancelled, _)`.
fn inflate_streaming_all(
input : Bytes,
max_output : Int?,
exact : Bool,
cancelled : () -> Bool,
) -> Bytes raise InflateError {
guard !cancelled() else {
raise InflateError(Cancelled, "cancelled by caller")
}
let inflater = Inflater()
let scratch = FixedArray::make(INFLATE_SCRATCH_SIZE, b'\x00')
let decoded : Array[Byte] = []
let mut input_pos = 0
for ;; {
let status = inflater.step_into(input[input_pos:], scratch)
let consumed = inflater.last_consumed()
let produced = inflater.last_produced()
input_pos = input_pos + consumed
guard !cancelled() else {
raise InflateError(Cancelled, "cancelled by caller")
}
guard max_output.map_or(true, limit => produced <= limit - decoded.length()) else {
raise InflateError(
OutputLimitExceeded,
"decoded output exceeds max_output",
)
}
decoded.append(scratch[:produced])
match status {
Done => {
if exact && input_pos != input.length() {
raise InflateError(TrailingData, "trailing data after DEFLATE stream")
}
return Bytes::from_array(decoded)
}
NeedMoreOutput => ()
NeedMoreInput => raise InflateError(Truncated, "unexpected end of input")
}
}
}
///|
/// Decompress exactly one raw DEFLATE stream. Unlike `inflate_all`, this rejects
/// any bytes after the final block.
///
/// Optional controls:
/// - `max_output`: decoded output beyond this size raises
/// `InflateError(OutputLimitExceeded, _)`. A negative value is rejected the
/// same way.
/// - `cancelled`: polled at entry and then once per internal step (every 8192
/// produced bytes); returning `true` raises `InflateError(Cancelled, _)`.
/// - `preallocated`: when `true`, decode runs twice — a counting pass that
/// validates the whole stream, then a second pass filling one exactly sized
/// allocation. Peak memory stays at roughly one decoded output, at the cost
/// of roughly double the decode work; the default `false` grows one buffer
/// in a single pass, trading transient memory for one decode pass.
pub fn inflate_exact(
input : Bytes,
max_output? : Int? = None,
cancelled? : () -> Bool = () => false,
preallocated? : Bool = false,
) -> Bytes raise InflateError {
let limit = max_output.map_or(max_output, limit => {
guard limit >= 0 else {
raise InflateError(OutputLimitExceeded, "max_output must be non-negative")
}
max_output
})
if preallocated {
inflate_preallocated(input, limit, cancelled)
} else {
inflate_streaming_all(input, limit, true, cancelled)
}
}
///|
/// Drive one inflater over the whole in-memory input, delivering produced
/// chunks to `emit` (called once per step with the scratch contents). Returns
/// the total number of bytes produced. `max_output` raises once the total
/// exceeds it; `exact` rejects bytes after the final block; `cancelled` is
/// polled once per step and raises `InflateError(Cancelled, _)`.
fn inflate_drive(
inflater : Inflater,
input : Bytes,
max_output : Int?,
exact : Bool,
cancelled : () -> Bool,
emit : (FixedArray[Byte], Int) -> Unit,
) -> Int raise InflateError {
let scratch = FixedArray::make(INFLATE_SCRATCH_SIZE, b'\x00')
let mut total = 0
let mut input_pos = 0
for ;; {
let status = inflater.step_into(input[input_pos:], scratch, end=true)
let consumed = inflater.last_consumed()
let produced = inflater.last_produced()
input_pos = input_pos + consumed
total = total + produced
guard !cancelled() else {
raise InflateError(Cancelled, "cancelled by caller")
}
guard max_output.map_or(true, limit => total <= limit) else {
raise InflateError(
OutputLimitExceeded,
"decoded output exceeds max_output",
)
}
emit(scratch, produced)
// `Inflater::step(end=true)` raises `Truncated` on exhausted input, so
// only `Done` and `NeedMoreOutput` can be returned here.
guard status is Done else { continue }
if exact && input_pos != input.length() {
raise InflateError(TrailingData, "trailing data after DEFLATE stream")
}
return total
}
}
///|
/// The replay pass must produce exactly the size measured by the counting pass.
/// A mismatch indicates an internal decoder invariant failure rather than bad
/// caller input, so this defensive branch is excluded from behavior coverage.
#coverage.skip
fn validate_preallocation_size(
size : Int,
filled : Int,
) -> Unit raise InflateError {
guard filled == size else {
raise InflateError(Corrupt, "corrupt: preallocation size mismatch")
}
}
///|
/// `inflate_exact` with `preallocated=true`: validate and count in one pass
/// over an 8 KiB scratch, then replay the deterministic decode into one exact
/// allocation (the same `Inflater` is reused via `reset`).
fn inflate_preallocated(
input : Bytes,
max_output : Int?,
cancelled : () -> Bool,
) -> Bytes raise InflateError {
guard !cancelled() else {
raise InflateError(Cancelled, "cancelled by caller")
}
let inflater = Inflater()
let size = inflate_drive(inflater, input, max_output, true, cancelled, (
_scratch,
_produced,
) => ())
inflater.reset()
let buf = FixedArray::make(size, b'\x00')
let mut filled = 0
ignore(
inflate_drive(inflater, input, None, true, cancelled, (scratch, produced) => {
scratch.blit_to(buf, len=produced, dst_offset=filled)
filled = filled + produced
}),
)
validate_preallocation_size(size, filled)
Bytes::from_array(buf)
}
///|
/// Decompress one raw DEFLATE stream without allowing the returned output to
/// exceed `max_output`. Bytes after the final block retain `inflate_all`'s
/// prefix semantics and are ignored. `cancelled` follows `inflate_all`'s
/// polling contract.
pub fn inflate_all_limited(
input : Bytes,
max_output~ : Int,
cancelled? : () -> Bool = () => false,
) -> Bytes raise InflateError {
guard max_output >= 0 else {
raise InflateError(OutputLimitExceeded, "max_output must be non-negative")
}
inflate_streaming_all(input, Some(max_output), false, cancelled)
}