omnizip 0.4.7-aarch64-linux

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 (632) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +32 -0
  4. data/.rubocop_todo.yml +293 -0
  5. data/CHANGELOG.md +1647 -0
  6. data/CONTEXT.md +68 -0
  7. data/COPYING +502 -0
  8. data/Gemfile +19 -0
  9. data/LICENSE +12 -0
  10. data/README.adoc +1327 -0
  11. data/Rakefile +15 -0
  12. data/TODO.refactor/00-overview.md +67 -0
  13. data/TODO.refactor/01-registry-base-class.md +81 -0
  14. data/TODO.refactor/02-error-hierarchy.md +57 -0
  15. data/TODO.refactor/03-autoload-entry-point.md +69 -0
  16. data/TODO.refactor/04-filter-base-consolidation.md +49 -0
  17. data/TODO.refactor/05-send-private-methods.md +64 -0
  18. data/TODO.refactor/06-instance-variable-access.md +69 -0
  19. data/TODO.refactor/07-respond-to-replacement.md +237 -0
  20. data/TODO.refactor/08-spec-doubles.md +27 -0
  21. data/TODO.refactor/09-cli-shared-module.md +35 -0
  22. data/TODO.refactor/10-format-detector-ocp.md +34 -0
  23. data/TODO.refactor/11-convenience-decoupling.md +38 -0
  24. data/TODO.refactor/12-thread-safety.md +8 -0
  25. data/TODO.refactor/13-lutaml-model-migration.md +158 -0
  26. data/TODO.refactor/14-add-missing-specs.md +25 -0
  27. data/benchmark/README.md +260 -0
  28. data/benchmark/benchmark_suite.rb +125 -0
  29. data/benchmark/compression_bench.rb +181 -0
  30. data/benchmark/filter_bench.rb +180 -0
  31. data/benchmark/models/benchmark_result.rb +59 -0
  32. data/benchmark/models/comparison_result.rb +69 -0
  33. data/benchmark/profile_suite.rb +167 -0
  34. data/benchmark/reporter.rb +150 -0
  35. data/benchmark/run_benchmarks.rb +66 -0
  36. data/benchmark/test_data.rb +137 -0
  37. data/config/formats/rar3_spec.yml +92 -0
  38. data/config/formats/rar5_spec.yml +102 -0
  39. data/docs/.github/workflows/docs.yml +142 -0
  40. data/docs/.gitignore +21 -0
  41. data/docs/.lychee.toml +67 -0
  42. data/docs/Gemfile +13 -0
  43. data/docs/RAR_WRITE_SUPPORT.md +26 -0
  44. data/docs/README.md +101 -0
  45. data/docs/_config.yml +112 -0
  46. data/docs/assets/logo.svg +1 -0
  47. data/docs/assets/omnizip-logo.pdf +1540 -11
  48. data/docs/comparison/feature-matrix.adoc +694 -0
  49. data/docs/comparison/index.adoc +113 -0
  50. data/docs/comparison/vs-7zip.adoc +309 -0
  51. data/docs/comparison/vs-peazip.adoc +77 -0
  52. data/docs/comparison/vs-rubyzip.adoc +342 -0
  53. data/docs/comparison/vs-winrar.adoc +100 -0
  54. data/docs/compatibility.adoc +579 -0
  55. data/docs/concepts/index.adoc +129 -0
  56. data/docs/developer/architecture.adoc +256 -0
  57. data/docs/developer/contributing.adoc +158 -0
  58. data/docs/developer/index.adoc +25 -0
  59. data/docs/developer/testing.adoc +212 -0
  60. data/docs/getting-started/basic-usage.adoc +263 -0
  61. data/docs/getting-started/index.adoc +42 -0
  62. data/docs/getting-started/installation.adoc +138 -0
  63. data/docs/getting-started/quick-start.adoc +183 -0
  64. data/docs/getting-started/your-first-archive.adoc +219 -0
  65. data/docs/guides/advanced-features/encryption.adoc +277 -0
  66. data/docs/guides/advanced-features/index.adoc +59 -0
  67. data/docs/guides/advanced-features/parallel-processing.adoc +144 -0
  68. data/docs/guides/advanced-features/progress-tracking.adoc +124 -0
  69. data/docs/guides/advanced-features/streaming.adoc +142 -0
  70. data/docs/guides/archive-formats/gzip-format.adoc +105 -0
  71. data/docs/guides/archive-formats/index.adoc +160 -0
  72. data/docs/guides/archive-formats/ole-format.adoc +151 -0
  73. data/docs/guides/archive-formats/rar-format.adoc +104 -0
  74. data/docs/guides/archive-formats/rar5.adoc +521 -0
  75. data/docs/guides/archive-formats/rpm-format.adoc +108 -0
  76. data/docs/guides/archive-formats/seven-zip-format.adoc +35 -0
  77. data/docs/guides/archive-formats/tar-format.adoc +106 -0
  78. data/docs/guides/archive-formats/xz-format.adoc +118 -0
  79. data/docs/guides/archive-formats/zip-format.adoc +35 -0
  80. data/docs/guides/compression-algorithms/bzip2.adoc +114 -0
  81. data/docs/guides/compression-algorithms/deflate.adoc +313 -0
  82. data/docs/guides/compression-algorithms/index.adoc +190 -0
  83. data/docs/guides/compression-algorithms/lzma.adoc +394 -0
  84. data/docs/guides/compression-algorithms/lzma2.adoc +244 -0
  85. data/docs/guides/compression-algorithms/ppmd.adoc +313 -0
  86. data/docs/guides/compression-algorithms/zstandard.adoc +296 -0
  87. data/docs/guides/creating-archives.adoc +348 -0
  88. data/docs/guides/extracting-archives.adoc +53 -0
  89. data/docs/guides/format-conversion.adoc +64 -0
  90. data/docs/guides/index.adoc +49 -0
  91. data/docs/guides/migration-rubyzip.adoc +217 -0
  92. data/docs/guides/parity-archives.adoc +605 -0
  93. data/docs/guides/performance-tuning.adoc +88 -0
  94. data/docs/index.adoc +228 -0
  95. data/docs/lychee.toml +67 -0
  96. data/docs/reference/api/overview.adoc +187 -0
  97. data/docs/reference/cli/compress-command.adoc +114 -0
  98. data/docs/reference/cli/overview.adoc +140 -0
  99. data/docs/reference/index.adoc +26 -0
  100. data/docs/resources/faq.adoc +185 -0
  101. data/docs/resources/quick-reference.adoc +222 -0
  102. data/docs/rust-backend.md +0 -0
  103. data/docs/troubleshooting/index.adoc +207 -0
  104. data/docs/xar_format.md +216 -0
  105. data/examples/api_comparison.rb +205 -0
  106. data/examples/deflate64_example.rb +96 -0
  107. data/examples/par2_demo.rb +121 -0
  108. data/examples/quick_start_native.rb +150 -0
  109. data/examples/quick_start_rubyzip.rb +115 -0
  110. data/examples/rubyzip_compatibility_demo.rb +194 -0
  111. data/exe/omnizip +27 -0
  112. data/lib/omnizip/algorithm.rb +176 -0
  113. data/lib/omnizip/algorithm_registry.rb +65 -0
  114. data/lib/omnizip/algorithms/.keep +0 -0
  115. data/lib/omnizip/algorithms/bzip2/bwt.rb +236 -0
  116. data/lib/omnizip/algorithms/bzip2/bz2.rb +687 -0
  117. data/lib/omnizip/algorithms/bzip2/rle.rb +146 -0
  118. data/lib/omnizip/algorithms/bzip2.rb +108 -0
  119. data/lib/omnizip/algorithms/deflate/constants.rb +28 -0
  120. data/lib/omnizip/algorithms/deflate/decoder.rb +37 -0
  121. data/lib/omnizip/algorithms/deflate/encoder.rb +45 -0
  122. data/lib/omnizip/algorithms/deflate.rb +135 -0
  123. data/lib/omnizip/algorithms/deflate64/constants.rb +45 -0
  124. data/lib/omnizip/algorithms/deflate64/decoder.rb +151 -0
  125. data/lib/omnizip/algorithms/deflate64/encoder.rb +95 -0
  126. data/lib/omnizip/algorithms/deflate64/huffman_coder.rb +352 -0
  127. data/lib/omnizip/algorithms/deflate64/lz77_encoder.rb +140 -0
  128. data/lib/omnizip/algorithms/deflate64.rb +118 -0
  129. data/lib/omnizip/algorithms/lzma/bit_model.rb +120 -0
  130. data/lib/omnizip/algorithms/lzma/constants.rb +112 -0
  131. data/lib/omnizip/algorithms/lzma/decoder.rb +146 -0
  132. data/lib/omnizip/algorithms/lzma/dictionary.rb +70 -0
  133. data/lib/omnizip/algorithms/lzma/distance_coder.rb +326 -0
  134. data/lib/omnizip/algorithms/lzma/encoder.rb +139 -0
  135. data/lib/omnizip/algorithms/lzma/length_coder.rb +172 -0
  136. data/lib/omnizip/algorithms/lzma/literal_decoder.rb +204 -0
  137. data/lib/omnizip/algorithms/lzma/literal_encoder.rb +208 -0
  138. data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +368 -0
  139. data/lib/omnizip/algorithms/lzma/lzma1_encoder.rb +105 -0
  140. data/lib/omnizip/algorithms/lzma/lzma_alone_decoder.rb +194 -0
  141. data/lib/omnizip/algorithms/lzma/lzma_state.rb +128 -0
  142. data/lib/omnizip/algorithms/lzma/match.rb +32 -0
  143. data/lib/omnizip/algorithms/lzma/match_finder.rb +233 -0
  144. data/lib/omnizip/algorithms/lzma/match_finder_config.rb +142 -0
  145. data/lib/omnizip/algorithms/lzma/match_finder_factory.rb +84 -0
  146. data/lib/omnizip/algorithms/lzma/optimal_encoder.rb +159 -0
  147. data/lib/omnizip/algorithms/lzma/probability_models.rb +72 -0
  148. data/lib/omnizip/algorithms/lzma/range_coder.rb +82 -0
  149. data/lib/omnizip/algorithms/lzma/range_decoder.rb +274 -0
  150. data/lib/omnizip/algorithms/lzma/range_encoder.rb +202 -0
  151. data/lib/omnizip/algorithms/lzma/state.rb +127 -0
  152. data/lib/omnizip/algorithms/lzma/xz_buffered_range_encoder.rb +323 -0
  153. data/lib/omnizip/algorithms/lzma/xz_encoder.rb +420 -0
  154. data/lib/omnizip/algorithms/lzma/xz_encoder_fast.rb +640 -0
  155. data/lib/omnizip/algorithms/lzma/xz_match_finder_adapter.rb +224 -0
  156. data/lib/omnizip/algorithms/lzma/xz_price_calculator.rb +167 -0
  157. data/lib/omnizip/algorithms/lzma/xz_probability_models.rb +258 -0
  158. data/lib/omnizip/algorithms/lzma/xz_range_encoder.rb +223 -0
  159. data/lib/omnizip/algorithms/lzma/xz_range_encoder_exact.rb +314 -0
  160. data/lib/omnizip/algorithms/lzma/xz_state.rb +108 -0
  161. data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +1311 -0
  162. data/lib/omnizip/algorithms/lzma.rb +266 -0
  163. data/lib/omnizip/algorithms/lzma2/chunk_manager.rb +180 -0
  164. data/lib/omnizip/algorithms/lzma2/constants.rb +38 -0
  165. data/lib/omnizip/algorithms/lzma2/encoder.rb +145 -0
  166. data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +165 -0
  167. data/lib/omnizip/algorithms/lzma2/properties.rb +179 -0
  168. data/lib/omnizip/algorithms/lzma2/simple_lzma2_encoder.rb +122 -0
  169. data/lib/omnizip/algorithms/lzma2/xz_encoder_adapter.rb +85 -0
  170. data/lib/omnizip/algorithms/lzma2.rb +133 -0
  171. data/lib/omnizip/algorithms/ppmd7/constants.rb +74 -0
  172. data/lib/omnizip/algorithms/ppmd7/context.rb +151 -0
  173. data/lib/omnizip/algorithms/ppmd7/decoder.rb +127 -0
  174. data/lib/omnizip/algorithms/ppmd7/encoder.rb +153 -0
  175. data/lib/omnizip/algorithms/ppmd7/model.rb +245 -0
  176. data/lib/omnizip/algorithms/ppmd7/symbol_state.rb +57 -0
  177. data/lib/omnizip/algorithms/ppmd7.rb +146 -0
  178. data/lib/omnizip/algorithms/ppmd8/constants.rb +61 -0
  179. data/lib/omnizip/algorithms/ppmd8/context.rb +32 -0
  180. data/lib/omnizip/algorithms/ppmd8/decoder.rb +104 -0
  181. data/lib/omnizip/algorithms/ppmd8/encoder.rb +135 -0
  182. data/lib/omnizip/algorithms/ppmd8/model.rb +246 -0
  183. data/lib/omnizip/algorithms/ppmd8/restoration_method.rb +78 -0
  184. data/lib/omnizip/algorithms/ppmd8.rb +122 -0
  185. data/lib/omnizip/algorithms/ppmd_base.rb +142 -0
  186. data/lib/omnizip/algorithms/registration.rb +28 -0
  187. data/lib/omnizip/algorithms/zstandard/constants.rb +158 -0
  188. data/lib/omnizip/algorithms/zstandard/decoder.rb +249 -0
  189. data/lib/omnizip/algorithms/zstandard/dictionary.rb +73 -0
  190. data/lib/omnizip/algorithms/zstandard/encoder.rb +294 -0
  191. data/lib/omnizip/algorithms/zstandard/frame/block.rb +143 -0
  192. data/lib/omnizip/algorithms/zstandard/frame/header.rb +233 -0
  193. data/lib/omnizip/algorithms/zstandard/frame.rb +33 -0
  194. data/lib/omnizip/algorithms/zstandard/fse/bitstream.rb +183 -0
  195. data/lib/omnizip/algorithms/zstandard/fse/encoder.rb +534 -0
  196. data/lib/omnizip/algorithms/zstandard/fse/interleaved.rb +92 -0
  197. data/lib/omnizip/algorithms/zstandard/fse/table.rb +188 -0
  198. data/lib/omnizip/algorithms/zstandard/fse/table_description.rb +203 -0
  199. data/lib/omnizip/algorithms/zstandard/fse.rb +53 -0
  200. data/lib/omnizip/algorithms/zstandard/huffman.rb +276 -0
  201. data/lib/omnizip/algorithms/zstandard/huffman_encoder.rb +327 -0
  202. data/lib/omnizip/algorithms/zstandard/ldm.rb +121 -0
  203. data/lib/omnizip/algorithms/zstandard/literals.rb +241 -0
  204. data/lib/omnizip/algorithms/zstandard/literals_encoder.rb +96 -0
  205. data/lib/omnizip/algorithms/zstandard/match_finder.rb +483 -0
  206. data/lib/omnizip/algorithms/zstandard/sequences.rb +350 -0
  207. data/lib/omnizip/algorithms/zstandard/sequences_encoder.rb +333 -0
  208. data/lib/omnizip/algorithms/zstandard/xxhash.rb +159 -0
  209. data/lib/omnizip/algorithms/zstandard.rb +210 -0
  210. data/lib/omnizip/algorithms.rb +35 -0
  211. data/lib/omnizip/archive/builder.rb +48 -0
  212. data/lib/omnizip/archive/reader_session.rb +67 -0
  213. data/lib/omnizip/archive.rb +86 -0
  214. data/lib/omnizip/archive_handler.rb +82 -0
  215. data/lib/omnizip/archive_handlers/cpio_handler.rb +47 -0
  216. data/lib/omnizip/archive_handlers/iso_handler.rb +54 -0
  217. data/lib/omnizip/archive_handlers/ole_handler.rb +37 -0
  218. data/lib/omnizip/archive_handlers/rar_handler.rb +96 -0
  219. data/lib/omnizip/archive_handlers/rpm_handler.rb +45 -0
  220. data/lib/omnizip/archive_handlers/seven_zip_handler.rb +89 -0
  221. data/lib/omnizip/archive_handlers/tar_handler.rb +75 -0
  222. data/lib/omnizip/archive_handlers/xar_handler.rb +49 -0
  223. data/lib/omnizip/archive_handlers/zip_handler.rb +115 -0
  224. data/lib/omnizip/archive_handlers.rb +18 -0
  225. data/lib/omnizip/backends.rb +160 -0
  226. data/lib/omnizip/buffer/memory_archive.rb +246 -0
  227. data/lib/omnizip/buffer/memory_extractor.rb +234 -0
  228. data/lib/omnizip/buffer/seven_zip_bridge.rb +171 -0
  229. data/lib/omnizip/buffer.rb +187 -0
  230. data/lib/omnizip/checksum_registry.rb +41 -0
  231. data/lib/omnizip/checksums/crc32.rb +101 -0
  232. data/lib/omnizip/checksums/crc64.rb +102 -0
  233. data/lib/omnizip/checksums/crc_base.rb +158 -0
  234. data/lib/omnizip/checksums/registration.rb +16 -0
  235. data/lib/omnizip/checksums/verifier.rb +130 -0
  236. data/lib/omnizip/checksums.rb +11 -0
  237. data/lib/omnizip/chunked/memory_manager.rb +194 -0
  238. data/lib/omnizip/chunked/reader.rb +78 -0
  239. data/lib/omnizip/chunked/writer.rb +123 -0
  240. data/lib/omnizip/chunked.rb +123 -0
  241. data/lib/omnizip/cli/output_formatter.rb +112 -0
  242. data/lib/omnizip/cli.rb +604 -0
  243. data/lib/omnizip/commands/.keep +0 -0
  244. data/lib/omnizip/commands/archive_create_command.rb +245 -0
  245. data/lib/omnizip/commands/archive_extract_command.rb +231 -0
  246. data/lib/omnizip/commands/archive_list_command.rb +204 -0
  247. data/lib/omnizip/commands/archive_repair_command.rb +129 -0
  248. data/lib/omnizip/commands/archive_verify_command.rb +115 -0
  249. data/lib/omnizip/commands/compress_command.rb +115 -0
  250. data/lib/omnizip/commands/decompress_command.rb +149 -0
  251. data/lib/omnizip/commands/list_command.rb +51 -0
  252. data/lib/omnizip/commands/metadata_command.rb +215 -0
  253. data/lib/omnizip/commands/parity_create_command.rb +114 -0
  254. data/lib/omnizip/commands/parity_repair_command.rb +120 -0
  255. data/lib/omnizip/commands/parity_verify_command.rb +122 -0
  256. data/lib/omnizip/commands/profile_list_command.rb +54 -0
  257. data/lib/omnizip/commands/profile_show_command.rb +44 -0
  258. data/lib/omnizip/commands.rb +21 -0
  259. data/lib/omnizip/convenience.rb +389 -0
  260. data/lib/omnizip/converter/conversion_registry.rb +50 -0
  261. data/lib/omnizip/converter/conversion_strategy.rb +108 -0
  262. data/lib/omnizip/converter/extract_repack_strategy.rb +51 -0
  263. data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +43 -0
  264. data/lib/omnizip/converter/zip_to_seven_zip_strategy.rb +54 -0
  265. data/lib/omnizip/converter.rb +124 -0
  266. data/lib/omnizip/crypto/aes256/cipher.rb +104 -0
  267. data/lib/omnizip/crypto/aes256/constants.rb +28 -0
  268. data/lib/omnizip/crypto/aes256/key_derivation.rb +100 -0
  269. data/lib/omnizip/crypto/aes256.rb +102 -0
  270. data/lib/omnizip/crypto.rb +11 -0
  271. data/lib/omnizip/entry.rb +44 -0
  272. data/lib/omnizip/error.rb +110 -0
  273. data/lib/omnizip/eta/exponential_smoothing_estimator.rb +95 -0
  274. data/lib/omnizip/eta/moving_average_estimator.rb +96 -0
  275. data/lib/omnizip/eta/rate_calculator.rb +104 -0
  276. data/lib/omnizip/eta/sample_history.rb +143 -0
  277. data/lib/omnizip/eta/time_estimator.rb +106 -0
  278. data/lib/omnizip/eta.rb +68 -0
  279. data/lib/omnizip/extraction/filter_chain.rb +179 -0
  280. data/lib/omnizip/extraction/glob_pattern.rb +140 -0
  281. data/lib/omnizip/extraction/pattern_matcher.rb +66 -0
  282. data/lib/omnizip/extraction/predicate_pattern.rb +52 -0
  283. data/lib/omnizip/extraction/regex_pattern.rb +50 -0
  284. data/lib/omnizip/extraction/selective_extractor.rb +247 -0
  285. data/lib/omnizip/extraction.rb +115 -0
  286. data/lib/omnizip/file_type/mime_classifier.rb +144 -0
  287. data/lib/omnizip/file_type.rb +117 -0
  288. data/lib/omnizip/filter.rb +141 -0
  289. data/lib/omnizip/filter_pipeline.rb +108 -0
  290. data/lib/omnizip/filter_registry.rb +113 -0
  291. data/lib/omnizip/filters/bcj.rb +277 -0
  292. data/lib/omnizip/filters/bcj2/constants.rb +53 -0
  293. data/lib/omnizip/filters/bcj2/decoder.rb +263 -0
  294. data/lib/omnizip/filters/bcj2/encoder.rb +58 -0
  295. data/lib/omnizip/filters/bcj2/stream_data.rb +91 -0
  296. data/lib/omnizip/filters/bcj2.rb +93 -0
  297. data/lib/omnizip/filters/bcj_arm.rb +174 -0
  298. data/lib/omnizip/filters/bcj_arm64.rb +242 -0
  299. data/lib/omnizip/filters/bcj_ia64.rb +194 -0
  300. data/lib/omnizip/filters/bcj_ppc.rb +188 -0
  301. data/lib/omnizip/filters/bcj_sparc.rb +174 -0
  302. data/lib/omnizip/filters/bcj_x86.rb +191 -0
  303. data/lib/omnizip/filters/delta.rb +195 -0
  304. data/lib/omnizip/filters/xz_delta.rb +258 -0
  305. data/lib/omnizip/filters.rb +20 -0
  306. data/lib/omnizip/format_detector.rb +166 -0
  307. data/lib/omnizip/formats/.keep +0 -0
  308. data/lib/omnizip/formats/bzip2_file.rb +159 -0
  309. data/lib/omnizip/formats/cpio/bounded_io.rb +66 -0
  310. data/lib/omnizip/formats/cpio/constants.rb +55 -0
  311. data/lib/omnizip/formats/cpio/entry.rb +389 -0
  312. data/lib/omnizip/formats/cpio/reader.rb +194 -0
  313. data/lib/omnizip/formats/cpio/writer.rb +232 -0
  314. data/lib/omnizip/formats/cpio.rb +132 -0
  315. data/lib/omnizip/formats/format_spec_loader.rb +228 -0
  316. data/lib/omnizip/formats/gzip.rb +236 -0
  317. data/lib/omnizip/formats/iso/directory_builder.rb +321 -0
  318. data/lib/omnizip/formats/iso/directory_record.rb +162 -0
  319. data/lib/omnizip/formats/iso/joliet.rb +204 -0
  320. data/lib/omnizip/formats/iso/path_table.rb +125 -0
  321. data/lib/omnizip/formats/iso/reader.rb +194 -0
  322. data/lib/omnizip/formats/iso/rock_ridge.rb +349 -0
  323. data/lib/omnizip/formats/iso/volume_builder.rb +319 -0
  324. data/lib/omnizip/formats/iso/volume_descriptor.rb +168 -0
  325. data/lib/omnizip/formats/iso/writer.rb +543 -0
  326. data/lib/omnizip/formats/iso.rb +133 -0
  327. data/lib/omnizip/formats/lzip.rb +161 -0
  328. data/lib/omnizip/formats/lzma_alone.rb +149 -0
  329. data/lib/omnizip/formats/msi/cab_extractor.rb +181 -0
  330. data/lib/omnizip/formats/msi/constants.rb +157 -0
  331. data/lib/omnizip/formats/msi/directory_resolver.rb +188 -0
  332. data/lib/omnizip/formats/msi/entry.rb +134 -0
  333. data/lib/omnizip/formats/msi/reader.rb +301 -0
  334. data/lib/omnizip/formats/msi/string_pool.rb +246 -0
  335. data/lib/omnizip/formats/msi/table_parser.rb +244 -0
  336. data/lib/omnizip/formats/msi.rb +73 -0
  337. data/lib/omnizip/formats/ole/allocation_table.rb +263 -0
  338. data/lib/omnizip/formats/ole/constants.rb +61 -0
  339. data/lib/omnizip/formats/ole/dirent.rb +439 -0
  340. data/lib/omnizip/formats/ole/header.rb +199 -0
  341. data/lib/omnizip/formats/ole/ranges_io.rb +347 -0
  342. data/lib/omnizip/formats/ole/storage.rb +482 -0
  343. data/lib/omnizip/formats/ole/types/variant.rb +338 -0
  344. data/lib/omnizip/formats/ole.rb +143 -0
  345. data/lib/omnizip/formats/rar/archive_repairer.rb +240 -0
  346. data/lib/omnizip/formats/rar/archive_verifier.rb +192 -0
  347. data/lib/omnizip/formats/rar/block_parser.rb +282 -0
  348. data/lib/omnizip/formats/rar/compression/bit_stream.rb +180 -0
  349. data/lib/omnizip/formats/rar/compression/dispatcher.rb +210 -0
  350. data/lib/omnizip/formats/rar/compression/lz77_huffman/decoder.rb +212 -0
  351. data/lib/omnizip/formats/rar/compression/lz77_huffman/encoder.rb +154 -0
  352. data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_builder.rb +217 -0
  353. data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_coder.rb +189 -0
  354. data/lib/omnizip/formats/rar/compression/lz77_huffman/match_finder.rb +135 -0
  355. data/lib/omnizip/formats/rar/compression/lz77_huffman/sliding_window.rb +165 -0
  356. data/lib/omnizip/formats/rar/compression/lz77_huffman.rb +25 -0
  357. data/lib/omnizip/formats/rar/compression/ppmd/context.rb +103 -0
  358. data/lib/omnizip/formats/rar/compression/ppmd/decoder.rb +238 -0
  359. data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +250 -0
  360. data/lib/omnizip/formats/rar/compression/ppmd.rb +16 -0
  361. data/lib/omnizip/formats/rar/compression.rb +17 -0
  362. data/lib/omnizip/formats/rar/compression_method_registry.rb +106 -0
  363. data/lib/omnizip/formats/rar/constants.rb +88 -0
  364. data/lib/omnizip/formats/rar/decompressor.rb +316 -0
  365. data/lib/omnizip/formats/rar/external_writer.rb +311 -0
  366. data/lib/omnizip/formats/rar/header.rb +209 -0
  367. data/lib/omnizip/formats/rar/license_validator.rb +111 -0
  368. data/lib/omnizip/formats/rar/models/rar_archive.rb +77 -0
  369. data/lib/omnizip/formats/rar/models/rar_entry.rb +65 -0
  370. data/lib/omnizip/formats/rar/models/rar_volume.rb +56 -0
  371. data/lib/omnizip/formats/rar/models.rb +14 -0
  372. data/lib/omnizip/formats/rar/parity_handler.rb +299 -0
  373. data/lib/omnizip/formats/rar/rar5/compression/lzma.rb +200 -0
  374. data/lib/omnizip/formats/rar/rar5/compression/lzss.rb +584 -0
  375. data/lib/omnizip/formats/rar/rar5/compression/store.rb +60 -0
  376. data/lib/omnizip/formats/rar/rar5/crc32.rb +39 -0
  377. data/lib/omnizip/formats/rar/rar5/encryption/aes256_cbc.rb +96 -0
  378. data/lib/omnizip/formats/rar/rar5/encryption/encryption_header.rb +114 -0
  379. data/lib/omnizip/formats/rar/rar5/encryption/encryption_manager.rb +162 -0
  380. data/lib/omnizip/formats/rar/rar5/encryption/key_derivation.rb +97 -0
  381. data/lib/omnizip/formats/rar/rar5/header.rb +208 -0
  382. data/lib/omnizip/formats/rar/rar5/models/encryption_options.rb +74 -0
  383. data/lib/omnizip/formats/rar/rar5/models/recovery_options.rb +63 -0
  384. data/lib/omnizip/formats/rar/rar5/models/solid_options.rb +63 -0
  385. data/lib/omnizip/formats/rar/rar5/models/volume_options.rb +74 -0
  386. data/lib/omnizip/formats/rar/rar5/multi_volume/ARCHITECTURE.md +290 -0
  387. data/lib/omnizip/formats/rar/rar5/multi_volume/volume_manager.rb +260 -0
  388. data/lib/omnizip/formats/rar/rar5/multi_volume/volume_splitter.rb +155 -0
  389. data/lib/omnizip/formats/rar/rar5/multi_volume/volume_writer.rb +176 -0
  390. data/lib/omnizip/formats/rar/rar5/solid/solid_encoder.rb +108 -0
  391. data/lib/omnizip/formats/rar/rar5/solid/solid_manager.rb +139 -0
  392. data/lib/omnizip/formats/rar/rar5/solid/solid_stream.rb +121 -0
  393. data/lib/omnizip/formats/rar/rar5/vint.rb +53 -0
  394. data/lib/omnizip/formats/rar/rar5/writer.rb +540 -0
  395. data/lib/omnizip/formats/rar/rar_format_base.rb +239 -0
  396. data/lib/omnizip/formats/rar/reader.rb +377 -0
  397. data/lib/omnizip/formats/rar/recovery_record.rb +245 -0
  398. data/lib/omnizip/formats/rar/volume_manager.rb +166 -0
  399. data/lib/omnizip/formats/rar/writer.rb +437 -0
  400. data/lib/omnizip/formats/rar.rb +193 -0
  401. data/lib/omnizip/formats/rar3/compressor.rb +71 -0
  402. data/lib/omnizip/formats/rar3/decompressor.rb +64 -0
  403. data/lib/omnizip/formats/rar3/reader.rb +107 -0
  404. data/lib/omnizip/formats/rar3/writer.rb +220 -0
  405. data/lib/omnizip/formats/rar3.rb +15 -0
  406. data/lib/omnizip/formats/rar5/compressor.rb +71 -0
  407. data/lib/omnizip/formats/rar5/decompressor.rb +64 -0
  408. data/lib/omnizip/formats/rar5/reader.rb +130 -0
  409. data/lib/omnizip/formats/rar5/writer.rb +65 -0
  410. data/lib/omnizip/formats/rar5.rb +15 -0
  411. data/lib/omnizip/formats/rpm/constants.rb +58 -0
  412. data/lib/omnizip/formats/rpm/entry.rb +106 -0
  413. data/lib/omnizip/formats/rpm/header.rb +106 -0
  414. data/lib/omnizip/formats/rpm/lead.rb +132 -0
  415. data/lib/omnizip/formats/rpm/tag.rb +228 -0
  416. data/lib/omnizip/formats/rpm/writer.rb +652 -0
  417. data/lib/omnizip/formats/rpm.rb +458 -0
  418. data/lib/omnizip/formats/seven_zip/bcj2_stream_decompressor.rb +274 -0
  419. data/lib/omnizip/formats/seven_zip/coder_chain.rb +169 -0
  420. data/lib/omnizip/formats/seven_zip/constants.rb +126 -0
  421. data/lib/omnizip/formats/seven_zip/encoded_header.rb +110 -0
  422. data/lib/omnizip/formats/seven_zip/encrypted_header.rb +139 -0
  423. data/lib/omnizip/formats/seven_zip/file_collector.rb +143 -0
  424. data/lib/omnizip/formats/seven_zip/header.rb +102 -0
  425. data/lib/omnizip/formats/seven_zip/header_encryptor.rb +132 -0
  426. data/lib/omnizip/formats/seven_zip/header_writer.rb +425 -0
  427. data/lib/omnizip/formats/seven_zip/models/coder_info.rb +30 -0
  428. data/lib/omnizip/formats/seven_zip/models/file_entry.rb +65 -0
  429. data/lib/omnizip/formats/seven_zip/models/folder.rb +67 -0
  430. data/lib/omnizip/formats/seven_zip/models/stream_info.rb +42 -0
  431. data/lib/omnizip/formats/seven_zip/models.rb +20 -0
  432. data/lib/omnizip/formats/seven_zip/parser.rb +671 -0
  433. data/lib/omnizip/formats/seven_zip/reader.rb +626 -0
  434. data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +611 -0
  435. data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +308 -0
  436. data/lib/omnizip/formats/seven_zip/stream_compressor.rb +151 -0
  437. data/lib/omnizip/formats/seven_zip/stream_decompressor.rb +166 -0
  438. data/lib/omnizip/formats/seven_zip/writer.rb +801 -0
  439. data/lib/omnizip/formats/seven_zip.rb +179 -0
  440. data/lib/omnizip/formats/tar/constants.rb +73 -0
  441. data/lib/omnizip/formats/tar/entry.rb +98 -0
  442. data/lib/omnizip/formats/tar/header.rb +206 -0
  443. data/lib/omnizip/formats/tar/reader.rb +119 -0
  444. data/lib/omnizip/formats/tar/writer.rb +212 -0
  445. data/lib/omnizip/formats/tar.rb +74 -0
  446. data/lib/omnizip/formats/xar/constants.rb +119 -0
  447. data/lib/omnizip/formats/xar/entry.rb +268 -0
  448. data/lib/omnizip/formats/xar/header.rb +223 -0
  449. data/lib/omnizip/formats/xar/reader.rb +400 -0
  450. data/lib/omnizip/formats/xar/toc.rb +570 -0
  451. data/lib/omnizip/formats/xar/writer.rb +461 -0
  452. data/lib/omnizip/formats/xar.rb +145 -0
  453. data/lib/omnizip/formats/xz/reader.rb +121 -0
  454. data/lib/omnizip/formats/xz/writer.rb +416 -0
  455. data/lib/omnizip/formats/xz.rb +210 -0
  456. data/lib/omnizip/formats/xz_const.rb +49 -0
  457. data/lib/omnizip/formats/xz_impl/block_decoder.rb +761 -0
  458. data/lib/omnizip/formats/xz_impl/block_encoder.rb +304 -0
  459. data/lib/omnizip/formats/xz_impl/block_header.rb +210 -0
  460. data/lib/omnizip/formats/xz_impl/block_header_parser.rb +182 -0
  461. data/lib/omnizip/formats/xz_impl/index_decoder.rb +174 -0
  462. data/lib/omnizip/formats/xz_impl/index_encoder.rb +120 -0
  463. data/lib/omnizip/formats/xz_impl/stream_decoder.rb +470 -0
  464. data/lib/omnizip/formats/xz_impl/stream_encoder.rb +95 -0
  465. data/lib/omnizip/formats/xz_impl/stream_footer.rb +79 -0
  466. data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +115 -0
  467. data/lib/omnizip/formats/xz_impl/stream_header.rb +53 -0
  468. data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +106 -0
  469. data/lib/omnizip/formats/xz_impl/vli.rb +128 -0
  470. data/lib/omnizip/formats/xz_impl.rb +52 -0
  471. data/lib/omnizip/formats/zip/central_directory_header.rb +223 -0
  472. data/lib/omnizip/formats/zip/constants.rb +69 -0
  473. data/lib/omnizip/formats/zip/end_of_central_directory.rb +132 -0
  474. data/lib/omnizip/formats/zip/local_file_header.rb +136 -0
  475. data/lib/omnizip/formats/zip/reader.rb +376 -0
  476. data/lib/omnizip/formats/zip/unix_extra_field.rb +153 -0
  477. data/lib/omnizip/formats/zip/writer.rb +489 -0
  478. data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +102 -0
  479. data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +64 -0
  480. data/lib/omnizip/formats/zip/zip64_extra_field.rb +112 -0
  481. data/lib/omnizip/formats/zip.rb +50 -0
  482. data/lib/omnizip/formats.rb +29 -0
  483. data/lib/omnizip/implementations/base/lzma2_decoder_base.rb +75 -0
  484. data/lib/omnizip/implementations/base/lzma2_encoder_base.rb +128 -0
  485. data/lib/omnizip/implementations/base/lzma_decoder_base.rb +83 -0
  486. data/lib/omnizip/implementations/base/lzma_encoder_base.rb +108 -0
  487. data/lib/omnizip/implementations/base/state_machine_base.rb +182 -0
  488. data/lib/omnizip/implementations/base.rb +19 -0
  489. data/lib/omnizip/implementations/rust/archive.rb +93 -0
  490. data/lib/omnizip/implementations/rust/library.rb +221 -0
  491. data/lib/omnizip/implementations/rust.rb +26 -0
  492. data/lib/omnizip/implementations/seven_zip/lzma/decoder.rb +428 -0
  493. data/lib/omnizip/implementations/seven_zip/lzma/encoder.rb +456 -0
  494. data/lib/omnizip/implementations/seven_zip/lzma/match_finder.rb +285 -0
  495. data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +197 -0
  496. data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +194 -0
  497. data/lib/omnizip/implementations/seven_zip/lzma/state_machine.rb +139 -0
  498. data/lib/omnizip/implementations/seven_zip/lzma.rb +21 -0
  499. data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +56 -0
  500. data/lib/omnizip/implementations/seven_zip/lzma2.rb +12 -0
  501. data/lib/omnizip/implementations/seven_zip.rb +11 -0
  502. data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +547 -0
  503. data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +748 -0
  504. data/lib/omnizip/implementations/xz_utils/lzma2.rb +13 -0
  505. data/lib/omnizip/implementations/xz_utils.rb +10 -0
  506. data/lib/omnizip/implementations.rb +25 -0
  507. data/lib/omnizip/io/buffered_input.rb +147 -0
  508. data/lib/omnizip/io/buffered_output.rb +106 -0
  509. data/lib/omnizip/io/source.rb +143 -0
  510. data/lib/omnizip/io/stream_manager.rb +117 -0
  511. data/lib/omnizip/io.rb +15 -0
  512. data/lib/omnizip/link_handler/hard_link.rb +79 -0
  513. data/lib/omnizip/link_handler/symbolic_link.rb +74 -0
  514. data/lib/omnizip/link_handler.rb +129 -0
  515. data/lib/omnizip/metadata/archive_metadata.rb +114 -0
  516. data/lib/omnizip/metadata/entry_metadata.rb +153 -0
  517. data/lib/omnizip/metadata/metadata_editor.rb +169 -0
  518. data/lib/omnizip/metadata/metadata_registry.rb +71 -0
  519. data/lib/omnizip/metadata/metadata_validator.rb +101 -0
  520. data/lib/omnizip/metadata.rb +58 -0
  521. data/lib/omnizip/models/.keep +0 -0
  522. data/lib/omnizip/models/algorithm_metadata.rb +41 -0
  523. data/lib/omnizip/models/attribute_apply.rb +40 -0
  524. data/lib/omnizip/models/compression_options.rb +53 -0
  525. data/lib/omnizip/models/conversion_options.rb +79 -0
  526. data/lib/omnizip/models/conversion_result.rb +141 -0
  527. data/lib/omnizip/models/eta_result.rb +45 -0
  528. data/lib/omnizip/models/extraction_rule.rb +115 -0
  529. data/lib/omnizip/models/filter_chain.rb +142 -0
  530. data/lib/omnizip/models/filter_config.rb +148 -0
  531. data/lib/omnizip/models/match_result.rb +141 -0
  532. data/lib/omnizip/models/optimization_suggestion.rb +95 -0
  533. data/lib/omnizip/models/parallel_options.rb +116 -0
  534. data/lib/omnizip/models/performance_result.rb +85 -0
  535. data/lib/omnizip/models/profile_report.rb +116 -0
  536. data/lib/omnizip/models/progress_options.rb +39 -0
  537. data/lib/omnizip/models/split_options.rb +116 -0
  538. data/lib/omnizip/optimization_registry.rb +45 -0
  539. data/lib/omnizip/parallel/engine.rb +52 -0
  540. data/lib/omnizip/parallel/job_queue.rb +209 -0
  541. data/lib/omnizip/parallel/job_scheduler.rb +225 -0
  542. data/lib/omnizip/parallel/parallel_compressor.rb +326 -0
  543. data/lib/omnizip/parallel/parallel_extractor.rb +238 -0
  544. data/lib/omnizip/parallel/thread_pool.rb +60 -0
  545. data/lib/omnizip/parallel/worker_pool.rb +227 -0
  546. data/lib/omnizip/parallel.rb +156 -0
  547. data/lib/omnizip/parity/chunked_block_processor.rb +194 -0
  548. data/lib/omnizip/parity/galois16.rb +145 -0
  549. data/lib/omnizip/parity/models/creator_packet.rb +71 -0
  550. data/lib/omnizip/parity/models/file_description_packet.rb +131 -0
  551. data/lib/omnizip/parity/models/ifsc_packet.rb +111 -0
  552. data/lib/omnizip/parity/models/main_packet.rb +126 -0
  553. data/lib/omnizip/parity/models/packet.rb +156 -0
  554. data/lib/omnizip/parity/models/packet_registry.rb +102 -0
  555. data/lib/omnizip/parity/models/recovery_slice_packet.rb +76 -0
  556. data/lib/omnizip/parity/models.rb +27 -0
  557. data/lib/omnizip/parity/par2_creator.rb +496 -0
  558. data/lib/omnizip/parity/par2_repairer.rb +409 -0
  559. data/lib/omnizip/parity/par2_verifier.rb +373 -0
  560. data/lib/omnizip/parity/par2cmdline_algorithm.rb +108 -0
  561. data/lib/omnizip/parity/par2cmdline_coefficients.rb +78 -0
  562. data/lib/omnizip/parity/reed_solomon_decoder.rb +263 -0
  563. data/lib/omnizip/parity/reed_solomon_encoder.rb +108 -0
  564. data/lib/omnizip/parity/reed_solomon_matrix.rb +339 -0
  565. data/lib/omnizip/parity.rb +207 -0
  566. data/lib/omnizip/password/encryption_registry.rb +26 -0
  567. data/lib/omnizip/password/encryption_strategy.rb +96 -0
  568. data/lib/omnizip/password/password_validator.rb +129 -0
  569. data/lib/omnizip/password/winzip_aes_strategy.rb +190 -0
  570. data/lib/omnizip/password/zip_crypto_strategy.rb +135 -0
  571. data/lib/omnizip/password.rb +88 -0
  572. data/lib/omnizip/pipe/stream_compressor.rb +124 -0
  573. data/lib/omnizip/pipe/stream_decompressor.rb +173 -0
  574. data/lib/omnizip/pipe.rb +120 -0
  575. data/lib/omnizip/platform/ntfs_streams.rb +200 -0
  576. data/lib/omnizip/platform.rb +189 -0
  577. data/lib/omnizip/profile/archive_profile.rb +36 -0
  578. data/lib/omnizip/profile/balanced_profile.rb +31 -0
  579. data/lib/omnizip/profile/binary_profile.rb +33 -0
  580. data/lib/omnizip/profile/compression_profile.rb +157 -0
  581. data/lib/omnizip/profile/custom_profile.rb +155 -0
  582. data/lib/omnizip/profile/fast_profile.rb +31 -0
  583. data/lib/omnizip/profile/maximum_profile.rb +31 -0
  584. data/lib/omnizip/profile/profile_detector.rb +108 -0
  585. data/lib/omnizip/profile/profile_registry.rb +160 -0
  586. data/lib/omnizip/profile/text_profile.rb +33 -0
  587. data/lib/omnizip/profile.rb +261 -0
  588. data/lib/omnizip/profiler/memory_profiler.rb +66 -0
  589. data/lib/omnizip/profiler/method_profiler.rb +49 -0
  590. data/lib/omnizip/profiler/report_generator.rb +158 -0
  591. data/lib/omnizip/profiler.rb +192 -0
  592. data/lib/omnizip/progress/callback_reporter.rb +34 -0
  593. data/lib/omnizip/progress/console_reporter.rb +59 -0
  594. data/lib/omnizip/progress/log_reporter.rb +89 -0
  595. data/lib/omnizip/progress/operation_progress.rb +118 -0
  596. data/lib/omnizip/progress/progress_bar.rb +156 -0
  597. data/lib/omnizip/progress/progress_reporter.rb +40 -0
  598. data/lib/omnizip/progress/progress_tracker.rb +215 -0
  599. data/lib/omnizip/progress/silent_reporter.rb +22 -0
  600. data/lib/omnizip/progress.rb +131 -0
  601. data/lib/omnizip/registry.rb +111 -0
  602. data/lib/omnizip/rubyzip_compat.rb +61 -0
  603. data/lib/omnizip/temp/safe_extract.rb +124 -0
  604. data/lib/omnizip/temp/temp_file.rb +123 -0
  605. data/lib/omnizip/temp/temp_file_pool.rb +109 -0
  606. data/lib/omnizip/temp.rb +184 -0
  607. data/lib/omnizip/version.rb +5 -0
  608. data/lib/omnizip/zip/entry.rb +160 -0
  609. data/lib/omnizip/zip/file.rb +485 -0
  610. data/lib/omnizip/zip/input_stream.rb +270 -0
  611. data/lib/omnizip/zip/output_stream.rb +320 -0
  612. data/lib/omnizip/zip.rb +23 -0
  613. data/lib/omnizip.rb +157 -0
  614. data/lib/tasks/msi_test_files.rake +66 -0
  615. data/lib/tasks/platform_gems.rake +147 -0
  616. data/lib/tasks/rust_backend.rake +48 -0
  617. data/readme-docs/advanced-features.adoc +514 -0
  618. data/readme-docs/api-usage.adoc +445 -0
  619. data/readme-docs/architecture.adoc +450 -0
  620. data/readme-docs/archive-formats.adoc +479 -0
  621. data/readme-docs/cli-usage.adoc +222 -0
  622. data/readme-docs/compression-algorithms.adoc +446 -0
  623. data/readme-docs/compression-profiles.adoc +247 -0
  624. data/readme-docs/encryption-checksums.adoc +328 -0
  625. data/readme-docs/format-converter.adoc +316 -0
  626. data/readme-docs/installation.adoc +228 -0
  627. data/readme-docs/par2-archives.adoc +608 -0
  628. data/readme-docs/performance-profiler.adoc +389 -0
  629. data/readme-docs/preprocessing-filters.adoc +280 -0
  630. data/vendor/libomnizip_ffi.so +0 -0
  631. data/xz-file-format-1.2.1.txt +1174 -0
  632. metadata +768 -0
@@ -0,0 +1,748 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (C) 2025 Ribose Inc.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a
6
+ # copy of this software and associated documentation files (the "Software"),
7
+ # to deal in the Software without restriction, including without limitation
8
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
9
+ # and/or sell copies of the Software, and to permit persons to whom the
10
+ # Software is furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
+ # DEALINGS IN THE SOFTWARE.
22
+
23
+ require "stringio"
24
+
25
+ module Omnizip
26
+ module Implementations
27
+ module XZUtils
28
+ module LZMA2
29
+ # XZ Utils LZMA2 encoder implementation.
30
+ #
31
+ # This is the original XzLZMA2Encoder moved from algorithms/lzma2/xz_lzma2_encoder.rb
32
+ # to the new namespace structure.
33
+ #
34
+ # Ported from XZ Utils liblzma/lzma2_encoder.c
35
+ #
36
+ # Constants
37
+ UINT32_MAX = 0xFFFFFFFF
38
+ REPS = 4
39
+
40
+ # XZ Utils LZMA2 encoder.
41
+ class Encoder < Base::LZMA2EncoderBase
42
+ include Omnizip::Algorithms::LZMA::Constants
43
+
44
+ # XZ Utils limits (from lzma2_encoder.h)
45
+ # Maximum UNCOMPRESSED size per chunk: 2MB
46
+ UNCOMPRESSED_MAX = 1 << 21 # 2,097,152 bytes
47
+ # Maximum COMPRESSED size per chunk: 64KB
48
+ COMPRESSED_MAX = 1 << 16 # 65,536 bytes
49
+ # Uncompressed chunks store their size in a 16-bit field,
50
+ # so each uncompressed chunk is also limited to 64KB.
51
+ UNCOMPRESSED_CHUNK_MAX = 1 << 16
52
+
53
+ # Initialize the encoder
54
+ # @param options [Hash] Encoding options
55
+ # @option options [Integer] :dict_size Dictionary size (default: 8MB)
56
+ # @option options [Integer] :lc Literal context bits (default: 3)
57
+ # @option options [Integer] :lp Literal position bits (default: 0)
58
+ # @option options [Integer] :pb Position bits (default: 2)
59
+ # @option options [Boolean] :standalone If true, write property byte at start (default: true)
60
+ def initialize(options = {})
61
+ dict_size = options.fetch(:dict_size, 8 * 1024 * 1024)
62
+ lc = options.fetch(:lc, 3)
63
+ lp = options.fetch(:lp, 0)
64
+ pb = options.fetch(:pb, 2)
65
+ standalone = options.fetch(:standalone, true)
66
+
67
+ super(
68
+ dict_size: dict_size,
69
+ lc: lc,
70
+ lp: lp,
71
+ pb: pb,
72
+ standalone: standalone
73
+ )
74
+
75
+ # Shared state across all chunks
76
+ @dictionary = Omnizip::Algorithms::LZMA::Dictionary.new(dict_size)
77
+ @state = Omnizip::Algorithms::LZMA::LZMAState.new(0)
78
+ @models = Omnizip::Algorithms::LZMA::XzProbabilityModels.new(lc,
79
+ lp, pb)
80
+ @match_finder = Omnizip::Algorithms::LZMA::MatchFinder.new(@dictionary)
81
+ @optimal = Omnizip::Algorithms::LZMA::OptimalEncoder.new(mode: :fast)
82
+
83
+ # Track previous byte for literal context
84
+ @prev_byte = 0
85
+
86
+ # CRITICAL: For XZ Utils compatibility, first chunk MUST reset the dictionary
87
+ # (matches XZ Utils behavior - see lzma2_encoder.c:334-336)
88
+ # need_dictionary_reset is set to true for the first compressed chunk
89
+ @need_state_reset = false
90
+ @need_dictionary_reset = true # Always reset dictionary for first chunk (XZ Utils compatibility)
91
+ end
92
+
93
+ def encode(input_data)
94
+ # CRITICAL: Reset match finder state for each encoding session
95
+ # This ensures hash table and hash chain start fresh for each Xz.create call
96
+ @match_finder.reset
97
+
98
+ output = StringIO.new
99
+ output.set_encoding(Encoding::BINARY)
100
+
101
+ # Write property byte if standalone mode (for .lz2 files)
102
+ # The property byte encodes dictionary size
103
+ if @standalone
104
+ output.putc(encode_dict_size(@dict_size))
105
+ end
106
+
107
+ input = StringIO.new(input_data)
108
+ input.set_encoding(Encoding::BINARY)
109
+
110
+ # Process in chunks (UNCOMPRESSED_MAX = 2MB per chunk)
111
+ while !input.eof?
112
+ chunk_data = input.read(UNCOMPRESSED_MAX)
113
+ break if chunk_data.nil? || chunk_data.empty?
114
+
115
+ consume_chunk(chunk_data, output)
116
+ end
117
+
118
+ # End marker (0x00) is REQUIRED for all LZMA2 streams
119
+ # The @standalone flag only controls whether a property byte is written
120
+ # at the START (for raw LZMA2 format like .lz2), not the end marker.
121
+ # XZ format requires the end marker to properly terminate the LZMA2 stream.
122
+ output.write([0x00].pack("C"))
123
+
124
+ output.string
125
+ end
126
+
127
+ # Get implementation identifier.
128
+ #
129
+ # @return [Symbol] :xz_utils
130
+ def implementation_name
131
+ :xz_utils
132
+ end
133
+
134
+ private
135
+
136
+ # Encode chunk_data (<= UNCOMPRESSED_MAX bytes), emitting one or
137
+ # more LZMA2 chunks.
138
+ #
139
+ # Multiple chunks are required when the compressed output reaches
140
+ # COMPRESSED_MAX (the compressed size is stored in a 16-bit field)
141
+ # or when compression is not beneficial and the data must be
142
+ # stored as uncompressed chunks (each capped at 64KB).
143
+ def consume_chunk(chunk_data, output)
144
+ @match_finder.feed(chunk_data)
145
+ @match_finder.skip(@match_finder.buffer.bytesize)
146
+
147
+ offset = 0
148
+ while offset < chunk_data.bytesize
149
+ slice = chunk_data.byteslice(offset, chunk_data.bytesize - offset)
150
+
151
+ # A state reset must apply BEFORE encoding the chunk that
152
+ # announces it (0xC0/0xE0 control byte), so that the encoder's
153
+ # models match the decoder's from the chunk's first bit.
154
+ reset_lzma_state! if @need_state_reset || @need_dictionary_reset
155
+
156
+ compressed, consumed = try_compress(slice)
157
+
158
+ use_compressed =
159
+ consumed.positive? &&
160
+ compressed.bytesize <= COMPRESSED_MAX &&
161
+ compressed.bytesize < (consumed == slice.bytesize ? slice.bytesize : consumed)
162
+
163
+ if use_compressed
164
+ uncompressed = chunk_data.byteslice(offset, consumed)
165
+ emit_compressed_chunk(output, uncompressed, compressed)
166
+
167
+ offset += consumed
168
+ @need_state_reset = false
169
+ else
170
+ # Compression didn't help (or hit the cap immediately):
171
+ # store the whole slice as uncompressed chunks.
172
+ emit_uncompressed_chunks(output, slice)
173
+
174
+ offset += slice.bytesize
175
+ # After an uncompressed chunk the LZMA state is lost, so the
176
+ # next compressed chunk must reset it (XZ Utils behavior,
177
+ # lzma2_encoder.c).
178
+ @need_state_reset = true
179
+ end
180
+ @need_dictionary_reset = false
181
+ end
182
+
183
+ # Update prev_byte for next chunk
184
+ if chunk_data.bytesize.positive?
185
+ @prev_byte = chunk_data.getbyte(chunk_data.bytesize - 1)
186
+ end
187
+ end
188
+
189
+ # Reset the LZMA state machine and probability models.
190
+ #
191
+ # MUST be called before encoding any chunk whose control byte
192
+ # announces a state reset (0xC0/0xE0), because the decoder will
193
+ # reinitialize its models on seeing that byte.
194
+ def reset_lzma_state!
195
+ @state = Omnizip::Algorithms::LZMA::LZMAState.new(0)
196
+ @models = Omnizip::Algorithms::LZMA::XzProbabilityModels.new(@lc,
197
+ @lp,
198
+ @pb)
199
+ @prev_byte = 0
200
+ end
201
+
202
+ # Emit a compressed LZMA2 chunk.
203
+ def emit_compressed_chunk(output, uncompressed, compressed)
204
+ # For compressed chunks, properties encode lc/lp/pb:
205
+ # (pb * 5 + lp) * 9 + lc
206
+ chunk_properties = (((@pb * 5) + @lp) * 9) + @lc
207
+
208
+ # A dictionary reset implies a state reset; props accompany
209
+ # every state reset (control 0xE0 or 0xC0).
210
+ state_reset = @need_state_reset || @need_dictionary_reset
211
+
212
+ chunk = Omnizip::Algorithms::LZMA2::LZMA2Chunk.new(
213
+ chunk_type: :compressed,
214
+ uncompressed_data: uncompressed,
215
+ compressed_data: compressed,
216
+ compressed_size: compressed.bytesize,
217
+ properties: chunk_properties,
218
+ need_dict_reset: @need_dictionary_reset,
219
+ need_state_reset: state_reset,
220
+ need_props: state_reset,
221
+ )
222
+ output.write(chunk.to_bytes)
223
+
224
+ # Update dictionary with the chunk data
225
+ @dictionary.append(uncompressed)
226
+ end
227
+
228
+ # Emit uncompressed LZMA2 chunks for data (splitting at the
229
+ # 64KB per-chunk limit imposed by the 16-bit size field).
230
+ def emit_uncompressed_chunks(output, data)
231
+ offset = 0
232
+ while offset < data.bytesize
233
+ slice = data.byteslice(offset, UNCOMPRESSED_CHUNK_MAX)
234
+ chunk = Omnizip::Algorithms::LZMA2::LZMA2Chunk.new(
235
+ chunk_type: :uncompressed,
236
+ uncompressed_data: slice,
237
+ compressed_data: "",
238
+ need_dict_reset: @need_dictionary_reset,
239
+ need_state_reset: false,
240
+ need_props: false,
241
+ )
242
+ output.write(chunk.to_bytes)
243
+ @need_dictionary_reset = false
244
+
245
+ offset += slice.bytesize
246
+ @dictionary.append(slice)
247
+ end
248
+ end
249
+
250
+ # Compress data starting at the current dictionary position.
251
+ #
252
+ # Stops early (returning fewer bytes than data.bytesize) when the
253
+ # compressed output is about to exceed COMPRESSED_MAX, leaving the
254
+ # range encoder at a clean item boundary.
255
+ #
256
+ # @return [Array<String, Integer>] compressed bytes and number of
257
+ # input bytes consumed
258
+ def try_compress(data)
259
+ # Create output buffer to capture compressed data
260
+ output_buffer = StringIO.new
261
+ output_buffer.set_encoding(Encoding::BINARY)
262
+
263
+ # Create range encoder (direct XZ Utils port)
264
+ encoder = Omnizip::Algorithms::LZMA::XzRangeEncoder.new(output_buffer)
265
+
266
+ # The whole chunk has already been fed to the match finder by
267
+ # consume_chunk; positions map from the dictionary position.
268
+ start_pos = @dictionary.buffer.bytesize
269
+
270
+ # Headroom for pending symbols plus the final flush
271
+ size_cap = COMPRESSED_MAX - 64
272
+
273
+ pos = 0
274
+ while pos < data.bytesize
275
+ # Drain before every item: a single worst-case match (long
276
+ # length + dist slot >= 14 with up to 30 footer bits) queues
277
+ # ~48 symbols, and RC_SYMBOLS_MAX is 53, so the queue MUST be
278
+ # empty when an item starts or `bit` raises "Symbol buffer
279
+ # overflow" (issue #26).
280
+ encode_queued_symbols(encoder, output_buffer)
281
+
282
+ # Enforce the 64KB compressed-chunk cap at an item boundary
283
+ break if pos.positive? && output_buffer.size >= size_cap
284
+
285
+ # Position in match finder's buffer for encoding
286
+ match_pos = start_pos + pos
287
+
288
+ # Get optimal encoding choice
289
+ # Note: find_optimal calls find_matches internally, no need to call twice
290
+ distance, length = @optimal.find_optimal(
291
+ match_pos,
292
+ @match_finder,
293
+ @state,
294
+ @state.reps,
295
+ @models,
296
+ )
297
+
298
+ # DEBUG: Trace encoding decisions
299
+ puts "[DEBUG] pos=#{pos} distance=#{distance} length=#{length} state=#{@state.value} reps=#{@state.reps.inspect}" if ENV["DEBUG"]
300
+
301
+ # Encode based on choice
302
+ # CRITICAL: Use UINT32_MAX to check for literal (not distance.zero?)
303
+ # because distance=0 means repeated match rep0, not literal!
304
+ if distance == UINT32_MAX || length == 1
305
+ # Encode literal. The position argument must be GLOBAL
306
+ # (dictionary position), because the decoder keeps counting
307
+ # bytes across chunk boundaries for pos_state.
308
+ encode_literal(data.getbyte(pos), encoder, match_pos)
309
+ pos += 1
310
+ elsif distance < REPS
311
+ # Encode repeated match (distance is 0-3 for rep0-rep3)
312
+ encode_repeated_match(distance, length, encoder, match_pos,
313
+ match_pos)
314
+ pos += length
315
+ else
316
+ # Encode normal match (distance is actual_distance + REPS)
317
+ actual_distance = distance - REPS
318
+ encode_match(actual_distance, length, encoder, match_pos,
319
+ match_pos, data)
320
+ pos += length
321
+ end
322
+ end
323
+
324
+ # Flush encoder to write remaining bytes
325
+ # IMPORTANT: Encode all pending symbols FIRST, before queue_flush
326
+ encode_queued_symbols(encoder, output_buffer)
327
+
328
+ # Now flush the encoder (adds 5 RC_FLUSH symbols)
329
+ encoder.queue_flush
330
+
331
+ # Encode the flush symbols
332
+ # This will write additional bytes to output_buffer
333
+ encode_queued_symbols(encoder, output_buffer)
334
+
335
+ [output_buffer.string, pos]
336
+ end
337
+
338
+ # Encode queued symbols to output
339
+ def encode_queued_symbols(encoder, output)
340
+ return if encoder.none?
341
+
342
+ # Reused scratch buffer: allocating a fresh 10 KB String
343
+ # per drain showed up as half the encoder's runtime via
344
+ # GC (String#* in profiles).
345
+ @symbol_buffer ||= "\0".b * 10_000
346
+ temp_buffer = @symbol_buffer
347
+ out_pos = Omnizip::Algorithms::LZMA::IntRef.new(0)
348
+
349
+ # Track size before encoding
350
+ size_before = output.size
351
+
352
+ # Encode symbols to buffer
353
+ encoder.encode_symbols(temp_buffer, out_pos, 10000)
354
+
355
+ # Write to output stream
356
+ if out_pos.value.positive?
357
+ # Ruby's [] operator has a bug with null bytes that can return extra bytes
358
+ # See: https://bugs.ruby-lang.org/issues/15985
359
+ output.write(temp_buffer.byteslice(0, out_pos.value))
360
+ end
361
+
362
+ # Return the number of bytes written
363
+ output.size - size_before
364
+ end
365
+
366
+ # Encode literal byte
367
+ # pos is the GLOBAL position (dictionary position) because the
368
+ # decoder counts bytes continuously across chunks.
369
+ def encode_literal(symbol, encoder, pos)
370
+ pos_state = pos & ((1 << @pb) - 1)
371
+
372
+ # Encode is_match bit (0 for literal) - uses OLD state value
373
+ prob_is_match = @models.is_match[@state.value][pos_state]
374
+ encoder.queue_bit(prob_is_match, 0)
375
+
376
+ # Get literal subcoder flat index (uses OLD state value via @prev_byte)
377
+ # This is the base offset into the flat literal array
378
+ literal_offset = get_literal_state(pos, @prev_byte)
379
+
380
+ # CRITICAL: Check encoding path BEFORE updating state (XZ Utils order)
381
+ # The is_literal_state check happens on the current state
382
+ use_matched = @state.use_matched_literal?
383
+
384
+ # Now update state (this is the update_literal() call in XZ)
385
+ @state.update_literal!
386
+
387
+ if use_matched
388
+ # Matched literal (compare with match byte at rep0)
389
+ # XZ Utils: mf->buffer[mf->read_pos - coder->reps[0] - 1 - mf->read_ahead]
390
+ # We don't use read_ahead, so it's 0
391
+ match_byte_pos = pos - @state.reps[0] - 1
392
+ match_byte = @match_finder.buffer.getbyte(match_byte_pos) if match_byte_pos >= 0 && match_byte_pos < @match_finder.buffer.bytesize
393
+
394
+ # If match_byte is nil (shouldn't happen in normal operation),
395
+ # fall back to normal literal encoding
396
+ if match_byte.nil?
397
+ encode_normal_literal(literal_offset, symbol, encoder)
398
+ else
399
+ encode_matched_literal(literal_offset, match_byte, symbol,
400
+ encoder)
401
+ end
402
+ else
403
+ # Normal literal (8-bit tree)
404
+ encode_normal_literal(literal_offset, symbol, encoder)
405
+ end
406
+
407
+ # Update prev_byte
408
+ @prev_byte = symbol
409
+ end
410
+
411
+ # Encode normal match
412
+ def encode_match(distance, length, encoder, pos, match_pos,
413
+ _input_data)
414
+ pos_state = pos & ((1 << @pb) - 1)
415
+
416
+ # Encode is_match bit (1 for match) - uses OLD state value
417
+ prob_is_match = @models.is_match[@state.value][pos_state]
418
+ encoder.queue_bit(prob_is_match, 1)
419
+
420
+ # Encode is_rep bit (0 for normal match) - uses OLD state value
421
+ prob_is_rep = @models.is_rep[@state.value]
422
+ encoder.queue_bit(prob_is_rep, 0)
423
+
424
+ # CRITICAL: Update state BEFORE encoding length/distance (XZ Utils order)
425
+ # This also updates reps. Reps are stored 0-based (distance - 1),
426
+ # matching XZ Utils and the decoder's rep0 convention.
427
+ @state.update_match!(distance - 1)
428
+
429
+ # Encode length - uses NEW state value
430
+ encode_match_length(length, pos_state, encoder)
431
+
432
+ # Encode distance - uses NEW state value
433
+ encode_distance(distance, length, encoder)
434
+
435
+ # Update prev_byte (last byte of match)
436
+ # Read from match finder buffer: match_pos - distance + length - 1
437
+ last_byte_pos = match_pos - distance + length - 1
438
+ @prev_byte = @match_finder.buffer.getbyte(last_byte_pos) if last_byte_pos >= 0 && last_byte_pos < @match_finder.buffer.bytesize
439
+ end
440
+
441
+ # Encode repeated match (using rep0-rep3)
442
+ # Ported from XZ Utils rep_match function
443
+ def encode_repeated_match(rep, length, encoder, pos, match_pos)
444
+ pos_state = pos & ((1 << @pb) - 1)
445
+
446
+ # Encode is_match bit (1 for match) - uses OLD state value
447
+ prob_is_match = @models.is_match[@state.value][pos_state]
448
+ encoder.queue_bit(prob_is_match, 1)
449
+
450
+ # Encode is_rep bit (1 for repeated match) - uses OLD state value
451
+ prob_is_rep = @models.is_rep[@state.value]
452
+ encoder.queue_bit(prob_is_rep, 1)
453
+
454
+ prob_is_rep0 = @models.is_rep0[@state.value]
455
+ if rep.zero?
456
+ # rep0 (shortest distance)
457
+ encoder.queue_bit(prob_is_rep0, 0)
458
+
459
+ prob_is_rep0_long = @models.is_rep0_long[@state.value][pos_state]
460
+ encoder.queue_bit(prob_is_rep0_long, length == 1 ? 0 : 1)
461
+ else
462
+ # rep1, rep2, or rep3
463
+ encoder.queue_bit(prob_is_rep0, 1)
464
+
465
+ prob_is_rep1 = @models.is_rep1[@state.value]
466
+ if rep == 1
467
+ # rep1
468
+ encoder.queue_bit(prob_is_rep1, 0)
469
+ else
470
+ # rep2 or rep3
471
+ encoder.queue_bit(prob_is_rep1, 1)
472
+
473
+ prob_is_rep2 = @models.is_rep2[@state.value]
474
+ encoder.queue_bit(prob_is_rep2, rep - 2)
475
+ end
476
+
477
+ # XZ Utils rep_match: capture the selected distance BEFORE
478
+ # rotating, then shift reps down (lzma_encoder.c):
479
+ # distance = reps[rep];
480
+ # for (i = rep; i > 0; --i) reps[i] = reps[i-1];
481
+ # reps[0] = distance;
482
+ distance = @state.reps[rep]
483
+ rep.downto(1) { |i| @state.reps[i] = @state.reps[i - 1] }
484
+ @state.reps[0] = distance
485
+ end
486
+
487
+ # Update state based on match length
488
+ if length == 1
489
+ @state.update_short_rep!
490
+ else
491
+ # Encode length using the REP length coder (rep matches have
492
+ # their own length models, decoder's @rep_length_coder)
493
+ encode_match_length(length, pos_state, encoder,
494
+ @models.rep_len_encoder)
495
+ @state.update_long_rep!
496
+ end
497
+
498
+ # Update prev_byte (last byte of the match region in the stream)
499
+ last_byte_pos = match_pos + length - 1
500
+ @prev_byte = @match_finder.buffer.getbyte(last_byte_pos) if last_byte_pos >= 0 && last_byte_pos < @match_finder.buffer.bytesize
501
+ end
502
+
503
+ # Get literal subcoder flat index
504
+ # Ported from XZ Utils literal_subcoder macro in lzma_common.h:
505
+ # #define literal_subcoder(probs, lc, literal_mask, pos, prev_byte) \
506
+ # ((probs) + UINT32_C(3) * \
507
+ # (((((pos) << 8) + (prev_byte)) & (literal_mask)) << (lc)))
508
+ # where literal_mask = (0x100 << lp) - (0x100 >> lc)
509
+ #
510
+ # The factor of 3 gives each context a 0x300-sized subcoder and
511
+ # MUST match the decoder's base_offset = 3 * (lit_state << lc).
512
+ #
513
+ # Returns the flat index into the literal probability array.
514
+ def get_literal_state(pos, prev_byte)
515
+ literal_mask = (0x100 << @lp) - (0x100 >> @lc)
516
+ 3 * ((((pos << 8) + prev_byte) & literal_mask) << @lc)
517
+ end
518
+
519
+ # Get byte from dictionary at distance back
520
+ def get_dictionary_byte(distance)
521
+ if distance.positive? &&
522
+ distance <= @dictionary.buffer.bytesize
523
+ @dictionary.get_byte(distance)
524
+ end
525
+ end
526
+
527
+ # Encode normal literal (8-bit tree)
528
+ # Ported from XZ Utils rc_bittree() for normal literals
529
+ # @param literal_offset [Integer] Base offset into flat literal array
530
+ # @param symbol [Integer] The literal byte to encode (0-255)
531
+ # @param encoder [XZBufferedRangeEncoder] The range encoder
532
+ def encode_normal_literal(literal_offset, symbol, encoder)
533
+ context = 1
534
+ 8.downto(1) do |i|
535
+ bit = (symbol >> (i - 1)) & 1
536
+ encoder.queue_bit(@models.literal[literal_offset + context], bit)
537
+ context = (context << 1) | bit
538
+ end
539
+ end
540
+
541
+ # Encode matched literal (compare with match byte)
542
+ # Ported from XZ Utils literal_matched() in lzma_encoder.c
543
+ # @param literal_offset [Integer] Base offset into flat literal array
544
+ # @param match_byte [Integer] The match byte to compare against
545
+ # @param symbol [Integer] The literal byte to encode (0-255)
546
+ # @param encoder [XZBufferedRangeEncoder] The range encoder
547
+ def encode_matched_literal(literal_offset, match_byte, symbol,
548
+ encoder)
549
+ offset = 0x100
550
+ symbol += 0x100 # Start symbol at 256 (XZ Utils algorithm)
551
+
552
+ # Loop until symbol reaches 0x10000 (65536)
553
+ while symbol < 0x10000
554
+ match_byte <<= 1
555
+ match_bit = match_byte & offset
556
+ subcoder_index = offset + match_bit + (symbol >> 8)
557
+ bit = (symbol >> 7) & 1
558
+
559
+ encoder.queue_bit(@models.literal[literal_offset + subcoder_index],
560
+ bit)
561
+
562
+ symbol <<= 1
563
+ offset &= ~(match_byte ^ symbol)
564
+ end
565
+ end
566
+
567
+ # Encode match length
568
+ # len_encoder is the length model set to use: match_len_encoder for
569
+ # normal matches, rep_len_encoder for repeated matches.
570
+ def encode_match_length(length, pos_state, encoder,
571
+ len_encoder = @models.match_len_encoder)
572
+ len = length - MATCH_LEN_MIN
573
+
574
+ if len < LEN_LOW_SYMBOLS
575
+ # Low: 0-7
576
+ encoder.queue_bit(len_encoder.choice, 0)
577
+ encode_bittree(
578
+ len_encoder.low[pos_state],
579
+ NUM_LEN_LOW_BITS,
580
+ len,
581
+ encoder,
582
+ )
583
+ elsif len < LEN_LOW_SYMBOLS + LEN_MID_SYMBOLS
584
+ # Mid: 8-15
585
+ encoder.queue_bit(len_encoder.choice, 1)
586
+ encoder.queue_bit(len_encoder.choice2, 0)
587
+ encode_bittree(
588
+ len_encoder.mid[pos_state],
589
+ NUM_LEN_MID_BITS,
590
+ len - LEN_LOW_SYMBOLS,
591
+ encoder,
592
+ )
593
+ else
594
+ # High: 16-271
595
+ encoder.queue_bit(len_encoder.choice, 1)
596
+ encoder.queue_bit(len_encoder.choice2, 1)
597
+ high_len = len - LEN_LOW_SYMBOLS - LEN_MID_SYMBOLS
598
+ encode_bittree(
599
+ len_encoder.high,
600
+ NUM_LEN_HIGH_BITS,
601
+ high_len,
602
+ encoder,
603
+ )
604
+ end
605
+ end
606
+
607
+ # Encode distance using slot encoding
608
+ def encode_distance(distance, length, encoder)
609
+ # get_dist_slot expects a 0-based distance (xz fastpos.h:
610
+ # dist 4 -> slot 4, dist 5 -> slot 4). The decoder computes
611
+ # rep0 = base + footer the same 0-based way.
612
+ dist0 = distance - 1
613
+ dist_slot = get_dist_slot(dist0)
614
+ len_state = get_len_to_pos_state(length)
615
+
616
+ # Encode distance slot
617
+ # @dist_slot is organized as [len_to_pos_state][dist_slot]
618
+ encode_bittree(
619
+ @models.dist_slot[len_state],
620
+ NUM_DIST_SLOT_BITS,
621
+ dist_slot,
622
+ encoder,
623
+ )
624
+
625
+ # Encode distance footer
626
+ if dist_slot >= START_POS_MODEL_INDEX
627
+ footer_bits = (dist_slot >> 1) - 1
628
+ base = (2 | (dist_slot & 1)) << footer_bits
629
+ dist_reduced = dist0 - base
630
+
631
+ if dist_slot < END_POS_MODEL_INDEX
632
+ # Use probability models
633
+ # XZ Utils: rc_bittree_reverse(&coder->rc, coder->dist_special + base - dist_slot - 1, ...)
634
+ encode_bittree_reverse(
635
+ @models.dist_special,
636
+ dist_reduced,
637
+ footer_bits,
638
+ base - dist_slot - 1,
639
+ encoder,
640
+ )
641
+ else
642
+ # Direct bits + alignment
643
+ direct_bits = footer_bits - DIST_ALIGN_BITS
644
+ encoder.queue_direct_bits(
645
+ dist_reduced >> DIST_ALIGN_BITS,
646
+ direct_bits,
647
+ )
648
+ align_mask = (1 << DIST_ALIGN_BITS) - 1
649
+ encode_bittree_reverse(
650
+ @models.dist_align,
651
+ dist_reduced & align_mask,
652
+ DIST_ALIGN_BITS,
653
+ 0,
654
+ encoder,
655
+ )
656
+ end
657
+ end
658
+ end
659
+
660
+ # Encode bittree (MSB first)
661
+ def encode_bittree(probs, num_bits, value, encoder)
662
+ context = 1
663
+ num_bits.downto(1) do |i|
664
+ bit = (value >> (i - 1)) & 1
665
+ encoder.queue_bit(probs[context], bit)
666
+ context = (context << 1) | bit
667
+ end
668
+ end
669
+
670
+ # Encode bittree in reverse (LSB first)
671
+ def encode_bittree_reverse(probs, value, num_bits, offset, encoder)
672
+ context = 1
673
+ num_bits.times do |i|
674
+ bit = (value >> i) & 1
675
+ encoder.queue_bit(probs[offset + context], bit)
676
+ context = (context << 1) | bit
677
+ end
678
+ end
679
+
680
+ # Get distance slot for distance
681
+ def get_dist_slot(distance)
682
+ if distance < NUM_FULL_DISTANCES
683
+ distance < 4 ? distance : fast_pos_small(distance)
684
+ else
685
+ fast_pos_large(distance)
686
+ end
687
+ end
688
+
689
+ # Fast position calculation for small distances
690
+ def fast_pos_small(distance)
691
+ slot = 0
692
+ dist = distance
693
+ while dist > 3
694
+ dist >>= 1
695
+ slot += 2
696
+ end
697
+ slot + dist
698
+ end
699
+
700
+ # Fast position calculation for large distances
701
+ def fast_pos_large(distance)
702
+ # Find highest bit position
703
+ n = 31
704
+ while n >= 0
705
+ break if (distance >> n) != 0
706
+
707
+ n -= 1
708
+ end
709
+ # slot = 2 * n + high_bit
710
+ ((n << 1) + ((distance >> (n - 1)) & 1))
711
+ end
712
+
713
+ # Map length to position state
714
+ def get_len_to_pos_state(length)
715
+ len = length - MATCH_LEN_MIN
716
+ len < NUM_LEN_TO_POS_STATES ? len : NUM_LEN_TO_POS_STATES - 1
717
+ end
718
+
719
+ # Encode dictionary size to LZMA2 property byte
720
+ # @param dict_size [Integer] Dictionary size
721
+ # @return [Integer] Property byte (0-40)
722
+ def encode_dict_size(dict_size)
723
+ # Clamp to valid range
724
+ d = [dict_size, Omnizip::Algorithms::LZMA2Const::DICT_SIZE_MIN].max
725
+
726
+ # Calculate log2 of dict_size
727
+ log2_size = 0
728
+ temp = d
729
+ while temp > 1
730
+ log2_size += 1
731
+ temp >>= 1
732
+ end
733
+
734
+ # Encoding formula for power-of-2 sizes:
735
+ # d = 2 * (log2_size - 12)
736
+ if d == (1 << log2_size)
737
+ # Exact power of 2
738
+ [(log2_size - 12) * 2, 40].min
739
+ else
740
+ # Between 2^n and 2^n + 2^(n-1), use odd encoding
741
+ [((log2_size - 12) * 2) + 1, 40].min
742
+ end
743
+ end
744
+ end
745
+ end
746
+ end
747
+ end
748
+ end