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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +51 -50
- data/Gemfile +2 -0
- data/Rakefile +3 -0
- data/TODO.refactor/00-overview.md +67 -0
- data/TODO.refactor/01-registry-base-class.md +81 -0
- data/TODO.refactor/02-error-hierarchy.md +57 -0
- data/TODO.refactor/03-autoload-entry-point.md +69 -0
- data/TODO.refactor/04-filter-base-consolidation.md +49 -0
- data/TODO.refactor/05-send-private-methods.md +64 -0
- data/TODO.refactor/06-instance-variable-access.md +69 -0
- data/TODO.refactor/07-respond-to-replacement.md +91 -0
- data/TODO.refactor/08-spec-doubles.md +27 -0
- data/TODO.refactor/09-cli-shared-module.md +35 -0
- data/TODO.refactor/10-format-detector-ocp.md +34 -0
- data/TODO.refactor/11-convenience-decoupling.md +38 -0
- data/TODO.refactor/12-thread-safety.md +8 -0
- data/TODO.refactor/13-lutaml-model-migration.md +82 -0
- data/TODO.refactor/14-add-missing-specs.md +25 -0
- data/lib/omnizip/algorithm.rb +0 -2
- data/lib/omnizip/algorithm_registry.rb +29 -57
- data/lib/omnizip/algorithms/lzma/bit_model.rb +1 -1
- data/lib/omnizip/algorithms/lzma/dictionary.rb +4 -3
- data/lib/omnizip/algorithms/lzma/distance_coder.rb +5 -102
- data/lib/omnizip/algorithms/lzma/length_coder.rb +4 -92
- data/lib/omnizip/algorithms/lzma/literal_decoder.rb +4 -119
- data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +2 -0
- data/lib/omnizip/algorithms/lzma/match_finder.rb +59 -29
- data/lib/omnizip/algorithms/lzma/optimal_encoder.rb +20 -12
- data/lib/omnizip/algorithms/lzma/range_decoder.rb +77 -269
- data/lib/omnizip/algorithms/lzma/range_encoder.rb +0 -9
- data/lib/omnizip/algorithms/lzma/xz_range_encoder_exact.rb +0 -15
- data/lib/omnizip/algorithms/lzma/xz_state.rb +0 -8
- data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +189 -968
- data/lib/omnizip/algorithms/lzma2/encoder.rb +0 -2
- data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +0 -2
- data/lib/omnizip/algorithms/lzma2/properties.rb +0 -2
- data/lib/omnizip/algorithms/ppmd7/model.rb +1 -1
- data/lib/omnizip/algorithms/ppmd8/context.rb +0 -2
- data/lib/omnizip/algorithms/ppmd8/model.rb +1 -1
- data/lib/omnizip/algorithms/ppmd8.rb +0 -2
- data/lib/omnizip/algorithms/ppmd_base.rb +0 -2
- data/lib/omnizip/algorithms/sevenzip_lzma2.rb +0 -5
- data/lib/omnizip/algorithms/xz_lzma2.rb +0 -5
- data/lib/omnizip/algorithms/zstandard/frame/header.rb +3 -5
- data/lib/omnizip/algorithms.rb +1 -0
- data/lib/omnizip/archive_handler.rb +75 -0
- data/lib/omnizip/archive_handlers/tar_handler.rb +51 -0
- data/lib/omnizip/archive_handlers/zip_handler.rb +73 -0
- data/lib/omnizip/archive_handlers.rb +11 -0
- data/lib/omnizip/checksum_registry.rb +23 -96
- data/lib/omnizip/cli/shared.rb +31 -0
- data/lib/omnizip/cli.rb +6 -43
- data/lib/omnizip/commands/archive_create_command.rb +0 -6
- data/lib/omnizip/commands/archive_extract_command.rb +0 -4
- data/lib/omnizip/commands/archive_list_command.rb +0 -4
- data/lib/omnizip/commands/archive_repair_command.rb +0 -2
- data/lib/omnizip/commands/archive_verify_command.rb +0 -2
- data/lib/omnizip/commands/compress_command.rb +0 -2
- data/lib/omnizip/commands/decompress_command.rb +0 -2
- data/lib/omnizip/commands/list_command.rb +0 -2
- data/lib/omnizip/commands/metadata_command.rb +0 -5
- data/lib/omnizip/commands/parity_create_command.rb +0 -2
- data/lib/omnizip/commands/parity_repair_command.rb +0 -2
- data/lib/omnizip/commands/parity_verify_command.rb +0 -2
- data/lib/omnizip/commands/profile_list_command.rb +0 -2
- data/lib/omnizip/commands/profile_show_command.rb +0 -2
- data/lib/omnizip/convenience.rb +111 -234
- data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +1 -1
- data/lib/omnizip/crypto/aes256/cipher.rb +6 -1
- data/lib/omnizip/error.rb +22 -18
- data/lib/omnizip/filter.rb +5 -3
- data/lib/omnizip/filter_registry.rb +74 -98
- data/lib/omnizip/filters/bcj2/decoder.rb +196 -130
- data/lib/omnizip/filters/bcj2.rb +0 -2
- data/lib/omnizip/filters/bcj_x86.rb +0 -2
- data/lib/omnizip/filters/filter_base.rb +4 -70
- data/lib/omnizip/filters/registry.rb +0 -2
- data/lib/omnizip/format_detector.rb +21 -12
- data/lib/omnizip/format_registry.rb +79 -38
- data/lib/omnizip/formats/bzip2_file.rb +0 -2
- data/lib/omnizip/formats/cpio/entry.rb +0 -2
- data/lib/omnizip/formats/cpio/reader.rb +0 -1
- data/lib/omnizip/formats/cpio/writer.rb +0 -1
- data/lib/omnizip/formats/cpio.rb +0 -1
- data/lib/omnizip/formats/format_spec_loader.rb +1 -1
- data/lib/omnizip/formats/gzip.rb +0 -2
- data/lib/omnizip/formats/iso/directory_record.rb +6 -3
- data/lib/omnizip/formats/iso/reader.rb +2 -2
- data/lib/omnizip/formats/iso.rb +0 -1
- data/lib/omnizip/formats/lzip.rb +5 -25
- data/lib/omnizip/formats/lzma_alone.rb +7 -25
- data/lib/omnizip/formats/msi/cab_extractor.rb +181 -0
- data/lib/omnizip/formats/msi/constants.rb +157 -0
- data/lib/omnizip/formats/msi/directory_resolver.rb +188 -0
- data/lib/omnizip/formats/msi/entry.rb +128 -0
- data/lib/omnizip/formats/msi/reader.rb +301 -0
- data/lib/omnizip/formats/msi/string_pool.rb +246 -0
- data/lib/omnizip/formats/msi/table_parser.rb +244 -0
- data/lib/omnizip/formats/msi.rb +84 -0
- data/lib/omnizip/formats/ole/allocation_table.rb +10 -1
- data/lib/omnizip/formats/ole/dirent.rb +20 -3
- data/lib/omnizip/formats/ole/header.rb +0 -1
- data/lib/omnizip/formats/ole/ranges_io.rb +21 -3
- data/lib/omnizip/formats/ole/storage.rb +19 -4
- data/lib/omnizip/formats/ole.rb +0 -2
- data/lib/omnizip/formats/rar/block_parser.rb +0 -1
- data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +1 -1
- data/lib/omnizip/formats/rar/decompressor.rb +38 -1
- data/lib/omnizip/formats/rar/header.rb +0 -1
- data/lib/omnizip/formats/rar/rar5/writer.rb +0 -2
- data/lib/omnizip/formats/rar/reader.rb +2 -3
- data/lib/omnizip/formats/rar/writer.rb +0 -1
- data/lib/omnizip/formats/rar.rb +2 -2
- data/lib/omnizip/formats/rpm/entry.rb +0 -1
- data/lib/omnizip/formats/rpm/header.rb +3 -8
- data/lib/omnizip/formats/rpm/lead.rb +37 -26
- data/lib/omnizip/formats/rpm/tag.rb +0 -1
- data/lib/omnizip/formats/rpm/writer.rb +0 -1
- data/lib/omnizip/formats/seven_zip/bcj2_stream_decompressor.rb +98 -61
- data/lib/omnizip/formats/seven_zip/coder_chain.rb +1 -2
- data/lib/omnizip/formats/seven_zip/encoded_header.rb +0 -1
- data/lib/omnizip/formats/seven_zip/encrypted_header.rb +0 -1
- data/lib/omnizip/formats/seven_zip/header.rb +0 -1
- data/lib/omnizip/formats/seven_zip/header_encryptor.rb +0 -1
- data/lib/omnizip/formats/seven_zip/header_writer.rb +22 -61
- data/lib/omnizip/formats/seven_zip/models/file_entry.rb +1 -1
- data/lib/omnizip/formats/seven_zip/parser.rb +14 -13
- data/lib/omnizip/formats/seven_zip/reader.rb +82 -19
- data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +0 -15
- data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +0 -1
- data/lib/omnizip/formats/seven_zip/writer.rb +22 -68
- data/lib/omnizip/formats/seven_zip.rb +63 -5
- data/lib/omnizip/formats/tar/entry.rb +0 -2
- data/lib/omnizip/formats/tar/header.rb +0 -2
- data/lib/omnizip/formats/tar/reader.rb +0 -2
- data/lib/omnizip/formats/tar/writer.rb +0 -2
- data/lib/omnizip/formats/tar.rb +0 -1
- data/lib/omnizip/formats/xar/entry.rb +0 -1
- data/lib/omnizip/formats/xar/header.rb +0 -1
- data/lib/omnizip/formats/xar/reader.rb +0 -1
- data/lib/omnizip/formats/xar/toc.rb +3 -3
- data/lib/omnizip/formats/xar/writer.rb +11 -3
- data/lib/omnizip/formats/xar.rb +0 -1
- data/lib/omnizip/formats/xz/reader.rb +7 -3
- data/lib/omnizip/formats/xz.rb +3 -40
- data/lib/omnizip/formats/xz_impl/block_decoder.rb +4 -10
- data/lib/omnizip/formats/xz_impl/block_encoder.rb +0 -2
- data/lib/omnizip/formats/xz_impl/block_header_parser.rb +0 -2
- data/lib/omnizip/formats/xz_impl/index_decoder.rb +0 -1
- data/lib/omnizip/formats/xz_impl/index_encoder.rb +0 -1
- data/lib/omnizip/formats/xz_impl/stream_encoder.rb +1 -1
- data/lib/omnizip/formats/xz_impl/stream_footer.rb +0 -1
- data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +0 -1
- data/lib/omnizip/formats/xz_impl/stream_header.rb +0 -1
- data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +0 -2
- data/lib/omnizip/formats/zip/central_directory_header.rb +2 -3
- data/lib/omnizip/formats/zip/end_of_central_directory.rb +0 -2
- data/lib/omnizip/formats/zip/local_file_header.rb +0 -2
- data/lib/omnizip/formats/zip/reader.rb +12 -2
- data/lib/omnizip/formats/zip/writer.rb +73 -1
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +0 -2
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +0 -2
- data/lib/omnizip/formats/zip/zip64_extra_field.rb +0 -2
- data/lib/omnizip/formats/zip.rb +0 -1
- data/lib/omnizip/formats.rb +9 -22
- data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +2 -2
- data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +1 -1
- data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +0 -6
- data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +18 -185
- data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +3 -3
- data/lib/omnizip/io/source.rb +136 -0
- data/lib/omnizip/io.rb +2 -0
- data/lib/omnizip/link_handler.rb +7 -3
- data/lib/omnizip/metadata/metadata_editor.rb +1 -1
- data/lib/omnizip/models/algorithm_metadata.rb +33 -60
- data/lib/omnizip/models/compression_options.rb +40 -53
- data/lib/omnizip/models/conversion_options.rb +9 -18
- data/lib/omnizip/models/parallel_options.rb +25 -0
- data/lib/omnizip/optimization_registry.rb +8 -44
- data/lib/omnizip/parallel/job_scheduler.rb +26 -8
- data/lib/omnizip/parallel/parallel_compressor.rb +10 -20
- data/lib/omnizip/parallel/parallel_extractor.rb +20 -11
- data/lib/omnizip/parity/par2_repairer.rb +5 -5
- data/lib/omnizip/parity/par2_verifier.rb +27 -22
- data/lib/omnizip/parity.rb +4 -4
- data/lib/omnizip/password/encryption_registry.rb +11 -50
- data/lib/omnizip/password/zip_crypto_strategy.rb +1 -4
- data/lib/omnizip/pipe/stream_decompressor.rb +2 -3
- data/lib/omnizip/profile/archive_profile.rb +0 -2
- data/lib/omnizip/profile/binary_profile.rb +0 -2
- data/lib/omnizip/profile/compression_profile.rb +0 -1
- data/lib/omnizip/profile/profile_detector.rb +0 -2
- data/lib/omnizip/profile/text_profile.rb +0 -2
- data/lib/omnizip/profile.rb +0 -2
- data/lib/omnizip/registry.rb +112 -0
- data/lib/omnizip/version.rb +1 -1
- data/lib/omnizip/zip/file.rb +20 -9
- data/lib/omnizip.rb +26 -55
- data/lib/tasks/msi_test_files.rake +66 -0
- metadata +57 -6
|
@@ -26,15 +26,24 @@ module Omnizip
|
|
|
26
26
|
# Match Finder using hash chain algorithm for LZ77 compression
|
|
27
27
|
# Ported from XZ Utils lz_encoder.c
|
|
28
28
|
class MatchFinder
|
|
29
|
-
|
|
29
|
+
# Larger hash table for better distribution
|
|
30
|
+
# 16384 slots with 4-byte keys gives better collision resistance
|
|
31
|
+
HASH_SIZE = 16384
|
|
32
|
+
HASH_MASK = HASH_SIZE - 1
|
|
30
33
|
MAX_MATCHES = 274
|
|
34
|
+
# Maximum hash chain depth to prevent O(n) lookups
|
|
35
|
+
# XZ Utils uses different values based on compression level (4-256)
|
|
36
|
+
# Ruby needs much lower values due to slower string operations
|
|
37
|
+
# Testing shows depth 16 gives same compression ratio as 32-64 but 2-3x faster
|
|
38
|
+
DEFAULT_CHAIN_DEPTH = 16
|
|
31
39
|
|
|
32
40
|
attr_reader :dictionary, :buffer, :position
|
|
33
41
|
|
|
34
|
-
def initialize(dictionary)
|
|
42
|
+
def initialize(dictionary, chain_depth: DEFAULT_CHAIN_DEPTH)
|
|
35
43
|
@dictionary = dictionary
|
|
36
44
|
@buffer = String.new(encoding: Encoding::BINARY)
|
|
37
45
|
@position = 0
|
|
46
|
+
@chain_depth = chain_depth
|
|
38
47
|
# Use nil as empty marker (not 0) to distinguish from position 0
|
|
39
48
|
@hash_table = Array.new(HASH_SIZE, nil)
|
|
40
49
|
@hash_chain = Array.new(0)
|
|
@@ -84,38 +93,32 @@ module Omnizip
|
|
|
84
93
|
# @return [Array<Match>] Array of matches sorted by length (descending)
|
|
85
94
|
def find_matches(current_pos = @buffer.bytesize - 273)
|
|
86
95
|
# Calculate hash for current position
|
|
96
|
+
# Note: Using 4-byte hash now (calc_hash checks pos + 4)
|
|
87
97
|
hash = nil
|
|
88
|
-
if current_pos >= 0 && current_pos +
|
|
98
|
+
if current_pos >= 0 && current_pos + 4 <= @buffer.bytesize
|
|
89
99
|
hash = calc_hash(@buffer, current_pos)
|
|
90
100
|
end
|
|
91
101
|
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
# XZ Utils calls this "skip" - update hash without finding matches
|
|
95
|
-
# CRITICAL: Only update if this position hasn't been processed yet
|
|
96
|
-
# (i.e., @hash_table[hash] != current_pos)
|
|
97
|
-
# This prevents overwriting the hash chain when find_matches is called
|
|
98
|
-
# after skip() has already initialized the hash table
|
|
99
|
-
if hash && @hash_table[hash] != current_pos
|
|
100
|
-
@hash_chain[current_pos] = @hash_table[hash]
|
|
101
|
-
@hash_table[hash] = current_pos
|
|
102
|
-
end
|
|
102
|
+
# Skip hash table update here - skip() already built the table for all positions
|
|
103
|
+
# Adding current_pos to the chain is redundant since skip processed all positions
|
|
103
104
|
|
|
104
105
|
# Can't find matches if no hash or insufficient data
|
|
105
106
|
# Note: We CAN find matches at early positions (e.g., position 2 can match position 0)
|
|
106
|
-
# The only requirement is that there's enough data for hash calculation (current_pos +
|
|
107
|
+
# The only requirement is that there's enough data for hash calculation (current_pos + 4 <= buffer size)
|
|
107
108
|
# and that there's at least 2 bytes of history (for MIN_MATCH_LENGTH=2)
|
|
108
109
|
# CRITICAL: Don't produce matches until position >= 2 to ensure decoder has enough dict_full
|
|
109
110
|
# The decoder validates: dict_full > distance, where dict_full starts at 0 after 1st byte
|
|
110
111
|
# For distance=1 match to be valid, decoder needs dict_full >= 2 (at least 2 bytes decoded)
|
|
111
112
|
# This happens after processing position 1 (first byte was literal at position 0)
|
|
112
113
|
# So we can only produce matches starting at position 2
|
|
113
|
-
return [] if hash.nil? || @buffer.bytesize < 4 || current_pos +
|
|
114
|
+
return [] if hash.nil? || @buffer.bytesize < 4 || current_pos + 4 > @buffer.bytesize || current_pos < 2
|
|
114
115
|
|
|
115
116
|
@matches_count = 0
|
|
116
117
|
chain_pos = @hash_chain[current_pos]
|
|
118
|
+
chain_depth = 0
|
|
117
119
|
|
|
118
|
-
while chain_pos && @matches_count < MAX_MATCHES
|
|
120
|
+
while chain_pos && @matches_count < MAX_MATCHES && chain_depth < @chain_depth
|
|
121
|
+
chain_depth += 1
|
|
119
122
|
# CRITICAL: Skip invalid chain_pos values (beyond buffer or negative)
|
|
120
123
|
next if chain_pos >= @buffer.bytesize || chain_pos.negative?
|
|
121
124
|
|
|
@@ -168,34 +171,61 @@ module Omnizip
|
|
|
168
171
|
|
|
169
172
|
private
|
|
170
173
|
|
|
171
|
-
# Calculate hash for position
|
|
174
|
+
# Calculate hash for position using 4-byte key with mixing
|
|
175
|
+
# Uses FNV-1a inspired mixing for better distribution
|
|
172
176
|
#
|
|
173
177
|
# @param data [String] Buffer data
|
|
174
178
|
# @param pos [Integer] Position to hash
|
|
175
179
|
# @return [Integer] Hash value
|
|
176
180
|
def calc_hash(data, pos)
|
|
177
|
-
return 0 if pos +
|
|
178
|
-
|
|
179
|
-
(
|
|
180
|
-
|
|
181
|
-
|
|
181
|
+
return 0 if pos + 4 > data.bytesize
|
|
182
|
+
|
|
183
|
+
# Read 4 bytes as 32-bit integer (little-endian)
|
|
184
|
+
v = data.getbyte(pos) |
|
|
185
|
+
(data.getbyte(pos + 1) << 8) |
|
|
186
|
+
(data.getbyte(pos + 2) << 16) |
|
|
187
|
+
(data.getbyte(pos + 3) << 24)
|
|
188
|
+
|
|
189
|
+
# FNV-1a style mixing for better distribution
|
|
190
|
+
# Hash avalanche: each input bit affects all output bits
|
|
191
|
+
v = (v ^ (v >> 16)) * 0x45D9F3B
|
|
192
|
+
v = (v ^ (v >> 16)) * 0x45D9F3B
|
|
193
|
+
(v ^ (v >> 16)) & HASH_MASK
|
|
182
194
|
end
|
|
183
195
|
|
|
184
196
|
# Verify match length between two positions
|
|
197
|
+
# Uses 4-byte quick check then binary search with integer comparison
|
|
185
198
|
#
|
|
186
199
|
# @param pos1 [Integer] First position
|
|
187
200
|
# @param pos2 [Integer] Second position
|
|
188
201
|
# @return [Integer] Match length
|
|
189
202
|
def verify_match(pos1, pos2)
|
|
190
|
-
|
|
191
|
-
|
|
203
|
+
buf = @buffer
|
|
204
|
+
max_len = [273, buf.bytesize - pos1, buf.bytesize - pos2].min
|
|
205
|
+
return 0 if max_len <= 0
|
|
206
|
+
|
|
207
|
+
# Quick rejection: check first 4 bytes as integer
|
|
208
|
+
if max_len >= 4
|
|
209
|
+
v1 = buf.byteslice(pos1, 4).unpack1("L<")
|
|
210
|
+
v2 = buf.byteslice(pos2, 4).unpack1("L<")
|
|
211
|
+
return 0 if v1 != v2
|
|
212
|
+
end
|
|
192
213
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
214
|
+
# Binary search for exact match length
|
|
215
|
+
# Creates O(log n) strings but does efficient integer comparison
|
|
216
|
+
low = 0
|
|
217
|
+
high = max_len
|
|
218
|
+
|
|
219
|
+
while low < high
|
|
220
|
+
mid = (low + high + 1) / 2
|
|
221
|
+
if buf.byteslice(pos1, mid) == buf.byteslice(pos2, mid)
|
|
222
|
+
low = mid
|
|
223
|
+
else
|
|
224
|
+
high = mid - 1
|
|
225
|
+
end
|
|
196
226
|
end
|
|
197
227
|
|
|
198
|
-
|
|
228
|
+
low
|
|
199
229
|
end
|
|
200
230
|
end
|
|
201
231
|
end
|
|
@@ -31,8 +31,6 @@ module Omnizip
|
|
|
31
31
|
buf_pos = position
|
|
32
32
|
buf_avail = [buf.bytesize - buf_pos, MATCH_LEN_MAX].min
|
|
33
33
|
|
|
34
|
-
# puts "[OPTIMUM] position=#{position} buf_avail=#{buf_avail} buf.bytesize=#{buf.bytesize}" if ENV['DEBUG']
|
|
35
|
-
|
|
36
34
|
# Not enough input for a match
|
|
37
35
|
return [UINT32_MAX, 1] if buf_avail < 2
|
|
38
36
|
|
|
@@ -51,26 +49,36 @@ module Omnizip
|
|
|
51
49
|
# In Ruby: buf_back_index = buf_pos - rep_dist - 1
|
|
52
50
|
buf_back_index = buf_pos - rep_dist - 1
|
|
53
51
|
|
|
54
|
-
# DEBUG: Trace repeated match check
|
|
55
|
-
# puts "[OPTIMAL] Checking rep#{i}: rep_dist=#{rep_dist} buf_pos=#{buf_pos} buf_back_index=#{buf_back_index}" if ENV['DEBUG']
|
|
56
|
-
|
|
57
52
|
# Skip if out of bounds
|
|
58
53
|
next if buf_back_index.negative? || buf_back_index >= buf.bytesize
|
|
59
54
|
|
|
60
|
-
# puts "[OPTIMAL] buf[#{buf_pos}]=#{'%02x' % buf.getbyte(buf_pos)} buf[#{buf_back_index}]=#{'%02x' % buf.getbyte(buf_back_index)}" if ENV['DEBUG']
|
|
61
|
-
|
|
62
55
|
# If the first two bytes (2 == MATCH_LEN_MIN) do not match,
|
|
63
56
|
# this rep is not useful.
|
|
64
57
|
next if buf.getbyte(buf_pos) != buf.getbyte(buf_back_index) ||
|
|
65
58
|
buf.getbyte(buf_pos + 1) != buf.getbyte(buf_back_index + 1)
|
|
66
59
|
|
|
67
60
|
# The first two bytes matched. Calculate the length of the match.
|
|
61
|
+
# Use 64-bit integer comparison for speed (no string allocation)
|
|
62
|
+
max_match_len = [
|
|
63
|
+
buf.bytesize - buf_pos,
|
|
64
|
+
buf.bytesize - buf_back_index,
|
|
65
|
+
MATCH_LEN_MAX,
|
|
66
|
+
buf_avail,
|
|
67
|
+
].min
|
|
68
|
+
|
|
68
69
|
len = 2
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
# Compare 8 bytes at a time using 64-bit integers
|
|
71
|
+
while len + 8 <= max_match_len
|
|
72
|
+
# Read 8 bytes as little-endian 64-bit integers
|
|
73
|
+
v1 = buf.byteslice(buf_pos + len, 8).unpack1("Q<")
|
|
74
|
+
v2 = buf.byteslice(buf_back_index + len, 8).unpack1("Q<")
|
|
75
|
+
break if v1 != v2
|
|
76
|
+
|
|
77
|
+
len += 8
|
|
78
|
+
end
|
|
79
|
+
# Finalize with byte-by-byte for remaining bytes
|
|
80
|
+
while len < max_match_len &&
|
|
81
|
+
buf.getbyte(buf_pos + len) == buf.getbyte(buf_back_index + len)
|
|
74
82
|
len += 1
|
|
75
83
|
end
|
|
76
84
|
|