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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +32 -0
- data/.rubocop_todo.yml +754 -0
- data/COPYING +502 -0
- data/Gemfile +17 -0
- data/LICENSE +12 -0
- data/README.adoc +1045 -0
- data/Rakefile +12 -0
- data/benchmark/README.md +260 -0
- data/benchmark/benchmark_suite.rb +125 -0
- data/benchmark/compression_bench.rb +181 -0
- data/benchmark/filter_bench.rb +180 -0
- data/benchmark/models/benchmark_result.rb +59 -0
- data/benchmark/models/comparison_result.rb +69 -0
- data/benchmark/profile_suite.rb +167 -0
- data/benchmark/reporter.rb +150 -0
- data/benchmark/run_benchmarks.rb +66 -0
- data/benchmark/test_data.rb +137 -0
- data/config/formats/rar3_spec.yml +91 -0
- data/config/formats/rar5_spec.yml +102 -0
- data/docs/.github/workflows/docs.yml +142 -0
- data/docs/.gitignore +21 -0
- data/docs/.lychee.toml +67 -0
- data/docs/Gemfile +13 -0
- data/docs/RAR_WRITE_SUPPORT.md +26 -0
- data/docs/README.md +101 -0
- data/docs/_config.yml +112 -0
- data/docs/assets/logo.svg +1 -0
- data/docs/assets/omnizip-logo.pdf +1540 -11
- data/docs/comparison/feature-matrix.adoc +694 -0
- data/docs/comparison/index.adoc +113 -0
- data/docs/comparison/vs-7zip.adoc +309 -0
- data/docs/comparison/vs-peazip.adoc +77 -0
- data/docs/comparison/vs-rubyzip.adoc +342 -0
- data/docs/comparison/vs-winrar.adoc +100 -0
- data/docs/compatibility.adoc +579 -0
- data/docs/concepts/index.adoc +129 -0
- data/docs/developer/architecture.adoc +256 -0
- data/docs/developer/contributing.adoc +158 -0
- data/docs/developer/index.adoc +25 -0
- data/docs/developer/testing.adoc +212 -0
- data/docs/getting-started/basic-usage.adoc +271 -0
- data/docs/getting-started/index.adoc +42 -0
- data/docs/getting-started/installation.adoc +138 -0
- data/docs/getting-started/quick-start.adoc +185 -0
- data/docs/getting-started/your-first-archive.adoc +218 -0
- data/docs/guides/advanced-features/encryption.adoc +300 -0
- data/docs/guides/advanced-features/index.adoc +49 -0
- data/docs/guides/advanced-features/parallel-processing.adoc +246 -0
- data/docs/guides/advanced-features/progress-tracking.adoc +320 -0
- data/docs/guides/advanced-features/streaming.adoc +212 -0
- data/docs/guides/archive-formats/gzip-format.adoc +107 -0
- data/docs/guides/archive-formats/index.adoc +130 -0
- data/docs/guides/archive-formats/rar-format.adoc +104 -0
- data/docs/guides/archive-formats/rar5.adoc +521 -0
- data/docs/guides/archive-formats/seven-zip-format.adoc +35 -0
- data/docs/guides/archive-formats/tar-format.adoc +106 -0
- data/docs/guides/archive-formats/xz-format.adoc +118 -0
- data/docs/guides/archive-formats/zip-format.adoc +35 -0
- data/docs/guides/compression-algorithms/bzip2.adoc +113 -0
- data/docs/guides/compression-algorithms/deflate.adoc +319 -0
- data/docs/guides/compression-algorithms/index.adoc +190 -0
- data/docs/guides/compression-algorithms/lzma.adoc +398 -0
- data/docs/guides/compression-algorithms/lzma2.adoc +327 -0
- data/docs/guides/compression-algorithms/ppmd.adoc +316 -0
- data/docs/guides/compression-algorithms/zstandard.adoc +361 -0
- data/docs/guides/creating-archives.adoc +354 -0
- data/docs/guides/extracting-archives.adoc +53 -0
- data/docs/guides/format-conversion.adoc +64 -0
- data/docs/guides/index.adoc +49 -0
- data/docs/guides/migration-rubyzip.adoc +217 -0
- data/docs/guides/parity-archives.adoc +605 -0
- data/docs/guides/performance-tuning.adoc +88 -0
- data/docs/index.adoc +218 -0
- data/docs/lychee.toml +67 -0
- data/docs/reference/api/overview.adoc +188 -0
- data/docs/reference/cli/compress-command.adoc +114 -0
- data/docs/reference/cli/overview.adoc +140 -0
- data/docs/reference/index.adoc +26 -0
- data/docs/resources/faq.adoc +185 -0
- data/docs/resources/quick-reference.adoc +222 -0
- data/docs/troubleshooting/index.adoc +208 -0
- data/examples/api_comparison.rb +205 -0
- data/examples/deflate64_example.rb +96 -0
- data/examples/par2_demo.rb +121 -0
- data/examples/quick_start_native.rb +150 -0
- data/examples/quick_start_rubyzip.rb +115 -0
- data/examples/rubyzip_compatibility_demo.rb +194 -0
- data/exe/omnizip +27 -0
- data/lib/omnizip/algorithm.rb +130 -0
- data/lib/omnizip/algorithm_registry.rb +86 -0
- data/lib/omnizip/algorithms/.keep +0 -0
- data/lib/omnizip/algorithms/bzip2/bwt.rb +225 -0
- data/lib/omnizip/algorithms/bzip2/decoder.rb +193 -0
- data/lib/omnizip/algorithms/bzip2/encoder.rb +237 -0
- data/lib/omnizip/algorithms/bzip2/huffman.rb +206 -0
- data/lib/omnizip/algorithms/bzip2/mtf.rb +101 -0
- data/lib/omnizip/algorithms/bzip2/rle.rb +151 -0
- data/lib/omnizip/algorithms/bzip2.rb +130 -0
- data/lib/omnizip/algorithms/deflate/constants.rb +28 -0
- data/lib/omnizip/algorithms/deflate/decoder.rb +38 -0
- data/lib/omnizip/algorithms/deflate/encoder.rb +46 -0
- data/lib/omnizip/algorithms/deflate.rb +128 -0
- data/lib/omnizip/algorithms/deflate64/constants.rb +45 -0
- data/lib/omnizip/algorithms/deflate64/decoder.rb +153 -0
- data/lib/omnizip/algorithms/deflate64/encoder.rb +98 -0
- data/lib/omnizip/algorithms/deflate64/huffman_coder.rb +354 -0
- data/lib/omnizip/algorithms/deflate64/lz77_encoder.rb +142 -0
- data/lib/omnizip/algorithms/deflate64.rb +109 -0
- data/lib/omnizip/algorithms/lzma/bit_model.rb +120 -0
- data/lib/omnizip/algorithms/lzma/constants.rb +112 -0
- data/lib/omnizip/algorithms/lzma/decoder.rb +148 -0
- data/lib/omnizip/algorithms/lzma/dictionary.rb +69 -0
- data/lib/omnizip/algorithms/lzma/distance_coder.rb +415 -0
- data/lib/omnizip/algorithms/lzma/encoder.rb +142 -0
- data/lib/omnizip/algorithms/lzma/length_coder.rb +260 -0
- data/lib/omnizip/algorithms/lzma/literal_decoder.rb +320 -0
- data/lib/omnizip/algorithms/lzma/literal_encoder.rb +210 -0
- data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +341 -0
- data/lib/omnizip/algorithms/lzma/lzma_alone_decoder.rb +192 -0
- data/lib/omnizip/algorithms/lzma/lzma_state.rb +128 -0
- data/lib/omnizip/algorithms/lzma/match.rb +32 -0
- data/lib/omnizip/algorithms/lzma/match_finder.rb +205 -0
- data/lib/omnizip/algorithms/lzma/match_finder_config.rb +142 -0
- data/lib/omnizip/algorithms/lzma/match_finder_factory.rb +88 -0
- data/lib/omnizip/algorithms/lzma/optimal_encoder.rb +130 -0
- data/lib/omnizip/algorithms/lzma/probability_models.rb +72 -0
- data/lib/omnizip/algorithms/lzma/range_coder.rb +85 -0
- data/lib/omnizip/algorithms/lzma/range_decoder.rb +434 -0
- data/lib/omnizip/algorithms/lzma/range_encoder.rb +194 -0
- data/lib/omnizip/algorithms/lzma/state.rb +127 -0
- data/lib/omnizip/algorithms/lzma/xz_buffered_range_encoder.rb +325 -0
- data/lib/omnizip/algorithms/lzma/xz_encoder.rb +426 -0
- data/lib/omnizip/algorithms/lzma/xz_encoder_fast.rb +645 -0
- data/lib/omnizip/algorithms/lzma/xz_match_finder_adapter.rb +227 -0
- data/lib/omnizip/algorithms/lzma/xz_price_calculator.rb +169 -0
- data/lib/omnizip/algorithms/lzma/xz_probability_models.rb +261 -0
- data/lib/omnizip/algorithms/lzma/xz_range_encoder.rb +223 -0
- data/lib/omnizip/algorithms/lzma/xz_range_encoder_exact.rb +331 -0
- data/lib/omnizip/algorithms/lzma/xz_state.rb +116 -0
- data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +2055 -0
- data/lib/omnizip/algorithms/lzma.rb +238 -0
- data/lib/omnizip/algorithms/lzma2/chunk_manager.rb +182 -0
- data/lib/omnizip/algorithms/lzma2/constants.rb +41 -0
- data/lib/omnizip/algorithms/lzma2/encoder.rb +147 -0
- data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +161 -0
- data/lib/omnizip/algorithms/lzma2/properties.rb +179 -0
- data/lib/omnizip/algorithms/lzma2/simple_lzma2_encoder.rb +127 -0
- data/lib/omnizip/algorithms/lzma2/xz_encoder_adapter.rb +85 -0
- data/lib/omnizip/algorithms/lzma2.rb +141 -0
- data/lib/omnizip/algorithms/ppmd7/constants.rb +74 -0
- data/lib/omnizip/algorithms/ppmd7/context.rb +154 -0
- data/lib/omnizip/algorithms/ppmd7/decoder.rb +126 -0
- data/lib/omnizip/algorithms/ppmd7/encoder.rb +163 -0
- data/lib/omnizip/algorithms/ppmd7/model.rb +248 -0
- data/lib/omnizip/algorithms/ppmd7/symbol_state.rb +57 -0
- data/lib/omnizip/algorithms/ppmd7.rb +116 -0
- data/lib/omnizip/algorithms/ppmd8/constants.rb +61 -0
- data/lib/omnizip/algorithms/ppmd8/context.rb +34 -0
- data/lib/omnizip/algorithms/ppmd8/decoder.rb +107 -0
- data/lib/omnizip/algorithms/ppmd8/encoder.rb +138 -0
- data/lib/omnizip/algorithms/ppmd8/model.rb +250 -0
- data/lib/omnizip/algorithms/ppmd8/restoration_method.rb +78 -0
- data/lib/omnizip/algorithms/ppmd8.rb +82 -0
- data/lib/omnizip/algorithms/ppmd_base.rb +138 -0
- data/lib/omnizip/algorithms/sevenzip_lzma2.rb +123 -0
- data/lib/omnizip/algorithms/xz_lzma2.rb +118 -0
- data/lib/omnizip/algorithms/zstandard/constants.rb +25 -0
- data/lib/omnizip/algorithms/zstandard/decoder.rb +46 -0
- data/lib/omnizip/algorithms/zstandard/encoder.rb +51 -0
- data/lib/omnizip/algorithms/zstandard.rb +138 -0
- data/lib/omnizip/buffer/memory_archive.rb +251 -0
- data/lib/omnizip/buffer/memory_extractor.rb +224 -0
- data/lib/omnizip/buffer.rb +176 -0
- data/lib/omnizip/checksum_registry.rb +114 -0
- data/lib/omnizip/checksums/crc32.rb +100 -0
- data/lib/omnizip/checksums/crc64.rb +101 -0
- data/lib/omnizip/checksums/crc_base.rb +158 -0
- data/lib/omnizip/checksums/verifier.rb +131 -0
- data/lib/omnizip/chunked/memory_manager.rb +194 -0
- data/lib/omnizip/chunked/reader.rb +78 -0
- data/lib/omnizip/chunked/writer.rb +120 -0
- data/lib/omnizip/chunked.rb +129 -0
- data/lib/omnizip/cli/output_formatter.rb +104 -0
- data/lib/omnizip/cli.rb +572 -0
- data/lib/omnizip/commands/.keep +0 -0
- data/lib/omnizip/commands/archive_create_command.rb +427 -0
- data/lib/omnizip/commands/archive_extract_command.rb +272 -0
- data/lib/omnizip/commands/archive_list_command.rb +218 -0
- data/lib/omnizip/commands/archive_repair_command.rb +131 -0
- data/lib/omnizip/commands/archive_verify_command.rb +117 -0
- data/lib/omnizip/commands/compress_command.rb +117 -0
- data/lib/omnizip/commands/decompress_command.rb +120 -0
- data/lib/omnizip/commands/list_command.rb +53 -0
- data/lib/omnizip/commands/metadata_command.rb +153 -0
- data/lib/omnizip/commands/parity_create_command.rb +122 -0
- data/lib/omnizip/commands/parity_repair_command.rb +122 -0
- data/lib/omnizip/commands/parity_verify_command.rb +124 -0
- data/lib/omnizip/commands/profile_list_command.rb +56 -0
- data/lib/omnizip/commands/profile_show_command.rb +44 -0
- data/lib/omnizip/convenience.rb +359 -0
- data/lib/omnizip/converter/conversion_registry.rb +49 -0
- data/lib/omnizip/converter/conversion_strategy.rb +121 -0
- data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +97 -0
- data/lib/omnizip/converter/zip_to_seven_zip_strategy.rb +112 -0
- data/lib/omnizip/converter.rb +105 -0
- data/lib/omnizip/crypto/aes256/cipher.rb +100 -0
- data/lib/omnizip/crypto/aes256/constants.rb +28 -0
- data/lib/omnizip/crypto/aes256/key_derivation.rb +101 -0
- data/lib/omnizip/crypto/aes256.rb +102 -0
- data/lib/omnizip/error.rb +106 -0
- data/lib/omnizip/eta/exponential_smoothing_estimator.rb +98 -0
- data/lib/omnizip/eta/moving_average_estimator.rb +99 -0
- data/lib/omnizip/eta/rate_calculator.rb +104 -0
- data/lib/omnizip/eta/sample_history.rb +143 -0
- data/lib/omnizip/eta/time_estimator.rb +106 -0
- data/lib/omnizip/eta.rb +63 -0
- data/lib/omnizip/extraction/filter_chain.rb +177 -0
- data/lib/omnizip/extraction/glob_pattern.rb +140 -0
- data/lib/omnizip/extraction/pattern_matcher.rb +70 -0
- data/lib/omnizip/extraction/predicate_pattern.rb +52 -0
- data/lib/omnizip/extraction/regex_pattern.rb +50 -0
- data/lib/omnizip/extraction/selective_extractor.rb +240 -0
- data/lib/omnizip/extraction.rb +111 -0
- data/lib/omnizip/file_type/mime_classifier.rb +144 -0
- data/lib/omnizip/file_type.rb +113 -0
- data/lib/omnizip/filter.rb +139 -0
- data/lib/omnizip/filter_pipeline.rb +108 -0
- data/lib/omnizip/filter_registry.rb +166 -0
- data/lib/omnizip/filters/bcj.rb +279 -0
- data/lib/omnizip/filters/bcj2/constants.rb +53 -0
- data/lib/omnizip/filters/bcj2/decoder.rb +200 -0
- data/lib/omnizip/filters/bcj2/encoder.rb +61 -0
- data/lib/omnizip/filters/bcj2/stream_data.rb +93 -0
- data/lib/omnizip/filters/bcj2.rb +99 -0
- data/lib/omnizip/filters/bcj_arm.rb +176 -0
- data/lib/omnizip/filters/bcj_arm64.rb +244 -0
- data/lib/omnizip/filters/bcj_ia64.rb +196 -0
- data/lib/omnizip/filters/bcj_ppc.rb +190 -0
- data/lib/omnizip/filters/bcj_sparc.rb +176 -0
- data/lib/omnizip/filters/bcj_x86.rb +193 -0
- data/lib/omnizip/filters/delta.rb +196 -0
- data/lib/omnizip/filters/filter_base.rb +72 -0
- data/lib/omnizip/filters/registry.rb +123 -0
- data/lib/omnizip/filters/xz_delta.rb +258 -0
- data/lib/omnizip/format_detector.rb +162 -0
- data/lib/omnizip/format_registry.rb +59 -0
- data/lib/omnizip/formats/.keep +0 -0
- data/lib/omnizip/formats/bzip2_file.rb +172 -0
- data/lib/omnizip/formats/cpio/constants.rb +55 -0
- data/lib/omnizip/formats/cpio/entry.rb +385 -0
- data/lib/omnizip/formats/cpio/reader.rb +196 -0
- data/lib/omnizip/formats/cpio/writer.rb +234 -0
- data/lib/omnizip/formats/cpio.rb +140 -0
- data/lib/omnizip/formats/format_spec_loader.rb +230 -0
- data/lib/omnizip/formats/gzip.rb +238 -0
- data/lib/omnizip/formats/iso/directory_builder.rb +297 -0
- data/lib/omnizip/formats/iso/directory_record.rb +152 -0
- data/lib/omnizip/formats/iso/joliet.rb +204 -0
- data/lib/omnizip/formats/iso/path_table.rb +125 -0
- data/lib/omnizip/formats/iso/reader.rb +197 -0
- data/lib/omnizip/formats/iso/rock_ridge.rb +349 -0
- data/lib/omnizip/formats/iso/volume_builder.rb +320 -0
- data/lib/omnizip/formats/iso/volume_descriptor.rb +168 -0
- data/lib/omnizip/formats/iso/writer.rb +530 -0
- data/lib/omnizip/formats/iso.rb +140 -0
- data/lib/omnizip/formats/lzip.rb +175 -0
- data/lib/omnizip/formats/lzma_alone.rb +171 -0
- data/lib/omnizip/formats/rar/archive_repairer.rb +243 -0
- data/lib/omnizip/formats/rar/archive_verifier.rb +195 -0
- data/lib/omnizip/formats/rar/block_parser.rb +243 -0
- data/lib/omnizip/formats/rar/compression/bit_stream.rb +180 -0
- data/lib/omnizip/formats/rar/compression/dispatcher.rb +217 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/decoder.rb +216 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/encoder.rb +158 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_builder.rb +217 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/huffman_coder.rb +189 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/match_finder.rb +135 -0
- data/lib/omnizip/formats/rar/compression/lz77_huffman/sliding_window.rb +165 -0
- data/lib/omnizip/formats/rar/compression/ppmd/context.rb +105 -0
- data/lib/omnizip/formats/rar/compression/ppmd/decoder.rb +219 -0
- data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +262 -0
- data/lib/omnizip/formats/rar/compression_method_registry.rb +106 -0
- data/lib/omnizip/formats/rar/constants.rb +82 -0
- data/lib/omnizip/formats/rar/decompressor.rb +238 -0
- data/lib/omnizip/formats/rar/external_writer.rb +312 -0
- data/lib/omnizip/formats/rar/header.rb +192 -0
- data/lib/omnizip/formats/rar/license_validator.rb +109 -0
- data/lib/omnizip/formats/rar/models/rar_archive.rb +77 -0
- data/lib/omnizip/formats/rar/models/rar_entry.rb +65 -0
- data/lib/omnizip/formats/rar/models/rar_volume.rb +56 -0
- data/lib/omnizip/formats/rar/parity_handler.rb +292 -0
- data/lib/omnizip/formats/rar/rar5/compression/lzma.rb +202 -0
- data/lib/omnizip/formats/rar/rar5/compression/lzss.rb +578 -0
- data/lib/omnizip/formats/rar/rar5/compression/store.rb +60 -0
- data/lib/omnizip/formats/rar/rar5/crc32.rb +39 -0
- data/lib/omnizip/formats/rar/rar5/encryption/aes256_cbc.rb +97 -0
- data/lib/omnizip/formats/rar/rar5/encryption/encryption_header.rb +114 -0
- data/lib/omnizip/formats/rar/rar5/encryption/encryption_manager.rb +166 -0
- data/lib/omnizip/formats/rar/rar5/encryption/key_derivation.rb +97 -0
- data/lib/omnizip/formats/rar/rar5/header.rb +187 -0
- data/lib/omnizip/formats/rar/rar5/models/encryption_options.rb +74 -0
- data/lib/omnizip/formats/rar/rar5/models/recovery_options.rb +63 -0
- data/lib/omnizip/formats/rar/rar5/models/solid_options.rb +63 -0
- data/lib/omnizip/formats/rar/rar5/models/volume_options.rb +74 -0
- data/lib/omnizip/formats/rar/rar5/multi_volume/ARCHITECTURE.md +290 -0
- data/lib/omnizip/formats/rar/rar5/multi_volume/volume_manager.rb +264 -0
- data/lib/omnizip/formats/rar/rar5/multi_volume/volume_splitter.rb +155 -0
- data/lib/omnizip/formats/rar/rar5/multi_volume/volume_writer.rb +194 -0
- data/lib/omnizip/formats/rar/rar5/solid/solid_encoder.rb +109 -0
- data/lib/omnizip/formats/rar/rar5/solid/solid_manager.rb +142 -0
- data/lib/omnizip/formats/rar/rar5/solid/solid_stream.rb +121 -0
- data/lib/omnizip/formats/rar/rar5/vint.rb +65 -0
- data/lib/omnizip/formats/rar/rar5/writer.rb +466 -0
- data/lib/omnizip/formats/rar/rar_format_base.rb +241 -0
- data/lib/omnizip/formats/rar/reader.rb +366 -0
- data/lib/omnizip/formats/rar/recovery_record.rb +245 -0
- data/lib/omnizip/formats/rar/volume_manager.rb +168 -0
- data/lib/omnizip/formats/rar/writer.rb +431 -0
- data/lib/omnizip/formats/rar.rb +205 -0
- data/lib/omnizip/formats/rar3/compressor.rb +73 -0
- data/lib/omnizip/formats/rar3/decompressor.rb +66 -0
- data/lib/omnizip/formats/rar3/reader.rb +386 -0
- data/lib/omnizip/formats/rar3/writer.rb +219 -0
- data/lib/omnizip/formats/rar5/compressor.rb +73 -0
- data/lib/omnizip/formats/rar5/decompressor.rb +66 -0
- data/lib/omnizip/formats/rar5/reader.rb +342 -0
- data/lib/omnizip/formats/rar5/writer.rb +214 -0
- data/lib/omnizip/formats/seven_zip/coder_chain.rb +150 -0
- data/lib/omnizip/formats/seven_zip/constants.rb +126 -0
- data/lib/omnizip/formats/seven_zip/encoded_header.rb +114 -0
- data/lib/omnizip/formats/seven_zip/encrypted_header.rb +142 -0
- data/lib/omnizip/formats/seven_zip/file_collector.rb +144 -0
- data/lib/omnizip/formats/seven_zip/header.rb +106 -0
- data/lib/omnizip/formats/seven_zip/header_encryptor.rb +134 -0
- data/lib/omnizip/formats/seven_zip/header_writer.rb +466 -0
- data/lib/omnizip/formats/seven_zip/models/coder_info.rb +30 -0
- data/lib/omnizip/formats/seven_zip/models/file_entry.rb +58 -0
- data/lib/omnizip/formats/seven_zip/models/folder.rb +69 -0
- data/lib/omnizip/formats/seven_zip/models/stream_info.rb +42 -0
- data/lib/omnizip/formats/seven_zip/parser.rb +660 -0
- data/lib/omnizip/formats/seven_zip/reader.rb +458 -0
- data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +632 -0
- data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +315 -0
- data/lib/omnizip/formats/seven_zip/stream_compressor.rb +151 -0
- data/lib/omnizip/formats/seven_zip/stream_decompressor.rb +162 -0
- data/lib/omnizip/formats/seven_zip/writer.rb +740 -0
- data/lib/omnizip/formats/seven_zip.rb +93 -0
- data/lib/omnizip/formats/tar/constants.rb +73 -0
- data/lib/omnizip/formats/tar/entry.rb +94 -0
- data/lib/omnizip/formats/tar/header.rb +168 -0
- data/lib/omnizip/formats/tar/reader.rb +121 -0
- data/lib/omnizip/formats/tar/writer.rb +216 -0
- data/lib/omnizip/formats/tar.rb +84 -0
- data/lib/omnizip/formats/xz/reader.rb +116 -0
- data/lib/omnizip/formats/xz.rb +237 -0
- data/lib/omnizip/formats/xz_impl/block_decoder.rb +754 -0
- data/lib/omnizip/formats/xz_impl/block_encoder.rb +306 -0
- data/lib/omnizip/formats/xz_impl/block_header.rb +210 -0
- data/lib/omnizip/formats/xz_impl/block_header_parser.rb +186 -0
- data/lib/omnizip/formats/xz_impl/constants.rb +49 -0
- data/lib/omnizip/formats/xz_impl/index_decoder.rb +174 -0
- data/lib/omnizip/formats/xz_impl/index_encoder.rb +122 -0
- data/lib/omnizip/formats/xz_impl/stream_decoder.rb +468 -0
- data/lib/omnizip/formats/xz_impl/stream_encoder.rb +99 -0
- data/lib/omnizip/formats/xz_impl/stream_footer.rb +81 -0
- data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +117 -0
- data/lib/omnizip/formats/xz_impl/stream_header.rb +55 -0
- data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +108 -0
- data/lib/omnizip/formats/xz_impl/vli.rb +128 -0
- data/lib/omnizip/formats/xz_impl/writer.rb +421 -0
- data/lib/omnizip/formats/zip/central_directory_header.rb +195 -0
- data/lib/omnizip/formats/zip/constants.rb +69 -0
- data/lib/omnizip/formats/zip/end_of_central_directory.rb +133 -0
- data/lib/omnizip/formats/zip/local_file_header.rb +138 -0
- data/lib/omnizip/formats/zip/reader.rb +250 -0
- data/lib/omnizip/formats/zip/unix_extra_field.rb +153 -0
- data/lib/omnizip/formats/zip/writer.rb +375 -0
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +104 -0
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +66 -0
- data/lib/omnizip/formats/zip/zip64_extra_field.rb +114 -0
- data/lib/omnizip/formats/zip.rb +50 -0
- data/lib/omnizip/implementations/base/lzma2_decoder_base.rb +75 -0
- data/lib/omnizip/implementations/base/lzma2_encoder_base.rb +128 -0
- data/lib/omnizip/implementations/base/lzma_decoder_base.rb +83 -0
- data/lib/omnizip/implementations/base/lzma_encoder_base.rb +108 -0
- data/lib/omnizip/implementations/base/state_machine_base.rb +182 -0
- data/lib/omnizip/implementations/seven_zip/lzma/decoder.rb +421 -0
- data/lib/omnizip/implementations/seven_zip/lzma/encoder.rb +465 -0
- data/lib/omnizip/implementations/seven_zip/lzma/match_finder.rb +288 -0
- data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +200 -0
- data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +197 -0
- data/lib/omnizip/implementations/seven_zip/lzma/state_machine.rb +141 -0
- data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +519 -0
- data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +723 -0
- data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +750 -0
- data/lib/omnizip/io/buffered_input.rb +146 -0
- data/lib/omnizip/io/buffered_output.rb +105 -0
- data/lib/omnizip/io/stream_manager.rb +115 -0
- data/lib/omnizip/link_handler/hard_link.rb +79 -0
- data/lib/omnizip/link_handler/symbolic_link.rb +74 -0
- data/lib/omnizip/link_handler.rb +124 -0
- data/lib/omnizip/metadata/archive_metadata.rb +114 -0
- data/lib/omnizip/metadata/entry_metadata.rb +146 -0
- data/lib/omnizip/metadata/metadata_editor.rb +171 -0
- data/lib/omnizip/metadata/metadata_registry.rb +64 -0
- data/lib/omnizip/metadata/metadata_validator.rb +99 -0
- data/lib/omnizip/metadata.rb +57 -0
- data/lib/omnizip/models/.keep +0 -0
- data/lib/omnizip/models/algorithm_metadata.rb +73 -0
- data/lib/omnizip/models/compression_options.rb +71 -0
- data/lib/omnizip/models/conversion_options.rb +87 -0
- data/lib/omnizip/models/conversion_result.rb +135 -0
- data/lib/omnizip/models/eta_result.rb +46 -0
- data/lib/omnizip/models/extraction_rule.rb +115 -0
- data/lib/omnizip/models/filter_chain.rb +144 -0
- data/lib/omnizip/models/filter_config.rb +183 -0
- data/lib/omnizip/models/match_result.rb +124 -0
- data/lib/omnizip/models/optimization_suggestion.rb +91 -0
- data/lib/omnizip/models/parallel_options.rb +104 -0
- data/lib/omnizip/models/performance_result.rb +79 -0
- data/lib/omnizip/models/profile_report.rb +82 -0
- data/lib/omnizip/models/progress_options.rb +38 -0
- data/lib/omnizip/models/split_options.rb +116 -0
- data/lib/omnizip/optimization_registry.rb +81 -0
- data/lib/omnizip/parallel/job_queue.rb +209 -0
- data/lib/omnizip/parallel/job_scheduler.rb +203 -0
- data/lib/omnizip/parallel/parallel_compressor.rb +347 -0
- data/lib/omnizip/parallel/parallel_extractor.rb +329 -0
- data/lib/omnizip/parallel/worker_pool.rb +223 -0
- data/lib/omnizip/parallel.rb +149 -0
- data/lib/omnizip/parity/chunked_block_processor.rb +196 -0
- data/lib/omnizip/parity/galois16.rb +145 -0
- data/lib/omnizip/parity/models/creator_packet.rb +73 -0
- data/lib/omnizip/parity/models/file_description_packet.rb +133 -0
- data/lib/omnizip/parity/models/ifsc_packet.rb +123 -0
- data/lib/omnizip/parity/models/main_packet.rb +128 -0
- data/lib/omnizip/parity/models/packet.rb +156 -0
- data/lib/omnizip/parity/models/packet_registry.rb +109 -0
- data/lib/omnizip/parity/models/recovery_slice_packet.rb +78 -0
- data/lib/omnizip/parity/par2_creator.rb +531 -0
- data/lib/omnizip/parity/par2_repairer.rb +407 -0
- data/lib/omnizip/parity/par2_verifier.rb +364 -0
- data/lib/omnizip/parity/par2cmdline_algorithm.rb +110 -0
- data/lib/omnizip/parity/par2cmdline_coefficients.rb +78 -0
- data/lib/omnizip/parity/reed_solomon_decoder.rb +266 -0
- data/lib/omnizip/parity/reed_solomon_encoder.rb +111 -0
- data/lib/omnizip/parity/reed_solomon_matrix.rb +342 -0
- data/lib/omnizip/parity.rb +186 -0
- data/lib/omnizip/password/encryption_registry.rb +65 -0
- data/lib/omnizip/password/encryption_strategy.rb +96 -0
- data/lib/omnizip/password/password_validator.rb +129 -0
- data/lib/omnizip/password/winzip_aes_strategy.rb +192 -0
- data/lib/omnizip/password/zip_crypto_strategy.rb +141 -0
- data/lib/omnizip/password.rb +87 -0
- data/lib/omnizip/pipe/stream_compressor.rb +124 -0
- data/lib/omnizip/pipe/stream_decompressor.rb +174 -0
- data/lib/omnizip/pipe.rb +121 -0
- data/lib/omnizip/platform/ntfs_streams.rb +201 -0
- data/lib/omnizip/platform.rb +189 -0
- data/lib/omnizip/profile/archive_profile.rb +39 -0
- data/lib/omnizip/profile/balanced_profile.rb +33 -0
- data/lib/omnizip/profile/binary_profile.rb +36 -0
- data/lib/omnizip/profile/compression_profile.rb +158 -0
- data/lib/omnizip/profile/custom_profile.rb +157 -0
- data/lib/omnizip/profile/fast_profile.rb +33 -0
- data/lib/omnizip/profile/maximum_profile.rb +33 -0
- data/lib/omnizip/profile/profile_detector.rb +110 -0
- data/lib/omnizip/profile/profile_registry.rb +161 -0
- data/lib/omnizip/profile/text_profile.rb +36 -0
- data/lib/omnizip/profile.rb +190 -0
- data/lib/omnizip/profiler/memory_profiler.rb +66 -0
- data/lib/omnizip/profiler/method_profiler.rb +49 -0
- data/lib/omnizip/profiler/report_generator.rb +169 -0
- data/lib/omnizip/profiler.rb +204 -0
- data/lib/omnizip/progress/callback_reporter.rb +36 -0
- data/lib/omnizip/progress/console_reporter.rb +62 -0
- data/lib/omnizip/progress/log_reporter.rb +91 -0
- data/lib/omnizip/progress/operation_progress.rb +118 -0
- data/lib/omnizip/progress/progress_bar.rb +156 -0
- data/lib/omnizip/progress/progress_reporter.rb +40 -0
- data/lib/omnizip/progress/progress_tracker.rb +190 -0
- data/lib/omnizip/progress/silent_reporter.rb +24 -0
- data/lib/omnizip/progress.rb +127 -0
- data/lib/omnizip/rubyzip_compat.rb +63 -0
- data/lib/omnizip/temp/safe_extract.rb +168 -0
- data/lib/omnizip/temp/temp_file.rb +124 -0
- data/lib/omnizip/temp/temp_file_pool.rb +109 -0
- data/lib/omnizip/temp.rb +181 -0
- data/lib/omnizip/version.rb +5 -0
- data/lib/omnizip/zip/entry.rb +156 -0
- data/lib/omnizip/zip/file.rb +485 -0
- data/lib/omnizip/zip/input_stream.rb +273 -0
- data/lib/omnizip/zip/output_stream.rb +324 -0
- data/lib/omnizip.rb +156 -0
- data/readme-docs/advanced-features.adoc +515 -0
- data/readme-docs/api-usage.adoc +444 -0
- data/readme-docs/architecture.adoc +449 -0
- data/readme-docs/archive-formats.adoc +479 -0
- data/readme-docs/cli-usage.adoc +222 -0
- data/readme-docs/compression-algorithms.adoc +442 -0
- data/readme-docs/compression-profiles.adoc +247 -0
- data/readme-docs/encryption-checksums.adoc +328 -0
- data/readme-docs/format-converter.adoc +325 -0
- data/readme-docs/installation.adoc +228 -0
- data/readme-docs/par2-archives.adoc +608 -0
- data/readme-docs/performance-profiler.adoc +389 -0
- data/readme-docs/preprocessing-filters.adoc +280 -0
- data/xz-file-format-1.2.1.txt +1174 -0
- 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
|