omnizip 0.3.8 → 0.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +51 -50
  3. data/Gemfile +2 -0
  4. data/Rakefile +3 -0
  5. data/TODO.refactor/00-overview.md +67 -0
  6. data/TODO.refactor/01-registry-base-class.md +81 -0
  7. data/TODO.refactor/02-error-hierarchy.md +57 -0
  8. data/TODO.refactor/03-autoload-entry-point.md +69 -0
  9. data/TODO.refactor/04-filter-base-consolidation.md +49 -0
  10. data/TODO.refactor/05-send-private-methods.md +64 -0
  11. data/TODO.refactor/06-instance-variable-access.md +69 -0
  12. data/TODO.refactor/07-respond-to-replacement.md +91 -0
  13. data/TODO.refactor/08-spec-doubles.md +27 -0
  14. data/TODO.refactor/09-cli-shared-module.md +35 -0
  15. data/TODO.refactor/10-format-detector-ocp.md +34 -0
  16. data/TODO.refactor/11-convenience-decoupling.md +38 -0
  17. data/TODO.refactor/12-thread-safety.md +8 -0
  18. data/TODO.refactor/13-lutaml-model-migration.md +82 -0
  19. data/TODO.refactor/14-add-missing-specs.md +25 -0
  20. data/lib/omnizip/algorithm.rb +0 -2
  21. data/lib/omnizip/algorithm_registry.rb +29 -57
  22. data/lib/omnizip/algorithms/lzma/bit_model.rb +1 -1
  23. data/lib/omnizip/algorithms/lzma/dictionary.rb +4 -3
  24. data/lib/omnizip/algorithms/lzma/distance_coder.rb +5 -102
  25. data/lib/omnizip/algorithms/lzma/length_coder.rb +4 -92
  26. data/lib/omnizip/algorithms/lzma/literal_decoder.rb +4 -119
  27. data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +2 -0
  28. data/lib/omnizip/algorithms/lzma/match_finder.rb +59 -29
  29. data/lib/omnizip/algorithms/lzma/optimal_encoder.rb +20 -12
  30. data/lib/omnizip/algorithms/lzma/range_decoder.rb +77 -269
  31. data/lib/omnizip/algorithms/lzma/range_encoder.rb +0 -9
  32. data/lib/omnizip/algorithms/lzma/xz_range_encoder_exact.rb +0 -15
  33. data/lib/omnizip/algorithms/lzma/xz_state.rb +0 -8
  34. data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +189 -968
  35. data/lib/omnizip/algorithms/lzma2/encoder.rb +0 -2
  36. data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +0 -2
  37. data/lib/omnizip/algorithms/lzma2/properties.rb +0 -2
  38. data/lib/omnizip/algorithms/ppmd7/model.rb +1 -1
  39. data/lib/omnizip/algorithms/ppmd8/context.rb +0 -2
  40. data/lib/omnizip/algorithms/ppmd8/model.rb +1 -1
  41. data/lib/omnizip/algorithms/ppmd8.rb +0 -2
  42. data/lib/omnizip/algorithms/ppmd_base.rb +0 -2
  43. data/lib/omnizip/algorithms/sevenzip_lzma2.rb +0 -5
  44. data/lib/omnizip/algorithms/xz_lzma2.rb +0 -5
  45. data/lib/omnizip/algorithms/zstandard/frame/header.rb +3 -5
  46. data/lib/omnizip/algorithms.rb +1 -0
  47. data/lib/omnizip/archive_handler.rb +75 -0
  48. data/lib/omnizip/archive_handlers/tar_handler.rb +51 -0
  49. data/lib/omnizip/archive_handlers/zip_handler.rb +73 -0
  50. data/lib/omnizip/archive_handlers.rb +11 -0
  51. data/lib/omnizip/checksum_registry.rb +23 -96
  52. data/lib/omnizip/cli/shared.rb +31 -0
  53. data/lib/omnizip/cli.rb +6 -43
  54. data/lib/omnizip/commands/archive_create_command.rb +0 -6
  55. data/lib/omnizip/commands/archive_extract_command.rb +0 -4
  56. data/lib/omnizip/commands/archive_list_command.rb +0 -4
  57. data/lib/omnizip/commands/archive_repair_command.rb +0 -2
  58. data/lib/omnizip/commands/archive_verify_command.rb +0 -2
  59. data/lib/omnizip/commands/compress_command.rb +0 -2
  60. data/lib/omnizip/commands/decompress_command.rb +0 -2
  61. data/lib/omnizip/commands/list_command.rb +0 -2
  62. data/lib/omnizip/commands/metadata_command.rb +0 -5
  63. data/lib/omnizip/commands/parity_create_command.rb +0 -2
  64. data/lib/omnizip/commands/parity_repair_command.rb +0 -2
  65. data/lib/omnizip/commands/parity_verify_command.rb +0 -2
  66. data/lib/omnizip/commands/profile_list_command.rb +0 -2
  67. data/lib/omnizip/commands/profile_show_command.rb +0 -2
  68. data/lib/omnizip/convenience.rb +111 -234
  69. data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +1 -1
  70. data/lib/omnizip/crypto/aes256/cipher.rb +6 -1
  71. data/lib/omnizip/error.rb +22 -18
  72. data/lib/omnizip/filter.rb +5 -3
  73. data/lib/omnizip/filter_registry.rb +74 -98
  74. data/lib/omnizip/filters/bcj2/decoder.rb +196 -130
  75. data/lib/omnizip/filters/bcj2.rb +0 -2
  76. data/lib/omnizip/filters/bcj_x86.rb +0 -2
  77. data/lib/omnizip/filters/filter_base.rb +4 -70
  78. data/lib/omnizip/filters/registry.rb +0 -2
  79. data/lib/omnizip/format_detector.rb +21 -12
  80. data/lib/omnizip/format_registry.rb +79 -38
  81. data/lib/omnizip/formats/bzip2_file.rb +0 -2
  82. data/lib/omnizip/formats/cpio/entry.rb +0 -2
  83. data/lib/omnizip/formats/cpio/reader.rb +0 -1
  84. data/lib/omnizip/formats/cpio/writer.rb +0 -1
  85. data/lib/omnizip/formats/cpio.rb +0 -1
  86. data/lib/omnizip/formats/format_spec_loader.rb +1 -1
  87. data/lib/omnizip/formats/gzip.rb +0 -2
  88. data/lib/omnizip/formats/iso/directory_record.rb +6 -3
  89. data/lib/omnizip/formats/iso/reader.rb +2 -2
  90. data/lib/omnizip/formats/iso.rb +0 -1
  91. data/lib/omnizip/formats/lzip.rb +5 -25
  92. data/lib/omnizip/formats/lzma_alone.rb +7 -25
  93. data/lib/omnizip/formats/msi/cab_extractor.rb +181 -0
  94. data/lib/omnizip/formats/msi/constants.rb +157 -0
  95. data/lib/omnizip/formats/msi/directory_resolver.rb +188 -0
  96. data/lib/omnizip/formats/msi/entry.rb +128 -0
  97. data/lib/omnizip/formats/msi/reader.rb +301 -0
  98. data/lib/omnizip/formats/msi/string_pool.rb +246 -0
  99. data/lib/omnizip/formats/msi/table_parser.rb +244 -0
  100. data/lib/omnizip/formats/msi.rb +84 -0
  101. data/lib/omnizip/formats/ole/allocation_table.rb +10 -1
  102. data/lib/omnizip/formats/ole/dirent.rb +20 -3
  103. data/lib/omnizip/formats/ole/header.rb +0 -1
  104. data/lib/omnizip/formats/ole/ranges_io.rb +21 -3
  105. data/lib/omnizip/formats/ole/storage.rb +19 -4
  106. data/lib/omnizip/formats/ole.rb +0 -2
  107. data/lib/omnizip/formats/rar/block_parser.rb +0 -1
  108. data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +1 -1
  109. data/lib/omnizip/formats/rar/decompressor.rb +38 -1
  110. data/lib/omnizip/formats/rar/header.rb +0 -1
  111. data/lib/omnizip/formats/rar/rar5/writer.rb +0 -2
  112. data/lib/omnizip/formats/rar/reader.rb +2 -3
  113. data/lib/omnizip/formats/rar/writer.rb +0 -1
  114. data/lib/omnizip/formats/rar.rb +2 -2
  115. data/lib/omnizip/formats/rpm/entry.rb +0 -1
  116. data/lib/omnizip/formats/rpm/header.rb +3 -8
  117. data/lib/omnizip/formats/rpm/lead.rb +37 -26
  118. data/lib/omnizip/formats/rpm/tag.rb +0 -1
  119. data/lib/omnizip/formats/rpm/writer.rb +0 -1
  120. data/lib/omnizip/formats/seven_zip/bcj2_stream_decompressor.rb +98 -61
  121. data/lib/omnizip/formats/seven_zip/coder_chain.rb +1 -2
  122. data/lib/omnizip/formats/seven_zip/encoded_header.rb +0 -1
  123. data/lib/omnizip/formats/seven_zip/encrypted_header.rb +0 -1
  124. data/lib/omnizip/formats/seven_zip/header.rb +0 -1
  125. data/lib/omnizip/formats/seven_zip/header_encryptor.rb +0 -1
  126. data/lib/omnizip/formats/seven_zip/header_writer.rb +22 -61
  127. data/lib/omnizip/formats/seven_zip/models/file_entry.rb +1 -1
  128. data/lib/omnizip/formats/seven_zip/parser.rb +14 -13
  129. data/lib/omnizip/formats/seven_zip/reader.rb +82 -19
  130. data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +0 -15
  131. data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +0 -1
  132. data/lib/omnizip/formats/seven_zip/writer.rb +22 -68
  133. data/lib/omnizip/formats/seven_zip.rb +63 -5
  134. data/lib/omnizip/formats/tar/entry.rb +0 -2
  135. data/lib/omnizip/formats/tar/header.rb +0 -2
  136. data/lib/omnizip/formats/tar/reader.rb +0 -2
  137. data/lib/omnizip/formats/tar/writer.rb +0 -2
  138. data/lib/omnizip/formats/tar.rb +0 -1
  139. data/lib/omnizip/formats/xar/entry.rb +0 -1
  140. data/lib/omnizip/formats/xar/header.rb +0 -1
  141. data/lib/omnizip/formats/xar/reader.rb +0 -1
  142. data/lib/omnizip/formats/xar/toc.rb +3 -3
  143. data/lib/omnizip/formats/xar/writer.rb +11 -3
  144. data/lib/omnizip/formats/xar.rb +0 -1
  145. data/lib/omnizip/formats/xz/reader.rb +7 -3
  146. data/lib/omnizip/formats/xz.rb +3 -40
  147. data/lib/omnizip/formats/xz_impl/block_decoder.rb +4 -10
  148. data/lib/omnizip/formats/xz_impl/block_encoder.rb +0 -2
  149. data/lib/omnizip/formats/xz_impl/block_header_parser.rb +0 -2
  150. data/lib/omnizip/formats/xz_impl/index_decoder.rb +0 -1
  151. data/lib/omnizip/formats/xz_impl/index_encoder.rb +0 -1
  152. data/lib/omnizip/formats/xz_impl/stream_encoder.rb +1 -1
  153. data/lib/omnizip/formats/xz_impl/stream_footer.rb +0 -1
  154. data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +0 -1
  155. data/lib/omnizip/formats/xz_impl/stream_header.rb +0 -1
  156. data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +0 -2
  157. data/lib/omnizip/formats/zip/central_directory_header.rb +2 -3
  158. data/lib/omnizip/formats/zip/end_of_central_directory.rb +0 -2
  159. data/lib/omnizip/formats/zip/local_file_header.rb +0 -2
  160. data/lib/omnizip/formats/zip/reader.rb +12 -2
  161. data/lib/omnizip/formats/zip/writer.rb +73 -1
  162. data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +0 -2
  163. data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +0 -2
  164. data/lib/omnizip/formats/zip/zip64_extra_field.rb +0 -2
  165. data/lib/omnizip/formats/zip.rb +0 -1
  166. data/lib/omnizip/formats.rb +9 -22
  167. data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +2 -2
  168. data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +1 -1
  169. data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +0 -6
  170. data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +18 -185
  171. data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +3 -3
  172. data/lib/omnizip/io/source.rb +136 -0
  173. data/lib/omnizip/io.rb +2 -0
  174. data/lib/omnizip/link_handler.rb +7 -3
  175. data/lib/omnizip/metadata/metadata_editor.rb +1 -1
  176. data/lib/omnizip/models/algorithm_metadata.rb +33 -60
  177. data/lib/omnizip/models/compression_options.rb +40 -53
  178. data/lib/omnizip/models/conversion_options.rb +9 -18
  179. data/lib/omnizip/models/parallel_options.rb +25 -0
  180. data/lib/omnizip/optimization_registry.rb +8 -44
  181. data/lib/omnizip/parallel/job_scheduler.rb +26 -8
  182. data/lib/omnizip/parallel/parallel_compressor.rb +10 -20
  183. data/lib/omnizip/parallel/parallel_extractor.rb +20 -11
  184. data/lib/omnizip/parity/par2_repairer.rb +5 -5
  185. data/lib/omnizip/parity/par2_verifier.rb +27 -22
  186. data/lib/omnizip/parity.rb +4 -4
  187. data/lib/omnizip/password/encryption_registry.rb +11 -50
  188. data/lib/omnizip/password/zip_crypto_strategy.rb +1 -4
  189. data/lib/omnizip/pipe/stream_decompressor.rb +2 -3
  190. data/lib/omnizip/profile/archive_profile.rb +0 -2
  191. data/lib/omnizip/profile/binary_profile.rb +0 -2
  192. data/lib/omnizip/profile/compression_profile.rb +0 -1
  193. data/lib/omnizip/profile/profile_detector.rb +0 -2
  194. data/lib/omnizip/profile/text_profile.rb +0 -2
  195. data/lib/omnizip/profile.rb +0 -2
  196. data/lib/omnizip/registry.rb +112 -0
  197. data/lib/omnizip/version.rb +1 -1
  198. data/lib/omnizip/zip/file.rb +20 -9
  199. data/lib/omnizip.rb +26 -55
  200. data/lib/tasks/msi_test_files.rake +66 -0
  201. metadata +57 -6
@@ -33,7 +33,7 @@ module Omnizip
33
33
  # extract the original bit values. It maintains a code value
34
34
  # that represents the current position within the range.
35
35
  class RangeDecoder < RangeCoder
36
- attr_reader :code
36
+ attr_reader :code, :init_bytes_remaining, :range, :stream
37
37
 
38
38
  # Initialize the range decoder
39
39
  #
@@ -48,13 +48,6 @@ module Omnizip
48
48
 
49
49
  # Update the input stream (for LZMA2 multi-chunk streams)
50
50
  #
51
- # When processing LZMA2 chunks, we need to update the stream
52
- # reference for each new chunk while preserving the range decoder
53
- # state (range, code) across chunks.
54
- #
55
- # XZ Utils pattern: The range coder uses a buffer pointer that's
56
- # updated for each chunk, while rc_reset() resets range/code.
57
- #
58
51
  # @param new_stream [IO] New input stream
59
52
  # @return [void]
60
53
  def update_stream(new_stream)
@@ -63,167 +56,77 @@ module Omnizip
63
56
 
64
57
  # Decode a single bit using a probability model
65
58
  #
66
- # The range is split based on the bit's probability,
67
- # and the code value determines which portion contains
68
- # the actual bit value.
69
- #
70
- # XZ Utils pattern (rc_if_0): normalize BEFORE bound calculation
71
- # See: /Users/mulgogi/src/external/xz/src/liblzma/rangecoder/range_decoder.h:181-184
59
+ # This is the hottest method (~5 billion calls for a 600MB file).
60
+ # normalize() and model.update() are inlined to eliminate method dispatch.
72
61
  #
73
62
  # @param model [BitModel] The probability model for this bit
74
63
  # @return [Integer] The decoded bit value (0 or 1)
75
64
  def decode_bit(model)
76
- # XZ Utils: rc_normalize FIRST, then calculate bound
77
- normalize
78
- bound = (@range >> 11) * model.probability
79
-
80
- # DEBUG: Trace model updates to find probability corruption
81
- trace_model_updates = ENV.fetch("TRACE_MODEL_UPDATES", nil)
82
- prob_before = model.probability if trace_model_updates
83
-
84
- # DEBUG: Trace is_rep bit decoding
85
- trace_is_rep = ENV.fetch("TRACE_IS_REP_BITS",
86
- nil) && (bound > 1_000_000)
87
-
88
- if trace_is_rep
89
- puts " [RangeDecoder.decode_bit] BEFORE: range=#{@range}, code=#{@code}, bound=#{bound}, prob=#{model.probability}"
90
- end
91
-
92
- # DEBUG: Trace model selection at dict_full=227
93
- if ENV["TRACE_MODEL_SELECTION"]
94
- begin
95
- ObjectSpace.each_object(Omnizip::Algorithms::XzUtilsDecoder) do |decoder|
96
- dict_full = decoder.instance_variable_get(:@dict_full)
97
- if dict_full && dict_full >= 220 && dict_full <= 235
98
- pos = decoder.instance_variable_get(:@pos)
99
- state = decoder.instance_variable_get(:@state)
100
- puts " [decode_bit] dict_full=#{dict_full}, pos=#{pos}, state=#{state}"
101
- puts " [decode_bit] model.object_id=#{model.object_id}, prob=#{model.probability}"
102
- puts " [decode_bit] range=0x#{@range.to_s(16)}, code=0x#{@code.to_s(16)}, bound=0x#{bound.to_s(16)}"
103
- $stderr.flush
104
- end
105
- break
106
- end
107
- rescue StandardError => e
108
- # Context not available
109
- puts " [decode_bit] ERROR: #{e.message}"
110
- $stderr.flush
65
+ # Inline normalize: only the hot path (range < TOP check)
66
+ # Init bytes are handled eagerly in reset(), not here
67
+ if @range < 0x01000000
68
+ @range <<= 8
69
+ byte = @stream.getbyte
70
+ if byte.nil?
71
+ raise Omnizip::DecompressionError,
72
+ "Truncated LZMA stream during range decoder normalization"
111
73
  end
112
- end
113
-
114
- # DEBUG: Trace decode_bit for lit_state=96 literal decoding
115
- if ENV["TRACE_DECODE_BIT_LIT96"]
116
- puts " decode_bit: range=0x#{@range.to_s(16)}, code=0x#{@code.to_s(16)}, prob=#{model.probability}, bound=0x#{bound.to_s(16)}, code<bound?=#{@code < bound}"
117
- end
118
74
 
119
- # DEBUG: Trace decode_bit for specific problematic state
120
- if ENV.fetch("TRACE_SPECIFIC_DECODE",
121
- nil) && @range == 0x40000000 && @code == 0x21407d82
122
- puts " === CRITICAL DECODE_BIT (MATCHED LITERAL) ==="
123
- puts " BEFORE: range=0x#{@range.to_s(16)} (#{@range})"
124
- puts " BEFORE: code=0x#{@code.to_s(16)} (#{@code})"
125
- puts " probability=#{model.probability}"
126
- puts " bound=0x#{bound.to_s(16)} (#{bound})"
127
- puts " range >> 11 = 0x#{(@range >> 11).to_s(16)} (#{@range >> 11})"
128
- puts " (range >> 11) * probability = 0x#{((@range >> 11) * model.probability).to_s(16)} (#{(@range >> 11) * model.probability})"
129
- puts " code < bound? #{@code < bound}"
130
- puts " result should be: #{@code < bound ? 0 : 1}"
131
- puts " =========================================="
75
+ @code = ((@code << 8) | byte) & 0xFFFFFFFF
132
76
  end
133
77
 
134
- # DEBUG: Trace decode_bit for model_index=257 (the problematic one)
135
- if ENV["TRACE_DECODE_BIT_257"]
136
- # We need to know which model is being used
137
- # Unfortunately, we don't have direct access to the model_index here
138
- puts " [decode_bit] range=0x#{@range.to_s(16)}, code=0x#{@code.to_s(16)}, prob=#{model.probability}, bound=0x#{bound.to_s(16)}, code<bound?=#{@code < bound}, result=#{@code < bound ? 0 : 1}"
139
- end
78
+ prob = model.probability
79
+ bound = (@range >> 11) * prob
140
80
 
141
81
  if @code < bound
142
82
  @range = bound
143
- model.update(0)
144
- if trace_model_updates && prob_before != model.probability
145
- puts " [decode_bit] model UPDATE: #{prob_before} -> #{model.probability} (bit=0, object_id=#{model.object_id})"
146
- end
147
- if trace_is_rep
148
- puts " [RangeDecoder.decode_bit] AFTER (bit=0): range=#{@range}, code=#{@code}"
149
- end
83
+ # Inline model.update(0): prob += (2048 - prob) >> 5
84
+ model.probability = prob + ((2048 - prob) >> 5)
150
85
  0
151
86
  else
152
87
  @code -= bound
153
88
  @range -= bound
154
- model.update(1)
155
- if trace_model_updates && prob_before != model.probability
156
- puts " [decode_bit] model UPDATE: #{prob_before} -> #{model.probability} (bit=1, object_id=#{model.object_id})"
157
- end
158
- if trace_is_rep
159
- puts " [RangeDecoder.decode_bit] AFTER (bit=1): range=#{@range}, code=#{@code}"
160
- end
89
+ # Inline model.update(1): prob -= prob >> 5
90
+ model.probability = prob - (prob >> 5)
161
91
  1
162
92
  end
163
93
  end
164
94
 
165
95
  # Decode bits directly without using probability model
166
96
  #
167
- # This is used for decoding values with uniform distribution
168
- # where all bit values are equally likely.
169
- #
170
97
  # @param num_bits [Integer] Number of bits to decode
171
98
  # @return [Integer] The decoded value
172
99
  def decode_direct_bits(num_bits)
173
100
  result = 0
174
- trace_this = (num_bits == 25)
175
- iteration = 0
176
-
177
- if trace_this
178
- begin
179
- warn " decode_direct_bits START: num_bits=#{num_bits}"
180
- warn " BEFORE: range=#{@range.inspect}, code=#{@code.inspect}"
181
- $stderr.flush
182
- rescue StandardError => e
183
- warn " ERROR in trace: #{e.message}"
184
- $stderr.flush
185
- end
186
- end
187
-
188
- begin
189
- num_bits.downto(1) do |_i|
190
- iteration += 1
191
- normalize
192
- @range >>= 1
193
101
 
194
- bit = @code >= @range ? 1 : 0
195
- if trace_this && iteration <= 3 # Only first 3 iterations
196
- warn " [#{iteration}/#{num_bits}] range=#{@range.inspect}, code=#{@code.inspect}, bit=#{bit}, result=#{result}"
197
- $stderr.flush
102
+ num_bits.downto(1) do |_i|
103
+ # Inline normalize
104
+ if @range < 0x01000000
105
+ @range <<= 8
106
+ byte = @stream.getbyte
107
+ if byte.nil?
108
+ raise Omnizip::DecompressionError,
109
+ "Truncated LZMA stream during range decoder normalization"
198
110
  end
199
111
 
200
- if bit == 1
201
- @code -= @range
202
- result = (result << 1) | 1
203
- else
204
- result = (result << 1) | 0
205
- end
112
+ @code = ((@code << 8) | byte) & 0xFFFFFFFF
206
113
  end
207
- rescue StandardError => e
208
- warn " ERROR in iteration #{iteration}: #{e.message}"
209
- warn " range=#{@range.inspect}, code=#{@code.inspect}"
210
- $stderr.flush
211
- raise
212
- end
114
+ @range >>= 1
115
+
116
+ bit = @code >= @range ? 1 : 0
213
117
 
214
- if trace_this
215
- warn " AFTER #{iteration} iterations: result=#{result}"
216
- $stderr.flush
118
+ if bit == 1
119
+ @code -= @range
120
+ result = (result << 1) | 1
121
+ else
122
+ result = (result << 1) | 0
123
+ end
217
124
  end
218
125
 
219
126
  result
220
127
  end
221
128
 
222
- # Decode a cumulative frequency value
223
- #
224
- # This is used by PPMd for decoding symbols based on their
225
- # frequency distribution. Returns the cumulative frequency
226
- # that can be mapped back to a symbol.
129
+ # Decode a cumulative frequency value (PPMd)
227
130
  #
228
131
  # @param total_freq [Integer] Total frequency of all symbols in context
229
132
  # @return [Integer] The cumulative frequency value
@@ -235,9 +138,6 @@ module Omnizip
235
138
 
236
139
  # Normalize after decoding a symbol with frequency
237
140
  #
238
- # After using decode_freq to get the cumulative frequency,
239
- # call this to update the range decoder state.
240
- #
241
141
  # @param cum_freq [Integer] Cumulative frequency of decoded symbol
242
142
  # @param freq [Integer] Frequency of decoded symbol
243
143
  # @param total_freq [Integer] Total frequency of all symbols
@@ -253,144 +153,84 @@ module Omnizip
253
153
 
254
154
  # Decode bits directly using a base value (XZ Utils rc_direct pattern)
255
155
  #
256
- # This method implements the XZ Utils rc_direct macro which is used
257
- # for decoding distance values in slots 14+. The pattern matches
258
- # XZ Utils' implementation in rangecoder/range_decoder.h:366-375.
259
- #
260
- # XZ Utils rc_direct behavior (from C macro):
261
- # - dest = (dest << 1) + 1 (unconditionally)
262
- # - Normalize range, halve it, subtract from code
263
- # - bound = 0 - (code >> 31) extracts sign bit
264
- # - If code >= range (bit=1): sign=0, bound=0, dest stays at (dest << 1) + 1
265
- # - If code < range (bit=0): sign=1, bound=-1, dest = (dest << 1) + 1 - 1 = dest << 1
266
- # - dest += bound
267
- # - code += range & bound (restore code if bit=0)
268
- #
269
- # In Ruby (without unsigned wraparound), we explicitly check if code >= range
270
- # and undo the +1 if the bit is 0.
271
- #
272
156
  # @param num_bits [Integer] Number of bits to decode
273
157
  # @param base [Integer] Base value to start from (2 or 3 for distances)
274
158
  # @return [Integer] The decoded value
275
159
  def decode_direct_bits_with_base(num_bits, base)
276
160
  result = base
277
- # DEBUG: Trace for slot=40 (num_bits=15)
278
- if ENV["TRACE_DIRECT_BITS_SLOT40"]
279
- puts " [decode_direct_bits_with_base] START: base=#{base}, num_bits=#{num_bits}"
280
- puts " BEFORE: range=0x#{@range.to_s(16)}, code=0x#{@code.to_s(16)}"
281
- end
282
- num_bits.times do |i|
161
+ num_bits.times do |_i|
283
162
  result = (result << 1) + 1
284
- normalize
163
+ # Inline normalize
164
+ if @range < 0x01000000
165
+ @range <<= 8
166
+ byte = @stream.getbyte
167
+ if byte.nil?
168
+ raise Omnizip::DecompressionError,
169
+ "Truncated LZMA stream during range decoder normalization"
170
+ end
171
+
172
+ @code = ((@code << 8) | byte) & 0xFFFFFFFF
173
+ end
285
174
  @range >>= 1
286
175
 
287
- # Check if bit is 1 before modifying @code
288
- # If code >= range, bit is 1; otherwise bit is 0
289
176
  bit = @code >= @range ? 1 : 0
290
177
 
291
- if ENV["TRACE_DIRECT_BITS_SLOT40"] && i < 15
292
- puts " [#{i + 1}/#{num_bits}] bit=#{bit}, result after this step = #{result - (bit.zero? ? 1 : 0)}, range=0x#{@range.to_s(16)}, code=0x#{@code.to_s(16)}"
293
- end
294
-
295
178
  if bit == 1
296
- # Bit is 1: result stays at (result << 1) + 1
297
179
  @code -= @range
298
180
  else
299
- # Bit is 0: undo the +1, result = (result << 1) + 1 - 1 = result << 1
300
181
  result -= 1
301
182
  end
302
183
  end
303
- if ENV["TRACE_DIRECT_BITS_SLOT40"]
304
- puts " [decode_direct_bits_with_base] END: result=#{result}"
305
- end
306
184
  result
307
185
  end
308
186
 
309
187
  # Reset the range decoder for a new chunk
310
188
  #
311
- # This matches XZ Utils rc_reset() behavior:
312
- # - Reset range to UINT32_MAX (0xFFFFFFFF)
313
- # - Reset code to 0
314
- # - Set init_bytes_remaining to 5 (lazy initialization)
315
- # - Let normalize() read the initialization bytes during actual decoding
316
- #
317
- # Called during state reset (control >= 0xA0) to reset the range decoder
318
- # for the new chunk's compressed data.
319
- #
320
- # XZ Utils reference: /Users/mulgogi/src/external/xz/src/liblzma/rangecoder/range_decoder.h:181
189
+ # Resets state only. Call read_init_bytes after the stream is set
190
+ # to the correct input.
321
191
  #
322
192
  # @return [void]
323
193
  def reset
324
- if ENV["LZMA_DEBUG"]
325
- stream_pos = begin
326
- @stream.pos
327
- rescue StandardError
328
- "N/A"
329
- end
330
- warn " RangeDecoder.reset: BEFORE reset, range=0x#{@range.to_s(16)}, code=0x#{@code.to_s(16)}, stream.pos=#{stream_pos}, init_bytes_remaining=#{@init_bytes_remaining}"
331
- end
332
194
  @range = 0xFFFFFFFF
333
195
  @code = 0
334
- # Lazy initialization: set remaining bytes but don't read yet
335
- # normalize() will read these bytes during actual decoding
336
196
  @init_bytes_remaining = 5
337
- if ENV["LZMA_DEBUG"]
338
- stream_pos_after = begin
339
- @stream.pos
340
- rescue StandardError
341
- "N/A"
197
+ end
198
+
199
+ # Eagerly read the 5 initialization bytes from the current stream.
200
+ # Must be called after the stream is set to the correct input.
201
+ #
202
+ # @return [void]
203
+ def read_init_bytes
204
+ while @init_bytes_remaining.positive?
205
+ byte = @stream.getbyte
206
+ if byte.nil?
207
+ raise Omnizip::DecompressionError,
208
+ "Truncated LZMA stream during range decoder initialization"
342
209
  end
343
- warn " RangeDecoder.reset: AFTER reset, code=0x#{@code.to_s(16)}, stream.pos=#{stream_pos_after}, init_bytes_remaining=#{@init_bytes_remaining}"
210
+
211
+ @code = ((@code << 8) | byte) & 0xFFFFFFFF
212
+ @init_bytes_remaining -= 1
344
213
  end
345
214
  end
346
215
 
347
216
  # Normalize the range when it becomes too small
348
217
  #
349
- # When range drops below TOP threshold, shift in a new
350
- # byte from the input stream and scale up the range by 256.
351
- #
352
- # XZ Utils pattern (rc_normalize): uses IF, not WHILE!
353
- # Each normalize call shifts in at most ONE byte.
354
- # See: /Users/mulgogi/src/external/xz/src/liblzma/rangecoder/range_decoder.h:143-149
355
- #
356
- # XZ Utils lazy initialization (range_decoder.h:146-149):
357
- # If init_bytes_remaining > 0, read byte for code initialization
358
- # Otherwise, read byte for range normalization
218
+ # Still needed for decode_freq() and other non-hot paths.
219
+ # The hot-path methods inline normalize directly.
359
220
  #
360
221
  # @return [void]
361
222
  def normalize
362
- # DEBUG: Trace normalize calls
223
+ # Handle lazy initialization if needed (for non-hot paths)
363
224
  if @init_bytes_remaining.positive?
364
- stream_pos_before = begin
365
- @stream.pos
366
- rescue StandardError
367
- "N/A"
368
- end
369
- stream_size = begin
370
- @stream.size
371
- rescue StandardError
372
- "N/A"
373
- end
374
- end
375
-
376
- # XZ Utils: Handle lazy initialization first
377
- # IMPORTANT: Read ALL initialization bytes in a loop, not just one!
378
- # XZ Utils rc_normalize reads one byte per call, but decode_bit only calls
379
- # normalize once at the start, so we need to loop to read all 5 bytes.
380
- while @init_bytes_remaining.positive?
381
- byte = @stream.getbyte
382
- byte ||= 0
383
- code_before = @code
384
- @code = ((code_before << 8) | byte) & 0xFFFFFFFF
385
- @init_bytes_remaining -= 1
225
+ while @init_bytes_remaining.positive?
226
+ byte = @stream.getbyte
227
+ if byte.nil?
228
+ raise Omnizip::DecompressionError,
229
+ "Truncated LZMA stream during range decoder initialization"
230
+ end
386
231
 
387
- if ENV["RANGE_DECODER_TRACE"]
388
- puts "\n=== RangeDecoder.normalize (init_bytes_remaining=#{@init_bytes_remaining + 1}) ==="
389
- puts " stream_pos_before=#{stream_pos_before}, stream_size=#{stream_size}"
390
- puts " byte=0x#{byte.to_s(16).upcase}, code_before=0x#{code_before.to_s(16).upcase}"
391
- puts " (code_before << 8) = 0x#{(code_before << 8).to_s(16).upcase}"
392
- puts " ((code_before << 8) | byte) = 0x#{((code_before << 8) | byte).to_s(16).upcase}"
393
- puts " code_after=0x#{@code.to_s(16).upcase}"
232
+ @code = ((@code << 8) | byte) & 0xFFFFFFFF
233
+ @init_bytes_remaining -= 1
394
234
  end
395
235
  end
396
236
 
@@ -398,15 +238,6 @@ module Omnizip
398
238
  byte = read_byte
399
239
  @range <<= 8
400
240
  @code = ((@code << 8) | byte) & 0xFFFFFFFF
401
- if ENV["RANGE_DECODER_TRACE"]
402
- pos = begin
403
- @stream.pos
404
- rescue StandardError
405
- "N/A"
406
- end
407
- warn " NORMALIZE: pos=#{pos}, byte=0x#{byte.to_s(16).upcase}, code=0x#{@code.to_s(16).upcase}, range=0x#{@range.to_s(16).upcase}"
408
- $stderr.flush
409
- end
410
241
  end
411
242
  end
412
243
 
@@ -414,11 +245,6 @@ module Omnizip
414
245
 
415
246
  # Initialize the decoder by reading the first bytes
416
247
  #
417
- # XZ Utils rc_read_init (range_decoder.h:160-167):
418
- # - Read 5 bytes and construct code value
419
- # - code is uint32_t, so it's automatically masked to 32 bits
420
- # - In Ruby, we need to explicitly mask to ensure 32-bit value
421
- #
422
248
  # @return [void]
423
249
  def init_decoder
424
250
  5.times do
@@ -435,29 +261,11 @@ module Omnizip
435
261
  def read_byte
436
262
  byte = @stream.getbyte
437
263
 
438
- # During normal decoding (after initialization), if we run out of input,
439
- # this indicates corrupted data - the compressed stream ended prematurely
440
264
  if byte.nil? && @initialization_complete && @init_bytes_remaining.zero?
441
265
  raise Omnizip::DecompressionError,
442
266
  "LZMA compressed data exhausted prematurely. The file may be corrupted or the uncompressed size field may be incorrect."
443
267
  end
444
268
 
445
- # Only track as data byte if initialization is complete
446
- if @initialization_complete && @init_bytes_remaining.zero?
447
- pos = begin
448
- @stream.pos
449
- rescue StandardError
450
- "N/A"
451
- end
452
- if ENV["RANGE_DECODER_TRACE"]
453
- warn " READ_BYTE: pos=#{pos.inspect}, byte=0x#{byte.to_s(16).upcase}"
454
- $stderr.flush
455
- end
456
- if ENV["LZMA_DEBUG"]
457
- warn " READ_BYTE: pos=#{pos.inspect}, byte=0x#{byte.to_s(16).upcase}, @code now=0x#{@code.to_s(16)}"
458
- end
459
- end
460
-
461
269
  byte || 0
462
270
  end
463
271
  end
@@ -61,11 +61,6 @@ module Omnizip
61
61
 
62
62
  prob = model.probability
63
63
 
64
- # DEBUG: Trace is_rep bit encoding
65
- if ENV["TRACE_IS_REP_BITS"] && bit.zero?
66
- puts " [RangeEncoder.encode_bit] BEFORE: range=#{@range}, low=#{@low}, prob=#{prob}, bit=#{bit}"
67
- end
68
-
69
64
  if bit.zero?
70
65
  # RC_BIT_0: shrink range to lower portion
71
66
  # rc->range = (rc->range >> 11) * prob
@@ -81,10 +76,6 @@ module Omnizip
81
76
  @range = (@range - bound) & 0xFFFFFFFF
82
77
  end
83
78
 
84
- if ENV["TRACE_IS_REP_BITS"] && bit.zero?
85
- puts " [RangeEncoder.encode_bit] AFTER: range=#{@range}, low=#{@low}"
86
- end
87
-
88
79
  # Update probability model based on the bit value
89
80
  # This matches the decoder's update behavior (proper OOP symmetry)
90
81
  model.update(bit)
@@ -128,7 +128,6 @@ module Omnizip
128
128
 
129
129
  # Queue flush operation (matches XZ Utils rc_flush)
130
130
  def flush
131
- puts "[FLUSH] Adding 5 RC_FLUSH symbols, @count before=#{@count}" if ENV["DEBUG"]
132
131
  5.times do
133
132
  raise "Symbol buffer overflow" if @count >= RC_SYMBOLS_MAX
134
133
 
@@ -136,7 +135,6 @@ module Omnizip
136
135
  @probs[@count] = nil
137
136
  @count += 1
138
137
  end
139
- puts "[FLUSH] @count after=#{@count}" if ENV["DEBUG"]
140
138
  end
141
139
  alias queue_flush flush
142
140
 
@@ -163,8 +161,6 @@ module Omnizip
163
161
  def encode(out, out_pos, out_size)
164
162
  raise "Symbol buffer overflow" if @count > RC_SYMBOLS_MAX
165
163
 
166
- puts "[ENCODE] Start: @count=#{@count} @pos=#{@pos} @out_total=#{@out_total}" if ENV["DEBUG"]
167
-
168
164
  skip_increment = false
169
165
 
170
166
  while @pos < @count
@@ -206,20 +202,11 @@ module Omnizip
206
202
  # Prevent further normalizations (XZ Utils behavior)
207
203
  @range = 0xFFFFFFFF
208
204
 
209
- puts "[ENCODE] RC_FLUSH: @pos=#{@pos} @count=#{@count}" if ENV["DEBUG"]
210
-
211
- iteration = 0
212
205
  # Flush the last five bytes (see rc_flush)
213
206
  begin
214
- iteration += 1
215
- puts "[ENCODE] RC_FLUSH iteration #{iteration}: @pos=#{@pos}" if ENV["DEBUG"]
216
207
  return true if shift_low(out, out_pos, out_size)
217
-
218
- puts "[ENCODE] After shift_low: @pos=#{@pos}" if ENV["DEBUG"]
219
208
  end while (@pos += 1) < @count
220
209
 
221
- puts "[ENCODE] After RC_FLUSH loop: total #{iteration} iterations" if ENV["DEBUG"]
222
-
223
210
  # Reset the range encoder (matches XZ Utils)
224
211
  reset
225
212
  # CRITICAL: Skip the @pos increment at loop end because do-while already did it
@@ -233,8 +220,6 @@ module Omnizip
233
220
  @pos += 1 unless skip_increment
234
221
  end
235
222
 
236
- puts "[ENCODE] End: @count=#{@count} @pos=#{@pos} @out_total=#{@out_total}" if ENV["DEBUG"]
237
-
238
223
  @count = 0
239
224
  @pos = 0
240
225
 
@@ -35,7 +35,6 @@ module Omnizip
35
35
  # Update state after encoding literal
36
36
  # Matches XZ Utils update_literal() macro
37
37
  def update_literal
38
- old_value = @value
39
38
  @value = if @value <= STATE_SHORTREP_LIT_LIT
40
39
  STATE_LIT_LIT
41
40
  elsif @value <= STATE_LIT_SHORTREP
@@ -43,19 +42,12 @@ module Omnizip
43
42
  else
44
43
  @value - 6
45
44
  end
46
- if ENV["LZMA_DEBUG"]
47
- warn "DEBUG: update_literal - state: #{old_value} → #{@value}"
48
- end
49
45
  end
50
46
 
51
47
  # Update state after encoding match
52
48
  # Matches XZ Utils update_match() macro
53
49
  def update_match
54
- old_value = @value
55
50
  @value = @value < LIT_STATES ? STATE_LIT_MATCH : STATE_NONLIT_MATCH
56
- if ENV["LZMA_DEBUG"]
57
- warn "DEBUG: update_match - state: #{old_value} → #{@value}"
58
- end
59
51
  end
60
52
 
61
53
  # Update state after encoding long rep match