Class: Cabriolet::Decompressors::MSZIP

Inherits:
Base
  • Object
show all
Defined in:
lib/cabriolet/decompressors/mszip.rb

Overview

MSZIP handles MSZIP (deflate) compressed data Based on RFC 1951 and libmspack implementation

Constant Summary collapse

FRAME_SIZE =

MSZIP frame size (32KB sliding window)

32_768
LITERAL_MAXSYMBOLS =

Huffman tree constants

288
LITERAL_TABLEBITS =
9
DISTANCE_MAXSYMBOLS =
32
DISTANCE_TABLEBITS =
6
SIGNATURE_BYTE_C =

MSZIP signature bytes

0x43
SIGNATURE_BYTE_K =

ASCII 'C'

0x4B
MAX_SIGNATURE_SEARCH =

Maximum bytes to search for CK signature (prevents infinite loops)

10_000
LIT_LENGTHS =

Match lengths for literal codes 257-285

[
  3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27,
  31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258
].freeze
DIST_OFFSETS =

Match offsets for distance codes 0-29

[
  1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385,
  513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12_289, 16_385, 24_577
].freeze
LIT_EXTRABITS =

Extra bits for literal codes 257-285

[
  0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2,
  2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0
].freeze
DIST_EXTRABITS =

Extra bits for distance codes 0-29

[
  0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6,
  6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13
].freeze
BITLEN_ORDER =

Order of bit length code lengths

[
  16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
].freeze

Instance Attribute Summary

Attributes inherited from Base

#buffer_size, #input, #io_system, #output

Instance Method Summary collapse

Constructor Details

#initialize(io_system, input, output, buffer_size, fix_mszip: false, salvage: false, **_kwargs) ⇒ MSZIP

Initialize MSZIP decompressor

Parameters:



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/cabriolet/decompressors/mszip.rb', line 60

def initialize(io_system, input, output, buffer_size, fix_mszip: false,
salvage: false, **_kwargs)
  super(io_system, input, output, buffer_size)
  @fix_mszip = fix_mszip

  # Initialize sliding window (must be binary to avoid UTF-8 character vs byte mismatch)
  @window = ("\0" * FRAME_SIZE).b
  @window_posn = 0
  @bytes_output = 0
  @window_offset = 0 # Offset into window for unconsumed data (for multi-file CFDATA blocks)

  # Initialize bitstream
  @bitstream = Binary::Bitstream.new(io_system, input, buffer_size,
                                     salvage: salvage)

  # Initialize Huffman trees
  @literal_lengths = Array.new(LITERAL_MAXSYMBOLS, 0)
  @distance_lengths = Array.new(DISTANCE_MAXSYMBOLS, 0)
  @literal_tree = nil
  @distance_tree = nil

  # Cache ENV lookups once at initialization
  @debug_mszip = ENV.fetch("DEBUG_MSZIP", nil)
  @debug_mszip_symbols = ENV.fetch("DEBUG_MSZIP_SYMBOLS", nil)
end

Instance Method Details

#decompress(bytes) ⇒ Integer

Decompress MSZIP data

Parameters:

  • bytes (Integer)

    Number of bytes to decompress

Returns:

  • (Integer)

    Number of bytes decompressed



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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/cabriolet/decompressors/mszip.rb', line 105

def decompress(bytes)
  total_written = 0

  if @debug_mszip
    warn "DEBUG MSZIP.decompress(#{bytes}): ENTRY bytes_output=#{@bytes_output} window_offset=#{@window_offset} window_posn=#{@window_posn}"
  end

  while bytes.positive?
    # Check if we have buffered data from previous inflate
    if @bytes_output.positive?
      if @debug_mszip
        warn "DEBUG MSZIP: Using buffered data: bytes_output=#{@bytes_output} window_offset=#{@window_offset}"
      end

      # Write from buffer
      write_amount = [bytes, @bytes_output].min
      io_system.write(output, @window[@window_offset, write_amount])
      total_written += write_amount
      bytes -= write_amount
      @bytes_output -= write_amount
      @window_offset += write_amount

      if @debug_mszip
        warn "DEBUG MSZIP: After buffer write: total_written=#{total_written} bytes_remaining=#{bytes} bytes_output=#{@bytes_output}"
      end

      # Continue loop to check if we need more data
      next
    end

    # No buffered data - need to inflate a new MSZIP frame
    # Reset window for new frame
    @window_offset = 0
    @window_posn = 0

    # Read 'CK' signature (marks start of MSZIP frame)
    # Every MSZIP frame starts with a CK signature
    if @debug_mszip
      warn "DEBUG MSZIP: Reading CK signature (new MSZIP frame)"
    end
    read_signature

    # Inflate the MSZIP frame (processes deflate blocks until last_block or window full)
    if @debug_mszip
      warn "DEBUG MSZIP: Calling inflate_block"
    end

    begin
      inflate_block
    rescue DecompressionError
      raise unless @fix_mszip

      # In repair mode, pad with zeros
      (@bytes_output...FRAME_SIZE).each do |i|
        @window.setbyte(i, 0)
      end
      @bytes_output = FRAME_SIZE
    end

    if @debug_mszip
      warn "DEBUG MSZIP: After inflate_block: bytes_output=#{@bytes_output} window_posn=#{@window_posn}"
    end

    # Now we have data in the window buffer - loop back to write from it
  end

  if @debug_mszip
    warn "DEBUG MSZIP.decompress: EXIT total_written=#{total_written}"
  end

  total_written
end

#freevoid

This method returns an undefined value.

Free resources used by the decompressor

Releases memory buffers to prevent memory leaks when the decompressor is no longer needed.



92
93
94
95
96
97
98
99
# File 'lib/cabriolet/decompressors/mszip.rb', line 92

def free
  @window = nil
  @bitstream = nil
  @literal_lengths = nil
  @distance_lengths = nil
  @literal_tree = nil
  @distance_tree = nil
end