omnizip 0.3.1

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 (511) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +32 -0
  4. data/.rubocop_todo.yml +754 -0
  5. data/COPYING +502 -0
  6. data/Gemfile +17 -0
  7. data/LICENSE +12 -0
  8. data/README.adoc +1045 -0
  9. data/Rakefile +12 -0
  10. data/benchmark/README.md +260 -0
  11. data/benchmark/benchmark_suite.rb +125 -0
  12. data/benchmark/compression_bench.rb +181 -0
  13. data/benchmark/filter_bench.rb +180 -0
  14. data/benchmark/models/benchmark_result.rb +59 -0
  15. data/benchmark/models/comparison_result.rb +69 -0
  16. data/benchmark/profile_suite.rb +167 -0
  17. data/benchmark/reporter.rb +150 -0
  18. data/benchmark/run_benchmarks.rb +66 -0
  19. data/benchmark/test_data.rb +137 -0
  20. data/config/formats/rar3_spec.yml +91 -0
  21. data/config/formats/rar5_spec.yml +102 -0
  22. data/docs/.github/workflows/docs.yml +142 -0
  23. data/docs/.gitignore +21 -0
  24. data/docs/.lychee.toml +67 -0
  25. data/docs/Gemfile +13 -0
  26. data/docs/RAR_WRITE_SUPPORT.md +26 -0
  27. data/docs/README.md +101 -0
  28. data/docs/_config.yml +112 -0
  29. data/docs/assets/logo.svg +1 -0
  30. data/docs/assets/omnizip-logo.pdf +1540 -11
  31. data/docs/comparison/feature-matrix.adoc +694 -0
  32. data/docs/comparison/index.adoc +113 -0
  33. data/docs/comparison/vs-7zip.adoc +309 -0
  34. data/docs/comparison/vs-peazip.adoc +77 -0
  35. data/docs/comparison/vs-rubyzip.adoc +342 -0
  36. data/docs/comparison/vs-winrar.adoc +100 -0
  37. data/docs/compatibility.adoc +579 -0
  38. data/docs/concepts/index.adoc +129 -0
  39. data/docs/developer/architecture.adoc +256 -0
  40. data/docs/developer/contributing.adoc +158 -0
  41. data/docs/developer/index.adoc +25 -0
  42. data/docs/developer/testing.adoc +212 -0
  43. data/docs/getting-started/basic-usage.adoc +271 -0
  44. data/docs/getting-started/index.adoc +42 -0
  45. data/docs/getting-started/installation.adoc +138 -0
  46. data/docs/getting-started/quick-start.adoc +185 -0
  47. data/docs/getting-started/your-first-archive.adoc +218 -0
  48. data/docs/guides/advanced-features/encryption.adoc +300 -0
  49. data/docs/guides/advanced-features/index.adoc +49 -0
  50. data/docs/guides/advanced-features/parallel-processing.adoc +246 -0
  51. data/docs/guides/advanced-features/progress-tracking.adoc +320 -0
  52. data/docs/guides/advanced-features/streaming.adoc +212 -0
  53. data/docs/guides/archive-formats/gzip-format.adoc +107 -0
  54. data/docs/guides/archive-formats/index.adoc +130 -0
  55. data/docs/guides/archive-formats/rar-format.adoc +104 -0
  56. data/docs/guides/archive-formats/rar5.adoc +521 -0
  57. data/docs/guides/archive-formats/seven-zip-format.adoc +35 -0
  58. data/docs/guides/archive-formats/tar-format.adoc +106 -0
  59. data/docs/guides/archive-formats/xz-format.adoc +118 -0
  60. data/docs/guides/archive-formats/zip-format.adoc +35 -0
  61. data/docs/guides/compression-algorithms/bzip2.adoc +113 -0
  62. data/docs/guides/compression-algorithms/deflate.adoc +319 -0
  63. data/docs/guides/compression-algorithms/index.adoc +190 -0
  64. data/docs/guides/compression-algorithms/lzma.adoc +398 -0
  65. data/docs/guides/compression-algorithms/lzma2.adoc +327 -0
  66. data/docs/guides/compression-algorithms/ppmd.adoc +316 -0
  67. data/docs/guides/compression-algorithms/zstandard.adoc +361 -0
  68. data/docs/guides/creating-archives.adoc +354 -0
  69. data/docs/guides/extracting-archives.adoc +53 -0
  70. data/docs/guides/format-conversion.adoc +64 -0
  71. data/docs/guides/index.adoc +49 -0
  72. data/docs/guides/migration-rubyzip.adoc +217 -0
  73. data/docs/guides/parity-archives.adoc +605 -0
  74. data/docs/guides/performance-tuning.adoc +88 -0
  75. data/docs/index.adoc +218 -0
  76. data/docs/lychee.toml +67 -0
  77. data/docs/reference/api/overview.adoc +188 -0
  78. data/docs/reference/cli/compress-command.adoc +114 -0
  79. data/docs/reference/cli/overview.adoc +140 -0
  80. data/docs/reference/index.adoc +26 -0
  81. data/docs/resources/faq.adoc +185 -0
  82. data/docs/resources/quick-reference.adoc +222 -0
  83. data/docs/troubleshooting/index.adoc +208 -0
  84. data/examples/api_comparison.rb +205 -0
  85. data/examples/deflate64_example.rb +96 -0
  86. data/examples/par2_demo.rb +121 -0
  87. data/examples/quick_start_native.rb +150 -0
  88. data/examples/quick_start_rubyzip.rb +115 -0
  89. data/examples/rubyzip_compatibility_demo.rb +194 -0
  90. data/exe/omnizip +27 -0
  91. data/lib/omnizip/algorithm.rb +130 -0
  92. data/lib/omnizip/algorithm_registry.rb +86 -0
  93. data/lib/omnizip/algorithms/.keep +0 -0
  94. data/lib/omnizip/algorithms/bzip2/bwt.rb +225 -0
  95. data/lib/omnizip/algorithms/bzip2/decoder.rb +193 -0
  96. data/lib/omnizip/algorithms/bzip2/encoder.rb +237 -0
  97. data/lib/omnizip/algorithms/bzip2/huffman.rb +206 -0
  98. data/lib/omnizip/algorithms/bzip2/mtf.rb +101 -0
  99. data/lib/omnizip/algorithms/bzip2/rle.rb +151 -0
  100. data/lib/omnizip/algorithms/bzip2.rb +130 -0
  101. data/lib/omnizip/algorithms/deflate/constants.rb +28 -0
  102. data/lib/omnizip/algorithms/deflate/decoder.rb +38 -0
  103. data/lib/omnizip/algorithms/deflate/encoder.rb +46 -0
  104. data/lib/omnizip/algorithms/deflate.rb +128 -0
  105. data/lib/omnizip/algorithms/deflate64/constants.rb +45 -0
  106. data/lib/omnizip/algorithms/deflate64/decoder.rb +153 -0
  107. data/lib/omnizip/algorithms/deflate64/encoder.rb +98 -0
  108. data/lib/omnizip/algorithms/deflate64/huffman_coder.rb +354 -0
  109. data/lib/omnizip/algorithms/deflate64/lz77_encoder.rb +142 -0
  110. data/lib/omnizip/algorithms/deflate64.rb +109 -0
  111. data/lib/omnizip/algorithms/lzma/bit_model.rb +120 -0
  112. data/lib/omnizip/algorithms/lzma/constants.rb +112 -0
  113. data/lib/omnizip/algorithms/lzma/decoder.rb +148 -0
  114. data/lib/omnizip/algorithms/lzma/dictionary.rb +69 -0
  115. data/lib/omnizip/algorithms/lzma/distance_coder.rb +415 -0
  116. data/lib/omnizip/algorithms/lzma/encoder.rb +142 -0
  117. data/lib/omnizip/algorithms/lzma/length_coder.rb +260 -0
  118. data/lib/omnizip/algorithms/lzma/literal_decoder.rb +320 -0
  119. data/lib/omnizip/algorithms/lzma/literal_encoder.rb +210 -0
  120. data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +341 -0
  121. data/lib/omnizip/algorithms/lzma/lzma_alone_decoder.rb +192 -0
  122. data/lib/omnizip/algorithms/lzma/lzma_state.rb +128 -0
  123. data/lib/omnizip/algorithms/lzma/match.rb +32 -0
  124. data/lib/omnizip/algorithms/lzma/match_finder.rb +205 -0
  125. data/lib/omnizip/algorithms/lzma/match_finder_config.rb +142 -0
  126. data/lib/omnizip/algorithms/lzma/match_finder_factory.rb +88 -0
  127. data/lib/omnizip/algorithms/lzma/optimal_encoder.rb +130 -0
  128. data/lib/omnizip/algorithms/lzma/probability_models.rb +72 -0
  129. data/lib/omnizip/algorithms/lzma/range_coder.rb +85 -0
  130. data/lib/omnizip/algorithms/lzma/range_decoder.rb +434 -0
  131. data/lib/omnizip/algorithms/lzma/range_encoder.rb +194 -0
  132. data/lib/omnizip/algorithms/lzma/state.rb +127 -0
  133. data/lib/omnizip/algorithms/lzma/xz_buffered_range_encoder.rb +325 -0
  134. data/lib/omnizip/algorithms/lzma/xz_encoder.rb +426 -0
  135. data/lib/omnizip/algorithms/lzma/xz_encoder_fast.rb +645 -0
  136. data/lib/omnizip/algorithms/lzma/xz_match_finder_adapter.rb +227 -0
  137. data/lib/omnizip/algorithms/lzma/xz_price_calculator.rb +169 -0
  138. data/lib/omnizip/algorithms/lzma/xz_probability_models.rb +261 -0
  139. data/lib/omnizip/algorithms/lzma/xz_range_encoder.rb +223 -0
  140. data/lib/omnizip/algorithms/lzma/xz_range_encoder_exact.rb +331 -0
  141. data/lib/omnizip/algorithms/lzma/xz_state.rb +116 -0
  142. data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +2055 -0
  143. data/lib/omnizip/algorithms/lzma.rb +238 -0
  144. data/lib/omnizip/algorithms/lzma2/chunk_manager.rb +182 -0
  145. data/lib/omnizip/algorithms/lzma2/constants.rb +41 -0
  146. data/lib/omnizip/algorithms/lzma2/encoder.rb +147 -0
  147. data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +161 -0
  148. data/lib/omnizip/algorithms/lzma2/properties.rb +179 -0
  149. data/lib/omnizip/algorithms/lzma2/simple_lzma2_encoder.rb +127 -0
  150. data/lib/omnizip/algorithms/lzma2/xz_encoder_adapter.rb +85 -0
  151. data/lib/omnizip/algorithms/lzma2.rb +141 -0
  152. data/lib/omnizip/algorithms/ppmd7/constants.rb +74 -0
  153. data/lib/omnizip/algorithms/ppmd7/context.rb +154 -0
  154. data/lib/omnizip/algorithms/ppmd7/decoder.rb +126 -0
  155. data/lib/omnizip/algorithms/ppmd7/encoder.rb +163 -0
  156. data/lib/omnizip/algorithms/ppmd7/model.rb +248 -0
  157. data/lib/omnizip/algorithms/ppmd7/symbol_state.rb +57 -0
  158. data/lib/omnizip/algorithms/ppmd7.rb +116 -0
  159. data/lib/omnizip/algorithms/ppmd8/constants.rb +61 -0
  160. data/lib/omnizip/algorithms/ppmd8/context.rb +34 -0
  161. data/lib/omnizip/algorithms/ppmd8/decoder.rb +107 -0
  162. data/lib/omnizip/algorithms/ppmd8/encoder.rb +138 -0
  163. data/lib/omnizip/algorithms/ppmd8/model.rb +250 -0
  164. data/lib/omnizip/algorithms/ppmd8/restoration_method.rb +78 -0
  165. data/lib/omnizip/algorithms/ppmd8.rb +82 -0
  166. data/lib/omnizip/algorithms/ppmd_base.rb +138 -0
  167. data/lib/omnizip/algorithms/sevenzip_lzma2.rb +123 -0
  168. data/lib/omnizip/algorithms/xz_lzma2.rb +118 -0
  169. data/lib/omnizip/algorithms/zstandard/constants.rb +25 -0
  170. data/lib/omnizip/algorithms/zstandard/decoder.rb +46 -0
  171. data/lib/omnizip/algorithms/zstandard/encoder.rb +51 -0
  172. data/lib/omnizip/algorithms/zstandard.rb +138 -0
  173. data/lib/omnizip/buffer/memory_archive.rb +251 -0
  174. data/lib/omnizip/buffer/memory_extractor.rb +224 -0
  175. data/lib/omnizip/buffer.rb +176 -0
  176. data/lib/omnizip/checksum_registry.rb +114 -0
  177. data/lib/omnizip/checksums/crc32.rb +100 -0
  178. data/lib/omnizip/checksums/crc64.rb +101 -0
  179. data/lib/omnizip/checksums/crc_base.rb +158 -0
  180. data/lib/omnizip/checksums/verifier.rb +131 -0
  181. data/lib/omnizip/chunked/memory_manager.rb +194 -0
  182. data/lib/omnizip/chunked/reader.rb +78 -0
  183. data/lib/omnizip/chunked/writer.rb +120 -0
  184. data/lib/omnizip/chunked.rb +129 -0
  185. data/lib/omnizip/cli/output_formatter.rb +104 -0
  186. data/lib/omnizip/cli.rb +572 -0
  187. data/lib/omnizip/commands/.keep +0 -0
  188. data/lib/omnizip/commands/archive_create_command.rb +427 -0
  189. data/lib/omnizip/commands/archive_extract_command.rb +272 -0
  190. data/lib/omnizip/commands/archive_list_command.rb +218 -0
  191. data/lib/omnizip/commands/archive_repair_command.rb +131 -0
  192. data/lib/omnizip/commands/archive_verify_command.rb +117 -0
  193. data/lib/omnizip/commands/compress_command.rb +117 -0
  194. data/lib/omnizip/commands/decompress_command.rb +120 -0
  195. data/lib/omnizip/commands/list_command.rb +53 -0
  196. data/lib/omnizip/commands/metadata_command.rb +153 -0
  197. data/lib/omnizip/commands/parity_create_command.rb +122 -0
  198. data/lib/omnizip/commands/parity_repair_command.rb +122 -0
  199. data/lib/omnizip/commands/parity_verify_command.rb +124 -0
  200. data/lib/omnizip/commands/profile_list_command.rb +56 -0
  201. data/lib/omnizip/commands/profile_show_command.rb +44 -0
  202. data/lib/omnizip/convenience.rb +359 -0
  203. data/lib/omnizip/converter/conversion_registry.rb +49 -0
  204. data/lib/omnizip/converter/conversion_strategy.rb +121 -0
  205. data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +97 -0
  206. data/lib/omnizip/converter/zip_to_seven_zip_strategy.rb +112 -0
  207. data/lib/omnizip/converter.rb +105 -0
  208. data/lib/omnizip/crypto/aes256/cipher.rb +100 -0
  209. data/lib/omnizip/crypto/aes256/constants.rb +28 -0
  210. data/lib/omnizip/crypto/aes256/key_derivation.rb +101 -0
  211. data/lib/omnizip/crypto/aes256.rb +102 -0
  212. data/lib/omnizip/error.rb +106 -0
  213. data/lib/omnizip/eta/exponential_smoothing_estimator.rb +98 -0
  214. data/lib/omnizip/eta/moving_average_estimator.rb +99 -0
  215. data/lib/omnizip/eta/rate_calculator.rb +104 -0
  216. data/lib/omnizip/eta/sample_history.rb +143 -0
  217. data/lib/omnizip/eta/time_estimator.rb +106 -0
  218. data/lib/omnizip/eta.rb +63 -0
  219. data/lib/omnizip/extraction/filter_chain.rb +177 -0
  220. data/lib/omnizip/extraction/glob_pattern.rb +140 -0
  221. data/lib/omnizip/extraction/pattern_matcher.rb +70 -0
  222. data/lib/omnizip/extraction/predicate_pattern.rb +52 -0
  223. data/lib/omnizip/extraction/regex_pattern.rb +50 -0
  224. data/lib/omnizip/extraction/selective_extractor.rb +240 -0
  225. data/lib/omnizip/extraction.rb +111 -0
  226. data/lib/omnizip/file_type/mime_classifier.rb +144 -0
  227. data/lib/omnizip/file_type.rb +113 -0
  228. data/lib/omnizip/filter.rb +139 -0
  229. data/lib/omnizip/filter_pipeline.rb +108 -0
  230. data/lib/omnizip/filter_registry.rb +166 -0
  231. data/lib/omnizip/filters/bcj.rb +279 -0
  232. data/lib/omnizip/filters/bcj2/constants.rb +53 -0
  233. data/lib/omnizip/filters/bcj2/decoder.rb +200 -0
  234. data/lib/omnizip/filters/bcj2/encoder.rb +61 -0
  235. data/lib/omnizip/filters/bcj2/stream_data.rb +93 -0
  236. data/lib/omnizip/filters/bcj2.rb +99 -0
  237. data/lib/omnizip/filters/bcj_arm.rb +176 -0
  238. data/lib/omnizip/filters/bcj_arm64.rb +244 -0
  239. data/lib/omnizip/filters/bcj_ia64.rb +196 -0
  240. data/lib/omnizip/filters/bcj_ppc.rb +190 -0
  241. data/lib/omnizip/filters/bcj_sparc.rb +176 -0
  242. data/lib/omnizip/filters/bcj_x86.rb +193 -0
  243. data/lib/omnizip/filters/delta.rb +196 -0
  244. data/lib/omnizip/filters/filter_base.rb +72 -0
  245. data/lib/omnizip/filters/registry.rb +123 -0
  246. data/lib/omnizip/filters/xz_delta.rb +258 -0
  247. data/lib/omnizip/format_detector.rb +162 -0
  248. data/lib/omnizip/format_registry.rb +59 -0
  249. data/lib/omnizip/formats/.keep +0 -0
  250. data/lib/omnizip/formats/bzip2_file.rb +172 -0
  251. data/lib/omnizip/formats/cpio/constants.rb +55 -0
  252. data/lib/omnizip/formats/cpio/entry.rb +385 -0
  253. data/lib/omnizip/formats/cpio/reader.rb +196 -0
  254. data/lib/omnizip/formats/cpio/writer.rb +234 -0
  255. data/lib/omnizip/formats/cpio.rb +140 -0
  256. data/lib/omnizip/formats/format_spec_loader.rb +230 -0
  257. data/lib/omnizip/formats/gzip.rb +238 -0
  258. data/lib/omnizip/formats/iso/directory_builder.rb +297 -0
  259. data/lib/omnizip/formats/iso/directory_record.rb +152 -0
  260. data/lib/omnizip/formats/iso/joliet.rb +204 -0
  261. data/lib/omnizip/formats/iso/path_table.rb +125 -0
  262. data/lib/omnizip/formats/iso/reader.rb +197 -0
  263. data/lib/omnizip/formats/iso/rock_ridge.rb +349 -0
  264. data/lib/omnizip/formats/iso/volume_builder.rb +320 -0
  265. data/lib/omnizip/formats/iso/volume_descriptor.rb +168 -0
  266. data/lib/omnizip/formats/iso/writer.rb +530 -0
  267. data/lib/omnizip/formats/iso.rb +140 -0
  268. data/lib/omnizip/formats/lzip.rb +175 -0
  269. data/lib/omnizip/formats/lzma_alone.rb +171 -0
  270. data/lib/omnizip/formats/rar/archive_repairer.rb +243 -0
  271. data/lib/omnizip/formats/rar/archive_verifier.rb +195 -0
  272. data/lib/omnizip/formats/rar/block_parser.rb +243 -0
  273. data/lib/omnizip/formats/rar/compression/bit_stream.rb +180 -0
  274. data/lib/omnizip/formats/rar/compression/dispatcher.rb +217 -0
  275. data/lib/omnizip/formats/rar/compression/lz77_huffman/decoder.rb +216 -0
  276. data/lib/omnizip/formats/rar/compression/lz77_huffman/encoder.rb +158 -0
  277. data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_builder.rb +217 -0
  278. data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_coder.rb +189 -0
  279. data/lib/omnizip/formats/rar/compression/lz77_huffman/match_finder.rb +135 -0
  280. data/lib/omnizip/formats/rar/compression/lz77_huffman/sliding_window.rb +165 -0
  281. data/lib/omnizip/formats/rar/compression/ppmd/context.rb +105 -0
  282. data/lib/omnizip/formats/rar/compression/ppmd/decoder.rb +219 -0
  283. data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +262 -0
  284. data/lib/omnizip/formats/rar/compression_method_registry.rb +106 -0
  285. data/lib/omnizip/formats/rar/constants.rb +82 -0
  286. data/lib/omnizip/formats/rar/decompressor.rb +238 -0
  287. data/lib/omnizip/formats/rar/external_writer.rb +312 -0
  288. data/lib/omnizip/formats/rar/header.rb +192 -0
  289. data/lib/omnizip/formats/rar/license_validator.rb +109 -0
  290. data/lib/omnizip/formats/rar/models/rar_archive.rb +77 -0
  291. data/lib/omnizip/formats/rar/models/rar_entry.rb +65 -0
  292. data/lib/omnizip/formats/rar/models/rar_volume.rb +56 -0
  293. data/lib/omnizip/formats/rar/parity_handler.rb +292 -0
  294. data/lib/omnizip/formats/rar/rar5/compression/lzma.rb +202 -0
  295. data/lib/omnizip/formats/rar/rar5/compression/lzss.rb +578 -0
  296. data/lib/omnizip/formats/rar/rar5/compression/store.rb +60 -0
  297. data/lib/omnizip/formats/rar/rar5/crc32.rb +39 -0
  298. data/lib/omnizip/formats/rar/rar5/encryption/aes256_cbc.rb +97 -0
  299. data/lib/omnizip/formats/rar/rar5/encryption/encryption_header.rb +114 -0
  300. data/lib/omnizip/formats/rar/rar5/encryption/encryption_manager.rb +166 -0
  301. data/lib/omnizip/formats/rar/rar5/encryption/key_derivation.rb +97 -0
  302. data/lib/omnizip/formats/rar/rar5/header.rb +187 -0
  303. data/lib/omnizip/formats/rar/rar5/models/encryption_options.rb +74 -0
  304. data/lib/omnizip/formats/rar/rar5/models/recovery_options.rb +63 -0
  305. data/lib/omnizip/formats/rar/rar5/models/solid_options.rb +63 -0
  306. data/lib/omnizip/formats/rar/rar5/models/volume_options.rb +74 -0
  307. data/lib/omnizip/formats/rar/rar5/multi_volume/ARCHITECTURE.md +290 -0
  308. data/lib/omnizip/formats/rar/rar5/multi_volume/volume_manager.rb +264 -0
  309. data/lib/omnizip/formats/rar/rar5/multi_volume/volume_splitter.rb +155 -0
  310. data/lib/omnizip/formats/rar/rar5/multi_volume/volume_writer.rb +194 -0
  311. data/lib/omnizip/formats/rar/rar5/solid/solid_encoder.rb +109 -0
  312. data/lib/omnizip/formats/rar/rar5/solid/solid_manager.rb +142 -0
  313. data/lib/omnizip/formats/rar/rar5/solid/solid_stream.rb +121 -0
  314. data/lib/omnizip/formats/rar/rar5/vint.rb +65 -0
  315. data/lib/omnizip/formats/rar/rar5/writer.rb +466 -0
  316. data/lib/omnizip/formats/rar/rar_format_base.rb +241 -0
  317. data/lib/omnizip/formats/rar/reader.rb +366 -0
  318. data/lib/omnizip/formats/rar/recovery_record.rb +245 -0
  319. data/lib/omnizip/formats/rar/volume_manager.rb +168 -0
  320. data/lib/omnizip/formats/rar/writer.rb +431 -0
  321. data/lib/omnizip/formats/rar.rb +205 -0
  322. data/lib/omnizip/formats/rar3/compressor.rb +73 -0
  323. data/lib/omnizip/formats/rar3/decompressor.rb +66 -0
  324. data/lib/omnizip/formats/rar3/reader.rb +386 -0
  325. data/lib/omnizip/formats/rar3/writer.rb +219 -0
  326. data/lib/omnizip/formats/rar5/compressor.rb +73 -0
  327. data/lib/omnizip/formats/rar5/decompressor.rb +66 -0
  328. data/lib/omnizip/formats/rar5/reader.rb +342 -0
  329. data/lib/omnizip/formats/rar5/writer.rb +214 -0
  330. data/lib/omnizip/formats/seven_zip/coder_chain.rb +150 -0
  331. data/lib/omnizip/formats/seven_zip/constants.rb +126 -0
  332. data/lib/omnizip/formats/seven_zip/encoded_header.rb +114 -0
  333. data/lib/omnizip/formats/seven_zip/encrypted_header.rb +142 -0
  334. data/lib/omnizip/formats/seven_zip/file_collector.rb +144 -0
  335. data/lib/omnizip/formats/seven_zip/header.rb +106 -0
  336. data/lib/omnizip/formats/seven_zip/header_encryptor.rb +134 -0
  337. data/lib/omnizip/formats/seven_zip/header_writer.rb +466 -0
  338. data/lib/omnizip/formats/seven_zip/models/coder_info.rb +30 -0
  339. data/lib/omnizip/formats/seven_zip/models/file_entry.rb +58 -0
  340. data/lib/omnizip/formats/seven_zip/models/folder.rb +69 -0
  341. data/lib/omnizip/formats/seven_zip/models/stream_info.rb +42 -0
  342. data/lib/omnizip/formats/seven_zip/parser.rb +660 -0
  343. data/lib/omnizip/formats/seven_zip/reader.rb +458 -0
  344. data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +632 -0
  345. data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +315 -0
  346. data/lib/omnizip/formats/seven_zip/stream_compressor.rb +151 -0
  347. data/lib/omnizip/formats/seven_zip/stream_decompressor.rb +162 -0
  348. data/lib/omnizip/formats/seven_zip/writer.rb +740 -0
  349. data/lib/omnizip/formats/seven_zip.rb +93 -0
  350. data/lib/omnizip/formats/tar/constants.rb +73 -0
  351. data/lib/omnizip/formats/tar/entry.rb +94 -0
  352. data/lib/omnizip/formats/tar/header.rb +168 -0
  353. data/lib/omnizip/formats/tar/reader.rb +121 -0
  354. data/lib/omnizip/formats/tar/writer.rb +216 -0
  355. data/lib/omnizip/formats/tar.rb +84 -0
  356. data/lib/omnizip/formats/xz/reader.rb +116 -0
  357. data/lib/omnizip/formats/xz.rb +237 -0
  358. data/lib/omnizip/formats/xz_impl/block_decoder.rb +754 -0
  359. data/lib/omnizip/formats/xz_impl/block_encoder.rb +306 -0
  360. data/lib/omnizip/formats/xz_impl/block_header.rb +210 -0
  361. data/lib/omnizip/formats/xz_impl/block_header_parser.rb +186 -0
  362. data/lib/omnizip/formats/xz_impl/constants.rb +49 -0
  363. data/lib/omnizip/formats/xz_impl/index_decoder.rb +174 -0
  364. data/lib/omnizip/formats/xz_impl/index_encoder.rb +122 -0
  365. data/lib/omnizip/formats/xz_impl/stream_decoder.rb +468 -0
  366. data/lib/omnizip/formats/xz_impl/stream_encoder.rb +99 -0
  367. data/lib/omnizip/formats/xz_impl/stream_footer.rb +81 -0
  368. data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +117 -0
  369. data/lib/omnizip/formats/xz_impl/stream_header.rb +55 -0
  370. data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +108 -0
  371. data/lib/omnizip/formats/xz_impl/vli.rb +128 -0
  372. data/lib/omnizip/formats/xz_impl/writer.rb +421 -0
  373. data/lib/omnizip/formats/zip/central_directory_header.rb +195 -0
  374. data/lib/omnizip/formats/zip/constants.rb +69 -0
  375. data/lib/omnizip/formats/zip/end_of_central_directory.rb +133 -0
  376. data/lib/omnizip/formats/zip/local_file_header.rb +138 -0
  377. data/lib/omnizip/formats/zip/reader.rb +250 -0
  378. data/lib/omnizip/formats/zip/unix_extra_field.rb +153 -0
  379. data/lib/omnizip/formats/zip/writer.rb +375 -0
  380. data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +104 -0
  381. data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +66 -0
  382. data/lib/omnizip/formats/zip/zip64_extra_field.rb +114 -0
  383. data/lib/omnizip/formats/zip.rb +50 -0
  384. data/lib/omnizip/implementations/base/lzma2_decoder_base.rb +75 -0
  385. data/lib/omnizip/implementations/base/lzma2_encoder_base.rb +128 -0
  386. data/lib/omnizip/implementations/base/lzma_decoder_base.rb +83 -0
  387. data/lib/omnizip/implementations/base/lzma_encoder_base.rb +108 -0
  388. data/lib/omnizip/implementations/base/state_machine_base.rb +182 -0
  389. data/lib/omnizip/implementations/seven_zip/lzma/decoder.rb +421 -0
  390. data/lib/omnizip/implementations/seven_zip/lzma/encoder.rb +465 -0
  391. data/lib/omnizip/implementations/seven_zip/lzma/match_finder.rb +288 -0
  392. data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +200 -0
  393. data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +197 -0
  394. data/lib/omnizip/implementations/seven_zip/lzma/state_machine.rb +141 -0
  395. data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +519 -0
  396. data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +723 -0
  397. data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +750 -0
  398. data/lib/omnizip/io/buffered_input.rb +146 -0
  399. data/lib/omnizip/io/buffered_output.rb +105 -0
  400. data/lib/omnizip/io/stream_manager.rb +115 -0
  401. data/lib/omnizip/link_handler/hard_link.rb +79 -0
  402. data/lib/omnizip/link_handler/symbolic_link.rb +74 -0
  403. data/lib/omnizip/link_handler.rb +124 -0
  404. data/lib/omnizip/metadata/archive_metadata.rb +114 -0
  405. data/lib/omnizip/metadata/entry_metadata.rb +146 -0
  406. data/lib/omnizip/metadata/metadata_editor.rb +171 -0
  407. data/lib/omnizip/metadata/metadata_registry.rb +64 -0
  408. data/lib/omnizip/metadata/metadata_validator.rb +99 -0
  409. data/lib/omnizip/metadata.rb +57 -0
  410. data/lib/omnizip/models/.keep +0 -0
  411. data/lib/omnizip/models/algorithm_metadata.rb +73 -0
  412. data/lib/omnizip/models/compression_options.rb +71 -0
  413. data/lib/omnizip/models/conversion_options.rb +87 -0
  414. data/lib/omnizip/models/conversion_result.rb +135 -0
  415. data/lib/omnizip/models/eta_result.rb +46 -0
  416. data/lib/omnizip/models/extraction_rule.rb +115 -0
  417. data/lib/omnizip/models/filter_chain.rb +144 -0
  418. data/lib/omnizip/models/filter_config.rb +183 -0
  419. data/lib/omnizip/models/match_result.rb +124 -0
  420. data/lib/omnizip/models/optimization_suggestion.rb +91 -0
  421. data/lib/omnizip/models/parallel_options.rb +104 -0
  422. data/lib/omnizip/models/performance_result.rb +79 -0
  423. data/lib/omnizip/models/profile_report.rb +82 -0
  424. data/lib/omnizip/models/progress_options.rb +38 -0
  425. data/lib/omnizip/models/split_options.rb +116 -0
  426. data/lib/omnizip/optimization_registry.rb +81 -0
  427. data/lib/omnizip/parallel/job_queue.rb +209 -0
  428. data/lib/omnizip/parallel/job_scheduler.rb +203 -0
  429. data/lib/omnizip/parallel/parallel_compressor.rb +347 -0
  430. data/lib/omnizip/parallel/parallel_extractor.rb +329 -0
  431. data/lib/omnizip/parallel/worker_pool.rb +223 -0
  432. data/lib/omnizip/parallel.rb +149 -0
  433. data/lib/omnizip/parity/chunked_block_processor.rb +196 -0
  434. data/lib/omnizip/parity/galois16.rb +145 -0
  435. data/lib/omnizip/parity/models/creator_packet.rb +73 -0
  436. data/lib/omnizip/parity/models/file_description_packet.rb +133 -0
  437. data/lib/omnizip/parity/models/ifsc_packet.rb +123 -0
  438. data/lib/omnizip/parity/models/main_packet.rb +128 -0
  439. data/lib/omnizip/parity/models/packet.rb +156 -0
  440. data/lib/omnizip/parity/models/packet_registry.rb +109 -0
  441. data/lib/omnizip/parity/models/recovery_slice_packet.rb +78 -0
  442. data/lib/omnizip/parity/par2_creator.rb +531 -0
  443. data/lib/omnizip/parity/par2_repairer.rb +407 -0
  444. data/lib/omnizip/parity/par2_verifier.rb +364 -0
  445. data/lib/omnizip/parity/par2cmdline_algorithm.rb +110 -0
  446. data/lib/omnizip/parity/par2cmdline_coefficients.rb +78 -0
  447. data/lib/omnizip/parity/reed_solomon_decoder.rb +266 -0
  448. data/lib/omnizip/parity/reed_solomon_encoder.rb +111 -0
  449. data/lib/omnizip/parity/reed_solomon_matrix.rb +342 -0
  450. data/lib/omnizip/parity.rb +186 -0
  451. data/lib/omnizip/password/encryption_registry.rb +65 -0
  452. data/lib/omnizip/password/encryption_strategy.rb +96 -0
  453. data/lib/omnizip/password/password_validator.rb +129 -0
  454. data/lib/omnizip/password/winzip_aes_strategy.rb +192 -0
  455. data/lib/omnizip/password/zip_crypto_strategy.rb +141 -0
  456. data/lib/omnizip/password.rb +87 -0
  457. data/lib/omnizip/pipe/stream_compressor.rb +124 -0
  458. data/lib/omnizip/pipe/stream_decompressor.rb +174 -0
  459. data/lib/omnizip/pipe.rb +121 -0
  460. data/lib/omnizip/platform/ntfs_streams.rb +201 -0
  461. data/lib/omnizip/platform.rb +189 -0
  462. data/lib/omnizip/profile/archive_profile.rb +39 -0
  463. data/lib/omnizip/profile/balanced_profile.rb +33 -0
  464. data/lib/omnizip/profile/binary_profile.rb +36 -0
  465. data/lib/omnizip/profile/compression_profile.rb +158 -0
  466. data/lib/omnizip/profile/custom_profile.rb +157 -0
  467. data/lib/omnizip/profile/fast_profile.rb +33 -0
  468. data/lib/omnizip/profile/maximum_profile.rb +33 -0
  469. data/lib/omnizip/profile/profile_detector.rb +110 -0
  470. data/lib/omnizip/profile/profile_registry.rb +161 -0
  471. data/lib/omnizip/profile/text_profile.rb +36 -0
  472. data/lib/omnizip/profile.rb +190 -0
  473. data/lib/omnizip/profiler/memory_profiler.rb +66 -0
  474. data/lib/omnizip/profiler/method_profiler.rb +49 -0
  475. data/lib/omnizip/profiler/report_generator.rb +169 -0
  476. data/lib/omnizip/profiler.rb +204 -0
  477. data/lib/omnizip/progress/callback_reporter.rb +36 -0
  478. data/lib/omnizip/progress/console_reporter.rb +62 -0
  479. data/lib/omnizip/progress/log_reporter.rb +91 -0
  480. data/lib/omnizip/progress/operation_progress.rb +118 -0
  481. data/lib/omnizip/progress/progress_bar.rb +156 -0
  482. data/lib/omnizip/progress/progress_reporter.rb +40 -0
  483. data/lib/omnizip/progress/progress_tracker.rb +190 -0
  484. data/lib/omnizip/progress/silent_reporter.rb +24 -0
  485. data/lib/omnizip/progress.rb +127 -0
  486. data/lib/omnizip/rubyzip_compat.rb +63 -0
  487. data/lib/omnizip/temp/safe_extract.rb +168 -0
  488. data/lib/omnizip/temp/temp_file.rb +124 -0
  489. data/lib/omnizip/temp/temp_file_pool.rb +109 -0
  490. data/lib/omnizip/temp.rb +181 -0
  491. data/lib/omnizip/version.rb +5 -0
  492. data/lib/omnizip/zip/entry.rb +156 -0
  493. data/lib/omnizip/zip/file.rb +485 -0
  494. data/lib/omnizip/zip/input_stream.rb +273 -0
  495. data/lib/omnizip/zip/output_stream.rb +324 -0
  496. data/lib/omnizip.rb +156 -0
  497. data/readme-docs/advanced-features.adoc +515 -0
  498. data/readme-docs/api-usage.adoc +444 -0
  499. data/readme-docs/architecture.adoc +449 -0
  500. data/readme-docs/archive-formats.adoc +479 -0
  501. data/readme-docs/cli-usage.adoc +222 -0
  502. data/readme-docs/compression-algorithms.adoc +442 -0
  503. data/readme-docs/compression-profiles.adoc +247 -0
  504. data/readme-docs/encryption-checksums.adoc +328 -0
  505. data/readme-docs/format-converter.adoc +325 -0
  506. data/readme-docs/installation.adoc +228 -0
  507. data/readme-docs/par2-archives.adoc +608 -0
  508. data/readme-docs/performance-profiler.adoc +389 -0
  509. data/readme-docs/preprocessing-filters.adoc +280 -0
  510. data/xz-file-format-1.2.1.txt +1174 -0
  511. metadata +617 -0
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "packet"
4
+
5
+ module Omnizip
6
+ module Parity
7
+ module Models
8
+ # PAR2 Creator packet
9
+ #
10
+ # Contains information about the tool that created the PAR2 file.
11
+ # This packet is optional but recommended for identification.
12
+ #
13
+ # Body structure:
14
+ # - creator_string (variable): Null-terminated string identifying the tool
15
+ class CreatorPacket < Packet
16
+ # Packet type identifier
17
+ PACKET_TYPE = "PAR 2.0\x00Creator\x00"
18
+
19
+ # Creator identification string (without null terminator)
20
+ attribute :creator_string, :string, default: -> { "Omnizip PAR2" }
21
+
22
+ # Initialize creator packet
23
+ #
24
+ # @param attributes [Hash] Packet attributes
25
+ def initialize(**attributes)
26
+ super
27
+ self.type = PACKET_TYPE
28
+ end
29
+
30
+ # Parse body data into attributes
31
+ #
32
+ # Body format:
33
+ # - creator_string: null-terminated string
34
+ def parse_body
35
+ return if body_data.nil? || body_data.empty?
36
+
37
+ # Read creator string (null-terminated)
38
+ self.creator_string = body_data.unpack1("Z*")
39
+ end
40
+
41
+ # Build body data from attributes
42
+ #
43
+ # Constructs binary body data with null terminator
44
+ #
45
+ # @return [String] Binary body data
46
+ def build_body
47
+ data = +""
48
+
49
+ # Write creator string (null-terminated)
50
+ data << creator_string
51
+ data << "\x00"
52
+
53
+ self.body_data = data
54
+ end
55
+
56
+ # Get creator tool name
57
+ #
58
+ # @return [String] Creator tool name
59
+ def tool_name
60
+ creator_string.split.first || creator_string
61
+ end
62
+
63
+ # Get creator version if available
64
+ #
65
+ # @return [String, nil] Version string or nil
66
+ def version
67
+ parts = creator_string.split
68
+ parts.size > 1 ? parts[1..].join(" ") : nil
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "packet"
4
+
5
+ module Omnizip
6
+ module Parity
7
+ module Models
8
+ # PAR2 File Description packet
9
+ #
10
+ # Contains metadata about a protected file including hashes and filename.
11
+ #
12
+ # Body structure:
13
+ # - file_id (16 bytes): MD5 hash identifying the file
14
+ # - file_hash (16 bytes): MD5 hash of complete file content
15
+ # - file_hash_16k (16 bytes): MD5 hash of first 16KB
16
+ # - length (8 bytes, Q<): File size in bytes
17
+ # - filename (variable): Null-terminated filename, padded to multiple of 4
18
+ class FileDescriptionPacket < Packet
19
+ # Packet type identifier
20
+ PACKET_TYPE = "PAR 2.0\x00FileDesc"
21
+
22
+ # File identifier (16 bytes MD5)
23
+ attribute :file_id, :string
24
+
25
+ # Full file hash (16 bytes MD5)
26
+ attribute :file_hash, :string
27
+
28
+ # First 16KB hash (16 bytes MD5)
29
+ attribute :file_hash_16k, :string
30
+
31
+ # File length in bytes
32
+ attribute :length, :integer
33
+
34
+ # Filename (without null terminator or padding)
35
+ attribute :filename, :string
36
+
37
+ # Initialize file description packet
38
+ #
39
+ # @param attributes [Hash] Packet attributes
40
+ def initialize(**attributes)
41
+ super
42
+ self.type = PACKET_TYPE
43
+ end
44
+
45
+ # Parse body data into attributes
46
+ #
47
+ # Body format:
48
+ # - file_id: 16 bytes
49
+ # - file_hash: 16 bytes
50
+ # - file_hash_16k: 16 bytes
51
+ # - length: 8 bytes (Q<)
52
+ # - filename: null-terminated, padded to multiple of 4
53
+ def parse_body
54
+ return if body_data.nil? || body_data.empty?
55
+
56
+ # Validate minimum size (16+16+16+8 = 56 bytes minimum)
57
+ if body_data.bytesize < 56
58
+ warn "FileDescriptionPacket body too short: #{body_data.bytesize} bytes"
59
+ return
60
+ end
61
+
62
+ pos = 0
63
+
64
+ # Read file_id (16 bytes)
65
+ self.file_id = body_data[pos, 16]
66
+ pos += 16
67
+
68
+ # Read file_hash (16 bytes)
69
+ self.file_hash = body_data[pos, 16]
70
+ pos += 16
71
+
72
+ # Read file_hash_16k (16 bytes)
73
+ self.file_hash_16k = body_data[pos, 16]
74
+ pos += 16
75
+
76
+ # Read length (8 bytes, little-endian unsigned 64-bit)
77
+ length_data = body_data[pos, 8]
78
+ return if length_data.nil? || length_data.bytesize < 8
79
+
80
+ self.length = length_data.unpack1("Q<")
81
+ pos += 8
82
+
83
+ # Read filename (null-terminated, remaining bytes)
84
+ filename_data = body_data[pos..]
85
+ self.filename = filename_data&.unpack1("Z*") || ""
86
+ end
87
+
88
+ # Build body data from attributes
89
+ #
90
+ # Constructs binary body data with proper padding
91
+ #
92
+ # @return [String] Binary body data
93
+ def build_body
94
+ data = +""
95
+
96
+ # Validate file_id, file_hash, file_hash_16k are 16 bytes
97
+ [file_id, file_hash, file_hash_16k].each_with_index do |hash, idx|
98
+ field_name = %w[file_id file_hash file_hash_16k][idx]
99
+ if hash.bytesize != 16
100
+ raise ArgumentError,
101
+ "#{field_name} must be 16 bytes, got #{hash.bytesize}"
102
+ end
103
+ end
104
+
105
+ # Write hashes
106
+ data << file_id
107
+ data << file_hash
108
+ data << file_hash_16k
109
+
110
+ # Write length (8 bytes, little-endian)
111
+ data << [length].pack("Q<")
112
+
113
+ # Write filename (null-terminated, padded to multiple of 4)
114
+ data << filename
115
+ data << "\x00"
116
+
117
+ # Add padding to make total length multiple of 4
118
+ padding = (4 - ((filename.bytesize + 1) % 4)) % 4
119
+ data << ("\x00" * padding) if padding.positive?
120
+
121
+ self.body_data = data
122
+ end
123
+
124
+ # Get basename of file
125
+ #
126
+ # @return [String] File basename
127
+ def basename
128
+ File.basename(filename)
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "packet"
4
+
5
+ module Omnizip
6
+ module Parity
7
+ module Models
8
+ # PAR2 IFSC (Input File Slice Checksum) packet
9
+ #
10
+ # Contains checksums for individual data blocks of a file.
11
+ # One IFSC packet contains checksums for ALL blocks of a file.
12
+ #
13
+ # Body structure:
14
+ # - file_id (16 bytes): File identifier
15
+ # - For each block:
16
+ # - block_hash (16 bytes): MD5 hash of block data
17
+ # - block_crc32 (4 bytes, L<): CRC32 of block data
18
+ class IfscPacket < Packet
19
+ # Packet type identifier
20
+ PACKET_TYPE = "PAR 2.0\x00IFSC\x00\x00\x00\x00"
21
+
22
+ # File identifier (16 bytes)
23
+ attribute :file_id, :string
24
+
25
+ # Array of block hashes (16 bytes MD5 each)
26
+ attribute :block_hashes, :string, collection: true, default: -> { [] }
27
+
28
+ # Array of block CRC32s (4 bytes each)
29
+ attribute :block_crc32s, :integer, collection: true, default: -> { [] }
30
+
31
+ # Initialize IFSC packet
32
+ #
33
+ # @param attributes [Hash] Packet attributes
34
+ def initialize(**attributes)
35
+ super
36
+ self.type = PACKET_TYPE
37
+ self.block_hashes = [] if block_hashes.nil?
38
+ self.block_crc32s = [] if block_crc32s.nil?
39
+ end
40
+
41
+ # Parse body data into attributes
42
+ #
43
+ # Body format:
44
+ # - file_id: 16 bytes
45
+ # - For each block:
46
+ # - block_hash: 16 bytes
47
+ # - block_crc32: 4 bytes (L<)
48
+ def parse_body
49
+ return if body_data.nil? || body_data.empty?
50
+ return if body_data.bytesize < 16
51
+
52
+ pos = 0
53
+
54
+ # Read file_id (16 bytes)
55
+ self.file_id = body_data[pos, 16]
56
+ pos += 16
57
+
58
+ # Read all blocks (each is 16 bytes hash + 4 bytes CRC = 20 bytes)
59
+ self.block_hashes = []
60
+ self.block_crc32s = []
61
+
62
+ while pos + 20 <= body_data.bytesize
63
+ # Read block_hash (16 bytes)
64
+ block_hash = body_data[pos, 16]
65
+ pos += 16
66
+
67
+ # Read block_crc32 (4 bytes, little-endian unsigned 32-bit)
68
+ block_crc32 = body_data[pos, 4].unpack1("L<")
69
+ pos += 4
70
+
71
+ block_hashes << block_hash
72
+ block_crc32s << block_crc32
73
+ end
74
+ end
75
+
76
+ # Build body data from attributes
77
+ #
78
+ # Constructs binary body data
79
+ #
80
+ # @return [String] Binary body data
81
+ def build_body
82
+ data = +""
83
+
84
+ # Validate file_id is 16 bytes
85
+ if file_id.bytesize != 16
86
+ raise ArgumentError,
87
+ "file_id must be 16 bytes, got #{file_id.bytesize}"
88
+ end
89
+
90
+ # Write file_id
91
+ data << file_id
92
+
93
+ # Write all blocks
94
+ block_hashes.each_with_index do |block_hash, i|
95
+ # Validate block_hash is 16 bytes
96
+ if block_hash.bytesize != 16
97
+ raise ArgumentError,
98
+ "block_hash must be 16 bytes, got #{block_hash.bytesize}"
99
+ end
100
+
101
+ # Write block_hash
102
+ data << block_hash
103
+
104
+ # Write block_crc32 (4 bytes, little-endian)
105
+ data << [block_crc32s[i]].pack("L<")
106
+ end
107
+
108
+ self.body_data = data
109
+ end
110
+
111
+ # Deprecated: for backward compatibility
112
+ def block_hash
113
+ block_hashes.first
114
+ end
115
+
116
+ # Deprecated: for backward compatibility
117
+ def block_crc32
118
+ block_crc32s.first
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "packet"
4
+
5
+ module Omnizip
6
+ module Parity
7
+ module Models
8
+ # PAR2 Main packet
9
+ #
10
+ # Contains recovery set metadata including block size and file IDs.
11
+ #
12
+ # Body structure:
13
+ # - block_size (8 bytes, Q<): Size of each data block
14
+ # - file_ids (16 bytes each): List of file IDs in the recovery set
15
+ #
16
+ # Note: In current Par2Creator implementation, the body includes
17
+ # additional fields after block_size that are not part of the
18
+ # standard PAR2 spec. This model follows the actual PAR2 spec.
19
+ class MainPacket < Packet
20
+ # Packet type identifier
21
+ PACKET_TYPE = "PAR 2.0\x00Main\x00\x00\x00\x00"
22
+
23
+ # Block size in bytes
24
+ attribute :block_size, :integer
25
+
26
+ # Array of 16-byte file IDs
27
+ attribute :file_ids, :string, collection: true, default: -> { [] }
28
+
29
+ # Initialize main packet
30
+ #
31
+ # @param attributes [Hash] Packet attributes
32
+ def initialize(**attributes)
33
+ super
34
+ self.type = PACKET_TYPE
35
+ end
36
+
37
+ # Parse body data into attributes
38
+ #
39
+ # Body format (PAR2 spec):
40
+ # - block_size: 8 bytes (Q<)
41
+ # - file_ids: 16 bytes each, until end of body
42
+ #
43
+ # par2cmdline variant also includes:
44
+ # - file_count: 4 bytes (L<) after block_size
45
+ # - then file_ids: 16 bytes each
46
+ #
47
+ # This method detects which format is used.
48
+ def parse_body
49
+ return if body_data.nil? || body_data.empty?
50
+
51
+ pos = 0
52
+
53
+ # Read block_size (8 bytes, little-endian unsigned 64-bit)
54
+ self.block_size = body_data[pos, 8].unpack1("Q<")
55
+ pos += 8
56
+
57
+ # Detect format: check if next 4 bytes are a file_count
58
+ remaining = body_data.bytesize - pos
59
+
60
+ if remaining >= 4
61
+ # Try to read potential file_count
62
+ potential_count = body_data[pos, 4].unpack1("L<")
63
+ expected_size = potential_count * 16
64
+
65
+ # Check if this looks like par2cmdline format:
66
+ # - Remaining bytes should be: 4 (count) + count * 16 (file_ids)
67
+ # - Count should be reasonable (> 0, < 10000 for sanity)
68
+ if remaining == (4 + expected_size) &&
69
+ potential_count.positive? &&
70
+ potential_count < 10_000
71
+ # par2cmdline format detected
72
+ pos += 4 # Skip the file_count field
73
+ end
74
+ end
75
+
76
+ # Read file IDs (16 bytes each) until end of body
77
+ self.file_ids = []
78
+ while pos < body_data.bytesize
79
+ file_id = body_data[pos, 16]
80
+ break if file_id.nil? || file_id.bytesize < 16
81
+
82
+ file_ids << file_id
83
+ pos += 16
84
+ end
85
+ end
86
+
87
+ # Build body data from attributes
88
+ #
89
+ # Constructs binary body data from block_size and file_ids
90
+ #
91
+ # @return [String] Binary body data
92
+ def build_body
93
+ data = +""
94
+
95
+ # Write block_size (8 bytes, little-endian)
96
+ data << [block_size].pack("Q<")
97
+
98
+ # Write file IDs (16 bytes each)
99
+ file_ids.each do |file_id|
100
+ # Ensure file_id is exactly 16 bytes
101
+ if file_id.bytesize != 16
102
+ raise ArgumentError,
103
+ "File ID must be 16 bytes, got #{file_id.bytesize}"
104
+ end
105
+ data << file_id
106
+ end
107
+
108
+ self.body_data = data
109
+ end
110
+
111
+ # Get number of files in recovery set
112
+ #
113
+ # @return [Integer] File count
114
+ def file_count
115
+ file_ids.size
116
+ end
117
+
118
+ # Check if packet contains specific file ID
119
+ #
120
+ # @param file_id [String] 16-byte file ID to check
121
+ # @return [Boolean] true if file ID is present
122
+ def includes_file?(file_id)
123
+ file_ids.include?(file_id)
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "lutaml/model"
5
+ rescue LoadError, ArgumentError
6
+ # lutaml-model not available, using simple classes
7
+ end
8
+
9
+ require "digest"
10
+
11
+ module Omnizip
12
+ module Parity
13
+ module Models
14
+ # Base class for PAR2 packets
15
+ #
16
+ # All PAR2 packets share a common 64-byte header structure:
17
+ # - magic (8 bytes): "PAR2\0PKT"
18
+ # - length (8 bytes): Total packet length including header (Q<)
19
+ # - packet_hash (16 bytes): MD5 hash of (set_id + type + data)
20
+ # - set_id (16 bytes): Recovery set identifier
21
+ # - type (16 bytes): Packet type identifier
22
+ #
23
+ # The packet body follows the header and varies by packet type.
24
+ class Packet < Lutaml::Model::Serializable
25
+ # PAR2 packet signature
26
+ PACKET_SIGNATURE = "PAR2\x00PKT".b.freeze
27
+
28
+ # Common header fields (64 bytes total)
29
+ attribute :magic, :string, default: -> { PACKET_SIGNATURE }
30
+ attribute :length, :integer # Total packet length (header + body)
31
+ attribute :packet_hash, :string # MD5 hash (16 bytes)
32
+ attribute :set_id, :string # Recovery set ID (16 bytes)
33
+ attribute :type, :string # Packet type (16 bytes)
34
+
35
+ # Packet body data (variable length, subclass-specific)
36
+ # NOT a lutaml-model attribute because it contains raw binary data
37
+ # that can include null bytes and invalid UTF-8 sequences
38
+ attr_accessor :body_data
39
+
40
+ # Initialize packet
41
+ #
42
+ # @param attributes [Hash] Packet attributes
43
+ def initialize(**attributes)
44
+ @body_data = "" # Initialize body_data with empty binary string
45
+ super
46
+ end
47
+
48
+ # Read packet from IO stream
49
+ #
50
+ # @param io [IO] Input stream
51
+ # @return [Packet, nil] Parsed packet or nil if EOF/invalid
52
+ def self.read_from(io)
53
+ # Read header (64 bytes total)
54
+ magic = io.read(8)
55
+ return nil unless magic == PACKET_SIGNATURE
56
+
57
+ length = io.read(8).unpack1("Q<")
58
+ packet_hash = io.read(16)
59
+ set_id = io.read(16)
60
+ type = io.read(16)
61
+
62
+ # Read body data
63
+ data_length = length - 64
64
+ body_data = io.read(data_length)
65
+
66
+ # Create instance
67
+ packet = new(
68
+ magic: magic,
69
+ length: length,
70
+ packet_hash: packet_hash,
71
+ set_id: set_id,
72
+ type: type,
73
+ )
74
+
75
+ # Set body_data directly to avoid lutaml-model attribute
76
+ # corruption of binary data
77
+ packet.body_data = body_data
78
+
79
+ # Verify packet hash
80
+ # NOTE: Hash validation temporarily disabled for par2cmdline compatibility testing
81
+ # unless packet.valid_hash?
82
+ # warn "Invalid packet hash detected"
83
+ # end
84
+
85
+ packet
86
+ end
87
+
88
+ # Write packet to IO stream
89
+ #
90
+ # @param io [IO] Output stream
91
+ def write_to(io)
92
+ # Calculate total length
93
+ self.length = 64 + body_data.bytesize
94
+
95
+ # Calculate packet hash (MD5 of set_id + type + body_data)
96
+ self.packet_hash = calculate_hash
97
+
98
+ # Write header
99
+ io.write(magic)
100
+ io.write([length].pack("Q<"))
101
+ io.write(packet_hash)
102
+ io.write(set_id)
103
+ io.write(type)
104
+
105
+ # Write body
106
+ io.write(body_data)
107
+ end
108
+
109
+ # Calculate MD5 hash of packet body
110
+ #
111
+ # Hash is computed over: set_id + type + body_data
112
+ #
113
+ # @return [String] 16-byte MD5 digest
114
+ def calculate_hash
115
+ data = +""
116
+ data << set_id
117
+ data << type
118
+ data << body_data
119
+ Digest::MD5.digest(data)
120
+ end
121
+
122
+ # Verify packet hash is correct
123
+ #
124
+ # @return [Boolean] true if hash matches
125
+ def valid_hash?
126
+ calculate_hash == packet_hash
127
+ end
128
+
129
+ # Get packet type identifier
130
+ #
131
+ # @return [String] 16-byte type identifier
132
+ def packet_type
133
+ type
134
+ end
135
+
136
+ # Parse body data into structured attributes
137
+ #
138
+ # Subclasses must implement this to parse body_data
139
+ # into their specific attributes.
140
+ def parse_body
141
+ raise NotImplementedError,
142
+ "#{self.class} must implement parse_body"
143
+ end
144
+
145
+ # Build body data from structured attributes
146
+ #
147
+ # Subclasses must implement this to build body_data
148
+ # from their specific attributes.
149
+ def build_body
150
+ raise NotImplementedError,
151
+ "#{self.class} must implement build_body"
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end