omnizip 0.3.13 → 0.3.15

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 (88) hide show
  1. checksums.yaml +4 -4
  2. data/lib/omnizip/algorithm.rb +36 -1
  3. data/lib/omnizip/algorithms/bzip2.rb +1 -3
  4. data/lib/omnizip/algorithms/deflate.rb +1 -3
  5. data/lib/omnizip/algorithms/deflate64.rb +2 -2
  6. data/lib/omnizip/algorithms/lzma/optimal_encoder.rb +7 -8
  7. data/lib/omnizip/algorithms/lzma.rb +1 -1
  8. data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +5 -1
  9. data/lib/omnizip/algorithms/lzma2.rb +4 -12
  10. data/lib/omnizip/algorithms/zstandard/constants.rb +38 -25
  11. data/lib/omnizip/algorithms/zstandard/decoder.rb +101 -136
  12. data/lib/omnizip/algorithms/zstandard/encoder.rb +81 -138
  13. data/lib/omnizip/algorithms/zstandard/frame/block.rb +17 -0
  14. data/lib/omnizip/algorithms/zstandard/frame/header.rb +42 -29
  15. data/lib/omnizip/algorithms/zstandard/fse/bitstream.rb +114 -117
  16. data/lib/omnizip/algorithms/zstandard/fse/encoder.rb +434 -222
  17. data/lib/omnizip/algorithms/zstandard/fse/interleaved.rb +92 -0
  18. data/lib/omnizip/algorithms/zstandard/fse/table.rb +98 -176
  19. data/lib/omnizip/algorithms/zstandard/fse/table_description.rb +203 -0
  20. data/lib/omnizip/algorithms/zstandard/fse.rb +21 -2
  21. data/lib/omnizip/algorithms/zstandard/huffman.rb +195 -188
  22. data/lib/omnizip/algorithms/zstandard/huffman_encoder.rb +232 -255
  23. data/lib/omnizip/algorithms/zstandard/literals.rb +170 -103
  24. data/lib/omnizip/algorithms/zstandard/literals_encoder.rb +47 -199
  25. data/lib/omnizip/algorithms/zstandard/sequences.rb +260 -252
  26. data/lib/omnizip/algorithms/zstandard/xxhash.rb +159 -0
  27. data/lib/omnizip/algorithms/zstandard.rb +20 -24
  28. data/lib/omnizip/algorithms.rb +14 -1
  29. data/lib/omnizip/buffer.rb +1 -2
  30. data/lib/omnizip/commands/archive_list_command.rb +9 -7
  31. data/lib/omnizip/convenience.rb +1 -2
  32. data/lib/omnizip/entry.rb +44 -0
  33. data/lib/omnizip/extraction/selective_extractor.rb +2 -10
  34. data/lib/omnizip/filter_pipeline.rb +1 -1
  35. data/lib/omnizip/filter_registry.rb +18 -16
  36. data/lib/omnizip/filters/bcj2.rb +1 -1
  37. data/lib/omnizip/filters/bcj_arm.rb +1 -1
  38. data/lib/omnizip/filters/bcj_arm64.rb +1 -1
  39. data/lib/omnizip/filters/bcj_ia64.rb +1 -1
  40. data/lib/omnizip/filters/bcj_ppc.rb +1 -1
  41. data/lib/omnizip/filters/bcj_sparc.rb +1 -1
  42. data/lib/omnizip/filters/bcj_x86.rb +1 -1
  43. data/lib/omnizip/filters.rb +0 -2
  44. data/lib/omnizip/formats/bzip2_file.rb +0 -7
  45. data/lib/omnizip/formats/cpio/entry.rb +6 -0
  46. data/lib/omnizip/formats/cpio.rb +0 -6
  47. data/lib/omnizip/formats/gzip.rb +0 -7
  48. data/lib/omnizip/formats/iso/directory_record.rb +7 -0
  49. data/lib/omnizip/formats/iso.rb +0 -5
  50. data/lib/omnizip/formats/lzip.rb +0 -7
  51. data/lib/omnizip/formats/lzma_alone.rb +0 -6
  52. data/lib/omnizip/formats/msi/entry.rb +6 -0
  53. data/lib/omnizip/formats/msi.rb +0 -9
  54. data/lib/omnizip/formats/ole/dirent.rb +6 -0
  55. data/lib/omnizip/formats/ole.rb +0 -9
  56. data/lib/omnizip/formats/rar.rb +0 -6
  57. data/lib/omnizip/formats/rar3/reader.rb +7 -0
  58. data/lib/omnizip/formats/rar5/reader.rb +7 -0
  59. data/lib/omnizip/formats/rpm/entry.rb +6 -0
  60. data/lib/omnizip/formats/seven_zip/models/file_entry.rb +7 -0
  61. data/lib/omnizip/formats/seven_zip.rb +0 -6
  62. data/lib/omnizip/formats/tar/entry.rb +6 -0
  63. data/lib/omnizip/formats/tar.rb +2 -10
  64. data/lib/omnizip/formats/xar/entry.rb +6 -0
  65. data/lib/omnizip/formats/xar.rb +0 -6
  66. data/lib/omnizip/formats/zip.rb +0 -6
  67. data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +33 -31
  68. data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +177 -163
  69. data/lib/omnizip/implementations.rb +18 -4
  70. data/lib/omnizip/io/source.rb +3 -0
  71. data/lib/omnizip/metadata/entry_metadata.rb +7 -0
  72. data/lib/omnizip/parallel/engine.rb +48 -0
  73. data/lib/omnizip/parallel/parallel_compressor.rb +5 -16
  74. data/lib/omnizip/parallel/parallel_extractor.rb +5 -16
  75. data/lib/omnizip/parallel.rb +1 -0
  76. data/lib/omnizip/profile/profile_registry.rb +1 -2
  77. data/lib/omnizip/registry.rb +0 -1
  78. data/lib/omnizip/rubyzip_compat.rb +0 -1
  79. data/lib/omnizip/temp/temp_file.rb +1 -2
  80. data/lib/omnizip/version.rb +1 -1
  81. data/lib/omnizip/zip/entry.rb +7 -0
  82. data/lib/omnizip/zip/file.rb +4 -8
  83. data/lib/omnizip.rb +1 -1
  84. metadata +7 -6
  85. data/lib/omnizip/filters/filter_base.rb +0 -6
  86. data/lib/omnizip/filters/registration.rb +0 -22
  87. data/lib/omnizip/filters/registry.rb +0 -111
  88. data/lib/omnizip/format_registry.rb +0 -100
@@ -23,318 +23,326 @@
23
23
  module Omnizip
24
24
  module Algorithms
25
25
  class Zstandard
26
- # Sequences section decoder (RFC 8878 Section 3.1.1.3.2)
26
+ # Sequences section decoder (RFC 8878 §3.1.1.3.2) and the LZ77
27
+ # executor (§3.1.2).
27
28
  #
28
- # Decodes sequences of (literals_length, match_length, offset)
29
- # which are then executed to produce the decompressed output.
29
+ # Decoding follows the C reference ZSTD_decodeSequence:
30
+ #
31
+ # - State initialization order: LL, OF, ML.
32
+ # - Per sequence: resolve the offset first (reading its extra
33
+ # bits and updating the repeat-offset slots), then read the ML
34
+ # extra bits, then the LL extra bits.
35
+ # - State updates (non-last sequences) in order LL, ML, OF.
30
36
  class SequencesDecoder
31
37
  include Constants
32
38
 
33
- # @return [Array<Hash>] Decoded sequences
39
+ Sequence = Struct.new(:literal_length, :match_length, :offset)
40
+
41
+ # @return [Array<Sequence>]
34
42
  attr_reader :sequences
35
43
 
36
- # Parse and decode sequences section
44
+ # @return [Hash] FSE tables for the next block's Repeat mode
45
+ attr_reader :fse_tables
46
+
47
+ # Decode the sequences section at the head of `input`.
37
48
  #
38
- # @param input [IO] Input stream positioned at sequences section
39
- # @param literals_size [Integer] Size of decoded literals
40
- # @param previous_tables [Hash] Previous FSE tables for REPEAT mode
41
- # @return [SequencesDecoder] Decoder with decoded sequences
42
- def self.decode(input, literals_size, previous_tables = {})
43
- decoder = new(input, literals_size, previous_tables)
44
- decoder.decode_section
45
- decoder
49
+ # @param input [String]
50
+ # @param previous_tables [Hash] tables carried from the previous
51
+ # compressed block (:ll, :ml, :of entries)
52
+ # @param executor [SequenceExecutor] repeat-offset state,
53
+ # updated in place while decoding
54
+ # @return [SequencesDecoder]
55
+ def self.decode(input, previous_tables = {}, executor = nil)
56
+ new(input, previous_tables, executor || SequenceExecutor.new).decode_section
46
57
  end
47
58
 
48
- # Initialize decoder
49
- #
50
- # @param input [IO] Input stream
51
- # @param literals_size [Integer] Size of decoded literals
52
- # @param previous_tables [Hash] Previous FSE tables
53
- def initialize(input, literals_size, previous_tables = {})
59
+ def initialize(input, previous_tables, executor)
54
60
  @input = input
55
- @literals_size = literals_size
56
61
  @previous_tables = previous_tables
62
+ @executor = executor
57
63
  @sequences = []
58
64
  @fse_tables = {}
59
65
  end
60
66
 
61
- # Decode the sequences section
62
- #
63
- # @return [void]
67
+ # rubocop:disable Metrics/MethodLength
68
+ # rubocop:disable Metrics/AbcSize
64
69
  def decode_section
65
- # Read number of sequences
66
- num_sequences = read_sequence_count
70
+ num_sequences, offset = read_sequence_count
71
+ return self if num_sequences.zero?
67
72
 
68
- return if num_sequences.zero?
69
-
70
- # Read symbol compression modes
71
- modes = read_symbol_modes
72
-
73
- # Build FSE tables based on modes
74
- build_fse_tables(modes)
75
-
76
- # Decode sequences
77
- decode_sequences_internal(num_sequences)
73
+ if @input.bytesize <= offset
74
+ raise Omnizip::DecompressionError,
75
+ "truncated sequences section: missing modes byte"
76
+ end
77
+ # rubocop:enable Metrics/AbcSize
78
+
79
+ modes = @input.getbyte(offset)
80
+ ll_mode = (modes >> 6) & 0x03
81
+ of_mode = (modes >> 4) & 0x03
82
+ ml_mode = (modes >> 2) & 0x03
83
+
84
+ cursor = offset + 1
85
+ @ll_table, cursor = build_table(ll_mode, :ll,
86
+ PREDEFINED_LL_DISTRIBUTION,
87
+ LITERALS_LENGTH_ACCURACY_LOG, cursor)
88
+ @of_table, cursor = build_table(of_mode, :of,
89
+ PREDEFINED_OFFSET_DISTRIBUTION,
90
+ OFFSET_ACCURACY_LOG, cursor)
91
+ @ml_table, cursor = build_table(ml_mode, :ml,
92
+ PREDEFINED_ML_DISTRIBUTION,
93
+ MATCH_LENGTH_ACCURACY_LOG, cursor)
94
+
95
+ decode_sequences(num_sequences, @input.byteslice(cursor..))
96
+ self
78
97
  end
98
+ # rubocop:enable Metrics/AbcSize
99
+ # rubocop:enable Metrics/MethodLength
79
100
 
80
101
  private
81
102
 
82
- # Read sequence count (1-3 bytes)
103
+ # Sequence count: 1-3 bytes (RFC 8878 §3.1.1.3.2.1).
104
+ # rubocop:disable Metrics/AbcSize
83
105
  def read_sequence_count
84
- byte1 = @input.read(1).ord
106
+ b0 = @input.getbyte(0)
107
+ if b0 < 0x80
108
+ [b0, 1]
109
+ elsif b0 == 0xFF
110
+ if @input.bytesize < 3
111
+ raise Omnizip::DecompressionError,
112
+ "truncated 3-byte sequence count"
113
+ end
114
+ # rubocop:enable Metrics/AbcSize
85
115
 
86
- if byte1.zero?
87
- 0
88
- elsif byte1 < 128
89
- byte1
116
+ [@input.byteslice(1, 2).unpack1("v") + 0x7F00, 3]
90
117
  else
91
- byte2 = @input.read(1).ord
92
- ((byte1 - 0x80) << 8) + byte2 + 0x80
93
- end
94
- end
95
-
96
- # Read symbol compression modes for LL, ML, OF
97
- def read_symbol_modes
98
- modes_byte = @input.read(1).ord
99
-
100
- {
101
- ll: (modes_byte >> 6) & 0x03, # Literals length mode
102
- of: (modes_byte >> 4) & 0x03, # Offset mode
103
- ml: (modes_byte >> 2) & 0x03, # Match length mode
104
- }
105
- end
118
+ if @input.bytesize < 2
119
+ raise Omnizip::DecompressionError,
120
+ "truncated 2-byte sequence count"
121
+ end
106
122
 
107
- # Build FSE tables based on compression modes
108
- def build_fse_tables(modes)
109
- @fse_tables[:ll] = build_fse_table(modes[:ll], :ll)
110
- @fse_tables[:ml] = build_fse_table(modes[:ml], :ml)
111
- @fse_tables[:of] = build_fse_table(modes[:of], :of)
123
+ [((b0 - 0x80) << 8) + @input.getbyte(1), 2]
124
+ end
112
125
  end
113
126
 
114
- # Build a single FSE table
115
- def build_fse_table(mode, type)
116
- case mode
117
- when MODE_PREDEFINED
118
- build_predefined_table(type)
119
- when MODE_RLE
120
- build_rle_table(type)
121
- when MODE_FSE
122
- build_fse_table_from_stream(type)
123
- when MODE_REPEAT
124
- @previous_tables[type] || build_predefined_table(type)
125
- end
127
+ # Build the table for one symbol type. Returns [table, new_cursor].
128
+ # rubocop:disable Metrics/AbcSize
129
+ def build_table(mode, type, predef_distribution, predef_log, cursor)
130
+ table = case mode
131
+ when MODE_PREDEFINED
132
+ FSE::Table.build_predefined(predef_distribution.to_a,
133
+ predef_log)
134
+ when MODE_RLE
135
+ if cursor >= @input.bytesize
136
+ raise Omnizip::DecompressionError,
137
+ "RLE mode: missing symbol byte"
138
+ end
139
+ # rubocop:enable Metrics/AbcSize
140
+
141
+ symbol = @input.getbyte(cursor)
142
+ limit = { ll: LITERAL_LENGTH_TABLE.length,
143
+ ml: MATCH_LENGTH_TABLE.length,
144
+ of: OF_BASE.length }[type]
145
+ if symbol >= limit
146
+ raise Omnizip::DecompressionError,
147
+ "RLE symbol #{symbol} out of range for #{type}"
148
+ end
149
+
150
+ FSE::Table.build_rle(symbol, 0)
151
+ when MODE_FSE
152
+ tbl, consumed = FSE.read_table(@input.byteslice(cursor..))
153
+ attach_code_values(tbl, type)
154
+ @fse_tables[type] = tbl
155
+ return [tbl, cursor + consumed]
156
+ when MODE_REPEAT
157
+ prev = @previous_tables[type]
158
+ if prev.nil?
159
+ raise Omnizip::DecompressionError,
160
+ "repeat mode for #{type} without a previous table"
161
+ end
162
+
163
+ return [prev, cursor]
164
+ else
165
+ raise Omnizip::DecompressionError,
166
+ "invalid sequence mode #{mode}"
167
+ end
168
+
169
+ attach_code_values(table, type)
170
+ @fse_tables[type] = table
171
+ cursor2 = mode == MODE_RLE ? cursor + 1 : cursor
172
+ [table, cursor2]
126
173
  end
127
174
 
128
- # Build predefined FSE table
129
- def build_predefined_table(type)
175
+ # Annotate each FSE state with the (base, extra_bits) of the LL /
176
+ # ML / OF code its symbol maps to, following the C reference's
177
+ # precomputed seqSymbol layout.
178
+ # rubocop:disable Metrics/AbcSize
179
+ def attach_code_values(table, type)
130
180
  case type
131
181
  when :ll
132
- FSE::Table.build_predefined(PREDEFINED_LL_DISTRIBUTION.to_a,
133
- LITERALS_LENGTH_ACCURACY_LOG)
182
+ table.states.each do |st|
183
+ base, bits = LITERAL_LENGTH_TABLE[st.symbol] || [0, 0]
184
+ st.base_val = base
185
+ st.nb_add_bits = bits
186
+ end
187
+ # rubocop:enable Metrics/AbcSize
134
188
  when :ml
135
- FSE::Table.build_predefined(PREDEFINED_ML_DISTRIBUTION.to_a,
136
- MATCH_LENGTH_ACCURACY_LOG)
189
+ table.states.each do |st|
190
+ base, bits = MATCH_LENGTH_TABLE[st.symbol] || [0, 0]
191
+ st.base_val = base
192
+ st.nb_add_bits = bits
193
+ end
137
194
  when :of
138
- FSE::Table.build_predefined(PREDEFINED_OFFSET_DISTRIBUTION.to_a,
139
- OFFSET_ACCURACY_LOG)
195
+ table.states.each do |st|
196
+ st.base_val = OF_BASE[st.symbol] || 0
197
+ st.nb_add_bits = OF_BITS[st.symbol] || 0
198
+ end
140
199
  end
141
200
  end
142
201
 
143
- # Build RLE FSE table (single symbol repeated)
144
- def build_rle_table(type)
145
- # Read the repeated symbol
146
- symbol = @input.read(1).ord
147
-
148
- # Create a simple distribution with just this symbol
149
- distribution = Array.new(symbol_count_for_type(type), 0)
150
- distribution[symbol] = 1 << accuracy_log_for_type(type)
151
-
152
- FSE::Table.build(distribution, accuracy_log_for_type(type))
153
- end
154
-
155
- # Build FSE table from compressed stream
156
- def build_fse_table_from_stream(type)
157
- # Read accuracy log
158
- @input.read(1).ord
159
-
160
- # For simplicity, return predefined table
161
- # Full implementation would read compressed distribution
162
- build_predefined_table(type)
163
- end
202
+ # rubocop:disable Metrics/MethodLength
203
+ # rubocop:disable Metrics/AbcSize
204
+ def decode_sequences(count, bitstream_bytes)
205
+ bs = FSE::BitStream.new(bitstream_bytes)
206
+
207
+ ll_tbl = @ll_table
208
+ of_tbl = @of_table
209
+ ml_tbl = @ml_table
210
+ ll_state = bs.read_bits(ll_tbl.accuracy_log)
211
+ bs.reload
212
+ of_state = bs.read_bits(of_tbl.accuracy_log)
213
+ bs.reload
214
+ ml_state = bs.read_bits(ml_tbl.accuracy_log)
215
+ bs.reload
216
+
217
+ count.times do |seq_idx|
218
+ is_last = seq_idx == count - 1
219
+
220
+ ll_e = ll_tbl[ll_state]
221
+ of_e = of_tbl[of_state]
222
+ ml_e = ml_tbl[ml_state]
223
+
224
+ ll0 = ll_e.base_val.zero?
225
+ offset = @executor.resolve_offset(of_e, ll0, bs)
226
+ match_length = ml_e.base_val + bs.read_bits(ml_e.nb_add_bits)
227
+ literal_length = ll_e.base_val + bs.read_bits(ll_e.nb_add_bits)
228
+
229
+ unless is_last
230
+ ll_state = ll_e.baseline + bs.read_bits(ll_e.num_bits)
231
+ ml_state = ml_e.baseline + bs.read_bits(ml_e.num_bits)
232
+ of_state = of_e.baseline + bs.read_bits(of_e.num_bits)
233
+ bs.reload
234
+ end
235
+ # rubocop:enable Metrics/AbcSize
164
236
 
165
- # Decode sequences using FSE tables
166
- def decode_sequences_internal(count)
167
- return if count.zero?
168
-
169
- # Read the bitstream (remaining data in block)
170
- bitstream_data = @input.read # Read remaining data
171
- bitstream = FSE::BitStream.new(bitstream_data)
172
-
173
- # Initialize FSE decoders
174
- ll_decoder = FSE::Decoder.new(@fse_tables[:ll]) if @fse_tables[:ll]
175
- ml_decoder = FSE::Decoder.new(@fse_tables[:ml]) if @fse_tables[:ml]
176
- of_decoder = FSE::Decoder.new(@fse_tables[:of]) if @fse_tables[:of]
177
-
178
- # Initialize states
179
- ll_decoder&.init_state(bitstream)
180
- ml_decoder&.init_state(bitstream)
181
- of_decoder&.init_state(bitstream)
182
-
183
- # Decode each sequence
184
- count.times do
185
- ll_symbol = ll_decoder ? ll_decoder.decode(bitstream) : 0
186
- ml_symbol = ml_decoder ? ml_decoder.decode(bitstream) : 0
187
- of_symbol = of_decoder ? of_decoder.decode(bitstream) : 0
188
-
189
- # Convert symbols to actual values
190
- ll_value = decode_literal_length(ll_symbol, bitstream)
191
- ml_value = decode_match_length(ml_symbol, bitstream)
192
- of_value = decode_offset(of_symbol, bitstream)
193
-
194
- @sequences << {
195
- literals_length: ll_value,
196
- match_length: ml_value,
197
- offset: of_value,
198
- }
237
+ @sequences << Sequence.new(literal_length, match_length, offset)
199
238
  end
200
239
  end
240
+ # rubocop:enable Metrics/MethodLength
241
+ end
201
242
 
202
- # Decode literal length value from symbol
203
- def decode_literal_length(symbol, bitstream)
204
- return 0 if symbol.nil? || symbol.negative? || symbol >= LITERAL_LENGTH_TABLE.length
205
-
206
- baseline, extra_bits = LITERAL_LENGTH_TABLE[symbol]
207
- return baseline if extra_bits.zero?
208
-
209
- extra = bitstream.read_bits(extra_bits)
210
- baseline + extra
211
- end
212
-
213
- # Decode match length value from symbol
214
- def decode_match_length(symbol, bitstream)
215
- return 3 if symbol.nil? || symbol.negative? || symbol >= MATCH_LENGTH_TABLE.length
216
-
217
- baseline, extra_bits = MATCH_LENGTH_TABLE[symbol]
218
- return baseline if extra_bits.zero?
219
-
220
- extra = bitstream.read_bits(extra_bits)
221
- baseline + extra
222
- end
243
+ # Stateful LZ77 sequence executor (RFC 8878 §3.1.2). Tracks the
244
+ # three repeat-offset slots across the frame; the slots are
245
+ # updated while decoding each sequence's offset.
246
+ class SequenceExecutor
247
+ include Constants
223
248
 
224
- # Decode offset value from symbol
225
- def decode_offset(symbol, _bitstream)
226
- # Offsets 1-3 are repeat offsets
227
- return symbol if symbol <= 3
249
+ # @return [Array<Integer>] repeat offsets, most recent first
250
+ attr_reader :repeat_offsets
228
251
 
229
- # For offsets > 3, read extra bits
230
- # The offset is symbol - 3 plus extra bits
231
- symbol - 3
232
- end
233
-
234
- # Get symbol count for type
235
- def symbol_count_for_type(type)
236
- case type
237
- when :ll then LITERAL_LENGTH_TABLE.length
238
- when :ml then MATCH_LENGTH_TABLE.length
239
- when :of then 32
240
- end
252
+ def initialize
253
+ @repeat_offsets = DEFAULT_REPEAT_OFFSETS.dup
241
254
  end
242
255
 
243
- # Get accuracy log for type
244
- def accuracy_log_for_type(type)
245
- case type
246
- when :ll then LITERALS_LENGTH_ACCURACY_LOG
247
- when :ml then MATCH_LENGTH_ACCURACY_LOG
248
- when :of then OFFSET_ACCURACY_LOG
249
- end
256
+ # Reset to the frame defaults [1, 4, 8].
257
+ def reset
258
+ @repeat_offsets = DEFAULT_REPEAT_OFFSETS.dup
250
259
  end
251
- end
252
260
 
253
- # Sequence executor (RFC 8878 Section 3.1.2.2.3)
254
- #
255
- # Executes decoded sequences to produce output.
256
- class SequenceExecutor
257
- include Constants
258
-
259
- # Execute sequences to produce decompressed output
261
+ # Resolve a sequence offset (C reference semantics).
260
262
  #
261
- # @param literals [String] Decoded literals
262
- # @param sequences [Array<Hash>] Decoded sequences
263
- # @return [String] Decompressed output
264
- def self.execute(literals, sequences)
265
- executor = new
266
- executor.execute(literals, sequences)
267
- end
263
+ # Offset codes 0 and 1 address the repeat slots; the exact
264
+ # behaviour depends on ll0 (whether the sequence emits no
265
+ # literals). Codes >= 2 carry a real distance:
266
+ # distance = OF_BASE[code] + read(code bits).
267
+ #
268
+ # @param of_e [FSE::State] offset table entry for this sequence
269
+ # @param ll0 [Boolean] literal length is zero
270
+ # @param bs [FSE::BitStream]
271
+ # @return [Integer] resolved byte distance
272
+ # rubocop:disable Metrics/AbcSize
273
+ def resolve_offset(of_e, ll0, bs)
274
+ of_bits = of_e.nb_add_bits
275
+ prev = @repeat_offsets
276
+
277
+ if of_bits > 1
278
+ offset = of_e.base_val + bs.read_bits(of_bits)
279
+ prev[2] = prev[1]
280
+ prev[1] = prev[0]
281
+ prev[0] = offset
282
+ offset
283
+ elsif of_bits.zero?
284
+ offset = prev[ll0 ? 1 : 0]
285
+ if ll0
286
+ prev[1] = prev[0]
287
+ prev[0] = offset
288
+ end
289
+ # rubocop:enable Metrics/AbcSize
290
+ offset
291
+ else
292
+ # of_bits == 1: the "rep3-1" special family.
293
+ selector = of_e.base_val + (ll0 ? 1 : 0) + bs.read_bits(1)
294
+ temp = case selector
295
+ when 1 then prev[1]
296
+ when 3 then prev[0] - 1
297
+ else prev[2]
298
+ end
299
+ if temp.zero?
300
+ raise Omnizip::DecompressionError,
301
+ "repeat offset 1 - 1 evaluated to 0 (corrupt stream)"
302
+ end
268
303
 
269
- # Initialize with default repeat offsets
270
- def initialize
271
- @repeat_offsets = DEFAULT_REPEAT_OFFSETS.dup
304
+ prev[2] = prev[1] unless selector == 1
305
+ prev[1] = prev[0]
306
+ prev[0] = temp
307
+ temp
308
+ end
272
309
  end
273
310
 
274
- # Execute sequences
311
+ # Execute decoded sequences against the literal buffer,
312
+ # appending to output and returning the produced bytes.
275
313
  #
276
- # @param literals [String] Decoded literals
277
- # @param sequences [Array<Hash>] Decoded sequences
278
- # @return [String] Decompressed output
279
- def execute(literals, sequences)
280
- output = String.new(encoding: Encoding::BINARY)
314
+ # @param literals [String]
315
+ # @param sequences [Array<SequencesDecoder::Sequence>]
316
+ # @param output [String] accumulator (frame output so far)
317
+ # @return [String] the block's output
318
+ # rubocop:disable Metrics/AbcSize
319
+ def execute(literals, sequences, output)
281
320
  lit_pos = 0
321
+ block_start = output.bytesize
282
322
 
283
323
  sequences.each do |seq|
284
- ll = seq[:literals_length] || 0
285
- ml = seq[:match_length] || 0
286
- offset_code = seq[:offset] || 0
287
-
288
- # Copy literals
289
- if ll.positive? && lit_pos < literals.length
290
- copy_len = [ll, literals.length - lit_pos].min
291
- output << literals.slice(lit_pos, copy_len)
292
- lit_pos += copy_len
324
+ ll = seq.literal_length
325
+ if ll.positive?
326
+ take = [ll, literals.bytesize - lit_pos].min
327
+ output << literals.byteslice(lit_pos, take)
328
+ lit_pos += take
293
329
  end
330
+ # rubocop:enable Metrics/AbcSize
294
331
 
295
- # Handle match
296
- if ml.positive?
297
- offset = resolve_offset(offset_code)
298
-
299
- if offset.positive? && offset <= output.length
300
- # Copy match from output history
301
- match_str = output.slice(-offset, [ml, offset].min) || ""
302
- # If match is longer than offset, we need to copy byte by byte
303
- while match_str.length < ml && output.length.positive?
304
- match_str << match_str[-offset] if offset <= match_str.length
305
- end
306
- output << match_str.slice(0, ml)
307
- end
332
+ distance = seq.offset
333
+ if distance.zero? || distance > output.bytesize
334
+ raise Omnizip::DecompressionError,
335
+ "match distance #{distance} exceeds decoded output " \
336
+ "length #{output.bytesize}"
308
337
  end
309
- end
310
338
 
311
- # Copy remaining literals (last sequence has no match)
312
- if lit_pos < literals.length
313
- output << literals.slice(lit_pos..-1)
339
+ ml = seq.match_length
340
+ src_start = output.bytesize - distance
341
+ ml.times { |i| output << output.getbyte(src_start + (i % distance)) }
314
342
  end
315
343
 
316
- output
317
- end
318
-
319
- private
320
-
321
- # Resolve offset code to actual offset
322
- def resolve_offset(code)
323
- case code
324
- when 1
325
- @repeat_offsets[0]
326
- when 2
327
- @repeat_offsets[1]
328
- when 3
329
- @repeat_offsets[2]
330
- else
331
- # New offset - update repeat offsets
332
- actual_offset = code - 3
333
- @repeat_offsets[2] = @repeat_offsets[1]
334
- @repeat_offsets[1] = @repeat_offsets[0]
335
- @repeat_offsets[0] = actual_offset
336
- actual_offset
337
- end
344
+ output << literals.byteslice(lit_pos..) if lit_pos < literals.bytesize
345
+ output.byteslice(block_start..)
338
346
  end
339
347
  end
340
348
  end