Class: Omnizip::Implementations::XZUtils::LZMA2::Decoder

Inherits:
Object
  • Object
show all
Includes:
Algorithms::LZMA2Const
Defined in:
lib/omnizip/implementations/xz_utils/lzma2/decoder.rb

Overview

XZ Utils LZMA2 decoder implementation.

This is the original Decoder moved from algorithms/lzma2/decoder.rb to the new namespace structure.

Constant Summary

Constants included from Algorithms::LZMA2Const

Algorithms::LZMA2Const::CHUNK_MAX, Algorithms::LZMA2Const::CONTROL_END, Algorithms::LZMA2Const::CONTROL_LZMA_MIN, Algorithms::LZMA2Const::CONTROL_UNCOMPRESSED, Algorithms::LZMA2Const::CONTROL_UNCOMPRESSED_RESET, Algorithms::LZMA2Const::DICT_SIZE_MAX, Algorithms::LZMA2Const::DICT_SIZE_MIN, Algorithms::LZMA2Const::FLAG_RESET_DICT, Algorithms::LZMA2Const::FLAG_RESET_PROPERTIES, Algorithms::LZMA2Const::FLAG_RESET_STATE, Algorithms::LZMA2Const::FLAG_UNCOMPRESSED_SIZE, Algorithms::LZMA2Const::HEADER_MAX, Algorithms::LZMA2Const::HEADER_UNCOMPRESSED, Algorithms::LZMA2Const::UNCOMPRESSED_MAX

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input, options = {}) ⇒ Decoder

Initialize the decoder

Parameters:

  • input (IO)

    Input stream of compressed data

  • options (Hash) (defaults to: {})

    Decoding options

Options Hash (options):

  • :raw_mode (Boolean)

    If true, skip property byte reading (for XZ format)

  • :dict_size (Integer)

    Dictionary size to use (required for raw_mode)



44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/omnizip/implementations/xz_utils/lzma2/decoder.rb', line 44

def initialize(input, options = {})
  @input = input
  @options = options
  @raw_mode = options[:raw_mode] || false

  if @raw_mode
    # In raw_mode (XZ format), property byte is provided by caller
    # Only dict_size comes from the XZ filter properties
    @dict_size = options[:dict_size] || (8 * 1024 * 1024)
    @properties = Omnizip::Algorithms::LZMA2::Properties.new(@dict_size)
  else
    read_property_byte
  end
end

Instance Attribute Details

#dict_sizeObject (readonly)

Returns the value of attribute dict_size.



36
37
38
# File 'lib/omnizip/implementations/xz_utils/lzma2/decoder.rb', line 36

def dict_size
  @dict_size
end

Instance Method Details

#decode_streamString

Decode a compressed stream

XZ Utils pattern (lzma2_decoder.c):

  • LZMA decoder is created ONCE and reused across all chunks
  • State (dictionary, probability models) persists between chunks
  • Reset only when control byte indicates new properties (control >= 0xC0)

Returns:

  • (String)

    Decompressed data



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/omnizip/implementations/xz_utils/lzma2/decoder.rb', line 67

def decode_stream
  output_io = StringIO.new
  output_io.set_encoding("ASCII-8BIT")

  # XZ Utils pattern: Create LZMA decoder ONCE (lzma2_decoder_init)
  # The decoder will be reused across all chunks
  @lzma_decoder = nil
  @need_properties = true # First LZMA chunk needs properties (XZ Utils line 45)
  @need_dictionary_reset = true # First chunk must reset dictionary (XZ Utils line 43)

  chunk_num = 0
  loop do
    control = read_control_byte

    break if control == CONTROL_END

    # XZ Utils pattern (lzma2_decoder.c:75-82):
    # Dictionary reset is needed if control >= 0xE0 or control == 1
    # If dictionary reset is needed but control doesn't do it, error
    # Reference: /Users/mulgogi/src/external/xz/src/liblzma/lzma/lzma2_decoder.c:75-82
    if control >= 0xE0 || control == CONTROL_UNCOMPRESSED_RESET
      @need_properties = true
      @need_dictionary_reset = true
    elsif @need_dictionary_reset
      raise Omnizip::FormatError,
            "LZMA2 dictionary reset required but not performed (control=0x#{control.to_s(16).upcase})"
    end

    # XZ Utils pattern (lzma2_decoder.c:121-126):
    # Clear dictionary reset flag. The actual dict_reset is handled
    # by the LZMA decoder when processing the chunk data.
    # Reference: /Users/mulgogi/src/external/xz/src/liblzma/lzma/lzma2_decoder.c:121-127
    if @need_dictionary_reset
      @need_dictionary_reset = false
    end

    # XZ Utils pattern (lzma2_decoder.c:84-110):
    # For LZMA chunks (control >= 0x80), validate properties requirements
    # Reference: /Users/mulgogi/src/external/xz/src/liblzma/lzma/lzma2_decoder.c:98-99
    if control >= 0x80
      if control >= 0xC0
        # New properties present - properties will be read below
        @need_properties = false
      elsif @need_properties
        # LZMA chunk without properties but properties are needed
        # This happens after dictionary reset when next chunk must have properties
        raise Omnizip::FormatError,
              "LZMA2 properties required but not provided (control=0x#{control.to_s(16).upcase})"
      end
    end

    chunk_data = decode_chunk(control, chunk_num)

    # XZ Utils pattern: Uncompressed chunks ALWAYS produce output
    # Dictionary reset chunks (control == 1) initialize the dictionary
    # with the chunk data, then the dictionary is flushed to output
    # So we should NEVER skip output for valid chunks
    # Reference: /Users/mulgogi/src/external/xz/src/liblzma/lzma/lzma2_decoder.c:121-127
    output_io.write(chunk_data)
    chunk_num += 1
  end

  output_io.string.force_encoding("ASCII-8BIT")
end