| // Copyright 2023 The Wuffs Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| // https://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your |
| // option. This file may not be copied, modified, or distributed |
| // except according to those terms. |
| // |
| // SPDX-License-Identifier: Apache-2.0 OR MIT |
| |
| pub status "#bad LZMA2 header" |
| pub status "#bad bitstream trailer" |
| pub status "#bad code" |
| pub status "#bad decoded length" |
| pub status "#bad distance" |
| pub status "#bad header" |
| pub status "#truncated input" |
| pub status "#unsupported decoded length" |
| pub status "#unsupported properties" |
| |
| pri status "#internal error: inconsistent I/O" |
| pri status "#internal error: inconsistent dictionary state" |
| |
| pub const DECODER_DST_HISTORY_RETAIN_LENGTH_MAX_INCL_WORST_CASE : base.u64 = 0 |
| pub const DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE : base.u64 = 0xFFFF_FFFF + 273 |
| |
| pub struct decoder? implements base.io_transformer( |
| // In theory, lc ranges in [0 ..= 8]. In practice, to keep the |
| // worst-case size of the statically allocated probs_lit array |
| // managable, we only support configurations where ((lc + lp) <= 4) and |
| // otherwise return "#unsupported properties". |
| // |
| // This matches "LZMA2's limited property condition" described at: |
| // https://github.com/jljusten/LZMA-SDK/blob/781863cdf592da3e97420f50de5dac056ad352a5/DOC/lzma-specification.txt#L192 |
| lc : base.u32[..= 4], // The number of Literal Context bits. Default: 3. |
| lp : base.u32[..= 4], // The number of Literal Position bits. Default: 0. |
| pb : base.u32[..= 4], // The number of Position Bits. Default: 2. |
| |
| format_extension : base.u32, |
| |
| // dict_size is the nominal size of the args.workbuf ringbuffer that |
| // holds historical decoded bytes. The workbuf_len is a little larger: |
| // see "ML" below. |
| dict_size : base.u32, |
| |
| // dict_workbuf_index indexes the args.workbuf ringbuffer, pointing to |
| // where add_history will next write to. |
| dict_workbuf_index : base.u32, |
| |
| // dict_seen is the cumulative sum (saturating at 0xFFFF_FFFF) of the |
| // number of bytes passed to add_history. |
| dict_seen : base.u32, |
| |
| decoded_length : base.u64, |
| |
| lzma2_encoded_length_have : base.u64, |
| lzma2_encoded_length_want : base.u64, |
| lzma2_need_prob_reset : base.bool, |
| lzma2_need_properties : base.bool, |
| lzma2_need_dict_reset : base.bool, |
| |
| prev_lzma2_chunk_was_uncompressed : base.bool, |
| |
| allow_non_zero_initial_byte : base.bool, |
| |
| end_of_chunk : base.bool, |
| |
| stashed_bytes : array[2] base.u8, // 0 is prev_byte, 1 is match_byte. |
| stashed_bits : base.u32, |
| stashed_range : base.u32, |
| stashed_state : base.u32[..= 11], |
| stashed_rep0 : base.u32[..= 0xFFFF_FFFE], |
| stashed_rep1 : base.u32[..= 0xFFFF_FFFE], |
| stashed_rep2 : base.u32[..= 0xFFFF_FFFE], |
| stashed_rep3 : base.u32[..= 0xFFFF_FFFE], |
| stashed_pos : base.u64, |
| stashed_pos_end : base.u64, |
| |
| util : base.utility, |
| ) + ( |
| // 12 is the number of states. 4 is the pb inclusive maximum. |
| // |
| // The AlgOveNN line numbers refer to the ./README.md file. |
| probs_ao00 : array[12 << 4] base.u16, // AlgOve00. |
| probs_ao20 : array[12] base.u16, // AlgOve20. |
| probs_ao40 : array[12] base.u16, // AlgOve40. |
| probs_ao41 : array[12 << 4] base.u16, // AlgOve41. |
| probs_ao60 : array[12] base.u16, // AlgOve60. |
| probs_ao63 : array[12] base.u16, // AlgOve63. |
| |
| // AlgOve22 and what "decodeLen expands to". |
| probs_match_len_low : array[1 << 4] array[1 << 3] base.u16, |
| probs_match_len_mid : array[1 << 4] array[1 << 3] base.u16, |
| probs_match_len_high : array[1 << 0] array[1 << 8] base.u16, |
| |
| // AlgOve80 and what "decodeLen expands to". |
| probs_longrep_len_low : array[1 << 4] array[1 << 3] base.u16, |
| probs_longrep_len_mid : array[1 << 4] array[1 << 3] base.u16, |
| probs_longrep_len_high : array[1 << 0] array[1 << 8] base.u16, |
| |
| // AlgOve23. The first index is min(len-2, 3). There are then 64 |
| // possible slots, so a 6-bit binary tree. |
| probs_slot : array[4] array[1 << 6] base.u16, |
| |
| // This holds the "xxxx" binary trees, packed per below. The first |
| // element and last 13 elements are unused. The index_small_dist_base |
| // constant is set to (Low - 1) and index_small_dist_extra variable |
| // starts at 1. (index_small_dist_base + index_small_dist_extra) ranges |
| // in Low .. High during the "decode a binary tree" loop. |
| // |
| // Slot Low .. High H-L NumberOfXByms |
| // 4 1 .. 2 1 1 |
| // 5 2 .. 3 1 1 |
| // 6 3 .. 6 3 2 |
| // 7 6 .. 9 3 2 |
| // 8 9 .. 16 7 3 |
| // 9 16 .. 23 7 3 |
| // 10 23 .. 38 15 4 |
| // 11 38 .. 53 15 4 |
| // 12 53 .. 84 31 5 |
| // 13 84 .. 115 31 5 |
| probs_small_dist : array[128] base.u16, |
| |
| // This holds the "zzzz" binary tree. |
| probs_large_dist : array[1 << 4] base.u16, |
| |
| // 4 is the (lc + lp) inclusive maximum. The 0x300 holds three 8-bit |
| // binary trees. The first is for (state < 7), when the previous packet |
| // was a LITERAL. The other two trees are for (state >= 7), when it was |
| // an LZ-copy. Which tree gets used depends on the bits in the next |
| // historical byte after the LZ-copy-source, up until the point where |
| // those bits don't match the currently decoded literal byte, when it |
| // drops back to the first tree. |
| probs_lit : array[1 << 4] array[0x300] base.u16, |
| |
| // Here's the size (and cumulative size) of each probs_etc array. This |
| // is in number of u16 elements, so multiply by 2 for byte size: |
| // |
| // probs_ao00 192 192 |
| // probs_ao20 12 204 |
| // probs_ao40 12 216 |
| // probs_ao41 192 408 |
| // probs_ao60 12 420 |
| // probs_ao63 12 432 |
| // probs_match_len_low 128 560 |
| // probs_match_len_mid 128 688 |
| // probs_match_len_high 256 944 |
| // probs_longrep_len_low 128 1072 |
| // probs_longrep_len_mid 128 1200 |
| // probs_longrep_len_high 256 1456 |
| // probs_slot 256 1712 |
| // probs_small_dist 128 1840 |
| // probs_large_dist 16 1856 |
| // probs_lit 12288 14144 |
| // |
| // 1856 is the properties-independent portion of "the number of |
| // probabilities we need to track". The properties-dependent |
| // proportion, the probs_lit array, could in theory be dynamically |
| // sized, depending on (lc + lp). For simplicity, it's statically sized |
| // here, large enough for the worst case, subject to ((lc + lp) <= 4) |
| // per the "LZMA2's limited property condition" comment above. Static |
| // (worst case) allocation is also what XZ-embedded does [Ref0]. |
| // |
| // 1856 is slightly smaller than the 1984 (also called NUM_BASE_PROBS) |
| // used in the current (as of 2024; version 18.05) version of the LZMA |
| // SDK [Ref1]. The difference of (2 * ((16 - 12) << 4)) is because the |
| // LZMA SDK uses what it calls (kNumStates2 << kNumPosBitsMax) instead |
| // of (kNumStates << kNumPosBitsMax) for its IsMatch and IsRep0Long |
| // portions (what Wuffs calls probs_ao00 and probs_ao41), and |
| // (kNumStates2 - kNumStates) = (16 - 12). LZMA (the file format) only |
| // has 12 states, so enlarging to 16 is redundant (and consumes more |
| // memory). It's not immediately obvious why the memory layout was |
| // re-arranged, but the layout is shared with LZMA SDK asm code. |
| // |
| // 1856 is slightly larger than the 1846 mentioned in both the current |
| // (2024) version of the LZMA SDK spec [Ref2] and as what an old (2004) |
| // version of the LZMA SDK code calls LZMA_BASE_SIZE [Ref3]. The |
| // difference of (14 - (2 * 2)) has two parts. 14 comes from the "first |
| // element and last 13 elements are unused" probs_small_dist comment |
| // above, so that the LZMA SDK code can pack their probs array tighter. |
| // 2 (twice, for AlgOve22 'match' and AlgOve80 'longrep') comes from |
| // moving what the LZMA SDK calls "Choice" and "Choice2" probabilties, |
| // repurposing what this decoder calls the otherwise-unused |
| // probs_etc_len_low[0][0] and probs_etc_len_mid[0][0] elements. |
| // |
| // [Ref0] |
| // https://github.com/torvalds/linux/blob/052d534373b7ed33712a63d5e17b2b6cdbce84fd/lib/xz/xz_dec_lzma2.c#L211 |
| // |
| // [Ref1] |
| // https://github.com/jljusten/LZMA-SDK/blob/781863cdf592da3e97420f50de5dac056ad352a5/C/LzmaDec.c#L151 |
| // |
| // [Ref2] |
| // https://raw.githubusercontent.com/jljusten/LZMA-SDK/781863cdf592da3e97420f50de5dac056ad352a5/DOC/lzma-specification.txt |
| // |
| // [Ref3] |
| // https://github.com/jljusten/LZMA-SDK/blob/f287b63f6bb8b88e281f18a9295340c732245167/Source/LzmaDecode.h#L48 |
| ) |
| |
| pri func decoder.add_history!(hist: slice base.u8, workbuf: slice base.u8) base.status { |
| var dict_workbuf_index : base.u64[..= 0xFFFF_FFFF] |
| var dict_size : base.u64[..= 0xFFFF_FFFF] |
| var hist_length : base.u64 |
| var s : slice base.u8 |
| var n_copied : base.u64 |
| var n : base.u64 |
| |
| dict_workbuf_index = this.dict_workbuf_index as base.u64 |
| dict_size = this.dict_size as base.u64 |
| if args.hist.length() == 0 { |
| return ok |
| } |
| |
| // 273 is ML, where ML is the Maximum Length of an LZMA Lempel-Ziv (length, |
| // distance) pair. After an add_history call, args.workbuf[dict_size .. |
| // (dict_size + ML)] duplicates args.workbuf[.. ML]. This simplifies |
| // copying up to (ML + 1) bytes from the ringbuffer, as there is no need to |
| // split the copy around the dict_size boundary. |
| // |
| // This technique is similar to the one used in the std/deflate package |
| // (although that package uses a different value of ML, 258). That package |
| // also extends the nominal dict_size by (ML - 1), not ML as is done here, |
| // because std/deflate copies up to ML bytes, but std/lzma copies up to (ML |
| // + 1) bytes. std/lzma also needs to know the later half of the two-byte |
| // cusp: the first byte after the copied history. |
| if args.workbuf.length() < (dict_size + 273) { |
| return base."#bad workbuf length" |
| } |
| assert args.workbuf.length() >= dict_size via "a >= b: a >= (b + c); 0 <= c"(c: 273) |
| assert dict_size <= args.workbuf.length() via "a <= b: b >= a"() |
| |
| hist_length = args.hist.length() |
| if hist_length > 0xFFFF_FFFF { |
| this.dict_seen = 0xFFFF_FFFF |
| } else { |
| this.dict_seen ~sat+= hist_length as base.u32 |
| } |
| |
| s = args.hist |
| if s.length() >= dict_size { |
| s = s.suffix(up_to: dict_size) |
| args.workbuf.copy_from_slice!(s: s) |
| this.dict_workbuf_index = 0 |
| |
| } else if dict_workbuf_index > dict_size { |
| return "#internal error: inconsistent dictionary state" |
| |
| } else { |
| n_copied = args.workbuf[dict_workbuf_index .. dict_size].copy_from_slice!(s: s) |
| if n_copied < s.length() { |
| n = args.workbuf.copy_from_slice!(s: s[n_copied ..]) |
| this.dict_workbuf_index = (n & 0xFFFF_FFFF) as base.u32 |
| } else { |
| n = dict_workbuf_index ~mod+ n_copied |
| if n < dict_size { |
| assert n < 0xFFFF_FFFF via "a < b: a < c; c <= b"(c: dict_size) |
| this.dict_workbuf_index = n as base.u32 |
| } else { |
| this.dict_workbuf_index = 0 |
| } |
| } |
| } |
| |
| // Have the tail duplicate the head. |
| if (273 > dict_size) or (dict_size > args.workbuf.length()) { |
| return "#internal error: inconsistent dictionary state" |
| } |
| assert 273 <= args.workbuf.length() via "a <= b: a <= c; c <= b"(c: dict_size) |
| args.workbuf[dict_size ..].copy_from_slice!(s: args.workbuf[.. 273]) |
| return ok |
| } |
| |
| pub func decoder.get_quirk(key: base.u32) base.u64 { |
| if args.key == QUIRK_ALLOW_NON_ZERO_INITIAL_BYTE { |
| if this.allow_non_zero_initial_byte { |
| return 1 |
| } |
| |
| } else if args.key == QUIRK_FORMAT_EXTENSION { |
| return this.format_extension as base.u64 |
| } |
| return 0 |
| } |
| |
| pub func decoder.set_quirk!(key: base.u32, value: base.u64) base.status { |
| var v : base.u32 |
| var n : base.u32 |
| |
| if args.key == QUIRK_ALLOW_NON_ZERO_INITIAL_BYTE { |
| this.allow_non_zero_initial_byte = args.value > 0 |
| |
| } else if args.key == QUIRK_FORMAT_EXTENSION { |
| if args.value == 0 { |
| this.format_extension = 0 |
| return ok |
| |
| } else if (args.value & 0xFF) == 1 { |
| if (args.value >> 8) <= 0xFF { |
| this.format_extension = (args.value & 0xFFFF_FFFF) as base.u32 |
| v = this.format_extension >> 8 |
| n = (1 as base.u32) << (v & 0x1F) |
| n ~sat-= (n >> 4) * ((v >> 5) & 7) |
| if (n < (1 << 12)) or ((1 << 29) < n) { |
| return base."#bad argument" |
| } |
| this.dict_size = n |
| return ok |
| } |
| |
| } else if (args.value & 0xFF) == 2 { |
| if (args.value >> 8) <= 40 { |
| this.format_extension = (args.value & 0xFFFF_FFFF) as base.u32 |
| v = this.format_extension >> 8 |
| if v < 40 { |
| this.dict_size = (2 | (v & 1)) << ((v >> 1) + 11) |
| } else { |
| this.dict_size = 0xFFFF_FFFF |
| } |
| return ok |
| } |
| } |
| |
| return base."#bad argument" |
| } |
| return base."#unsupported option" |
| } |
| |
| pub func decoder.dst_history_retain_length() base.optional_u63 { |
| return this.util.make_optional_u63(has_value: true, value: 0) |
| } |
| |
| pub func decoder.workbuf_len() base.range_ii_u64 { |
| var m : base.u64 |
| |
| if this.dict_size == 0 { |
| return this.util.make_range_ii_u64(min_incl: 0, max_incl: 0) |
| } |
| m = (this.dict_size as base.u64) + 273 |
| return this.util.make_range_ii_u64(min_incl: m, max_incl: m) |
| } |
| |
| pub func decoder.transform_io?(dst: base.io_writer, src: base.io_reader, workbuf: slice base.u8) { |
| var mark : base.u64 |
| var dti_status : base.status |
| var ah_status : base.status |
| |
| while true { |
| mark = args.dst.mark() |
| dti_status =? this.do_transform_io?(dst: args.dst, src: args.src, workbuf: args.workbuf) |
| if not dti_status.is_suspension() { |
| return dti_status |
| } else if (dti_status == base."$short read") and args.src.is_closed() { |
| return "#truncated input" |
| } |
| ah_status = this.add_history!(hist: args.dst.since(mark: mark), workbuf: args.workbuf) |
| if ah_status.is_error() { |
| return ah_status |
| } |
| yield? dti_status |
| } |
| } |
| |
| pri func decoder.do_transform_io?(dst: base.io_writer, src: base.io_reader, workbuf: roslice base.u8) { |
| var header_byte : base.u8 |
| var c8 : base.u8 |
| var c32 : base.u32 |
| var prop_byte : base.u8 |
| |
| var lc : base.u32[..= 8] |
| var lp : base.u32[..= 4] |
| var pb : base.u32[..= 4] |
| |
| var length : base.u32 |
| var n_copied : base.u32 |
| |
| var smark : base.u64 |
| var status : base.status |
| |
| this.lzma2_need_prob_reset = true |
| this.lzma2_need_properties = true |
| this.lzma2_need_dict_reset = true |
| |
| // Loop over chunks. LZMA1 has only one (implicit) chunk. For LZMA2's chunk |
| // header, see |
| // https://github.com/jljusten/LZMA-SDK/blob/781863cdf592da3e97420f50de5dac056ad352a5/C/Lzma2Dec.c#L17-L23 |
| while true { |
| if (this.format_extension & 0xFF) == 0 { // LZMA1. |
| prop_byte = args.src.read_u8?() |
| if prop_byte >= 225 { |
| return "#bad header" |
| } |
| lc = (prop_byte % 9) as base.u32 |
| prop_byte /= 9 |
| lp = (prop_byte % 5) as base.u32 |
| pb = (prop_byte / 5) as base.u32 |
| if (lc + lp) > 4 { |
| return "#unsupported properties" |
| } |
| this.lc = lc.min(no_more_than: 4) |
| this.lp = lp |
| this.pb = pb |
| |
| c32 = args.src.read_u32le?() |
| this.dict_size = c32.max(no_less_than: 4096) |
| |
| this.decoded_length = args.src.read_u64le?() |
| if (this.decoded_length >= 0x8000_0000_0000_0000) and |
| (this.decoded_length <> 0xFFFF_FFFF_FFFF_FFFF) { |
| return "#unsupported decoded length" |
| } |
| |
| this.initialize_probs!() |
| |
| } else if (this.format_extension & 0xFF) == 1 { // Lzip. |
| this.lc = 3 |
| this.lp = 0 |
| this.pb = 2 |
| this.decoded_length = 0xFFFF_FFFF_FFFF_FFFF |
| this.initialize_probs!() |
| |
| } else { // LZMA2. |
| while args.src.length() <= 0, |
| post args.src.length() > 0, |
| { |
| yield? base."$short read" |
| } |
| |
| if args.src.peek_u8() == 0x00 { |
| args.src.skip_u32_fast!(actual: 1, worst_case: 1) |
| break |
| } |
| |
| header_byte = args.src.read_u8?() |
| if header_byte < 0x80 { |
| if header_byte < 0x02 { |
| this.lzma2_need_prob_reset = true |
| this.lzma2_need_properties = true |
| this.lzma2_need_dict_reset = false |
| this.initialize_dict!() |
| } else if (header_byte > 0x02) or this.lzma2_need_dict_reset { |
| return "#bad LZMA2 header" |
| } |
| this.prev_lzma2_chunk_was_uncompressed = true |
| |
| c32 = args.src.read_u16be_as_u32?() |
| length = 1 + c32 |
| |
| while true { |
| n_copied = args.dst.limited_copy_u32_from_reader!(up_to: length, r: args.src) |
| this.stashed_pos ~sat+= n_copied as base.u64 |
| if length <= n_copied { |
| break |
| } |
| length -= n_copied |
| if args.dst.length() == 0 { |
| yield? base."$short write" |
| } else { |
| yield? base."$short read" |
| } |
| } |
| continue |
| } |
| this.decoded_length = header_byte as base.u64 |
| |
| c32 = args.src.read_u16be_as_u32?() |
| this.decoded_length = ((this.decoded_length & 0x1F) << 16) + ((1 + c32) as base.u64) |
| |
| c32 = args.src.read_u16be_as_u32?() |
| this.lzma2_encoded_length_want = (1 + c32) as base.u64 |
| |
| if header_byte >= 0xA0 { |
| this.initialize_probs!() |
| this.lzma2_need_prob_reset = false |
| } |
| |
| if header_byte >= 0xC0 { |
| prop_byte = args.src.read_u8?() |
| if prop_byte >= 225 { |
| return "#bad LZMA2 header" |
| } |
| lc = (prop_byte % 9) as base.u32 |
| prop_byte /= 9 |
| lp = (prop_byte % 5) as base.u32 |
| pb = (prop_byte / 5) as base.u32 |
| if (lc + lp) > 4 { |
| return "#bad LZMA2 header" |
| } |
| this.lc = lc.min(no_more_than: 4) |
| this.lp = lp |
| this.pb = pb |
| this.lzma2_need_properties = false |
| } |
| |
| if header_byte >= 0xE0 { |
| this.lzma2_need_dict_reset = false |
| this.initialize_dict!() |
| } else if this.prev_lzma2_chunk_was_uncompressed { |
| // Update the stashed prev_byte and matched_byte. We've copied |
| // an uncompressed chunk since the last compressed chunk. |
| this.update_stashed_bytes?(dst: args.dst, workbuf: args.workbuf) |
| } |
| this.prev_lzma2_chunk_was_uncompressed = false |
| |
| if this.lzma2_need_prob_reset or this.lzma2_need_properties or this.lzma2_need_dict_reset { |
| return "#bad LZMA2 header" |
| } |
| } |
| |
| c8 = args.src.read_u8?() |
| if (c8 <> 0x00) and not this.allow_non_zero_initial_byte { |
| return "#bad code" |
| } |
| this.stashed_bits = args.src.read_u32be?() |
| if this.stashed_bits == 0xFFFF_FFFF { |
| return "#bad code" |
| } |
| this.stashed_range = 0xFFFF_FFFF |
| this.stashed_pos_end = this.stashed_pos ~sat+ this.decoded_length |
| if (this.stashed_pos_end == 0xFFFF_FFFF_FFFF_FFFF) and |
| (this.decoded_length <> 0xFFFF_FFFF_FFFF_FFFF) { |
| return "#unsupported decoded length" |
| } |
| this.lzma2_encoded_length_have = 5 |
| |
| while args.workbuf.length() < ((this.dict_size as base.u64) + 273) { |
| yield? base."$short workbuf" |
| } |
| |
| while true { |
| smark = args.src.mark() |
| status =? this.decode_bitstream?(dst: args.dst, src: args.src, workbuf: args.workbuf) |
| this.lzma2_encoded_length_have ~sat+= args.src.count_since(mark: smark) |
| if status.is_ok() { |
| break |
| } |
| yield? status |
| } |
| |
| if this.decoded_length == 0xFFFF_FFFF_FFFF_FFFF { |
| if this.stashed_bits <> 0 { |
| return "#bad bitstream trailer" |
| } |
| } else if this.stashed_pos <> this.stashed_pos_end { |
| return "#bad decoded length" |
| } else if this.stashed_bits <> 0 { |
| // Almost all LZMA chunks have either an explicit decoded_length or |
| // an End of Stream marker. It is legitimate, though, for a chunk |
| // to have both. We cater for that here. |
| this.decode_optional_end_of_stream?(src: args.src, workbuf: args.workbuf) |
| |
| if this.stashed_bits <> 0 { |
| return "#bad bitstream trailer" |
| } |
| } |
| |
| if (this.format_extension & 0xFF) < 2 { |
| break |
| } else if this.lzma2_encoded_length_have <> this.lzma2_encoded_length_want { |
| return "#bad LZMA2 header" |
| } |
| } |
| } |
| |
| pri func decoder.decode_bitstream?(dst: base.io_writer, src: base.io_reader, workbuf: roslice base.u8) { |
| var status : base.status |
| |
| this.end_of_chunk = false |
| while true { |
| status = this.decode_bitstream_fast!(dst: args.dst, src: args.src, workbuf: args.workbuf) |
| if status.is_error() { |
| return status |
| } |
| if this.end_of_chunk { |
| break |
| } |
| this.decode_bitstream_slow?(dst: args.dst, src: args.src, workbuf: args.workbuf) |
| if this.end_of_chunk { |
| break |
| } |
| } |
| } |
| |
| pri func decoder.update_stashed_bytes?(dst: base.io_writer, workbuf: roslice base.u8) { |
| var dist : base.u32 |
| var which : base.u32 |
| var adj_dist : base.u32 |
| var wb_index : base.u64 |
| |
| while args.dst.length() <= 0 { |
| yield? base."$short write" |
| } |
| |
| dist = 1 |
| which = 0 |
| while which < 2 { |
| if (dist as base.u64) <= args.dst.history_length() { |
| args.dst.limited_copy_u32_from_history!(up_to: 1, distance: dist) |
| if not args.dst.can_undo_byte() { |
| return "#internal error: inconsistent dictionary state" |
| } |
| this.stashed_bytes[which] = args.dst.peek_undo_byte() |
| args.dst.undo_byte!() |
| |
| } else { |
| adj_dist = (((dist as base.u64) - args.dst.history_length()) & 0xFFFF_FFFF) as base.u32 |
| wb_index = (this.dict_workbuf_index as base.u64) ~mod- (adj_dist as base.u64) |
| while wb_index >= 0x8000_0000_0000_0000, |
| inv which < 2, |
| { |
| wb_index ~mod+= this.dict_size as base.u64 |
| } |
| if wb_index >= args.workbuf.length() { |
| return "#internal error: inconsistent dictionary state" |
| } |
| this.stashed_bytes[which] = args.workbuf[wb_index] |
| } |
| |
| dist = 1 + this.stashed_rep0 |
| which += 1 |
| } |
| } |
| |
| // Preconditions: |
| // - this.stashed_pos == this.stashed_pos_end |
| // - this.stashed_pos_end < 0xFFFF_FFFF_FFFF_FFFF |
| pri func decoder.decode_optional_end_of_stream?(src: base.io_reader, workbuf: roslice base.u8) { |
| var w : base.io_writer |
| var status : base.status |
| |
| this.stashed_pos_end = 0xFFFF_FFFF_FFFF_FFFF |
| while true { |
| io_bind (io: w, data: this.util.empty_slice_u8(), history_position: 0) { |
| status =? this.decode_bitstream_slow?(dst: w, src: args.src, workbuf: args.workbuf) |
| } |
| if status.is_ok() { |
| break |
| } else if status == base."$short write" { |
| return "#bad bitstream trailer" |
| } |
| yield? status |
| } |
| this.stashed_pos_end = this.stashed_pos |
| } |
| |
| pri func decoder.initialize_dict!() { |
| this.dict_workbuf_index = 0 |
| this.dict_seen = 0 |
| this.stashed_bytes[0] = 0 |
| this.stashed_bytes[1] = 0 |
| this.stashed_pos = 0 |
| } |
| |
| // initialize_probs sets all of the probs_foo[i][j] values to 1024. |
| pri func decoder.initialize_probs!() { |
| var i : base.u32 |
| var j : base.u32 |
| |
| i = 0 |
| while i < (12 << 4) { |
| this.probs_ao00[i] = 1024 |
| i += 1 |
| } |
| |
| i = 0 |
| while i < 12 { |
| this.probs_ao20[i] = 1024 |
| i += 1 |
| } |
| |
| i = 0 |
| while i < 12 { |
| this.probs_ao40[i] = 1024 |
| i += 1 |
| } |
| |
| i = 0 |
| while i < (12 << 4) { |
| this.probs_ao41[i] = 1024 |
| i += 1 |
| } |
| |
| i = 0 |
| while i < 12 { |
| this.probs_ao60[i] = 1024 |
| i += 1 |
| } |
| |
| i = 0 |
| while i < 12 { |
| this.probs_ao63[i] = 1024 |
| i += 1 |
| } |
| |
| i = 0 |
| while i < 16 { |
| j = 0 |
| while j < 8, |
| inv i < 16, |
| { |
| this.probs_match_len_low[i][j] = 1024 |
| j += 1 |
| } |
| i += 1 |
| } |
| |
| i = 0 |
| while i < 16 { |
| j = 0 |
| while j < 8, |
| inv i < 16, |
| { |
| this.probs_match_len_mid[i][j] = 1024 |
| j += 1 |
| } |
| i += 1 |
| } |
| |
| i = 0 |
| while i < 256 { |
| this.probs_match_len_high[0][i] = 1024 |
| i += 1 |
| } |
| |
| i = 0 |
| while i < 16 { |
| j = 0 |
| while j < 8, |
| inv i < 16, |
| { |
| this.probs_longrep_len_low[i][j] = 1024 |
| j += 1 |
| } |
| i += 1 |
| } |
| |
| i = 0 |
| while i < 16 { |
| j = 0 |
| while j < 8, |
| inv i < 16, |
| { |
| this.probs_longrep_len_mid[i][j] = 1024 |
| j += 1 |
| } |
| i += 1 |
| } |
| |
| i = 0 |
| while i < 256 { |
| this.probs_longrep_len_high[0][i] = 1024 |
| i += 1 |
| } |
| |
| i = 0 |
| while i < 4 { |
| j = 0 |
| while j < 64, |
| inv i < 4, |
| { |
| this.probs_slot[i][j] = 1024 |
| j += 1 |
| } |
| i += 1 |
| } |
| |
| i = 0 |
| while i < 128 { |
| this.probs_small_dist[i] = 1024 |
| i += 1 |
| } |
| |
| i = 0 |
| while i < 16 { |
| this.probs_large_dist[i] = 1024 |
| i += 1 |
| } |
| |
| i = 0 |
| while i < 16 { |
| j = 0 |
| while j < 0x300, |
| inv i < 16, |
| { |
| this.probs_lit[i][j] = 1024 |
| j += 1 |
| } |
| i += 1 |
| } |
| |
| // Reset some other LZMA state too. They're not probabilities, but they |
| // need resetting whenever the probabilities do. |
| this.stashed_state = 0 |
| this.stashed_rep0 = 0 |
| this.stashed_rep1 = 0 |
| this.stashed_rep2 = 0 |
| this.stashed_rep3 = 0 |
| } |
| |
| pri const STATE_TRANSITION_LITERAL : roarray[12] base.u8[..= 11] = [ |
| 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x4, 0x5, |
| ] |
| |
| pri const STATE_TRANSITION_MATCH : roarray[12] base.u8[..= 11] = [ |
| 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0xA, 0xA, 0xA, 0xA, 0xA, |
| ] |
| |
| pri const STATE_TRANSITION_LONGREP : roarray[12] base.u8[..= 11] = [ |
| 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0xB, 0xB, 0xB, 0xB, 0xB, |
| ] |
| |
| pri const STATE_TRANSITION_SHORTREP : roarray[12] base.u8[..= 11] = [ |
| 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0xB, 0xB, 0xB, 0xB, 0xB, |
| ] |
| |
| pri const CLAMP_NO_MORE_THAN_3 : roarray[8] base.u8[..= 3] = [ |
| 0, 1, 2, 3, 3, 3, 3, 3, |
| ] |