diff --git a/.gitignore b/.gitignore
index edd94d5c..4d21141d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,9 +2,6 @@
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
-## Custom
-Tests/temp
-
## Build generated
build/
DerivedData/
@@ -26,58 +23,16 @@ xcuserdata/
*.xccheckout
*.xcscmblueprint
-## Obj-C/Swift specific
-*.hmap
-*.ipa
-*.dSYM.zip
-*.dSYM
-
-## Playgrounds
-timeline.xctimeline
-playground.xcworkspace
-
# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
-.build/
Package.pins
-
-# CocoaPods
-Pods/
-
-# Carthage
-# Avoid checking in source code from Carthage dependencies.
-Carthage/Checkouts
-Carthage/Build
-Cartfile.resolved
-
-# fastlane
-#
-# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
-# screenshots whenever they are needed.
-# For more information about the recommended setup visit:
-# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
-
-fastlane/report.xml
-fastlane/Preview.html
-fastlane/screenshots
-fastlane/test_output
-
-# Symbolic link to swcomp executable built by SPM
-/swcomp
-
-# Package.resolved, because we don't want to fix dependencies' versions
Package.resolved
+.build/
-# Docs generated by SourceKitten
+# Ignore docs generated by SourceKitten
docs.json
-# Docs generated by Jazzy
+# Ignore docs generated by Jazzy
docs/
-
-# Vscode launch.json generated by Swift extension
-.vscode/launch.json
-
-# API baselines generate by swift package diagnose-api-breaking-changes
-api_baseline/
diff --git a/.jazzy.yaml b/.jazzy.yaml
index 7d7fbce6..96f38b96 100644
--- a/.jazzy.yaml
+++ b/.jazzy.yaml
@@ -3,11 +3,11 @@ sourcekitten_sourcefile: docs.json
clean: false
author: Timofey Solomko
module: SWCompression
-module_version: 4.8.7
-copyright: '© 2024 Timofey Solomko'
+module_version: 4.9.0
+copyright: '© 2026 Timofey Solomko'
readme: README.md
github_url: https://github.com/tsolomko/SWCompression
-github_file_prefix: https://github.com/tsolomko/SWCompression/tree/4.8.7
+github_file_prefix: https://github.com/tsolomko/SWCompression/tree/4.9.0
theme: fullwidth
custom_categories:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fbbedec5..008c23a9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,35 @@
# Changelog
+## 4.9.0
+
+- Swift versions from 5.3 up to and including 5.8 are no longer supported.
+- Cocoapods and Carthage installation methods are no longer supported.
+- Enabled visionOS platform for SPM.
+- Increased minimum deployment versions for Darwin platforms: macOS from 10.13 to 14.0, iOS from 11.0 to 17.0, tvOS from
+11.0 to 17.0, and watchOS from 4.0 to 10.0.
+- Increased the lowest required version of BitByteData dependency to 2.1.
+- Added `BZip2.multiDecompress` function.
+- Improved performance of BZip2 compression and decompression.
+- Slighlty improved performance of LZMA/LZMA2 decompression.
+- Improved performance of Deflate compression and decompression.
+- Fixed potential crashes when processing truncated BZip2 archives.
+- Fixed potential crashes when processing truncated Zlib archives.
+- Fixed potential crashes when processing truncated or corrupted data compressed with Deflate.
+- Fixed incorrect parsing of LZMA dictionary sizes larger than 224-1 in 7-Zip containers (PR #61 by @analytical-engines and @nekonya).
+- swcomp changes:
+ - SPM target is now enabled only on macOS.
+ - Added `-m`, `--multi-decompress` option to `bz2` command.
+ - Benchmark metadatas are now printed in a standard order.
+ - Benchmarking warmup iteration results are now printed and saved into a file.
+ - Benchmarking raw data is now saved into a file.
+ - Improved benchmarking output for multi-way comparsions.
+ - Added `--print-uuid` option to `benchmark show` command.
+ - Added `--metadata-only` option to `benchmark show` command.
+ - Added `--self-compare` option to `benchmark show` command.
+ - Added `benchmark remove-run` command.
+ - Updated benchmark results file format.
+ - Added `benchmark convert` command.
+
## 4.8.7
- Pinned BitByteData version to 2.0.4.
@@ -7,8 +37,8 @@
## 4.8.6
- Swift 5.2 is no longer supported.
-- Enable visionOS deployment target (only for Carthage users).
-- Add PrivacyInfo file.
+- Enabled visionOS deployment target (only for Carthage users).
+- Added PrivacyInfo file.
- Fixed a documentation error in `TarEntryInfo.deviceMinorNumber`.
## 4.8.5
diff --git a/Cartfile b/Cartfile
deleted file mode 100644
index 656bf838..00000000
--- a/Cartfile
+++ /dev/null
@@ -1 +0,0 @@
-github "tsolomko/BitByteData" ~> 2.0.0
diff --git a/LICENSE b/LICENSE
index 3dc58e8e..af3170d2 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024 Timofey Solomko
+Copyright (c) 2026 Timofey Solomko
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/Package.swift b/Package.swift
index 7d901ad0..b85bfb70 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,15 +1,14 @@
-// swift-tools-version:5.3
+// swift-tools-version:5.9
import PackageDescription
-let package = Package(
+var package = Package(
name: "SWCompression",
platforms: [
- .macOS(.v10_13),
- .iOS(.v11),
- .tvOS(.v11),
- .watchOS(.v4),
- // TODO: Enable after upgrading to Swift 5.9.
- // .visionOS(.v1)
+ .macOS(.v14),
+ .iOS(.v17),
+ .tvOS(.v17),
+ .watchOS(.v10),
+ .visionOS(.v1)
],
products: [
.library(
@@ -17,10 +16,7 @@ let package = Package(
targets: ["SWCompression"]),
],
dependencies: [
- .package(name: "BitByteData", url: "https://github.com/tsolomko/BitByteData",
- .exact("2.0.4")),
- .package(name: "SwiftCLI", url: "https://github.com/jakeheis/SwiftCLI",
- from: "6.0.0"),
+ .package(url: "https://github.com/tsolomko/BitByteData", from: "2.1.0"),
],
targets: [
.target(
@@ -30,12 +26,13 @@ let package = Package(
exclude: ["swcomp"],
sources: ["Common", "7-Zip", "BZip2", "Deflate", "GZip", "LZ4", "LZMA", "LZMA2", "TAR", "XZ", "ZIP", "Zlib"],
resources: [.copy("PrivacyInfo.xcprivacy")]),
- .target(
- name: "swcomp",
- dependencies: ["SWCompression", "SwiftCLI"],
- path: "Sources",
- exclude: ["Common", "7-Zip", "BZip2", "Deflate", "GZip", "LZ4", "LZMA", "LZMA2", "TAR", "XZ", "ZIP", "Zlib", "PrivacyInfo.xcprivacy"],
- sources: ["swcomp"]),
],
swiftLanguageVersions: [.v5]
)
+
+#if os(macOS)
+package.dependencies.append(.package(url: "https://github.com/jakeheis/SwiftCLI", from: "6.0.0"))
+package.targets.append(.executableTarget(name: "swcomp", dependencies: ["SWCompression", "SwiftCLI"], path: "Sources",
+ exclude: ["Common", "7-Zip", "BZip2", "Deflate", "GZip", "LZ4", "LZMA", "LZMA2", "TAR", "XZ", "ZIP", "Zlib", "PrivacyInfo.xcprivacy"],
+ sources: ["swcomp"]))
+#endif
diff --git a/README.md b/README.md
index ce180397..1ee5abfc 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# SWCompression
-[](https://developer.apple.com/swift/)
+[](https://developer.apple.com/swift/)
[](https://raw.githubusercontent.com/tsolomko/SWCompression/master/LICENSE)
[](https://dev.azure.com/tsolomko/SWCompression/_build/latest?definitionId=3&branchName=develop)
@@ -32,11 +32,12 @@ Also, SWCompression is _written with Swift only._
## Installation
-SWCompression can be integrated into your project using Swift Package Manager, CocoaPods, or Carthage.
+SWCompression can be integrated into your project using Swift Package Manager.
-### Swift Package Manager
+__Note:__ SWCompression versions 4.8.6 and earlier were also made available via CocoaPods or Carthage.
-To install using SPM, add SWCompression to you package dependencies and specify it as a dependency for your target, e.g.:
+To install with Swift Package manager, add SWCompression to you package dependencies and specify it as a dependency for
+your target, e.g.:
```swift
import PackageDescription
@@ -45,7 +46,7 @@ let package = Package(
name: "PackageName",
dependencies: [
.package(name: "SWCompression", url: "https://github.com/tsolomko/SWCompression.git",
- from: "4.8.0")
+ from: "4.9.0")
],
targets: [
.target(
@@ -58,59 +59,6 @@ let package = Package(
More details you can find in [Swift Package Manager's Documentation](https://github.com/apple/swift-package-manager/tree/main/Documentation).
-### CocoaPods
-
-Add `pod 'SWCompression', '~> 4.8'` and `use_frameworks!` lines to your Podfile.
-
-To complete installation, run `pod install`.
-
-If you need only some parts of framework, you can install only them using sub-podspecs. Available subspecs:
-
-- SWCompression/BZip2
-- SWCompression/Deflate
-- SWCompression/Gzip
-- SWCompression/LZMA
-- SWCompression/LZMA2
-- SWCompression/LZ4
-- SWCompression/SevenZip
-- SWCompression/TAR
-- SWCompression/XZ
-- SWCompression/Zlib
-- SWCompression/ZIP
-
-#### "Optional Dependencies"
-
-For both ZIP and 7-Zip there is the most commonly used compression method: Deflate and LZMA/LZMA2 correspondingly. Thus,
-SWCompression/ZIP subspec has SWCompression/Deflate subspec as a dependency and SWCompression/LZMA subspec is a
-dependency for SWCompression/SevenZip.
-
-But both of these formats also support other compression methods, and some of them are implemented in SWCompression.
-For CocoaPods configurations there are some sort of 'optional dependencies' for such compression methods.
-
-"Optional dependency" in this context means that SWCompression/ZIP or SWCompression/7-Zip will support a compression
-method only if a corresponding subspec is expicitly specified in your Podfile and installed.
-
-List of "optional dependecies":
-
-- For SWCompression/ZIP:
- - SWCompression/BZip2
- - SWCompression/LZMA
-- For SWCompression/SevenZip:
- - SWCompression/BZip2
- - SWCompression/Deflate
- - SWCompression/LZ4
-
-__Note:__ If you use Swift Package Manager or Carthage you always have everything (ZIP and 7-Zip are built with Deflate,
-BZip2, LZMA/LZMA2 and LZ4 support).
-
-### Carthage
-
-Add to your Cartfile `github "tsolomko/SWCompression" ~> 4.8`.
-
-Then you should run `carthage update --use-xcframeworks`. After that drag and drop both `SWCompression.xcframework` and
-`BitByteData.xcframework` files from from the `Carthage/Build/` directory into the "Frameworks, Libraries, and Embedded
-Content" section of your target's "General" tab in Xcode.
-
## Usage
### Basic Example
@@ -157,7 +105,7 @@ Every function or type of SWCompression's public API is documented. This documen
### Sophisticated example
There is a small command-line program, "swcomp", which is included in this repository in "Sources/swcomp". It can be
-built using Swift Package Manager.
+built using Swift Package Manager (only available on macOS).
__IMPORTANT:__ The "swcomp" command-line tool is NOT intended for general use.
@@ -171,15 +119,11 @@ In the case of a bug, it will be especially helpful if you attach a file (archiv
If you'd like to contribute, please [create a pull request](https://github.com/tsolomko/SWCompression/pulls) on GitHub.
-__Note:__ If you are considering working on SWCompression, please note that Xcode project (SWCompression.xcodeproj)
-was created manually and you shouldn't use `swift package generate-xcodeproj` command.
-
### Executing tests locally
If you want to run tests on your computer, you need to do a couple of additional steps after cloning the repository:
```bash
-./utils.py download-bbd-macos
git submodule update --init --recursive
cd "Tests/Test Files"
cp gitattributes-copy .gitattributes
@@ -187,15 +131,8 @@ git lfs pull
git lfs checkout
```
-The first command will download the BitByteData dependency, which requires having Carthage installed. When using Xcode
-12 or later, you should also pass the `--xcf` option, or use the
-[xconfig workaround](https://github.com/Carthage/Carthage/blob/master/Documentation/Xcode12Workaround.md). Please note
-that when building SWCompression's Xcode project you may see ld warnings about a directory not being found. These are
-expected and harmless. Finally, you should keep in mind that support for non-xcframework method of installing
-dependencies is likely to be dropped in the future major update.
-
-The remaining commands will download the files used in tests. These files are stored in a
-[separate repository](https://github.com/tsolomko/SWCompression-Test-Files), using Git LFS. There are two reasons for
+These commands will download the files used in tests which are stored in a
+[separate repository](https://github.com/tsolomko/SWCompression-Test-Files) using Git LFS. There are two reasons for
this complicated setup. Firstly, some of these files can be quite big, and it would be unfortunate if the users of
SWCompression had to download them during the installation. Secondly, Swift Package Manager and contemporary versions of
Xcode don't always work well with git-lfs-enabled repositories. To prevent any potential problems test files were moved
diff --git a/SWCompression.podspec b/SWCompression.podspec
deleted file mode 100644
index 0015555d..00000000
--- a/SWCompression.podspec
+++ /dev/null
@@ -1,95 +0,0 @@
-Pod::Spec.new do |s|
-
- s.name = "SWCompression"
- s.version = "4.8.7"
- s.summary = "A framework with functions for working with compression, archives and containers."
-
- s.description = "A framework with (de)compression algorithms and functions for processing various archives and containers."
-
- s.homepage = "https://github.com/tsolomko/SWCompression"
- s.documentation_url = "http://tsolomko.github.io/SWCompression"
-
- s.license = { :type => "MIT", :file => "LICENSE" }
-
- s.author = { "Timofey Solomko" => "tsolomko@gmail.com" }
-
- s.source = { :git => "https://github.com/tsolomko/SWCompression.git", :tag => "#{s.version}" }
-
- s.ios.deployment_target = "11.0"
- s.osx.deployment_target = "10.13"
- s.tvos.deployment_target = "11.0"
- s.watchos.deployment_target = "4.0"
- # s.visionos.deployment_target = "1.0"
-
- s.swift_versions = ["5"]
-
- s.dependency "BitByteData", "~> 2.0"
-
- s.subspec "Deflate" do |sp|
- sp.source_files = "Sources/{Deflate/*,Common/*,Common/CodingTree/*}.swift"
- sp.resource_bundles = {"SWCompression/Deflate" => ["Sources/PrivacyInfo.xcprivacy"]}
- sp.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DSWCOMPRESSION_POD_DEFLATE" }
- end
-
- s.subspec "GZip" do |sp|
- sp.dependency "SWCompression/Deflate"
- sp.resource_bundles = {"SWCompression/GZip" => ["Sources/PrivacyInfo.xcprivacy"]}
- sp.source_files = "Sources/{GZip/*,Common/*}.swift"
- end
-
- s.subspec "Zlib" do |sp|
- sp.dependency "SWCompression/Deflate"
- sp.resource_bundles = {"SWCompression/Zlib" => ["Sources/PrivacyInfo.xcprivacy"]}
- sp.source_files = "Sources/{Zlib/*,Common/*}.swift"
- end
-
- s.subspec "BZip2" do |sp|
- sp.source_files = "Sources/{BZip2/*,Common/*,Common/CodingTree/*}.swift"
- sp.resource_bundles = {"SWCompression/BZip2" => ["Sources/PrivacyInfo.xcprivacy"]}
- sp.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DSWCOMPRESSION_POD_BZ2" }
- end
-
- s.subspec "LZMA" do |sp|
- sp.source_files = "Sources/{LZMA/*,Common/*}.swift"
- sp.resource_bundles = {"SWCompression/LZMA" => ["Sources/PrivacyInfo.xcprivacy"]}
- sp.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DSWCOMPRESSION_POD_LZMA" }
- end
-
- s.subspec "LZMA2" do |sp|
- sp.dependency "SWCompression/LZMA"
- sp.source_files = "Sources/{LZMA2/*,Common/*}.swift"
- sp.resource_bundles = {"SWCompression/LZMA2" => ["Sources/PrivacyInfo.xcprivacy"]}
- end
-
- s.subspec "LZ4" do |sp|
- sp.source_files = "Sources/{LZ4/*,Common/*}.swift"
- sp.resource_bundles = {"SWCompression/LZ4" => ["Sources/PrivacyInfo.xcprivacy"]}
- sp.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DSWCOMPRESSION_POD_LZ4" }
- end
-
- s.subspec "XZ" do |sp|
- sp.dependency "SWCompression/LZMA2"
- sp.source_files = "Sources/{XZ/*,Common/*}.swift"
- sp.resource_bundles = {"SWCompression/XZ" => ["Sources/PrivacyInfo.xcprivacy"]}
- end
-
- s.subspec "ZIP" do |sp|
- sp.dependency "SWCompression/Deflate"
- sp.source_files = "Sources/{Zip/*,Common/*,Common/Container/*}.swift"
- sp.resource_bundles = {"SWCompression/ZIP" => ["Sources/PrivacyInfo.xcprivacy"]}
- sp.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DSWCOMPRESSION_POD_ZIP" }
- end
-
- s.subspec "TAR" do |sp|
- sp.source_files = "Sources/{TAR/*,Common/*,Common/Container/*}.swift"
- sp.resource_bundles = {"SWCompression/TAR" => ["Sources/PrivacyInfo.xcprivacy"]}
- end
-
- s.subspec "SevenZip" do |sp|
- sp.dependency "SWCompression/LZMA2"
- sp.source_files = "Sources/{7-Zip/*,Common/*,Common/Container/*}.swift"
- sp.resource_bundles = {"SWCompression/SevenZip" => ["Sources/PrivacyInfo.xcprivacy"]}
- sp.pod_target_xcconfig = { "OTHER_SWIFT_FLAGS" => "-DSWCOMPRESSION_POD_SEVENZIP" }
- end
-
-end
diff --git a/SWCompression.xcodeproj/SWCompression.plist b/SWCompression.xcodeproj/SWCompression.plist
index d220b4a9..6f786191 100644
--- a/SWCompression.xcodeproj/SWCompression.plist
+++ b/SWCompression.xcodeproj/SWCompression.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
FMWK
CFBundleShortVersionString
- 4.8.7
+ 4.9.0
CFBundleVersion
- 92
+ 93
NSHumanReadableCopyright
- Copyright © 2024 Timofey Solomko
+ Copyright © 2026 Timofey Solomko
diff --git a/SWCompression.xcodeproj/TestSWCompression.plist b/SWCompression.xcodeproj/TestSWCompression.plist
index 232a95cd..2de87aba 100644
--- a/SWCompression.xcodeproj/TestSWCompression.plist
+++ b/SWCompression.xcodeproj/TestSWCompression.plist
@@ -15,8 +15,8 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 4.8.7
+ 4.9.0
CFBundleVersion
- 92
+ 93
diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj
index 5162ab49..3e3f8614 100644
--- a/SWCompression.xcodeproj/project.pbxproj
+++ b/SWCompression.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 54;
+ objectVersion = 60;
objects = {
/* Begin PBXBuildFile section */
@@ -64,7 +64,6 @@
0686A6451FA4C47300E89C9E /* FileSystemType+Zip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0686A6441FA4C47300E89C9E /* FileSystemType+Zip.swift */; };
068D070221368242002A6C3B /* DeltaFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 068D070121368242002A6C3B /* DeltaFilter.swift */; };
068D070421368617002A6C3B /* test_delta_filter.7z in Resources */ = {isa = PBXBuildFile; fileRef = 068D070321368617002A6C3B /* test_delta_filter.7z */; };
- 06912BD21F5C8F3D0070BB60 /* Deflate+Lengths.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06912BD11F5C8F3D0070BB60 /* Deflate+Lengths.swift */; };
0694A74D1F7C0DF00023BC99 /* BurrowsWheeler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0694A74C1F7C0DF00023BC99 /* BurrowsWheeler.swift */; };
06955E491F65C761004D5D79 /* BZip2+Lengths.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06955E481F65C761004D5D79 /* BZip2+Lengths.swift */; };
069864CA21403CE700755D9B /* test_sha256.xz in Resources */ = {isa = PBXBuildFile; fileRef = 069864C921403CE700755D9B /* test_sha256.xz */; };
@@ -96,7 +95,7 @@
06BAC5941F8CFF28002443F4 /* ZipEntryInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06BAC5931F8CFF28002443F4 /* ZipEntryInfo.swift */; };
06BB8F301E0C2C720079FB9E /* LZMA2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06BB8F2F1E0C2C720079FB9E /* LZMA2.swift */; };
06CC3FDD1F8AAE8B00BD576D /* MsbBitReader+7z.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CC3FDC1F8AAE8B00BD576D /* MsbBitReader+7z.swift */; };
- 06CC3FE21F8AAF3100BD576D /* ByteReader+XZ.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CC3FE11F8AAF3100BD576D /* ByteReader+XZ.swift */; };
+ 06CC3FE21F8AAF3100BD576D /* LittleEndianByteReader+XZ.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CC3FE11F8AAF3100BD576D /* LittleEndianByteReader+XZ.swift */; };
06CDFC9E1F111D2600292758 /* Deflate+Compress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CDFC9D1F111D2600292758 /* Deflate+Compress.swift */; };
06CDFCA31F111D6C00292758 /* BZip2Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CDFCA21F111D6C00292758 /* BZip2Error.swift */; };
06CDFCA81F111D9700292758 /* DeflateError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CDFCA71F111D9700292758 /* DeflateError.swift */; };
@@ -222,6 +221,7 @@
E609D7B6271AFAA400068E79 /* test_small_dict_B5_BD.lz4 in Resources */ = {isa = PBXBuildFile; fileRef = E609D7B3271AFAA400068E79 /* test_small_dict_B5_BD.lz4 */; };
E609D7B7271AFAA400068E79 /* test_small_dict_B5.lz4 in Resources */ = {isa = PBXBuildFile; fileRef = E609D7B4271AFAA400068E79 /* test_small_dict_B5.lz4 */; };
E60CBA8A26AF379200A20130 /* test_only_dir_header.tar in Resources */ = {isa = PBXBuildFile; fileRef = E60CBA8926AF379200A20130 /* test_only_dir_header.tar */; };
+ E61055CF2FB3546600A684F6 /* test_lzma_big_dict.7z in Resources */ = {isa = PBXBuildFile; fileRef = E61055CE2FB3546600A684F6 /* test_lzma_big_dict.7z */; };
E631055927084D5D006EACC3 /* LZ4+Compress.swift in Sources */ = {isa = PBXBuildFile; fileRef = E631055827084D5D006EACC3 /* LZ4+Compress.swift */; };
E6421E8F271B54C6000359B6 /* test_7z_lz4.7z in Resources */ = {isa = PBXBuildFile; fileRef = E6421E8E271B54C6000359B6 /* test_7z_lz4.7z */; };
E644E11E2833AB2400EEFBAD /* minimal.gz in Resources */ = {isa = PBXBuildFile; fileRef = E644E11D2833AB2400EEFBAD /* minimal.gz */; };
@@ -229,7 +229,6 @@
E64F71BC26AAFD6A008BB308 /* Data+Tar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E64F71BB26AAFD6A008BB308 /* Data+Tar.swift */; };
E6522FB42789AF9600026B56 /* TarReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6522FB32789AF9600026B56 /* TarReaderTests.swift */; };
E6522FB62789AFA600026B56 /* TarWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6522FB52789AFA600026B56 /* TarWriterTests.swift */; };
- E6522FBD2789C4A300026B56 /* FileHandle+CloseCompat.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6522FBC2789C4A300026B56 /* FileHandle+CloseCompat.swift */; };
E652D8F3263D670900FC229B /* test_nonstandard_runlength.bz2 in Resources */ = {isa = PBXBuildFile; fileRef = E652D8F2263D670900FC229B /* test_nonstandard_runlength.bz2 */; };
E652D8F5263D678000FC229B /* test_nonstandard_runlength.answer in Resources */ = {isa = PBXBuildFile; fileRef = E652D8F4263D678000FC229B /* test_nonstandard_runlength.answer */; };
E652FECA27007E79006BC312 /* test3.lz4 in Resources */ = {isa = PBXBuildFile; fileRef = E652FEC127007E78006BC312 /* test3.lz4 */; };
@@ -263,6 +262,10 @@
E652FF002700A028006BC312 /* test_B6.lz4 in Resources */ = {isa = PBXBuildFile; fileRef = E652FEF42700A028006BC312 /* test_B6.lz4 */; };
E652FF012700A028006BC312 /* test_B5.lz4 in Resources */ = {isa = PBXBuildFile; fileRef = E652FEF52700A028006BC312 /* test_B5.lz4 */; };
E652FF022700A028006BC312 /* test_B4.lz4 in Resources */ = {isa = PBXBuildFile; fileRef = E652FEF62700A028006BC312 /* test_B4.lz4 */; };
+ E66D884E2F4F529100707F23 /* DeflateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E66D884D2F4F529100707F23 /* DeflateTests.swift */; };
+ E66D88612F4F53AB00707F23 /* test6.deflate in Resources */ = {isa = PBXBuildFile; fileRef = E66D885D2F4F53AB00707F23 /* test6.deflate */; };
+ E66D88622F4F53AB00707F23 /* test8.deflate in Resources */ = {isa = PBXBuildFile; fileRef = E66D885E2F4F53AB00707F23 /* test8.deflate */; };
+ E66D88632F4F53AB00707F23 /* test9.deflate in Resources */ = {isa = PBXBuildFile; fileRef = E66D885F2F4F53AB00707F23 /* test9.deflate */; };
E66F36242538726E00076A6E /* test_empty.lzma in Resources */ = {isa = PBXBuildFile; fileRef = E66F36232538726E00076A6E /* test_empty.lzma */; };
E66F362C2538E2B700076A6E /* test_empty.zlib in Resources */ = {isa = PBXBuildFile; fileRef = E66F362B2538E2B700076A6E /* test_empty.zlib */; };
E6791E1826FD001A003852A9 /* DataError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6791E1726FD001A003852A9 /* DataError.swift */; };
@@ -276,6 +279,7 @@
E6C057D627498F5A007C83DF /* TarWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6C057D527498F5A007C83DF /* TarWriter.swift */; };
E6C4150726FE230A00F9D36F /* XxHash32.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6C4150626FE230A00F9D36F /* XxHash32.swift */; };
E6D86D2F26FE35C50032CFFA /* XxHash32Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6D86D2E26FE35C50032CFFA /* XxHash32Tests.swift */; };
+ E6DCC9D82F30DA080042AE2E /* BitByteData in Frameworks */ = {isa = PBXBuildFile; productRef = E6DCC9D72F30DA080042AE2E /* BitByteData */; };
E6DF9ECE2704BF14003BD91E /* test_multi_frame.lz4 in Resources */ = {isa = PBXBuildFile; fileRef = E6DF9ECD2704BF14003BD91E /* test_multi_frame.lz4 */; };
E6E49D522BD5081D00BFC756 /* PrivacyInfo.xcprivacy in CopyFiles */ = {isa = PBXBuildFile; fileRef = E6E49D502BD507D700BFC756 /* PrivacyInfo.xcprivacy */; };
E6EDD6A826F7767F00884532 /* test_minor_version_3.7z in Resources */ = {isa = PBXBuildFile; fileRef = E6EDD6A626F7767E00884532 /* test_minor_version_3.7z */; };
@@ -367,7 +371,6 @@
0686A6441FA4C47300E89C9E /* FileSystemType+Zip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileSystemType+Zip.swift"; sourceTree = ""; };
068D070121368242002A6C3B /* DeltaFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeltaFilter.swift; sourceTree = ""; };
068D070321368617002A6C3B /* test_delta_filter.7z */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_delta_filter.7z; sourceTree = ""; };
- 06912BD11F5C8F3D0070BB60 /* Deflate+Lengths.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deflate+Lengths.swift"; sourceTree = ""; };
0694A74C1F7C0DF00023BC99 /* BurrowsWheeler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurrowsWheeler.swift; sourceTree = ""; };
06955E481F65C761004D5D79 /* BZip2+Lengths.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BZip2+Lengths.swift"; sourceTree = ""; };
069864C921403CE700755D9B /* test_sha256.xz */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_sha256.xz; sourceTree = ""; };
@@ -396,7 +399,7 @@
06BB8F2F1E0C2C720079FB9E /* LZMA2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LZMA2.swift; sourceTree = ""; };
06BE1AC81DB410F100EE0F59 /* SWCompression.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SWCompression.framework; sourceTree = BUILT_PRODUCTS_DIR; };
06CC3FDC1F8AAE8B00BD576D /* MsbBitReader+7z.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MsbBitReader+7z.swift"; sourceTree = ""; };
- 06CC3FE11F8AAF3100BD576D /* ByteReader+XZ.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ByteReader+XZ.swift"; sourceTree = ""; };
+ 06CC3FE11F8AAF3100BD576D /* LittleEndianByteReader+XZ.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LittleEndianByteReader+XZ.swift"; sourceTree = ""; };
06CDFC9D1F111D2600292758 /* Deflate+Compress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deflate+Compress.swift"; sourceTree = ""; };
06CDFCA21F111D6C00292758 /* BZip2Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BZip2Error.swift; sourceTree = ""; };
06CDFCA71F111D9700292758 /* DeflateError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeflateError.swift; sourceTree = ""; };
@@ -523,6 +526,7 @@
E609D7B3271AFAA400068E79 /* test_small_dict_B5_BD.lz4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_small_dict_B5_BD.lz4; sourceTree = ""; };
E609D7B4271AFAA400068E79 /* test_small_dict_B5.lz4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_small_dict_B5.lz4; sourceTree = ""; };
E60CBA8926AF379200A20130 /* test_only_dir_header.tar */ = {isa = PBXFileReference; lastKnownFileType = archive.tar; path = test_only_dir_header.tar; sourceTree = ""; };
+ E61055CE2FB3546600A684F6 /* test_lzma_big_dict.7z */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_lzma_big_dict.7z; sourceTree = ""; };
E631055827084D5D006EACC3 /* LZ4+Compress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LZ4+Compress.swift"; sourceTree = ""; };
E631055A27086132006EACC3 /* SWCompression.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = SWCompression.xctestplan; path = SWCompression.xcodeproj/SWCompression.xctestplan; sourceTree = SOURCE_ROOT; };
E6421E8E271B54C6000359B6 /* test_7z_lz4.7z */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_7z_lz4.7z; sourceTree = ""; };
@@ -531,7 +535,6 @@
E64F71BB26AAFD6A008BB308 /* Data+Tar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Tar.swift"; sourceTree = ""; };
E6522FB32789AF9600026B56 /* TarReaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TarReaderTests.swift; sourceTree = ""; };
E6522FB52789AFA600026B56 /* TarWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TarWriterTests.swift; sourceTree = ""; };
- E6522FBC2789C4A300026B56 /* FileHandle+CloseCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileHandle+CloseCompat.swift"; sourceTree = ""; };
E652D8F2263D670900FC229B /* test_nonstandard_runlength.bz2 */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_nonstandard_runlength.bz2; sourceTree = ""; };
E652D8F4263D678000FC229B /* test_nonstandard_runlength.answer */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_nonstandard_runlength.answer; sourceTree = ""; };
E652FEC127007E78006BC312 /* test3.lz4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = test3.lz4; sourceTree = ""; };
@@ -565,6 +568,10 @@
E652FEF42700A028006BC312 /* test_B6.lz4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_B6.lz4; sourceTree = ""; };
E652FEF52700A028006BC312 /* test_B5.lz4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_B5.lz4; sourceTree = ""; };
E652FEF62700A028006BC312 /* test_B4.lz4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_B4.lz4; sourceTree = ""; };
+ E66D884D2F4F529100707F23 /* DeflateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeflateTests.swift; sourceTree = ""; };
+ E66D885D2F4F53AB00707F23 /* test6.deflate */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = test6.deflate; sourceTree = ""; };
+ E66D885E2F4F53AB00707F23 /* test8.deflate */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = test8.deflate; sourceTree = ""; };
+ E66D885F2F4F53AB00707F23 /* test9.deflate */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = test9.deflate; sourceTree = ""; };
E66F36232538726E00076A6E /* test_empty.lzma */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_empty.lzma; sourceTree = ""; };
E66F362B2538E2B700076A6E /* test_empty.zlib */ = {isa = PBXFileReference; lastKnownFileType = file; path = test_empty.zlib; sourceTree = ""; };
E6791E1726FD001A003852A9 /* DataError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataError.swift; sourceTree = ""; };
@@ -594,6 +601,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ E6DCC9D52F30DA020042AE2E /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ E6DCC9D82F30DA080042AE2E /* BitByteData in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -633,7 +648,6 @@
children = (
061FCE251DBCC4BE0052F7BE /* Deflate.swift */,
06CDFC9D1F111D2600292758 /* Deflate+Compress.swift */,
- 06912BD11F5C8F3D0070BB60 /* Deflate+Lengths.swift */,
0631CB051F81109E00A2E0AB /* Deflate+Constants.swift */,
06CDFCA71F111D9700292758 /* DeflateError.swift */,
);
@@ -676,7 +690,7 @@
061C06201F0E89D900832F0C /* XZError.swift */,
065146221FA8E20C007F83F4 /* XZBlock.swift */,
0651461D1FA8E004007F83F4 /* XZStreamHeader.swift */,
- 06CC3FE11F8AAF3100BD576D /* ByteReader+XZ.swift */,
+ 06CC3FE11F8AAF3100BD576D /* LittleEndianByteReader+XZ.swift */,
06516B75213938B000C9823B /* Sha256.swift */,
);
path = XZ;
@@ -695,9 +709,9 @@
06BE1ABE1DB410F100EE0F59 = {
isa = PBXGroup;
children = (
- E6E49D502BD507D700BFC756 /* PrivacyInfo.xcprivacy */,
06BE1ACA1DB410F100EE0F59 /* Sources */,
06F065A01FFB763300312A82 /* Tests */,
+ E6E49D502BD507D700BFC756 /* PrivacyInfo.xcprivacy */,
E631055A27086132006EACC3 /* SWCompression.xctestplan */,
06BE1AC91DB410F100EE0F59 /* Products */,
);
@@ -835,6 +849,7 @@
06F065AC1FFB763300312A82 /* BZip2CompressionTests.swift */,
06F065A51FFB763300312A82 /* BZip2Tests.swift */,
06F065A21FFB763300312A82 /* DeflateCompressionTests.swift */,
+ E66D884D2F4F529100707F23 /* DeflateTests.swift */,
06F065A71FFB763300312A82 /* GzipTests.swift */,
06F065AB1FFB763300312A82 /* LzmaTests.swift */,
E6B18E84270884E300F9AB99 /* LZ4CompressionTests.swift */,
@@ -844,7 +859,6 @@
E6522FB52789AFA600026B56 /* TarWriterTests.swift */,
06F065A61FFB763300312A82 /* TarTests.swift */,
E6522FB32789AF9600026B56 /* TarReaderTests.swift */,
- E6522FBC2789C4A300026B56 /* FileHandle+CloseCompat.swift */,
06F065A91FFB763300312A82 /* XzTests.swift */,
06F065A41FFB763300312A82 /* ZipTests.swift */,
06D42DA62067B71000C1A98B /* TestZipExtraField.swift */,
@@ -861,6 +875,7 @@
06F065F61FFB763300312A82 /* 7z */,
06F065AE1FFB763300312A82 /* Answers */,
06F066081FFB763300312A82 /* BZip2 */,
+ E66D88602F4F53AB00707F23 /* Deflate */,
06F065B81FFB763300312A82 /* GZip */,
06F065F11FFB763300312A82 /* LZMA */,
E6791E1F26FD04D0003852A9 /* LZ4 */,
@@ -1033,6 +1048,7 @@
06F066011FFB763300312A82 /* test_7z_bzip2.7z */,
06F066021FFB763300312A82 /* test_7z_deflate.7z */,
E6421E8E271B54C6000359B6 /* test_7z_lz4.7z */,
+ E61055CE2FB3546600A684F6 /* test_lzma_big_dict.7z */,
06F066041FFB763300312A82 /* test_empty_file.7z */,
06F066051FFB763300312A82 /* test_empty_dir.7z */,
06F066061FFB763300312A82 /* test_win.7z */,
@@ -1063,6 +1079,16 @@
path = BZip2;
sourceTree = "";
};
+ E66D88602F4F53AB00707F23 /* Deflate */ = {
+ isa = PBXGroup;
+ children = (
+ E66D885D2F4F53AB00707F23 /* test6.deflate */,
+ E66D885E2F4F53AB00707F23 /* test8.deflate */,
+ E66D885F2F4F53AB00707F23 /* test9.deflate */,
+ );
+ path = Deflate;
+ sourceTree = "";
+ };
E6791E1926FD0070003852A9 /* LZ4 */ = {
isa = PBXGroup;
children = (
@@ -1128,10 +1154,12 @@
buildPhases = (
06BE1AC31DB410F100EE0F59 /* Sources */,
E6E49D512BD5081700BFC756 /* CopyFiles */,
+ E6DCC9D52F30DA020042AE2E /* Frameworks */,
);
buildRules = (
);
dependencies = (
+ E6DCC9DA2F30DB4E0042AE2E /* PBXTargetDependency */,
);
name = SWCompression;
productName = SWCompression;
@@ -1145,7 +1173,6 @@
06F065911FFB761600312A82 /* Sources */,
06F065921FFB761600312A82 /* Frameworks */,
06F065931FFB761600312A82 /* Resources */,
- 062BE32F1FFBADB300343CAD /* Copy BitByteData.framework into test bundle */,
);
buildRules = (
);
@@ -1165,7 +1192,7 @@
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 0920;
- LastUpgradeCheck = 1530;
+ LastUpgradeCheck = 2620;
ORGANIZATIONNAME = "Timofey Solomko";
TargetAttributes = {
06BE1AC71DB410F100EE0F59 = {
@@ -1179,7 +1206,7 @@
};
};
buildConfigurationList = 06BE1AC21DB410F100EE0F59 /* Build configuration list for PBXProject "SWCompression" */;
- compatibilityVersion = "Xcode 11.4";
+ compatibilityVersion = "Xcode 15.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
@@ -1187,6 +1214,9 @@
Base,
);
mainGroup = 06BE1ABE1DB410F100EE0F59;
+ packageReferences = (
+ E6DCC9D12F30D7610042AE2E /* XCRemoteSwiftPackageReference "BitByteData" */,
+ );
productRefGroup = 06BE1AC91DB410F100EE0F59 /* Products */;
projectDirPath = "";
projectRoot = "";
@@ -1258,6 +1288,7 @@
06F066671FFB763400312A82 /* test2.7z in Resources */,
062D591421248758003543BD /* test_delta_filter.xz in Resources */,
0698B10B2104E11200A7C551 /* test_gnu_inc_format.tar in Resources */,
+ E61055CF2FB3546600A684F6 /* test_lzma_big_dict.7z in Resources */,
06F0661F1FFB763300312A82 /* test3.answer in Resources */,
06F066281FFB763400312A82 /* test5.gz in Resources */,
064D01AC20FD077D00CAE058 /* test_unicode_ustar.tar in Resources */,
@@ -1345,6 +1376,9 @@
06F066581FFB763400312A82 /* full_test.tar in Resources */,
E6EDD6A926F7767F00884532 /* test_minor_version_4.7z in Resources */,
E652FEFB2700A028006BC312 /* test_B1234_BD.lz4 in Resources */,
+ E66D88612F4F53AB00707F23 /* test6.deflate in Resources */,
+ E66D88622F4F53AB00707F23 /* test8.deflate in Resources */,
+ E66D88632F4F53AB00707F23 /* test9.deflate in Resources */,
06F0662B1FFB763400312A82 /* test3.gz in Resources */,
E652FEF72700A028006BC312 /* test_B7.lz4 in Resources */,
06F0664C1FFB763400312A82 /* test_pax.tar in Resources */,
@@ -1358,25 +1392,6 @@
};
/* End PBXResourcesBuildPhase section */
-/* Begin PBXShellScriptBuildPhase section */
- 062BE32F1FFBADB300343CAD /* Copy BitByteData.framework into test bundle */ = {
- isa = PBXShellScriptBuildPhase;
- alwaysOutOfDate = 1;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "Copy BitByteData.framework into test bundle";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/bash;
- shellScript = "if [[ ${SDK_NAME} == iphone* ]]; then\n if [[ ! -d ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/ ]]; then\n mkdir -p ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\n if [[ ${SDK_NAME} == iphonesimulator* ]]; then\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/ios-*-simulator/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n else\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/ios-arm64*/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\nelif [[ ${SDK_NAME} == appletv* ]]; then\n if [[ ! -d ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/ ]]; then\n mkdir -p ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\n if [[ ${SDK_NAME} == appletvsimulator* ]]; then\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/tvos-*-simulator/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n else\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/tvos-arm64/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\nelif [[ ${SDK_NAME} == watch* ]]; then\n if [[ ! -d ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/ ]]; then\n mkdir -p ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\n if [[ ${SDK_NAME} == watchsimulator* ]]; then\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/watchos-*-simulator/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n else\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/watchos-arm64_32_armv7k/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Frameworks/\n fi\nelif [[ ${SDK_NAME} == macos* ]]; then\n if [[ ! -d ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Contents/Frameworks/ ]]; then\n mkdir -p ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Contents/Frameworks/\n fi\n cp -a ${PROJECT_DIR}/Carthage/Build/BitByteData.xcframework/macos-*/BitByteData.framework ${BUILT_PRODUCTS_DIR}/TestSWCompression.xctest/Contents/Frameworks/\nfi\n\nxattr -rc ${BUILT_PRODUCTS_DIR}\n";
- showEnvVarsInLog = 0;
- };
-/* End PBXShellScriptBuildPhase section */
-
/* Begin PBXSourcesBuildPhase section */
06BE1AC31DB410F100EE0F59 /* Sources */ = {
isa = PBXSourcesBuildPhase;
@@ -1432,7 +1447,6 @@
0631CB061F81109E00A2E0AB /* Deflate+Constants.swift in Sources */,
E648151D26A889B8009261F0 /* TarHeader.swift in Sources */,
06D95AC11F4C54E0006B46AC /* BZip2+Compress.swift in Sources */,
- 06912BD21F5C8F3D0070BB60 /* Deflate+Lengths.swift in Sources */,
06A9606A1F1E7E0D0078E6D1 /* 7zSubstreamInfo.swift in Sources */,
E6C4150726FE230A00F9D36F /* XxHash32.swift in Sources */,
0694A74D1F7C0DF00023BC99 /* BurrowsWheeler.swift in Sources */,
@@ -1455,7 +1469,7 @@
06CDFCA31F111D6C00292758 /* BZip2Error.swift in Sources */,
06D3802E21E36190008B50C6 /* Code.swift in Sources */,
06CFF6B22078C5A5003B8375 /* TarParser.swift in Sources */,
- 06CC3FE21F8AAF3100BD576D /* ByteReader+XZ.swift in Sources */,
+ 06CC3FE21F8AAF3100BD576D /* LittleEndianByteReader+XZ.swift in Sources */,
065D0ADC1F2B9D9100CE2DA8 /* 7zEntryInfo.swift in Sources */,
06F276DF1F2BAB4A00E67335 /* 7zEntry.swift in Sources */,
0651461E1FA8E004007F83F4 /* XZStreamHeader.swift in Sources */,
@@ -1491,6 +1505,7 @@
buildActionMask = 2147483647;
files = (
E6522FB62789AFA600026B56 /* TarWriterTests.swift in Sources */,
+ E66D884E2F4F529100707F23 /* DeflateTests.swift in Sources */,
06F066151FFB763300312A82 /* ZlibTests.swift in Sources */,
06F066141FFB763300312A82 /* DeflateCompressionTests.swift in Sources */,
E6791E3326FD05EC003852A9 /* LZ4Tests.swift in Sources */,
@@ -1506,7 +1521,6 @@
E6D86D2F26FE35C50032CFFA /* XxHash32Tests.swift in Sources */,
06F0661B1FFB763300312A82 /* XzTests.swift in Sources */,
06F0661E1FFB763300312A82 /* BZip2CompressionTests.swift in Sources */,
- E6522FBD2789C4A300026B56 /* FileHandle+CloseCompat.swift in Sources */,
06F066131FFB763300312A82 /* Constants.swift in Sources */,
06F0661C1FFB763300312A82 /* SevenZipTests.swift in Sources */,
06F066161FFB763300312A82 /* ZipTests.swift in Sources */,
@@ -1521,6 +1535,10 @@
target = 06BE1AC71DB410F100EE0F59 /* SWCompression */;
targetProxy = 06F0659B1FFB761600312A82 /* PBXContainerItemProxy */;
};
+ E6DCC9DA2F30DB4E0042AE2E /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ productRef = E6DCC9D92F30DB4E0042AE2E /* BitByteData */;
+ };
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
@@ -1548,25 +1566,13 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- CURRENT_PROJECT_VERSION = 92;
+ CURRENT_PROJECT_VERSION = 93;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
EAGER_LINKING = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
- "FRAMEWORK_SEARCH_PATHS[sdk=appletvos*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/tvos-arm64\"";
- "FRAMEWORK_SEARCH_PATHS[sdk=appletvsimulator*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/tvos-arm64_x86_64-simulator\"";
- "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = (
- "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64_armv7\"",
- "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64\"",
- );
- "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = (
- "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64_i386_x86_64-simulator\"",
- "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64_x86_64-simulator\"",
- );
- "FRAMEWORK_SEARCH_PATHS[sdk=macosx*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/macos-arm64_x86_64\"";
- "FRAMEWORK_SEARCH_PATHS[sdk=watchos*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/watchos-arm64_32_armv7k\"";
- "FRAMEWORK_SEARCH_PATHS[sdk=watchsimulator*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/watchos-arm64_i386_x86_64-simulator\"";
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@@ -1575,22 +1581,18 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
- MACOSX_DEPLOYMENT_TARGET = 10.13;
+ IPHONEOS_DEPLOYMENT_TARGET = 17.6;
+ MACOSX_DEPLOYMENT_TARGET = 14.6;
ONLY_ACTIVE_ARCH = YES;
- OTHER_CODE_SIGN_FLAGS = "--deep";
- OTHER_LDFLAGS = (
- "-framework",
- BitByteData,
- );
SDKROOT = macosx;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
SUPPORTED_PLATFORMS = "macosx watchsimulator iphonesimulator appletvsimulator watchos appletvos iphoneos xrsimulator xros";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
- TVOS_DEPLOYMENT_TARGET = 11.0;
+ TVOS_DEPLOYMENT_TARGET = 17.6;
VERSIONING_SYSTEM = "apple-generic";
- WATCHOS_DEPLOYMENT_TARGET = 4.0;
- XROS_DEPLOYMENT_TARGET = 1.0;
+ WATCHOS_DEPLOYMENT_TARGET = 10.6;
+ XROS_DEPLOYMENT_TARGET = 1.3;
};
name = Debug;
};
@@ -1618,24 +1620,12 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- CURRENT_PROJECT_VERSION = 92;
+ CURRENT_PROJECT_VERSION = 93;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
EAGER_LINKING = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
- "FRAMEWORK_SEARCH_PATHS[sdk=appletvos*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/tvos-arm64\"";
- "FRAMEWORK_SEARCH_PATHS[sdk=appletvsimulator*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/tvos-arm64_x86_64-simulator\"";
- "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = (
- "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64_armv7\"",
- "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64\"",
- );
- "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = (
- "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64_i386_x86_64-simulator\"",
- "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/ios-arm64_x86_64-simulator\"",
- );
- "FRAMEWORK_SEARCH_PATHS[sdk=macosx*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/macos-arm64_x86_64\"";
- "FRAMEWORK_SEARCH_PATHS[sdk=watchos*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/watchos-arm64_32_armv7k\"";
- "FRAMEWORK_SEARCH_PATHS[sdk=watchsimulator*]" = "\"$(SRCROOT)/Carthage/Build/BitByteData.xcframework/watchos-arm64_i386_x86_64-simulator\"";
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
@@ -1643,21 +1633,17 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 11.0;
- MACOSX_DEPLOYMENT_TARGET = 10.13;
- OTHER_CODE_SIGN_FLAGS = "--deep";
- OTHER_LDFLAGS = (
- "-framework",
- BitByteData,
- );
+ IPHONEOS_DEPLOYMENT_TARGET = 17.6;
+ MACOSX_DEPLOYMENT_TARGET = 14.6;
SDKROOT = macosx;
+ STRING_CATALOG_GENERATE_SYMBOLS = YES;
SUPPORTED_PLATFORMS = "macosx watchsimulator iphonesimulator appletvsimulator watchos appletvos iphoneos xrsimulator xros";
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_VERSION = 5.0;
- TVOS_DEPLOYMENT_TARGET = 11.0;
+ TVOS_DEPLOYMENT_TARGET = 17.6;
VERSIONING_SYSTEM = "apple-generic";
- WATCHOS_DEPLOYMENT_TARGET = 4.0;
- XROS_DEPLOYMENT_TARGET = 1.0;
+ WATCHOS_DEPLOYMENT_TARGET = 10.6;
+ XROS_DEPLOYMENT_TARGET = 1.3;
};
name = Release;
};
@@ -1667,24 +1653,13 @@
APPLICATION_EXTENSION_API_ONLY = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = 92;
+ DYLIB_CURRENT_VERSION = 93;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_MODULE_VERIFIER = YES;
INFOPLIST_FILE = SWCompression.xcodeproj/SWCompression.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@loader_path/Frameworks",
- "@executable_path/Frameworks",
- "@loader_path/../Frameworks",
- "@executable_path/../Frameworks",
- );
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++14";
- OTHER_LDFLAGS = (
- "-framework",
- BitByteData,
- );
PRODUCT_BUNDLE_IDENTIFIER = me.tsolomko.SWCompression;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@@ -1697,24 +1672,13 @@
APPLICATION_EXTENSION_API_ONLY = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
- DYLIB_CURRENT_VERSION = 92;
+ DYLIB_CURRENT_VERSION = 93;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_MODULE_VERIFIER = YES;
INFOPLIST_FILE = SWCompression.xcodeproj/SWCompression.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@loader_path/Frameworks",
- "@executable_path/Frameworks",
- "@loader_path/../Frameworks",
- "@executable_path/../Frameworks",
- );
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++14";
- OTHER_LDFLAGS = (
- "-framework",
- BitByteData,
- );
PRODUCT_BUNDLE_IDENTIFIER = me.tsolomko.SWCompression;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@@ -1726,13 +1690,6 @@
buildSettings = {
COPY_PHASE_STRIP = NO;
INFOPLIST_FILE = SWCompression.xcodeproj/TestSWCompression.plist;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@loader_path/Frameworks",
- "@executable_path/Frameworks",
- "@loader_path/../Frameworks",
- "@executable_path/../Frameworks",
- );
PRODUCT_BUNDLE_IDENTIFIER = me.tsolomko.TestSWCompression;
PRODUCT_NAME = "$(TARGET_NAME)";
};
@@ -1743,13 +1700,6 @@
buildSettings = {
COPY_PHASE_STRIP = NO;
INFOPLIST_FILE = SWCompression.xcodeproj/TestSWCompression.plist;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@loader_path/Frameworks",
- "@executable_path/Frameworks",
- "@loader_path/../Frameworks",
- "@executable_path/../Frameworks",
- );
PRODUCT_BUNDLE_IDENTIFIER = me.tsolomko.TestSWCompression;
PRODUCT_NAME = "$(TARGET_NAME)";
};
@@ -1786,6 +1736,30 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
+
+/* Begin XCRemoteSwiftPackageReference section */
+ E6DCC9D12F30D7610042AE2E /* XCRemoteSwiftPackageReference "BitByteData" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/tsolomko/BitByteData";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 2.1.0;
+ };
+ };
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ E6DCC9D72F30DA080042AE2E /* BitByteData */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = E6DCC9D12F30D7610042AE2E /* XCRemoteSwiftPackageReference "BitByteData" */;
+ productName = BitByteData;
+ };
+ E6DCC9D92F30DB4E0042AE2E /* BitByteData */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = E6DCC9D12F30D7610042AE2E /* XCRemoteSwiftPackageReference "BitByteData" */;
+ productName = BitByteData;
+ };
+/* End XCSwiftPackageProductDependency section */
};
rootObject = 06BE1ABF1DB410F100EE0F59 /* Project object */;
}
diff --git a/SWCompression.xcodeproj/xcshareddata/xcschemes/SWCompression.xcscheme b/SWCompression.xcodeproj/xcshareddata/xcschemes/SWCompression.xcscheme
index 7232fa36..1b98aff8 100644
--- a/SWCompression.xcodeproj/xcshareddata/xcschemes/SWCompression.xcscheme
+++ b/SWCompression.xcodeproj/xcshareddata/xcschemes/SWCompression.xcscheme
@@ -1,6 +1,6 @@
Bool {
diff --git a/Sources/7-Zip/7zCoder.swift b/Sources/7-Zip/7zCoder.swift
index 19b76995..483eb068 100644
--- a/Sources/7-Zip/7zCoder.swift
+++ b/Sources/7-Zip/7zCoder.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import BitByteData
class SevenZipCoder {
diff --git a/Sources/7-Zip/7zCoderInfo.swift b/Sources/7-Zip/7zCoderInfo.swift
index 44255eb1..d160080a 100644
--- a/Sources/7-Zip/7zCoderInfo.swift
+++ b/Sources/7-Zip/7zCoderInfo.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import BitByteData
class SevenZipCoderInfo {
diff --git a/Sources/7-Zip/7zContainer.swift b/Sources/7-Zip/7zContainer.swift
index 0f251711..685d58d5 100644
--- a/Sources/7-Zip/7zContainer.swift
+++ b/Sources/7-Zip/7zContainer.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/7-Zip/7zEntry.swift b/Sources/7-Zip/7zEntry.swift
index f6a5e394..46139a20 100644
--- a/Sources/7-Zip/7zEntry.swift
+++ b/Sources/7-Zip/7zEntry.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/7-Zip/7zEntryInfo.swift b/Sources/7-Zip/7zEntryInfo.swift
index ddfb6d93..31b63fea 100644
--- a/Sources/7-Zip/7zEntryInfo.swift
+++ b/Sources/7-Zip/7zEntryInfo.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/7-Zip/7zError.swift b/Sources/7-Zip/7zError.swift
index 03fff69a..29fce7b8 100644
--- a/Sources/7-Zip/7zError.swift
+++ b/Sources/7-Zip/7zError.swift
@@ -1,10 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
-
/**
Represents an error which happened while processing a 7-Zip container.
It may indicate that either container is damaged or it might not be 7-Zip container at all.
diff --git a/Sources/7-Zip/7zFileInfo.swift b/Sources/7-Zip/7zFileInfo.swift
index 03241362..6f45fc5f 100644
--- a/Sources/7-Zip/7zFileInfo.swift
+++ b/Sources/7-Zip/7zFileInfo.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/7-Zip/7zFolder.swift b/Sources/7-Zip/7zFolder.swift
index 3cc5ddec..b7fc9b6f 100644
--- a/Sources/7-Zip/7zFolder.swift
+++ b/Sources/7-Zip/7zFolder.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -147,17 +147,9 @@ class SevenZipFolder {
case .copy:
continue
case .deflate:
- #if (!SWCOMPRESSION_POD_SEVENZIP) || (SWCOMPRESSION_POD_SEVENZIP && SWCOMPRESSION_POD_DEFLATE)
- decodedData = try Deflate.decompress(data: decodedData)
- #else
- throw SevenZipError.compressionNotSupported
- #endif
+ decodedData = try Deflate.decompress(data: decodedData)
case .bzip2:
- #if (!SWCOMPRESSION_POD_SEVENZIP) || (SWCOMPRESSION_POD_SEVENZIP && SWCOMPRESSION_POD_BZ2)
- decodedData = try BZip2.decompress(data: decodedData)
- #else
- throw SevenZipError.compressionNotSupported
- #endif
+ decodedData = try BZip2.decompress(data: decodedData)
case .lzma2:
// Dictionary size is stored in coder's properties.
guard let properties = coder.properties,
@@ -172,7 +164,7 @@ class SevenZipFolder {
else { throw LZMAError.wrongProperties }
var dictionarySize = 0
- for i in 1..<4 {
+ for i in 1..<5 {
dictionarySize |= properties[i].toInt() << (8 * (i - 1))
}
let lzmaProperties = try LZMAProperties(lzmaByte: properties[0], dictionarySize)
@@ -187,11 +179,7 @@ class SevenZipFolder {
decodedData = DeltaFilter.decode(LittleEndianByteReader(data: decodedData), (properties[0] &+ 1).toInt())
} else if coder.id == [0x04, 0xF7, 0x11, 0x04] {
- #if (!SWCOMPRESSION_POD_SEVENZIP) || (SWCOMPRESSION_POD_SEVENZIP && SWCOMPRESSION_POD_LZ4)
- decodedData = try LZ4.decompress(data: decodedData)
- #else
- throw SevenZipError.compressionNotSupported
- #endif
+ decodedData = try LZ4.decompress(data: decodedData)
} else if coder.isEncryptionMethod {
throw SevenZipError.encryptionNotSupported
} else {
diff --git a/Sources/7-Zip/7zHeader.swift b/Sources/7-Zip/7zHeader.swift
index 1a50cdba..301e0e78 100644
--- a/Sources/7-Zip/7zHeader.swift
+++ b/Sources/7-Zip/7zHeader.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/7-Zip/7zPackInfo.swift b/Sources/7-Zip/7zPackInfo.swift
index 9998bcd6..6ccd01b5 100644
--- a/Sources/7-Zip/7zPackInfo.swift
+++ b/Sources/7-Zip/7zPackInfo.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import BitByteData
class SevenZipPackInfo {
diff --git a/Sources/7-Zip/7zProperty.swift b/Sources/7-Zip/7zProperty.swift
index b817adc4..c26aa9dc 100644
--- a/Sources/7-Zip/7zProperty.swift
+++ b/Sources/7-Zip/7zProperty.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import BitByteData
struct SevenZipProperty {
diff --git a/Sources/7-Zip/7zStreamInfo.swift b/Sources/7-Zip/7zStreamInfo.swift
index f8bca63b..a978efd5 100644
--- a/Sources/7-Zip/7zStreamInfo.swift
+++ b/Sources/7-Zip/7zStreamInfo.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import BitByteData
class SevenZipStreamInfo {
diff --git a/Sources/7-Zip/7zSubstreamInfo.swift b/Sources/7-Zip/7zSubstreamInfo.swift
index 1bcc715d..eed68239 100644
--- a/Sources/7-Zip/7zSubstreamInfo.swift
+++ b/Sources/7-Zip/7zSubstreamInfo.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import BitByteData
class SevenZipSubstreamInfo {
diff --git a/Sources/7-Zip/CompressionMethod+7z.swift b/Sources/7-Zip/CompressionMethod+7z.swift
index 103775ad..aeff7fa0 100644
--- a/Sources/7-Zip/CompressionMethod+7z.swift
+++ b/Sources/7-Zip/CompressionMethod+7z.swift
@@ -1,10 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
-
extension CompressionMethod {
init(_ coderID: [UInt8]) {
diff --git a/Sources/7-Zip/MsbBitReader+7z.swift b/Sources/7-Zip/MsbBitReader+7z.swift
index 55d4bfbf..2a22679e 100644
--- a/Sources/7-Zip/MsbBitReader+7z.swift
+++ b/Sources/7-Zip/MsbBitReader+7z.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import BitByteData
extension MsbBitReader {
diff --git a/Sources/BZip2/BZip2+BlockSize.swift b/Sources/BZip2/BZip2+BlockSize.swift
index 63b310aa..a477bbef 100644
--- a/Sources/BZip2/BZip2+BlockSize.swift
+++ b/Sources/BZip2/BZip2+BlockSize.swift
@@ -1,10 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
-
extension BZip2 {
/// Represents the size of the blocks in which data is split during BZip2 compression.
diff --git a/Sources/BZip2/BZip2+Compress.swift b/Sources/BZip2/BZip2+Compress.swift
index 676e971e..95b32a6a 100644
--- a/Sources/BZip2/BZip2+Compress.swift
+++ b/Sources/BZip2/BZip2+Compress.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -41,17 +41,18 @@ extension BZip2: CompressionAlgorithm {
public static func compress(data: Data, blockSize: BlockSize) -> Data {
let bitWriter = MsbBitWriter()
// We intentionally use smaller block size for compression to account for potential data size expansion
- // after intial RLE, which seems to be not being expected by original BZip2 implementation.
+ // after intial RLE, which seems to be not being expected by original BZip2 implementation.
+ // In the worst case initial RLE causes expansion by a factor of 1.25, so 1000 / 1.25 = 800.
let rawBlockSize = blockSize.sizeInKilobytes * 800
// BZip2 Header.
- bitWriter.write(number: 0x425a, bitsCount: 16) // Magic number = 'BZ'.
- bitWriter.write(number: 0x68, bitsCount: 8) // Version = 'h'.
+ bitWriter.write(unsignedNumber: 0x425a, bitsCount: 16) // Magic number = 'BZ'.
+ bitWriter.write(unsignedNumber: 0x68, bitsCount: 8) // Version = 'h'.
bitWriter.write(number: blockSize.headerByte, bitsCount: 8) // Block size.
var totalCRC: UInt32 = 0
for i in stride(from: data.startIndex, to: data.endIndex, by: rawBlockSize) {
- let blockData = data[i..> 31)
totalCRC ^= blockCRC
@@ -60,7 +61,7 @@ extension BZip2: CompressionAlgorithm {
bitWriter.write(bits: blockMarker) // Block magic number.
bitWriter.write(number: blockCRC.toInt(), bitsCount: 32) // Block crc32.
- process(block: blockData, bitWriter)
+ process(block, bitWriter)
}
// EOS magic number.
@@ -72,8 +73,8 @@ extension BZip2: CompressionAlgorithm {
return bitWriter.data
}
- private static func process(block data: Data, _ bitWriter: MsbBitWriter) {
- var out = initialRle(data)
+ private static func process(_ block: [UInt8], _ bitWriter: MsbBitWriter) {
+ var out = initialRle(block)
var pointer = 0
(out, pointer) = BurrowsWheeler.transform(bytes: out)
@@ -121,9 +122,9 @@ extension BZip2: CompressionAlgorithm {
// Otherwise, let's create a new table and check if it gives us better results.
// First, we calculate code lengths and codes for our current stats.
let lengths = BZip2.lengths(from: stats)
- let codes = Code.huffmanCodes(from: lengths)
+ let codes = Code.huffmanCodes(from: lengths).codes
// Then, using these codes, we create a new Huffman tree.
- let table = EncodingTree(codes: codes.codes, bitWriter)
+ let table = EncodingTree(codes, bitWriter)
if table.bitSize(for: stats) < minimumSize {
tables.append(table)
tablesLengths.append(lengths.sorted { $0.symbol < $1.symbol }.map { $0.codeLength })
@@ -147,13 +148,19 @@ extension BZip2: CompressionAlgorithm {
// Now, we perform encoding itself.
// But first, we need to finish block header.
- bitWriter.write(number: 0, bitsCount: 1) // "Randomized".
+ bitWriter.write(bit: 0) // "Randomized".
bitWriter.write(number: pointer, bitsCount: 24) // Original pointer (from BWT).
- var usedMap = Array(repeating: UInt8(0), count: 16)
+ // Encode which symbols (bytes) are used in the data. All possible 256 symbols (0...255) are split into 16 "maps"
+ // of 16 sequential symbols. Each map is a sequence of 16 bits where a non-zero bits indicate that used symbols.
+ // If a map would consist of only zero bits, then it is omitted. This is determined in the construction of `usedMap`.
+ var usedMap = Array(repeating: 0 as UInt8, count: 16)
for usedByte in usedBytes {
- guard usedByte <= 255
- else { fatalError("Incorrect used byte.") }
+ // `usedBytes` is an output of the BW transform which does not change symbols used. The input of the BW
+ // transform is an output of initial RLE encoding. Since a run length value is 255 or less and the input
+ // data consists of normal bytes which also have values of 255 or less, `usedByte` cannot be larger than 255
+ // by construction.
+ assert(usedByte <= 255, "Incorrect usedByte.")
usedMap[usedByte / 16] = 1
}
bitWriter.write(bits: usedMap)
@@ -162,7 +169,7 @@ extension BZip2: CompressionAlgorithm {
for i in 0..<16 {
guard usedMap[i] == 1 else { continue }
for j in 0..<16 {
- if usedBytesIndex < usedBytes.count && i * 16 + j == usedBytes[usedBytesIndex] {
+ if usedBytesIndex < usedBytes.endIndex && i * 16 + j == usedBytes[usedBytesIndex] {
bitWriter.write(bit: 1)
usedBytesIndex += 1
} else {
@@ -174,10 +181,14 @@ extension BZip2: CompressionAlgorithm {
bitWriter.write(number: tables.count, bitsCount: 3)
bitWriter.write(number: selectors.count, bitsCount: 15)
- let mtfSelectors = mtf(selectors, characters: Array(0.. length {
- bitWriter.write(bit: 1) // Decrement length.
- currentLength -= 1
- } else {
- bitWriter.write(bit: 0) // Increment length.
- currentLength += 1
- }
+ // In the worst case delta between two lengths is 19. This delta requires 19 * 2 = 38 bits to encode
+ // which fits into `UInt` (at least, on modern 64-bit systems), so we can use `write(unsignedNumber:)`.
+ if prevLength > length {
+ // Bits: 11 -> 11_11 -> 11_11_11 -> 11_11_11_11 -> ...
+ // Numbers: 3 -> 15 -> 63 -> 255 -> ...
+ // https://oeis.org/A024036
+ let diff = prevLength - length
+ bitWriter.write(unsignedNumber: (1 << (2 * diff)) - 1, bitsCount: 2 * diff)
+ } else if prevLength < length {
+ // Bits: 10 -> 10_10 -> 10_10_10 -> 10_10_10_10 -> ...
+ // Numbers: 2 -> 10 -> 42 -> 170 -> ...
+ // https://oeis.org/A020988
+ let diff = length - prevLength
+ bitWriter.write(unsignedNumber: ((1 << (2 * diff)) - 1) * 2 / 3 , bitsCount: 2 * diff)
}
+ prevLength = length
bitWriter.write(bit: 0)
}
}
// Contents.
var encoded = 0
- var selectorPointer = 0
- var t: EncodingTree?
+ var table = tables[selectors[selectors.startIndex]]
+ var selectorIndex = selectors.startIndex &+ 1
for symbol in out {
- encoded -= 1
- if encoded <= 0 {
- encoded = 50
- if selectorPointer == selectors.count {
- fatalError("Incorrect selector.")
- } else if selectorPointer < selectors.count {
- t = tables[selectors[selectorPointer]]
- selectorPointer += 1
- }
+ // New table is selected every 50 symbols.
+ if encoded >= 50 {
+ // Selectors were added every 50 symbols. So by construction `selectorIndex` can never exceed the
+ // amount of available selectors.
+ assert(selectorIndex < selectors.endIndex, "Incorrect selectorIndex.")
+ table = tables[selectors[selectorIndex]]
+ selectorIndex &+= 1
+ encoded = 0
}
- t?.code(symbol: symbol)
+ table.code(symbol: symbol)
+ encoded &+= 1
}
}
/// Initial Run Length Encoding.
- private static func initialRle(_ data: Data) -> [Int] {
+ private static func initialRle(_ block: [UInt8]) -> [Int] {
var out = [Int]()
- var index = data.startIndex
- while index < data.endIndex {
+ var index = block.startIndex
+ while index < block.endIndex {
var runLength = 1
- while index + 1 < data.endIndex && data[index] == data[index + 1] && runLength < 255 {
+ while index + 1 < block.endIndex && block[index] == block[index + 1] && runLength < 255 {
runLength += 1
index += 1
}
- let byte = data[index].toInt()
+ let byte = block[index].toInt()
for _ in 0.. [Int] {
+ /// Assumes that the characters are given by a list of integers from 0 up to and including `maxValue`.
+ private static func mtf(_ array: [Int], maxValue: Int) -> [Int] {
var out = [Int]()
- /// Mutable copy of `characters`.
- var dictionary = characters
+ var dictionary = Array(0...maxValue)
for i in 0.. [Data] {
+ let reader = MsbBitReader(data: data)
+ var result = [Data]()
+ while !reader.isFinished {
+ result.append(try decompress(reader))
+ reader.align()
+ }
+ return result
+ }
+
static func decompress(_ bitReader: MsbBitReader) throws -> Data {
- // Valid BZip2 "archive" must contain at least 14 bytes of data.
- guard bitReader.bitsLeft >= 14 * 8
+ // Valid BZip2 "archive" must contain at least 14 bytes of data: magic number (2 bytes), method (1 byte), and
+ // block size (1 byte).
+ guard bitReader.bitsLeft >= 32
else { throw BZip2Error.wrongMagic }
/// An array for storing output data
@@ -44,15 +67,18 @@ public class BZip2: DecompressionAlgorithm {
var totalCRC: UInt32 = 0
while true {
- // Using `Int64` because 48 bits may not fit into `Int` on some platforms.
+ // Each BZip2 block must contain block type (6 bytes) and block CRC32 (4 bytes).
+ guard bitReader.bitsLeft >= 80
+ else { throw BZip2Error.wrongMagic }
+ // Using `UInt64` because 48 bits may not fit into `Int` on some platforms.
let blockType = bitReader.uint64(fromBits: 48)
let blockCRC32 = bitReader.uint32(fromBits: 32)
if blockType == 0x314159265359 {
- let blockData = try decode(bitReader, blockSize)
- out.append(blockData)
- guard CheckSums.bzip2crc32(blockData) == blockCRC32
+ let block = try decode(bitReader, blockSize)
+ out.append(Data(block))
+ guard CheckSums.bzip2crc32(block) == blockCRC32
else { throw BZip2Error.wrongCRC(out) }
totalCRC = (totalCRC << 1) | (totalCRC >> 31)
totalCRC ^= blockCRC32
@@ -68,22 +94,32 @@ public class BZip2: DecompressionAlgorithm {
return out
}
- private static func decode(_ bitReader: MsbBitReader, _ blockSize: BlockSize) throws -> Data {
+ private static func decode(_ bitReader: MsbBitReader, _ blockSize: BlockSize) throws -> [UInt8] {
+ // Truncation checks: there is no such thing as "empty BZip2 blocks". Empty data after compression produces
+ // only a "CRC32 block". As such when processing a normal BZip2 block we can assume that various BZip2
+ // structures (such as table selectors or code lengths) are present, and there are at least some symbols encoded.
+
+ // Randomized bit, pointer (24 bits), and bitmap of used symbol blocks (16 bits) require 41 bits.
+ guard bitReader.bitsLeft >= 41
+ else { throw BZip2Error.wrongMagic }
+
let isRandomized = bitReader.bit()
guard isRandomized == 0
else { throw BZip2Error.randomizedBlock }
let pointer = bitReader.int(fromBits: 24)
- // Decoding which symbols are used in Huffman tables.
- // The "list" of all possible 256 symbols is split into 16 blocks.
- // If no symbols from a block are in use, then the block is not present.
- // First, we decode which blocks are present.
- let usedBlocksBitMap = UInt16(bitReader.int(fromBits: 16))
+ // Decoding which symbols are used in Huffman tables. All possible 256 symbols (0...255) are split into 16 "maps"
+ // of 16 sequential symbols. Each map is a sequence of 16 bits where non-zero bits indicate used symbols. A map
+ // is omitted if none of its symbols are in use. First, we decode which maps are present.
+ let usedBlocksBitMap = bitReader.uint16(fromBits: 16)
+ // For each non-zero bit in the bitmap there should be 16 bits of used symbols. After that there should be 3
+ // bits for the number of Huffman tables and 15 bits for selectors count.
+ guard bitReader.bitsLeft >= 16 * usedBlocksBitMap.nonzeroBitCount + 3 + 15
+ else { throw BZip2Error.wrongMagic }
+
var blockMask = 1 << 15 as UInt16
var usedSymbols = [UInt8]()
- // Two additional symbols are RUNA and RUNB.
- var usedSymbolsCount = 2
while blockMask > 0 {
if usedBlocksBitMap & blockMask > 0 {
// Each block, if present, is a set of 16 bits which, if set, represent that the corresponding symbols
@@ -92,7 +128,6 @@ public class BZip2: DecompressionAlgorithm {
var symbolMask = 1 << 15 as UInt16
while symbolMask > 0 {
if usedSymbolsBitMask & symbolMask > 0 {
- usedSymbolsCount += 1
usedSymbols.append(UInt8(blockMask.leadingZeroBitCount * 16 + symbolMask.leadingZeroBitCount))
}
symbolMask >>= 1
@@ -100,103 +135,114 @@ public class BZip2: DecompressionAlgorithm {
}
blockMask >>= 1
}
+ // Two additional symbols are RUNA and RUNB.
+ let usedSymbolsCount = 2 + usedSymbols.count
- let huffmanGroups = bitReader.int(fromBits: 3)
- guard huffmanGroups >= 2 && huffmanGroups <= 6
+ let huffmanTablesCount = bitReader.int(fromBits: 3)
+ guard huffmanTablesCount >= 2 && huffmanTablesCount <= 6
else { throw BZip2Error.wrongHuffmanGroups }
- func computeSelectors() throws -> [Int] {
- let selectorsUsed = bitReader.int(fromBits: 15)
+ let selectorsCount = bitReader.int(fromBits: 15)
+ var mtf = Array(0.. 0 {
- c += 1
- guard c < huffmanGroups
- else { throw BZip2Error.wrongSelector }
+ // It is impossible to calculate in advance how many bits encode table selectors. At minimum, if each selector
+ // is encoded by a single zero bit (which means only the zeroth Huffman table is utilized), `selectorsCount`
+ // bits are used. At maximum, when all symbols are encoded with the last Huffman table, each selector is given
+ // by `huffmanTablesCount - 1` non-zero bits and one zero bit. This gives `huffmanTablesCount * selectorsCount`
+ // bits in total.
+ // Due to this unpredictability we are forced to check if there are still bits left before every read operation,
+ // similarly to how bit reading is done in `DecodingTree.findNextSymbol()`.
+ var selectors = [Int]()
+ // Accessing `bitsLeft` property of `bitReader` incurs a lot of overhead, so we create a local copy.
+ var bitsLeft = bitReader.bitsLeft
+ for _ in 0.. 0 {
+ let bit = bitReader.bit()
+ bitsLeft -= 1
+ if bit == 0 {
+ break
}
- if c >= 0 {
- let el = mtf.remove(at: c)
- mtf.insert(el, at: 0)
- }
- selectorsList.append(mtf[0])
+ c += 1
}
-
- return selectorsList
+ guard c < huffmanTablesCount
+ else { throw BZip2Error.wrongSelector }
+ let el = mtf.remove(at: c)
+ mtf.insert(el, at: 0)
+ selectors.append(el)
}
- let selectors = try computeSelectors()
-
- func computeTables() throws -> [DecodingTree] {
- var tables = [DecodingTree]()
- for _ in 0..= 0 && length <= 20
+ // Similar to decoding selectors, code lengths are also encoded in unpredictable manner. As such, we check for
+ // input truncation before reading every bit.
+ var tables = [DecodingTree]()
+ for _ in 0..= 5
+ else { throw BZip2Error.wrongHuffmanCodeLength }
+ var length = bitReader.int(fromBits: 5)
+ bitsLeft -= 5
+ var codeLengths = [CodeLength]()
+ for i in 0..= 0 && length <= 20
+ else { throw BZip2Error.wrongHuffmanCodeLength }
+ while bitsLeft > 0 {
+ let bit = bitReader.bit()
+ bitsLeft -= 1
+ if bit == 0 {
+ break
+ }
+ guard bitsLeft > 0
else { throw BZip2Error.wrongHuffmanCodeLength }
- while bitReader.bit() > 0 {
- length -= (bitReader.bit().toInt() * 2 - 1)
- }
- if length > 0 {
- lengths.append(CodeLength(symbol: i, codeLength: length))
- }
+ length -= (bitReader.bit().toInt() * 2 - 1)
+ bitsLeft -= 1
}
- let codes = Code.huffmanCodes(from: lengths)
- let table = DecodingTree(codes: codes.codes, maxBits: codes.maxBits, bitReader)
- tables.append(table)
+ codeLengths.append(CodeLength(symbol: i, codeLength: length))
}
-
- return tables
+ let codes = Code.huffmanCodes(from: codeLengths)
+ let table = DecodingTree(codes, bitReader)
+ tables.append(table)
}
- let tables = try computeTables()
-
- var selectorPointer = 0
var decoded = 0
+ var table = tables[selectors[selectors.startIndex]]
+ var selectorIndex = selectors.startIndex &+ 1
var runLength = 0
- var repeatPower = 0
- var buffer: [UInt8] = []
- var currentTable: DecodingTree?
+ var repeatPower = 1
+ var buffer = [UInt8]()
while true {
- decoded -= 1
- if decoded <= 0 {
- decoded = 50
- if selectorPointer == selectors.count {
- throw BZip2Error.wrongSelector
- } else if selectorPointer < selectors.count {
- currentTable = tables[selectors[selectorPointer]]
- selectorPointer += 1
- }
+ if decoded >= 50 {
+ guard selectorIndex < selectorsCount
+ else { throw BZip2Error.wrongSelector }
+ table = tables[selectors[selectorIndex]]
+ selectorIndex &+= 1
+ decoded = 0
}
- guard let symbol = currentTable?.findNextSymbol(), symbol != -1
+ let symbol = table.findNextSymbol()
+ guard symbol != -1
else { throw BZip2Error.symbolNotFound }
+ decoded &+= 1
if symbol == 0 || symbol == 1 { // RUNA and RUNB symbols.
- if runLength == 0 {
- repeatPower = 1
- }
- runLength += repeatPower << symbol
+ runLength &+= repeatPower << symbol
repeatPower <<= 1
continue
- } else if runLength > 0 {
+ }
+ if runLength > 0 {
+ // There might have been a repeat run right before EOS symbol.
for _ in 0.. ([Int], Int) {
diff --git a/Sources/BZip2/SuffixArray.swift b/Sources/BZip2/SuffixArray.swift
index 5b1c0f26..f1d22647 100644
--- a/Sources/BZip2/SuffixArray.swift
+++ b/Sources/BZip2/SuffixArray.swift
@@ -1,10 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
-
/// Suffix Array by Induced Sorting
enum SuffixArray {
diff --git a/Sources/Common/Archive.swift b/Sources/Common/Archive.swift
index 1daca0d5..a0930705 100644
--- a/Sources/Common/Archive.swift
+++ b/Sources/Common/Archive.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/Common/CheckSums.swift b/Sources/Common/CheckSums.swift
index 5b8c7ddc..d61fdc98 100644
--- a/Sources/Common/CheckSums.swift
+++ b/Sources/Common/CheckSums.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -27,9 +27,9 @@ enum CheckSums {
return ~crc
}
- static func bzip2crc32(_ data: Data) -> UInt32 {
+ static func bzip2crc32(_ block: [UInt8]) -> UInt32 {
var crc: UInt32 = 0xFFFFFFFF
- for byte in data {
+ for byte in block {
let index = UInt32(truncatingIfNeeded: byte)
crc = (crc << 8) ^ CheckSums.bzip2CRC32table[((crc >> 24) ^ index).toInt()]
}
diff --git a/Sources/Common/CodingTree/Code.swift b/Sources/Common/CodingTree/Code.swift
index 8992126f..278011fc 100644
--- a/Sources/Common/CodingTree/Code.swift
+++ b/Sources/Common/CodingTree/Code.swift
@@ -1,9 +1,9 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
+typealias HuffmanCodes = (codes: [Code], maxBits: Int)
struct Code {
@@ -12,8 +12,7 @@ struct Code {
let code: Int
let symbol: Int
- /// `lengths` don't have to be sorted, but there must not be any 0 code lengths.
- static func huffmanCodes(from lengths: [CodeLength]) -> (codes: [Code], maxBits: Int) {
+ static func huffmanCodes(from lengths: [CodeLength]) -> HuffmanCodes {
// Sort `lengths` array to calculate canonical Huffman code.
let sortedLengths = lengths.sorted()
@@ -24,8 +23,7 @@ struct Code {
var loopBits = -1
var symbol = -1
- for length in sortedLengths {
- precondition(length.codeLength > 0, "Code length must not be 0 during HuffmanTree construction.")
+ for length in sortedLengths where length.codeLength > 0 {
symbol += 1
// We sometimes need to make symbol to have length.bits bit length.
let bits = length.codeLength
diff --git a/Sources/Common/CodingTree/CodeLength.swift b/Sources/Common/CodingTree/CodeLength.swift
index eabfa41a..348c3b23 100644
--- a/Sources/Common/CodingTree/CodeLength.swift
+++ b/Sources/Common/CodingTree/CodeLength.swift
@@ -1,10 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
-
struct CodeLength: Equatable {
let symbol: Int
diff --git a/Sources/Common/CodingTree/DecodingTree.swift b/Sources/Common/CodingTree/DecodingTree.swift
index a502baa1..f30df948 100644
--- a/Sources/Common/CodingTree/DecodingTree.swift
+++ b/Sources/Common/CodingTree/DecodingTree.swift
@@ -1,26 +1,25 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import BitByteData
-final class DecodingTree {
+struct DecodingTree {
private let bitReader: BitReader
private let tree: [Int]
private let leafCount: Int
- init(codes: [Code], maxBits: Int, _ bitReader: BitReader) {
+ init(_ huffmanCodes: HuffmanCodes, _ bitReader: BitReader) {
self.bitReader = bitReader
- // Calculate maximum amount of leaves in a tree.
- self.leafCount = 1 << (maxBits + 1)
+ // Maximum amount of leaves in a binary tree with the height equal to `huffmanCodes.maxBits`.
+ self.leafCount = (1 << (huffmanCodes.maxBits + 1)) - 1
var tree = Array(repeating: -1, count: leafCount)
- for code in codes {
+ for code in huffmanCodes.codes {
// Put code in its place in the tree.
var treeCode = code.code
var index = 0
diff --git a/Sources/Common/CodingTree/EncodingTree.swift b/Sources/Common/CodingTree/EncodingTree.swift
index 3fb51133..da922554 100644
--- a/Sources/Common/CodingTree/EncodingTree.swift
+++ b/Sources/Common/CodingTree/EncodingTree.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import BitByteData
fileprivate struct CodingIndex {
@@ -13,13 +12,13 @@ fileprivate struct CodingIndex {
}
-final class EncodingTree {
+struct EncodingTree {
private let bitWriter: BitWriter
private let codingIndices: [CodingIndex]
- init(codes: [Code], _ bitWriter: BitWriter, reverseCodes: Bool = false) {
+ init(_ codes: [Code], _ bitWriter: BitWriter, reverseCodes: Bool = false) {
self.bitWriter = bitWriter
var codingIndices = Array(repeating: CodingIndex(treeCode: -1, bitSize: -1), count: codes.count)
diff --git a/Sources/Common/CompressionAlgorithm.swift b/Sources/Common/CompressionAlgorithm.swift
index 1ae63323..e3dc9a77 100644
--- a/Sources/Common/CompressionAlgorithm.swift
+++ b/Sources/Common/CompressionAlgorithm.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/Common/CompressionMethod.swift b/Sources/Common/CompressionMethod.swift
index 1b796ff4..10127da9 100644
--- a/Sources/Common/CompressionMethod.swift
+++ b/Sources/Common/CompressionMethod.swift
@@ -1,10 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
-
/// Represents a (de)compression method.
public enum CompressionMethod {
/// BZip2.
diff --git a/Sources/Common/Container/Container.swift b/Sources/Common/Container/Container.swift
index f700655e..a2ef214b 100644
--- a/Sources/Common/Container/Container.swift
+++ b/Sources/Common/Container/Container.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/Common/Container/ContainerEntry.swift b/Sources/Common/Container/ContainerEntry.swift
index def4a7e0..f0e58c25 100644
--- a/Sources/Common/Container/ContainerEntry.swift
+++ b/Sources/Common/Container/ContainerEntry.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/Common/Container/ContainerEntryInfo.swift b/Sources/Common/Container/ContainerEntryInfo.swift
index 14d5a8dd..7b8c653f 100644
--- a/Sources/Common/Container/ContainerEntryInfo.swift
+++ b/Sources/Common/Container/ContainerEntryInfo.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/Common/Container/ContainerEntryType.swift b/Sources/Common/Container/ContainerEntryType.swift
index 09578c22..441de008 100644
--- a/Sources/Common/Container/ContainerEntryType.swift
+++ b/Sources/Common/Container/ContainerEntryType.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/Common/Container/DosAttributes.swift b/Sources/Common/Container/DosAttributes.swift
index 48d7cd7e..fad1f7b1 100644
--- a/Sources/Common/Container/DosAttributes.swift
+++ b/Sources/Common/Container/DosAttributes.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/Common/Container/Permissions.swift b/Sources/Common/Container/Permissions.swift
index 1286a095..d476395f 100644
--- a/Sources/Common/Container/Permissions.swift
+++ b/Sources/Common/Container/Permissions.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/Common/DataError.swift b/Sources/Common/DataError.swift
index f02e111c..bcccd831 100644
--- a/Sources/Common/DataError.swift
+++ b/Sources/Common/DataError.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/Common/DecompressionAlgorithm.swift b/Sources/Common/DecompressionAlgorithm.swift
index 254f03ba..9fa30b5c 100644
--- a/Sources/Common/DecompressionAlgorithm.swift
+++ b/Sources/Common/DecompressionAlgorithm.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/Common/DeltaFilter.swift b/Sources/Common/DeltaFilter.swift
index e75bc14f..795a3fe6 100644
--- a/Sources/Common/DeltaFilter.swift
+++ b/Sources/Common/DeltaFilter.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/Common/Extensions.swift b/Sources/Common/Extensions.swift
index bb602e29..28211283 100644
--- a/Sources/Common/Extensions.swift
+++ b/Sources/Common/Extensions.swift
@@ -1,10 +1,19 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
import Foundation
+extension Data {
+
+ @inlinable @inline(__always)
+ func toByteArray() -> [UInt8] {
+ return self.withUnsafeBytes { $0.map { $0 } }
+ }
+
+}
+
extension UnsignedInteger {
@inlinable @inline(__always)
@@ -32,10 +41,11 @@ extension Int {
/// Returns an integer with reversed order of bits.
func reversed(bits count: Int) -> Int {
- var a = 1 << 0
- var b = 1 << (count - 1)
+ // Arithmetic operations amount: 3 + 8 * ceil(count / 2)
+ var a = 1
+ var b = 1 << (count &- 1)
var z = 0
- for i in Swift.stride(from: count - 1, to: -1, by: -2) {
+ for i in Swift.stride(from: count &- 1, to: -1, by: -2) {
z |= (self >> i) & a
z |= (self << i) & b
a <<= 1
diff --git a/Sources/Common/FileSystemType.swift b/Sources/Common/FileSystemType.swift
index 97a2dc55..de6de79c 100644
--- a/Sources/Common/FileSystemType.swift
+++ b/Sources/Common/FileSystemType.swift
@@ -1,10 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
-
/**
Represents the type of the file system on which an archive or container was created. File system determines the meaning
of file attributes.
diff --git a/Sources/Deflate/Deflate+Compress.swift b/Sources/Deflate/Deflate+Compress.swift
index f7842601..90d22e5f 100644
--- a/Sources/Deflate/Deflate+Compress.swift
+++ b/Sources/Deflate/Deflate+Compress.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -30,8 +30,7 @@ extension Deflate: CompressionAlgorithm {
// Static Huffman size is more complicated...
let staticHuffmanBlockSize = staticHuffmanBitSize(bldCodes.stats)
- // Since `length` of uncompressed block is 16-bit integer,
- // there is a limitation on size of uncompressed block.
+ // Since `length` of uncompressed block is 16-bit integer, there is a limitation on size of uncompressed block.
// Falling back to static Huffman encoding in case of big uncompressed block is a band-aid solution.
if uncompBlockSize <= staticHuffmanBlockSize && uncompBlockSize <= 65535 {
// If according to our calculations static huffman will not make output smaller than input,
@@ -73,24 +72,13 @@ extension Deflate: CompressionAlgorithm {
}
private static func createUncompressedBlock(_ data: Data) -> Data {
- let bitWriter = LsbBitWriter()
-
- // Write block header.
+ assert(data.count <= 65535, "Cannot create uncompressed Deflate blocks larger than 65535 bytes.")
+ // Write block header, data's length and n-length. It is more efficient to avoid using LsbBitWriter.
// Note: Only one block is supported for now.
- bitWriter.write(bit: 1)
- bitWriter.write(bits: [0, 0])
-
- // Before writing lengths we need to discard remaining bits in current byte.
- bitWriter.align()
-
- // Write data's length.
- bitWriter.write(number: data.count, bitsCount: 16)
- // Write data's n-length.
- bitWriter.write(number: data.count ^ (1 << 16 - 1), bitsCount: 16)
-
- var out = bitWriter.data
+ let nLength = data.count ^ ((1 << 16) - 1)
+ var out = Data([1, UInt8(truncatingIfNeeded: data.count & 0xFF), UInt8(truncatingIfNeeded: (data.count >> 8) & 0xFF),
+ UInt8(truncatingIfNeeded: nLength & 0xFF), UInt8(truncatingIfNeeded: (nLength >> 8) & 0xFF)])
out.append(data)
-
return out
}
@@ -106,9 +94,9 @@ extension Deflate: CompressionAlgorithm {
// Constructing Huffman trees for the case of block with preset alphabets.
// In this case codes for literals and distances are fixed.
/// Huffman tree for literal and length symbols/codes.
- let mainLiterals = EncodingTree(codes: Constants.staticHuffmanLiteralCodes, bitWriter, reverseCodes: true)
+ let mainLiterals = EncodingTree(Constants.staticHuffmanLiteralCodes.codes, bitWriter, reverseCodes: true)
/// Huffman tree for backward distance symbols/codes.
- let mainDistances = EncodingTree(codes: Constants.staticHuffmanDistanceCodes, bitWriter, reverseCodes: true)
+ let mainDistances = EncodingTree(Constants.staticHuffmanDistanceCodes.codes, bitWriter, reverseCodes: true)
for code in bldCodes {
switch code {
diff --git a/Sources/Deflate/Deflate+Constants.swift b/Sources/Deflate/Deflate+Constants.swift
index 0c4c9ff3..4d0176ee 100644
--- a/Sources/Deflate/Deflate+Constants.swift
+++ b/Sources/Deflate/Deflate+Constants.swift
@@ -1,17 +1,15 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
-
extension Deflate {
struct Constants {
// Precomputed codes for the static Huffman literal and distance trees.
- static let staticHuffmanLiteralCodes =
- [Code(bits: 7, code: 0, symbol: 256), Code(bits: 7, code: 64, symbol: 257),
+ static let staticHuffmanLiteralCodes: HuffmanCodes =
+ ([Code(bits: 7, code: 0, symbol: 256), Code(bits: 7, code: 64, symbol: 257),
Code(bits: 7, code: 32, symbol: 258), Code(bits: 7, code: 96, symbol: 259),
Code(bits: 7, code: 16, symbol: 260), Code(bits: 7, code: 80, symbol: 261),
Code(bits: 7, code: 48, symbol: 262), Code(bits: 7, code: 112, symbol: 263),
@@ -154,10 +152,10 @@ extension Deflate {
Code(bits: 9, code: 63, symbol: 248), Code(bits: 9, code: 319, symbol: 249),
Code(bits: 9, code: 191, symbol: 250), Code(bits: 9, code: 447, symbol: 251),
Code(bits: 9, code: 127, symbol: 252), Code(bits: 9, code: 383, symbol: 253),
- Code(bits: 9, code: 255, symbol: 254), Code(bits: 9, code: 511, symbol: 255)]
+ Code(bits: 9, code: 255, symbol: 254), Code(bits: 9, code: 511, symbol: 255)], 9)
- static let staticHuffmanDistanceCodes =
- [Code(bits: 5, code: 0, symbol: 0), Code(bits: 5, code: 16, symbol: 1),
+ static let staticHuffmanDistanceCodes: HuffmanCodes =
+ ([Code(bits: 5, code: 0, symbol: 0), Code(bits: 5, code: 16, symbol: 1),
Code(bits: 5, code: 8, symbol: 2), Code(bits: 5, code: 24, symbol: 3),
Code(bits: 5, code: 4, symbol: 4), Code(bits: 5, code: 20, symbol: 5),
Code(bits: 5, code: 12, symbol: 6), Code(bits: 5, code: 28, symbol: 7),
@@ -172,7 +170,7 @@ extension Deflate {
Code(bits: 5, code: 3, symbol: 24), Code(bits: 5, code: 19, symbol: 25),
Code(bits: 5, code: 11, symbol: 26), Code(bits: 5, code: 27, symbol: 27),
Code(bits: 5, code: 7, symbol: 28), Code(bits: 5, code: 23, symbol: 29),
- Code(bits: 5, code: 15, symbol: 30), Code(bits: 5, code: 31, symbol: 31)]
+ Code(bits: 5, code: 15, symbol: 30), Code(bits: 5, code: 31, symbol: 31)], 5)
static let codeLengthOrders: [Int] =
[16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]
diff --git a/Sources/Deflate/Deflate+Lengths.swift b/Sources/Deflate/Deflate+Lengths.swift
deleted file mode 100644
index dd265c6b..00000000
--- a/Sources/Deflate/Deflate+Lengths.swift
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) 2024 Timofey Solomko
-// Licensed under MIT License
-//
-// See LICENSE for license information
-
-import Foundation
-
-// Deflate specific functions for generation of HuffmanLength arrays from different inputs.
-extension Deflate {
-
- /// - Note: Skips zero codeLengths.
- static func lengths(from orderedCodeLengths: [Int]) -> [CodeLength] {
- var lengths = [CodeLength]()
- for (i, codeLength) in orderedCodeLengths.enumerated() where codeLength > 0 {
- lengths.append(CodeLength(symbol: i, codeLength: codeLength))
- }
- return lengths
- }
-
-}
diff --git a/Sources/Deflate/Deflate.swift b/Sources/Deflate/Deflate.swift
index 56b6cf4c..68553e33 100644
--- a/Sources/Deflate/Deflate.swift
+++ b/Sources/Deflate/Deflate.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -77,8 +77,8 @@ public class Deflate: DecompressionAlgorithm {
if blockType == 1 { // Static Huffman
// In this case codes for literals and distances are fixed.
// Initialize trees from bootstraps.
- mainLiterals = DecodingTree(codes: Constants.staticHuffmanLiteralCodes, maxBits: 9, bitReader)
- mainDistances = DecodingTree(codes: Constants.staticHuffmanDistanceCodes, maxBits: 5, bitReader)
+ mainLiterals = DecodingTree(Constants.staticHuffmanLiteralCodes, bitReader)
+ mainDistances = DecodingTree(Constants.staticHuffmanDistanceCodes, bitReader)
} else { // Dynamic Huffman
// In this case there are Huffman codes for two alphabets in data right after block header.
// Each code defined by a sequence of code lengths (which are compressed themselves with Huffman).
@@ -88,6 +88,11 @@ public class Deflate: DecompressionAlgorithm {
/// Number of literals codes.
let literals = bitReader.int(fromBits: 5) + 257
+ // According to Deflate specification maximum amount of literal/length codes is 286. However, it
+ // is possible to encode a number larger than this with 5 bits. Specification seems to be silent
+ // about this scenario, so we treat it as a corrupted input.
+ guard literals <= 286
+ else { throw DeflateError.wrongSymbol }
/// Number of distances codes.
let distances = bitReader.int(fromBits: 5) + 1
/// Number of code lengths codes.
@@ -96,68 +101,70 @@ public class Deflate: DecompressionAlgorithm {
guard bitReader.bitsLeft >= 3 * codeLengthsCount
else { throw DeflateError.symbolNotFound }
+ // Code lengths of the literal/length and distance Huffman trees are encoded with another Huffman
+ // tree using a special alphabet. The code lengths for this latter tree are presented in a specific
+ // order which is stored in `Constants.codeLengthOrders`: `codeLengthOrders[i]` is the code lengths
+ // alphabet symbol at the i-th place in the sequence.
var orderedCodeLengths = Array(repeating: 0, count: 19)
for i in 0..= 0 && symbol <= 15 {
// It is a raw code length.
- count = 1
- what = symbol
- } else if symbol == 16 {
- // Copy previous code length 3 to 6 times.
- // Next two bits show how many times we need to copy.
+ codeLengths[n] = symbol
+ n += 1
+ } else if symbol == 16 && n > codeLengths.startIndex {
+ // Copy previous code length 3 to 6 times. Next two bits show how many times we need to copy.
+ // This symbol cannot be the first symbol encoding code lengths, since there is nothing to
+ // copy at this point.
guard bitReader.bitsLeft >= 2
else { throw DeflateError.symbolNotFound }
-
- count = bitReader.int(fromBits: 2) + 3
- what = codeLengths.last!
+ let copyCount = bitReader.int(fromBits: 2) + 3
+ guard n + copyCount <= codeLengths.endIndex
+ else { throw DeflateError.wrongSymbol }
+ for i in 0..= 3
else { throw DeflateError.symbolNotFound }
-
- count = bitReader.int(fromBits: 3) + 3
- what = 0
+ n += bitReader.int(fromBits: 3) + 3
} else if symbol == 18 {
- // Repeat code length 0 from 11 to 138 times.
- // Next seven bits show how many times we need to do this.
+ // Repeat code length 0 from 11 to 138 times. Next 7 bits show how many times we need to
+ // copy. Since `codeLengths` array was already preinitialized with zeros, we just have to
+ // skip ahead.
guard bitReader.bitsLeft >= 7
else { throw DeflateError.symbolNotFound }
-
- count = bitReader.int(fromBits: 7) + 11
- what = 0
+ n += bitReader.int(fromBits: 7) + 11
} else {
throw DeflateError.wrongSymbol
}
- for _ in 0.. [CodeLength] {
+ return orderedCodeLengths.enumerated().map({ CodeLength(symbol: $0, codeLength: $1) })
+ }
+
}
diff --git a/Sources/Deflate/DeflateError.swift b/Sources/Deflate/DeflateError.swift
index 802e5c30..aa47df8a 100644
--- a/Sources/Deflate/DeflateError.swift
+++ b/Sources/Deflate/DeflateError.swift
@@ -1,10 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
-
/**
Represents an error which happened during Deflate compression or decompression.
It may indicate that either the data is damaged or it might not be compressed with Deflate at all.
diff --git a/Sources/GZip/FileSystemType+Gzip.swift b/Sources/GZip/FileSystemType+Gzip.swift
index 6bdbf6b3..4b8d8048 100644
--- a/Sources/GZip/FileSystemType+Gzip.swift
+++ b/Sources/GZip/FileSystemType+Gzip.swift
@@ -1,10 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
-
extension FileSystemType {
init(_ gzipOS: UInt8) {
diff --git a/Sources/GZip/GzipArchive.swift b/Sources/GZip/GzipArchive.swift
index 32bd9701..8b09dd0a 100644
--- a/Sources/GZip/GzipArchive.swift
+++ b/Sources/GZip/GzipArchive.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/GZip/GzipError.swift b/Sources/GZip/GzipError.swift
index 6e037c30..ab4fb129 100644
--- a/Sources/GZip/GzipError.swift
+++ b/Sources/GZip/GzipError.swift
@@ -1,10 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
-
/**
Represents an error which happened while processing a GZip archive.
It may indicate that either archive is damaged or it might not be GZip archive at all.
diff --git a/Sources/GZip/GzipHeader+ExtraField.swift b/Sources/GZip/GzipHeader+ExtraField.swift
index b897c0b1..338c0cb8 100644
--- a/Sources/GZip/GzipHeader+ExtraField.swift
+++ b/Sources/GZip/GzipHeader+ExtraField.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/GZip/GzipHeader.swift b/Sources/GZip/GzipHeader.swift
index 03b7098b..7cdfaa4a 100644
--- a/Sources/GZip/GzipHeader.swift
+++ b/Sources/GZip/GzipHeader.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -118,12 +118,11 @@ public struct GzipHeader {
headerBytes.append(byte)
}
+ // There must be least four bytes of extra fields for SI1, SI2, and 2 bytes of the length parameter
+ // filled with zeros (minimal variant).
+ guard reader.bytesLeft >= xlen && xlen >= 4
+ else { throw GzipError.wrongMagic }
while xlen > 0 {
- // There must be least four bytes of extra fields for SI1, SI2, and 2 bytes of the length parameter
- // filled with zeros (minimal variant).
- guard reader.bytesLeft >= xlen && xlen >= 4
- else { throw GzipError.wrongMagic }
-
let si1 = reader.byte()
headerBytes.append(si1)
diff --git a/Sources/LZ4/LZ4+Compress.swift b/Sources/LZ4/LZ4+Compress.swift
index dfb57743..5ddd4ed0 100644
--- a/Sources/LZ4/LZ4+Compress.swift
+++ b/Sources/LZ4/LZ4+Compress.swift
@@ -1,10 +1,9 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
import Foundation
-import BitByteData
extension LZ4: CompressionAlgorithm {
diff --git a/Sources/LZ4/LZ4.swift b/Sources/LZ4/LZ4.swift
index aeb1c7c1..43c082b3 100644
--- a/Sources/LZ4/LZ4.swift
+++ b/Sources/LZ4/LZ4.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -155,7 +155,7 @@ public enum LZ4: DecompressionAlgorithm {
}
// The functions below return uncompressed data and the offset to the next byte after the processed frame (even if
- // the end of the input data was reached). The offset is needed to make multiDecompress function work.
+ // the end of the input data was reached). The offset is required to make multiDecompress function work.
private static func process(legacyFrame data: Data) throws -> (Data, Data.Index) {
let reader = LittleEndianByteReader(data: data)
@@ -229,11 +229,11 @@ public enum LZ4: DecompressionAlgorithm {
let contentSize: Int?
if contentSizePresent {
- // At this point valid LZ4 frame must have at least 13 bytes remaining for: content size (8 bytes), header
+ // At this point a valid LZ4 frame must have at least 13 bytes remaining for content size (8 bytes), header
// checksum (1 byte), and EndMark (4 bytes), assuming zero data blocks.
guard reader.bytesLeft >= 13
else { throw DataError.truncated }
- // Since Data is indexed by the Int type, the maximum size of the uncompressed data that we can decode is
+ // Since Data is indexed by the Int type, the maximum size of uncompressed data that we can decode is
// Int.max. However, LZ4 supports uncompressed data sizes up to UInt64.max, which is larger, so we check
// for this possibility.
let rawContentSize = reader.uint64()
@@ -249,7 +249,7 @@ public enum LZ4: DecompressionAlgorithm {
// If dictionary ID is present in the frame, then we must have a dictionary to successfully decode it.
guard dictionary != nil
else { throw DataError.corrupted }
- // At this point valid LZ4 frame must have at least 9 bytes remaining for: dictionary ID (4 bytes), header
+ // At this point a valid LZ4 frame must have at least 9 bytes remaining for dictionary ID (4 bytes), header
// checksum (1 byte), and EndMark (4 bytes), assuming zero data blocks.
guard reader.bytesLeft >= 9
else { throw DataError.truncated }
@@ -264,7 +264,7 @@ public enum LZ4: DecompressionAlgorithm {
}
if let extDictId = extDictId, let dictId = dictId {
- // If dictionary ID is present in the frame, and passed as an argument, then they must be equal.
+ // If dictionary ID is present in the frame and passed as an argument, then they must be equal.
guard extDictId == dictId
else { throw DataError.corrupted }
}
@@ -278,7 +278,7 @@ public enum LZ4: DecompressionAlgorithm {
while true {
guard reader.bytesLeft >= 4
else { throw DataError.truncated }
- // Either the size of the block, or the EndMark.
+ // Either the size of a block, or the EndMark.
let blockMark = reader.uint32()
// Check for the EndMark.
if blockMark == 0 {
@@ -287,8 +287,8 @@ public enum LZ4: DecompressionAlgorithm {
// The highest bit indicates if the block is compressed.
let compressed = blockMark & 0x80000000 == 0
let blockSize = (blockMark & 0x7FFFFFFF).toInt()
- // Since we don't do manual memory allocation we don't have to constraint the block size. Nevertheless, we
- // follow the reference implementation here and reject blocks with sizes greater than maximum block size.
+ // Since we don't do manual memory allocation we don't have to constrain the block size. Nevertheless, we
+ // follow the reference implementation here and reject blocks with sizes greater than the maximum block size.
guard blockSize <= maxBlockSize
else { throw DataError.corrupted }
@@ -350,7 +350,7 @@ public enum LZ4: DecompressionAlgorithm {
guard data.endIndex - reader.offset >= 1
else { throw DataError.truncated }
let byte = reader.byte()
- // There is no size limit on the literal count, so we need to check that it remains within Int range
+ // There is no size limit on the literal count, so we have to check that it remains within Int range
// (similar to content size considerations).
let (newLiteralCount, overflow) = literalCount.addingReportingOverflow(byte.toInt())
guard !overflow
@@ -388,7 +388,7 @@ public enum LZ4: DecompressionAlgorithm {
guard data.endIndex - reader.offset >= 1
else { throw DataError.truncated }
let byte = reader.byte()
- // Again, there is no size limit on the match length, so we need to check that it remains within Int
+ // Again, there is no size limit on the match length, so we have to check that it remains within Int
// range.
let (newMatchLength, overflow) = matchLength.addingReportingOverflow(byte.toInt())
guard !overflow
@@ -401,8 +401,7 @@ public enum LZ4: DecompressionAlgorithm {
}
// We record the start index of the last encountered match to verify it against end-of-block restrictions.
- // Note, that this refers to the bytes that we have found a match for, and not to the bytes that we're
- // matching to.
+ // This refers to the bytes that we have found a match for, and not to the bytes that we're matching to.
lastMatchStartIndex = out.endIndex
let matchStartIndex = out.endIndex - offset
for i in 0..> 31)
self.code = self.code &+ (self.range & t)
- if self.code == self.range {
- self.isCorrupted = true
- }
-
self.normalize()
res <<= 1
diff --git a/Sources/LZMA2/LZMA2.swift b/Sources/LZMA2/LZMA2.swift
index 46cbe674..501396d9 100644
--- a/Sources/LZMA2/LZMA2.swift
+++ b/Sources/LZMA2/LZMA2.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/LZMA2/LZMA2Decoder.swift b/Sources/LZMA2/LZMA2Decoder.swift
index d467d3f8..e6b08e78 100644
--- a/Sources/LZMA2/LZMA2Decoder.swift
+++ b/Sources/LZMA2/LZMA2Decoder.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import BitByteData
struct LZMA2Decoder {
diff --git a/Sources/LZMA2/LZMA2Error.swift b/Sources/LZMA2/LZMA2Error.swift
index 636b513c..03fd9bf3 100644
--- a/Sources/LZMA2/LZMA2Error.swift
+++ b/Sources/LZMA2/LZMA2Error.swift
@@ -1,10 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
-
/**
Represents an error which happened during LZMA2 decompression.
It may indicate that either data is damaged or it might not be compressed with LZMA2 at all.
diff --git a/Sources/TAR/ContainerEntryType+Tar.swift b/Sources/TAR/ContainerEntryType+Tar.swift
index 1602d3be..788c13ce 100644
--- a/Sources/TAR/ContainerEntryType+Tar.swift
+++ b/Sources/TAR/ContainerEntryType+Tar.swift
@@ -1,10 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
-
extension ContainerEntryType {
init(_ fileTypeIndicator: UInt8) {
diff --git a/Sources/TAR/Data+Tar.swift b/Sources/TAR/Data+Tar.swift
index 984c6324..8dbfdd3e 100644
--- a/Sources/TAR/Data+Tar.swift
+++ b/Sources/TAR/Data+Tar.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/TAR/LittleEndianByteReader+Tar.swift b/Sources/TAR/LittleEndianByteReader+Tar.swift
index 5f22a051..7b95959c 100644
--- a/Sources/TAR/LittleEndianByteReader+Tar.swift
+++ b/Sources/TAR/LittleEndianByteReader+Tar.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import BitByteData
extension LittleEndianByteReader {
diff --git a/Sources/TAR/TarContainer.swift b/Sources/TAR/TarContainer.swift
index 8c2db113..61fcc593 100644
--- a/Sources/TAR/TarContainer.swift
+++ b/Sources/TAR/TarContainer.swift
@@ -1,10 +1,9 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
import Foundation
-import BitByteData
/// Provides functions for work with TAR containers.
public class TarContainer: Container {
diff --git a/Sources/TAR/TarCreateError.swift b/Sources/TAR/TarCreateError.swift
index 61ead2bc..072f7acf 100644
--- a/Sources/TAR/TarCreateError.swift
+++ b/Sources/TAR/TarCreateError.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/TAR/TarEntry.swift b/Sources/TAR/TarEntry.swift
index 06625758..ea685eb6 100644
--- a/Sources/TAR/TarEntry.swift
+++ b/Sources/TAR/TarEntry.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/TAR/TarEntryInfo.swift b/Sources/TAR/TarEntryInfo.swift
index 18b3ed24..0f4842e6 100644
--- a/Sources/TAR/TarEntryInfo.swift
+++ b/Sources/TAR/TarEntryInfo.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/TAR/TarError.swift b/Sources/TAR/TarError.swift
index 2b9fe91e..271d0afb 100644
--- a/Sources/TAR/TarError.swift
+++ b/Sources/TAR/TarError.swift
@@ -1,10 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
-
/**
Represents an error which happened while processing a TAR container.
It may indicate that either container is damaged or it might not be TAR container at all.
diff --git a/Sources/TAR/TarExtendedHeader.swift b/Sources/TAR/TarExtendedHeader.swift
index aa052c10..1ff9becb 100644
--- a/Sources/TAR/TarExtendedHeader.swift
+++ b/Sources/TAR/TarExtendedHeader.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/TAR/TarHeader.swift b/Sources/TAR/TarHeader.swift
index fb4b660c..95f1f950 100644
--- a/Sources/TAR/TarHeader.swift
+++ b/Sources/TAR/TarHeader.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/TAR/TarParser.swift b/Sources/TAR/TarParser.swift
index 327d3db0..0defb6f6 100644
--- a/Sources/TAR/TarParser.swift
+++ b/Sources/TAR/TarParser.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -6,8 +6,8 @@
import Foundation
import BitByteData
-// While it is tempting to make Provider conform to `IteratorProtocol` and `Sequence` protocols, it is in fact
-// impossible to do so, since `TarHeader.init(...)` is throwing and `IteratorProtocol.next()` cannot be throwing.
+// While it is tempting to make the parser conform to `IteratorProtocol` and `Sequence` protocols, it is in fact
+// impossible to do so, since `TarHeader.init(_)` is throwing and `IteratorProtocol.next()` cannot be throwing.
struct TarParser {
enum ParsingResult {
diff --git a/Sources/TAR/TarReader.swift b/Sources/TAR/TarReader.swift
index 565ce4b7..f961e458 100644
--- a/Sources/TAR/TarReader.swift
+++ b/Sources/TAR/TarReader.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -23,12 +23,6 @@ import BitByteData
try handle.close()
```
Note that closing the `FileHandle` remains the responsibility of the caller.
-
- - Important: Due to the API availability limitations of Foundation's `FileHandle`, on certain platforms errors in
- `FileHandle` operations may result in unrecoverable runtime failures due to unhandled Objective-C exceptions (which are
- impossible to correctly handle in Swift code). As such, it is not recommended to use `TarReader` on those platforms.
- The following platforms are _unaffected_ by this issue: macOS 10.15.4+, iOS 13.4+, watchOS 6.2+, tvOS 13.4+, and any
- other platforms without Objective-C runtime.
*/
public struct TarReader {
@@ -86,7 +80,7 @@ public struct TarReader {
return nil
} else if headerData == Data(count: 512) {
// EOF marker case.
- let offset = try getOffset()
+ let offset = try handle.offset()
if try getData(size: 512) == Data(count: 512) {
return nil
} else {
@@ -94,7 +88,7 @@ public struct TarReader {
// match the EOF marker signature. In practice, this indicates a malformed TAR container, since a
// zero-filled block is not a valid TAR header (and in fact the end result is an error being thrown in
// TarHeader initializer later down the line).
- try set(offset: offset)
+ try handle.seek(toOffset: offset)
}
} else if headerData.count < 512 {
throw DataError.truncated
@@ -106,7 +100,7 @@ public struct TarReader {
// at most 512 bytes.
// Check, just in case, since we use blockStartIndex = -1 when creating TAR containers.
assert(header.blockStartIndex >= 0)
- let dataStartOffset = try getOffset()
+ let dataStartOffset = try handle.offset()
let entryData = try getData(size: header.size)
guard entryData.count == header.size
@@ -125,11 +119,11 @@ public struct TarReader {
case .longName:
longName = LittleEndianByteReader(data: entryData).tarCString(maxLength: header.size)
}
- try set(offset: dataStartOffset + UInt64(truncatingIfNeeded: header.size.roundTo512()))
+ try handle.seek(toOffset: dataStartOffset + UInt64(truncatingIfNeeded: header.size.roundTo512()))
return try read()
} else {
let info = TarEntryInfo(header, lastGlobalExtendedHeader, lastLocalExtendedHeader, longName, longLinkName)
- try set(offset: dataStartOffset + UInt64(truncatingIfNeeded: header.size.roundTo512()))
+ try handle.seek(toOffset: dataStartOffset + UInt64(truncatingIfNeeded: header.size.roundTo512()))
lastLocalExtendedHeader = nil
longName = nil
longLinkName = nil
@@ -144,40 +138,20 @@ public struct TarReader {
}
}
- private func getOffset() throws -> UInt64 {
- if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) {
- return try handle.offset()
- } else {
- return handle.offsetInFile
- }
- }
-
- private func set(offset: UInt64) throws {
- if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) {
- try handle.seek(toOffset: offset)
- } else {
- handle.seek(toFileOffset: offset)
- }
- }
-
+ @inline(__always)
private func getData(size: Int) throws -> Data {
assert(size >= 0, "TarReader.getData(size:): negative size.")
- if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) {
- // The documentation for FileHandle.read(upToCount:) is a bit misleading. This method does "return the data
- // obtained by reading length bytes starting at the current file pointer" even if the requested amount is
- // larger than the available data. What is not clear is when the method returns nil. Apparently, there are
- // (at least) two cases when it happens:
- // - the file pointer is at the EOF regardless of the argument value,
- // - the argument is zero.
- // It is also unclear what happens when the argument is negative (it seems that it reads everything until
- // the EOF), but the assertion above takes care of this. In any case, instead of returning nil we return
- // empty data since both of these situations logically seem equivalent for our purposes. This also allows us
- // to eliminate additional guard-check for the size parameter.
- return try handle.read(upToCount: size) ?? Data()
- } else {
- // Technically, this can throw NSException, but since it is ObjC exception we cannot handle it in Swift.
- return handle.readData(ofLength: size)
- }
+ // The documentation for FileHandle.read(upToCount:) is a bit misleading. This method does "return the data
+ // obtained by reading length bytes starting at the current file pointer" even if the requested amount is
+ // larger than the available data. What is not clear is when the method returns nil. Apparently, there are
+ // (at least) two cases when it happens:
+ // - the file pointer is at the EOF regardless of the argument value,
+ // - the argument is zero.
+ // It is also unclear what happens when the argument is negative (it seems that it reads everything until
+ // the EOF), but the assertion above takes care of this. In any case, instead of returning nil we return
+ // empty data since both of these situations logically seem equivalent for our purposes. This also allows us
+ // to eliminate additional guard-check for the size parameter.
+ return try handle.read(upToCount: size) ?? Data()
}
}
diff --git a/Sources/TAR/TarWriter.swift b/Sources/TAR/TarWriter.swift
index b19d9746..8003a83f 100644
--- a/Sources/TAR/TarWriter.swift
+++ b/Sources/TAR/TarWriter.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -26,12 +26,6 @@ import Foundation
```
Note that `TarWriter.finalize()` must be called after finishing appending entries to the container. In addition,
closing the `FileHandle` remains the responsibility of the caller.
-
- - Important: Due to the API availability limitations of Foundation's `FileHandle`, on certain platforms errors in
- `FileHandle` operations may result in unrecoverable runtime failures due to unhandled Objective-C exceptions (which are
- impossible to correctly handle in Swift code). As such, it is not recommended to use `TarWriter` on those platforms.
- The following platforms are _unaffected_ by this issue: macOS 10.15.4+, iOS 13.4+, watchOS 6.2+, tvOS 13.4+, and any
- other platforms without Objective-C runtime.
*/
public struct TarWriter {
@@ -132,13 +126,8 @@ public struct TarWriter {
}
private func write(_ data: Data) throws {
- if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) {
- try handle.write(contentsOf: data)
- try handle.synchronize()
- } else {
- handle.write(data)
- handle.synchronizeFile()
- }
+ try handle.write(contentsOf: data)
+ try handle.synchronize()
}
}
diff --git a/Sources/XZ/ByteReader+XZ.swift b/Sources/XZ/LittleEndianByteReader+XZ.swift
similarity index 92%
rename from Sources/XZ/ByteReader+XZ.swift
rename to Sources/XZ/LittleEndianByteReader+XZ.swift
index b8de7d96..5c135756 100644
--- a/Sources/XZ/ByteReader+XZ.swift
+++ b/Sources/XZ/LittleEndianByteReader+XZ.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import BitByteData
extension LittleEndianByteReader {
diff --git a/Sources/XZ/Sha256.swift b/Sources/XZ/Sha256.swift
index cca5894a..dc15f39d 100644
--- a/Sources/XZ/Sha256.swift
+++ b/Sources/XZ/Sha256.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/XZ/XZArchive.swift b/Sources/XZ/XZArchive.swift
index bfc275ac..48ccbd97 100644
--- a/Sources/XZ/XZArchive.swift
+++ b/Sources/XZ/XZArchive.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/XZ/XZBlock.swift b/Sources/XZ/XZBlock.swift
index 0c984df7..3fe2f3ce 100644
--- a/Sources/XZ/XZBlock.swift
+++ b/Sources/XZ/XZBlock.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/XZ/XZError.swift b/Sources/XZ/XZError.swift
index 5a6b52a2..8a301a6b 100644
--- a/Sources/XZ/XZError.swift
+++ b/Sources/XZ/XZError.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/XZ/XZStreamHeader.swift b/Sources/XZ/XZStreamHeader.swift
index be5b581a..32d8ca3b 100644
--- a/Sources/XZ/XZStreamHeader.swift
+++ b/Sources/XZ/XZStreamHeader.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import BitByteData
struct XZStreamHeader {
diff --git a/Sources/ZIP/BuiltinExtraFields.swift b/Sources/ZIP/BuiltinExtraFields.swift
index 345af697..70293b27 100644
--- a/Sources/ZIP/BuiltinExtraFields.swift
+++ b/Sources/ZIP/BuiltinExtraFields.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/ZIP/CompressionMethod+Zip.swift b/Sources/ZIP/CompressionMethod+Zip.swift
index bf91ba89..685a5477 100644
--- a/Sources/ZIP/CompressionMethod+Zip.swift
+++ b/Sources/ZIP/CompressionMethod+Zip.swift
@@ -1,10 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
-
extension CompressionMethod {
init(_ compression: UInt16) {
diff --git a/Sources/ZIP/FileSystemType+Zip.swift b/Sources/ZIP/FileSystemType+Zip.swift
index a0e93e83..62caadb7 100644
--- a/Sources/ZIP/FileSystemType+Zip.swift
+++ b/Sources/ZIP/FileSystemType+Zip.swift
@@ -1,10 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
-
extension FileSystemType {
init(_ versionMadeBy: UInt16) {
diff --git a/Sources/ZIP/LittleEndianByteReader+Zip.swift b/Sources/ZIP/LittleEndianByteReader+Zip.swift
index b70fb66a..e5f8b1da 100644
--- a/Sources/ZIP/LittleEndianByteReader+Zip.swift
+++ b/Sources/ZIP/LittleEndianByteReader+Zip.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/ZIP/ZipCentralDirectoryEntry.swift b/Sources/ZIP/ZipCentralDirectoryEntry.swift
index 0a996ed2..4333a038 100644
--- a/Sources/ZIP/ZipCentralDirectoryEntry.swift
+++ b/Sources/ZIP/ZipCentralDirectoryEntry.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import BitByteData
struct ZipCentralDirectoryEntry {
diff --git a/Sources/ZIP/ZipContainer.swift b/Sources/ZIP/ZipContainer.swift
index 8d555b30..92300590 100644
--- a/Sources/ZIP/ZipContainer.swift
+++ b/Sources/ZIP/ZipContainer.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -75,24 +75,16 @@ public class ZipContainer: Container {
bitReader.align()
byteReader.offset = bitReader.offset
case .bzip2:
- #if (!SWCOMPRESSION_POD_ZIP) || (SWCOMPRESSION_POD_ZIP && SWCOMPRESSION_POD_BZ2)
- // BZip2 algorithm uses different bit numbering scheme.
- let bitReader = MsbBitReader(byteReader)
- fileData = try BZip2.decompress(bitReader)
- // Sometimes bitReader is misaligned after BZip2 decompression, so we need to align before getting end
- // index back.
- bitReader.align()
- byteReader.offset = bitReader.offset
- #else
- throw ZipError.compressionNotSupported
- #endif
+ // BZip2 algorithm uses different bit numbering scheme.
+ let bitReader = MsbBitReader(byteReader)
+ fileData = try BZip2.decompress(bitReader)
+ // Sometimes bitReader is misaligned after BZip2 decompression, so we need to align before getting end
+ // index back.
+ bitReader.align()
+ byteReader.offset = bitReader.offset
case .lzma:
- #if (!SWCOMPRESSION_POD_ZIP) || (SWCOMPRESSION_POD_ZIP && SWCOMPRESSION_POD_LZMA)
- byteReader.offset += 4 // Skipping LZMA SDK version and size of properties.
- fileData = try LZMA.decompress(byteReader, LZMAProperties(byteReader), uncompSize.toInt())
- #else
- throw ZipError.compressionNotSupported
- #endif
+ byteReader.offset += 4 // Skipping LZMA SDK version and size of properties.
+ fileData = try LZMA.decompress(byteReader, LZMAProperties(byteReader), uncompSize.toInt())
default:
throw ZipError.compressionNotSupported
}
diff --git a/Sources/ZIP/ZipEndOfCentralDirectory.swift b/Sources/ZIP/ZipEndOfCentralDirectory.swift
index 4a7c135b..f0d6efab 100644
--- a/Sources/ZIP/ZipEndOfCentralDirectory.swift
+++ b/Sources/ZIP/ZipEndOfCentralDirectory.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import BitByteData
struct ZipEndOfCentralDirectory {
diff --git a/Sources/ZIP/ZipEntry.swift b/Sources/ZIP/ZipEntry.swift
index b29cb215..05cb9953 100644
--- a/Sources/ZIP/ZipEntry.swift
+++ b/Sources/ZIP/ZipEntry.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/ZIP/ZipEntryInfo.swift b/Sources/ZIP/ZipEntryInfo.swift
index 7499f1b3..927db60b 100644
--- a/Sources/ZIP/ZipEntryInfo.swift
+++ b/Sources/ZIP/ZipEntryInfo.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/ZIP/ZipEntryInfoHelper.swift b/Sources/ZIP/ZipEntryInfoHelper.swift
index 9d61185d..2e1ce4e1 100644
--- a/Sources/ZIP/ZipEntryInfoHelper.swift
+++ b/Sources/ZIP/ZipEntryInfoHelper.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import BitByteData
/**
diff --git a/Sources/ZIP/ZipError.swift b/Sources/ZIP/ZipError.swift
index be19107f..4d03b807 100644
--- a/Sources/ZIP/ZipError.swift
+++ b/Sources/ZIP/ZipError.swift
@@ -1,10 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
-
/**
Represents an error which happened while processing a ZIP container.
It may indicate that either container is damaged or it might not be ZIP container at all.
diff --git a/Sources/ZIP/ZipExtraField.swift b/Sources/ZIP/ZipExtraField.swift
index b9b4b5cc..ad04bc99 100644
--- a/Sources/ZIP/ZipExtraField.swift
+++ b/Sources/ZIP/ZipExtraField.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/ZIP/ZipLocalHeader.swift b/Sources/ZIP/ZipLocalHeader.swift
index fe93f56e..8c420779 100644
--- a/Sources/ZIP/ZipLocalHeader.swift
+++ b/Sources/ZIP/ZipLocalHeader.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import BitByteData
struct ZipLocalHeader {
diff --git a/Sources/Zlib/ZlibArchive.swift b/Sources/Zlib/ZlibArchive.swift
index 043629be..1ef7d2dc 100644
--- a/Sources/Zlib/ZlibArchive.swift
+++ b/Sources/Zlib/ZlibArchive.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -23,10 +23,6 @@ public class ZlibArchive: Archive {
- Returns: Unarchived data.
*/
public static func unarchive(archive data: Data) throws -> Data {
- // Valid Zlib archive must contain at least 8 bytes of data.
- guard data.count >= 8
- else { throw ZlibError.wrongCompressionMethod }
-
/// Object with input data which supports convenient work with bit shifts.
let bitReader = LsbBitReader(data: data)
@@ -35,6 +31,9 @@ public class ZlibArchive: Archive {
let out = try Deflate.decompress(bitReader)
bitReader.align()
+ guard bitReader.bytesLeft >= 4
+ else { throw ZlibError.wrongAdler32(out) }
+
let adler32 = bitReader.uint32().byteSwapped
guard CheckSums.adler32(out) == adler32
else { throw ZlibError.wrongAdler32(out) }
diff --git a/Sources/Zlib/ZlibError.swift b/Sources/Zlib/ZlibError.swift
index 1f0f5fad..55681a30 100644
--- a/Sources/Zlib/ZlibError.swift
+++ b/Sources/Zlib/ZlibError.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/Zlib/ZlibHeader.swift b/Sources/Zlib/ZlibHeader.swift
index 000a03f8..4a3256a6 100644
--- a/Sources/Zlib/ZlibHeader.swift
+++ b/Sources/Zlib/ZlibHeader.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -80,11 +80,13 @@ public struct ZlibHeader {
else { throw ZlibError.wrongCompressionLevel }
self.compressionLevel = compressionLevel
- guard (UInt(cmf) * 256 + UInt(flags)) % 31 == 0
+ guard (UInt(cmf) << 8 + UInt(flags)) % 31 == 0
else { throw ZlibError.wrongFcheck }
// If preset dictionary is present 4 bytes will be skipped.
if fdict == 1 {
+ guard reader.bytesLeft >= 4
+ else { throw ZlibError.wrongFcheck }
reader.offset += 4
}
}
diff --git a/Sources/swcomp/Archives/BZip2Command.swift b/Sources/swcomp/Archives/BZip2Command.swift
index ab69d86f..5a1daeed 100644
--- a/Sources/swcomp/Archives/BZip2Command.swift
+++ b/Sources/swcomp/Archives/BZip2Command.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -23,12 +23,15 @@ final class BZip2Command: Command {
@Flag("-d", "--decompress", description: "Decompress a BZip2 archive")
var decompress: Bool
+ @Flag("-m", "--multi-decompress", description: "Decompress multiple BZip2 archives concatenated together")
+ var multiDecompress: Bool
+
@Key("-b", "--block-size", description: "Set the block size for compression to a multiple of 100k bytes; possible " +
"values are from '1' (default) to '9'")
var blockSize: BZip2.BlockSize?
var optionGroups: [OptionGroup] {
- return [.exactlyOne($compress, $decompress)]
+ return [.exactlyOne($compress, $decompress, $multiDecompress)]
}
@Param var input: String
@@ -50,6 +53,28 @@ final class BZip2Command: Command {
let fileData = try Data(contentsOf: inputURL, options: .mappedIfSafe)
let decompressedData = try BZip2.decompress(data: fileData)
try decompressedData.write(to: outputURL)
+ } else if multiDecompress {
+ let inputURL = URL(fileURLWithPath: self.input)
+
+ let outputURL: URL
+ if let outputPath = output {
+ outputURL = URL(fileURLWithPath: outputPath)
+ } else if inputURL.pathExtension == "bz2" {
+ outputURL = inputURL.deletingPathExtension()
+ } else {
+ swcompExit(.noOutputPath)
+ }
+
+ let fileData = try Data(contentsOf: inputURL, options: .mappedIfSafe)
+ let output = try BZip2.multiDecompress(data: fileData)
+
+ try "".write(to: outputURL, atomically: true, encoding: .utf8)
+ let handle = try FileHandle(forWritingTo: outputURL)
+ for data in output {
+ try handle.write(contentsOf: data)
+ try handle.synchronize()
+ }
+ try handle.close()
} else if compress {
let inputURL = URL(fileURLWithPath: self.input)
diff --git a/Sources/swcomp/Archives/GZipCommand.swift b/Sources/swcomp/Archives/GZipCommand.swift
index 0fbbdf03..ad8d0a25 100644
--- a/Sources/swcomp/Archives/GZipCommand.swift
+++ b/Sources/swcomp/Archives/GZipCommand.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/swcomp/Archives/LZ4Command.swift b/Sources/swcomp/Archives/LZ4Command.swift
index 6f0c6abb..e9c1f272 100644
--- a/Sources/swcomp/Archives/LZ4Command.swift
+++ b/Sources/swcomp/Archives/LZ4Command.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/swcomp/Archives/LZMACommand.swift b/Sources/swcomp/Archives/LZMACommand.swift
index 08d7821a..22c00170 100644
--- a/Sources/swcomp/Archives/LZMACommand.swift
+++ b/Sources/swcomp/Archives/LZMACommand.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/swcomp/Archives/XZCommand.swift b/Sources/swcomp/Archives/XZCommand.swift
index e0cd6a15..b330cf5c 100644
--- a/Sources/swcomp/Archives/XZCommand.swift
+++ b/Sources/swcomp/Archives/XZCommand.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/swcomp/Benchmarks/Benchmark.swift b/Sources/swcomp/Benchmarks/Benchmark.swift
index 6b4c2c3a..695b9d3c 100644
--- a/Sources/swcomp/Benchmarks/Benchmark.swift
+++ b/Sources/swcomp/Benchmarks/Benchmark.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -9,7 +9,7 @@ protocol Benchmark {
init(_ input: String)
- func warmupIteration()
+ func warmupIteration() -> Double
func measure() -> Double
@@ -23,8 +23,8 @@ extension Benchmark {
return 10
}
- func warmupIteration() {
- _ = measure()
+ func warmupIteration() -> Double {
+ return measure()
}
func format(_ value: Double) -> String {
diff --git a/Sources/swcomp/Benchmarks/BenchmarkGroup.swift b/Sources/swcomp/Benchmarks/BenchmarkGroup.swift
index 4e9707fd..40eb0aa8 100644
--- a/Sources/swcomp/Benchmarks/BenchmarkGroup.swift
+++ b/Sources/swcomp/Benchmarks/BenchmarkGroup.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -14,7 +14,9 @@ final class BenchmarkGroup: CommandGroup {
let children: [Routable] = [
RunBenchmarkCommand(),
- ShowBenchmarkCommand()
+ ShowBenchmarkCommand(),
+ RemoveRunCommand(),
+ ConvertCommand()
]
}
diff --git a/Sources/swcomp/Benchmarks/BenchmarkMetadata.swift b/Sources/swcomp/Benchmarks/BenchmarkMetadata.swift
index c035a361..4f42f057 100644
--- a/Sources/swcomp/Benchmarks/BenchmarkMetadata.swift
+++ b/Sources/swcomp/Benchmarks/BenchmarkMetadata.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/swcomp/Benchmarks/BenchmarkResult.swift b/Sources/swcomp/Benchmarks/BenchmarkResult.swift
index 99d347e0..d26c2902 100644
--- a/Sources/swcomp/Benchmarks/BenchmarkResult.swift
+++ b/Sources/swcomp/Benchmarks/BenchmarkResult.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -12,6 +12,8 @@ struct BenchmarkResult: Codable {
var iterCount: Int
var avg: Double
var std: Double
+ var warmup: Double?
+ var iters: [Double]?
var id: String {
return [self.name, self.input, String(self.iterCount)].joined(separator: "<#>")
diff --git a/Sources/swcomp/Benchmarks/Benchmarks.swift b/Sources/swcomp/Benchmarks/Benchmarks.swift
index 98ab8f51..502bfe06 100644
--- a/Sources/swcomp/Benchmarks/Benchmarks.swift
+++ b/Sources/swcomp/Benchmarks/Benchmarks.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -270,8 +270,6 @@ struct CompRatioDeflate: Benchmark {
}
}
- func warmupIteration() { }
-
func measure() -> Double {
let outputData = Deflate.compress(data: self.data)
guard outputData.count > 0
@@ -325,8 +323,6 @@ struct CompRatioBz2: Benchmark {
}
}
- func warmupIteration() { }
-
func measure() -> Double {
let outputData = BZip2.compress(data: self.data)
guard outputData.count > 0
@@ -380,8 +376,6 @@ struct CompRatioLz4: Benchmark {
}
}
- func warmupIteration() { }
-
func measure() -> Double {
let outputData = LZ4.compress(data: self.data)
guard outputData.count > 0
@@ -436,8 +430,6 @@ struct CompRatioLz4Bd: Benchmark {
}
}
- func warmupIteration() { }
-
func measure() -> Double {
let outputData = LZ4.compress(data: self.data, independentBlocks: false, blockChecksums: false,
contentChecksum: true, contentSize: false, blockSize: 4 * 1024 * 1024, dictionary: nil, dictionaryID: nil)
@@ -594,7 +586,7 @@ struct ReaderTar: Benchmark {
}
}
let timeElapsed = Double(DispatchTime.now().uptimeNanoseconds - startTime) / 1_000_000_000
- try handle.closeCompat()
+ try handle.close()
return self.size / timeElapsed
} catch let error {
swcompExit(.benchmarkCannotMeasure(Self.self, error))
@@ -629,7 +621,7 @@ struct WriterTar: Benchmark {
}
try writer.finalize()
let timeElapsed = Double(DispatchTime.now().uptimeNanoseconds - startTime) / 1_000_000_000
- try handle.closeCompat()
+ try handle.close()
try FileManager.default.removeItem(at: url)
return self.size / timeElapsed
} catch let error {
diff --git a/Sources/swcomp/Benchmarks/ConvertCommand.swift b/Sources/swcomp/Benchmarks/ConvertCommand.swift
new file mode 100644
index 00000000..1781af30
--- /dev/null
+++ b/Sources/swcomp/Benchmarks/ConvertCommand.swift
@@ -0,0 +1,25 @@
+// Copyright (c) 2026 Timofey Solomko
+// Licensed under MIT License
+//
+// See LICENSE for license information
+
+import Foundation
+import SwiftCLI
+
+final class ConvertCommand: Command {
+
+ let name = "convert"
+ let shortDescription = "Converts save file to a new format"
+ let longDescription = "Converts specified with saved benchmark results to a new format"
+
+ @Param var path: String
+
+ func execute() throws {
+ let oldSaveFile = try OldSaveFile.load(from: self.path)
+ let encoder = JSONEncoder()
+ encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
+ let data = try encoder.encode(SaveFile(oldSaveFile))
+ try data.write(to: URL(fileURLWithPath: path))
+ }
+
+}
diff --git a/Sources/swcomp/Benchmarks/OldSaveFile.swift b/Sources/swcomp/Benchmarks/OldSaveFile.swift
new file mode 100644
index 00000000..dbaba7a3
--- /dev/null
+++ b/Sources/swcomp/Benchmarks/OldSaveFile.swift
@@ -0,0 +1,26 @@
+// Copyright (c) 2026 Timofey Solomko
+// Licensed under MIT License
+//
+// See LICENSE for license information
+
+import Foundation
+
+struct OldSaveFile: Codable {
+
+ struct Run: Codable {
+
+ var metadataUUID: UUID
+ var results: [BenchmarkResult]
+
+ }
+
+ var metadatas: [UUID: BenchmarkMetadata]
+ var runs: [Run]
+
+ static func load(from path: String) throws -> OldSaveFile {
+ let decoder = JSONDecoder()
+ let data = try Data(contentsOf: URL(fileURLWithPath: path))
+ return try decoder.decode(OldSaveFile.self, from: data)
+ }
+
+}
diff --git a/Sources/swcomp/Benchmarks/RemoveRunCommand.swift b/Sources/swcomp/Benchmarks/RemoveRunCommand.swift
new file mode 100644
index 00000000..c2d89e04
--- /dev/null
+++ b/Sources/swcomp/Benchmarks/RemoveRunCommand.swift
@@ -0,0 +1,33 @@
+// Copyright (c) 2026 Timofey Solomko
+// Licensed under MIT License
+//
+// See LICENSE for license information
+
+import Foundation
+import SwiftCLI
+
+final class RemoveRunCommand: Command {
+
+ let name = "remove-run"
+ let shortDescription = "Removes run from the file"
+ let longDescription = "Removes a run identified by UUID and any associated results from the specified file"
+
+ @Param var runUUID: String
+ @Param var path: String
+
+ func execute() throws {
+ var saveFile = try SaveFile.load(from: self.path)
+ guard let uuid = UUID(uuidString: self.runUUID)
+ else { swcompExit(.benchmarkBadUUID) }
+ if saveFile.runs.contains(where: { $0.uuid == uuid} ) {
+ saveFile.runs.removeAll(where: { $0.uuid == uuid })
+ let encoder = JSONEncoder()
+ encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
+ let data = try encoder.encode(saveFile)
+ try data.write(to: URL(fileURLWithPath: path))
+ } else {
+ print("WARNING: Specified run UUID is not found in the file. No changes made.")
+ }
+ }
+
+}
diff --git a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift
index ae9c95f5..ffa16220 100644
--- a/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift
+++ b/Sources/swcomp/Benchmarks/RunBenchmarkCommand.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -8,7 +8,6 @@
#endif
import Foundation
-import SWCompression
import SwiftCLI
final class RunBenchmarkCommand: Command {
@@ -32,7 +31,7 @@ final class RunBenchmarkCommand: Command {
@Key("-d", "--description", description: "Adds a custom description when saving results")
var description: String?
- @Flag("-t", "--preserve-timestamp", description: "Adds a timestamp when saving a result")
+ @Flag("-t", "--preserve-timestamp", description: "Adds a timestamp when saving results")
var preserveTimestamp: Bool
@Flag("-W", "--no-warmup", description: "Disables warmup iteration")
@@ -45,26 +44,13 @@ final class RunBenchmarkCommand: Command {
guard self.iterationCount == nil || self.iterationCount! >= 1
else { swcompExit(.benchmarkSmallIterCount) }
- var baseResults = [String: [(BenchmarkResult, UUID)]]()
- var baseMetadatas = [UUID: String]()
+ var baseRuns = [SaveFile.Run]()
if let comparePath = comparePath {
let baseSaveFile = try SaveFile.load(from: comparePath)
-
- baseMetadatas = Dictionary(uniqueKeysWithValues: zip(baseSaveFile.metadatas.keys, (1...baseSaveFile.metadatas.count).map { "(\($0))" }))
- if baseMetadatas.count == 1 {
- baseMetadatas[baseMetadatas.first!.key] = ""
- }
- for (metadataUUID, index) in baseMetadatas.sorted(by: { $0.value < $1.value }) {
- print("BASE\(index) Metadata")
- print("----------------")
- baseSaveFile.metadatas[metadataUUID]!.print()
- }
-
- for baseRun in baseSaveFile.runs {
- baseResults.merge(Dictionary(grouping: baseRun.results.map { ($0, baseRun.metadataUUID) }, by: { $0.0.id }),
- uniquingKeysWith: { $0 + $1 })
- }
+ baseRuns = baseSaveFile.runs
}
+ self.printBaseMetadatas(runs: baseRuns)
+ let baseResults = SaveFile.groupResults(runs: baseRuns)
let title = "\(self.selectedBenchmark.titleName) Benchmark\n"
print(String(repeating: "=", count: title.count))
@@ -77,10 +63,14 @@ final class RunBenchmarkCommand: Command {
let benchmark = self.selectedBenchmark.initialized(input)
let iterationCount = self.iterationCount ?? benchmark.defaultIterationCount
+ let warmup: Double?
if !self.noWarmup {
- print("Warmup iteration...")
+ print("Warmup iteration...", terminator: " ")
// Zeroth (excluded) iteration.
- benchmark.warmupIteration()
+ warmup = benchmark.warmupIteration()
+ print(benchmark.format(warmup!))
+ } else {
+ warmup = nil
}
var sum = 0.0
@@ -90,6 +80,7 @@ final class RunBenchmarkCommand: Command {
#if !os(Linux)
fflush(__stdoutp)
#endif
+ var iterations = [Double]()
for i in 1...iterationCount {
if i > 1 {
print(", ", terminator: "")
@@ -101,18 +92,23 @@ final class RunBenchmarkCommand: Command {
#endif
sum += speed
squareSum += speed * speed
+ iterations.append(speed)
}
let avg = sum / Double(iterationCount)
let std = sqrt(squareSum / Double(iterationCount) - sum * sum / Double(iterationCount * iterationCount))
let result = BenchmarkResult(name: self.selectedBenchmark.rawValue, input: input, iterCount: iterationCount,
- avg: avg, std: std)
+ avg: avg, std: std, warmup: warmup, iters: iterations)
if let baseResults = baseResults[result.id] {
print("\nNEW: average = \(benchmark.format(avg)), standard deviation = \(benchmark.format(std))")
- for (other, baseUUID) in baseResults {
- print("BASE\(baseMetadatas[baseUUID]!): average = \(benchmark.format(other.avg)), standard deviation = \(benchmark.format(other.std))")
- result.printComparison(with: other)
+ for (baseIndex, baseResult) in baseResults {
+ if let baseWarmup = baseResult.warmup {
+ print("BASE(\(baseIndex + 1)): average = \(benchmark.format(baseResult.avg)), standard deviation = \(benchmark.format(baseResult.std)), warmup = \(benchmark.format(baseWarmup))")
+ } else {
+ print("BASE(\(baseIndex + 1)): average = \(benchmark.format(baseResult.avg)), standard deviation = \(benchmark.format(baseResult.std))")
+ }
+ result.printComparison(with: baseResult)
}
} else {
print("\nAverage = \(benchmark.format(avg)), standard deviation = \(benchmark.format(std))")
@@ -129,32 +125,41 @@ final class RunBenchmarkCommand: Command {
var isDir = ObjCBool(false)
let saveFileExists = FileManager.default.fileExists(atPath: savePath, isDirectory: &isDir)
- if self.append && saveFileExists {
+ if self.append && saveFileExists {
if isDir.boolValue {
swcompExit(.benchmarkCannotAppendToDirectory)
}
saveFile = try SaveFile.load(from: savePath)
- var uuid: UUID
- if let foundUUID = saveFile.metadatas.first(where: { $0.value == metadata })?.key {
- uuid = foundUUID
+ if let foundRunIndex = saveFile.runs.firstIndex(where: { $0.metadata == metadata }) {
+ var foundRun = saveFile.runs[foundRunIndex]
+ foundRun.results.append(contentsOf: newResults)
+ foundRun.results.sort(by: { $0.id < $1.id })
+ saveFile.runs[foundRunIndex] = foundRun
} else {
+ var uuid: UUID
repeat {
uuid = UUID()
- } while saveFile.metadatas[uuid] != nil
- saveFile.metadatas[uuid] = metadata
+ } while saveFile.runs.contains(where: { $0.uuid == uuid })
+ saveFile.runs.append(SaveFile.Run(uuid: uuid, metadata: metadata, results: newResults.sorted(by: { $0.id < $1.id })))
}
- saveFile.runs.append(SaveFile.Run(metadataUUID: uuid, results: newResults))
} else {
let uuid = UUID()
- saveFile = SaveFile(metadatas: [uuid: metadata], runs: [SaveFile.Run(metadataUUID: uuid, results: newResults)])
+ saveFile = SaveFile(runs: [SaveFile.Run(uuid: uuid, metadata: metadata, results: newResults.sorted(by: { $0.id < $1.id }))])
}
let encoder = JSONEncoder()
- encoder.outputFormatting = .prettyPrinted
-
+ encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
let data = try encoder.encode(saveFile)
try data.write(to: URL(fileURLWithPath: savePath))
}
}
+ private func printBaseMetadatas(runs: [SaveFile.Run]) {
+ for (index, run) in runs.enumerated() {
+ print("BASE(\(index + 1)) Metadata")
+ print("---------------")
+ run.metadata.print()
+ }
+ }
+
}
diff --git a/Sources/swcomp/Benchmarks/SaveFile.swift b/Sources/swcomp/Benchmarks/SaveFile.swift
index 6dd7a434..2226d052 100644
--- a/Sources/swcomp/Benchmarks/SaveFile.swift
+++ b/Sources/swcomp/Benchmarks/SaveFile.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -9,19 +9,63 @@ struct SaveFile: Codable {
struct Run: Codable {
- var metadataUUID: UUID
+ var uuid: UUID
+ var metadata: BenchmarkMetadata
var results: [BenchmarkResult]
}
- var metadatas: [UUID: BenchmarkMetadata]
-
+ var formatVersion = 2
var runs: [Run]
+ init(_ oldSaveFile: OldSaveFile) {
+ var d = [UUID: [BenchmarkResult]]()
+ for run in oldSaveFile.runs {
+ d[run.metadataUUID] = (d[run.metadataUUID] ?? [BenchmarkResult]()) + run.results
+ }
+
+ self.runs = [Run]()
+ for (uuid, results) in d {
+ guard let metadata = oldSaveFile.metadatas[uuid]
+ else { swcompExit(.benchmarkOldFormatNoUUIDMetadata(uuid)) }
+ self.runs.append(Run(uuid: uuid, metadata: metadata, results: results.sorted(by: { $0.id < $1.id })))
+ }
+ }
+
+ init(runs: [SaveFile.Run]) {
+ self.runs = runs
+ }
+
static func load(from path: String) throws -> SaveFile {
- let decoder = JSONDecoder()
let data = try Data(contentsOf: URL(fileURLWithPath: path))
- return try decoder.decode(SaveFile.self, from: data)
+ guard let generalDict = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
+ else { swcompExit(.benchmarkUnrecognizedSaveFile) }
+ if let formatVersion = generalDict["formatVersion"] {
+ guard let intFormatVersion = formatVersion as? Int
+ else { swcompExit(.benchmarkUnrecognizedFormatVersion) }
+ guard intFormatVersion == 2
+ else { swcompExit(.benchmarkUnsupportedFormatVersion(intFormatVersion)) }
+ let decoder = JSONDecoder()
+ return try decoder.decode(SaveFile.self, from: data)
+ } else if generalDict["metadatas"] != nil && generalDict["runs"] != nil {
+ let decoder = JSONDecoder()
+ let oldSaveFile = try decoder.decode(OldSaveFile.self, from: data)
+ print("WARNING: Old save file format detected. Its support will be removed in the future. Use \'benchmark convert' to upgrade.")
+ return SaveFile(oldSaveFile)
+ } else {
+ swcompExit(.benchmarkUnrecognizedSaveFile)
+ }
+ }
+
+ static func groupResults(runs: [SaveFile.Run]) -> [String: [(Int, BenchmarkResult)]] {
+ var groupedResults = [String: [(Int, BenchmarkResult)]]()
+ for (index, run) in runs.enumerated() {
+ for result in run.results {
+ let resultId = result.id
+ groupedResults[resultId] = (groupedResults[resultId] ?? Array()) + [(index, result)]
+ }
+ }
+ return groupedResults
}
}
diff --git a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift
index bf023699..473e2677 100644
--- a/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift
+++ b/Sources/swcomp/Benchmarks/ShowBenchmarkCommand.swift
@@ -1,12 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-#if os(Linux)
- import CoreFoundation
-#endif
-
import Foundation
import SwiftCLI
@@ -18,67 +14,95 @@ final class ShowBenchmarkCommand: Command {
@Key("-c", "--compare", description: "Compare with other saved benchmarks results")
var comparePath: String?
+ @Key("--self-compare", description: "Compare runs within the same file with new results identified by run UUID")
+ var selfCompare: String?
+
+ @Flag("--print-uuid", description: "Prints internal UUIDs of saved benchmark runs")
+ var printUuid: Bool
+
+ @Flag("--metadata-only", description: "Prints only metadata of saved benchmark runs")
+ var metadataOnly: Bool
+
@Param var path: String
+ var optionGroups: [OptionGroup] {
+ return [.atMostOne($comparePath, $selfCompare)]
+ }
+
func execute() throws {
let newSaveFile = try SaveFile.load(from: self.path)
- var newMetadatas = Dictionary(uniqueKeysWithValues: zip(newSaveFile.metadatas.keys, (1...newSaveFile.metadatas.count).map { "(\($0))" }))
- if newMetadatas.count == 1 {
- newMetadatas[newMetadatas.first!.key] = ""
- }
- for (metadataUUID, index) in newMetadatas.sorted(by: { $0.value < $1.value }) {
- print("NEW\(index) Metadata")
- print("---------------")
- newSaveFile.metadatas[metadataUUID]!.print()
+ var newRuns: [SaveFile.Run]
+ if let newUUIDString = self.selfCompare {
+ guard let newUUID = UUID(uuidString: newUUIDString)
+ else { swcompExit(.benchmarkBadUUID) }
+ guard let newRun = newSaveFile.runs.first(where: { $0.uuid == newUUID} )
+ else { swcompExit(.benchmarkNoUUID) }
+ newRuns = [newRun]
+ } else {
+ newRuns = newSaveFile.runs
}
+ self.printMetadatas(runs: newRuns, name: "NEW")
- var newResults = [String: [(BenchmarkResult, UUID)]]()
- for newRun in newSaveFile.runs {
- newResults.merge(Dictionary(grouping: newRun.results.map { ($0, newRun.metadataUUID) }, by: { $0.0.id }),
- uniquingKeysWith: { $0 + $1 })
- }
-
- var baseResults = [String: [(BenchmarkResult, UUID)]]()
- var baseMetadatas = [UUID: String]()
+ var baseRuns = [SaveFile.Run]()
if let comparePath = comparePath {
let baseSaveFile = try SaveFile.load(from: comparePath)
-
- baseMetadatas = Dictionary(uniqueKeysWithValues: zip(baseSaveFile.metadatas.keys, (1...baseSaveFile.metadatas.count).map { "(\($0))" }))
- if baseMetadatas.count == 1 {
- baseMetadatas[baseMetadatas.first!.key] = ""
- }
- for (metadataUUID, index) in baseMetadatas.sorted(by: { $0.value < $1.value }) {
- print("BASE\(index) Metadata")
- print("----------------")
- baseSaveFile.metadatas[metadataUUID]!.print()
- }
-
- for baseRun in baseSaveFile.runs {
- baseResults.merge(Dictionary(grouping: baseRun.results.map { ($0, baseRun.metadataUUID) }, by: { $0.0.id }),
- uniquingKeysWith: { $0 + $1 })
- }
+ baseRuns = baseSaveFile.runs
+ } else if let newUUIDString = self.selfCompare {
+ // No need to double-check validity of input UUID. It was done already when loading the "new" run.
+ let newUUID = UUID(uuidString: newUUIDString)!
+ baseRuns = newSaveFile.runs.filter({ $0.uuid != newUUID })
}
+ self.printMetadatas(runs: baseRuns, name: "BASE")
- for resultId in newResults.keys.sorted() {
- let results = newResults[resultId]!
- for (result, metadataUUID) in results {
+ guard !self.metadataOnly
+ else { return }
+
+ // There might be results for the same benchmark-input-iterCount tuple in different runs. We want to print those
+ // results together, so we have to group them based on their `id`.
+ let newResults = SaveFile.groupResults(runs: newRuns)
+ let baseResults = SaveFile.groupResults(runs: baseRuns)
+
+ // Even though we try to sort results before writing a save file, after grouping they can appear in `newResults`
+ // in essentially arbitrary order.
+ for id in newResults.keys.sorted() {
+ let results = newResults[id]!
+ print()
+ print("----------------")
+ print()
+ print("\(results.first!.1.name) => \(results.first!.1.input), iterations = \(results.first!.1.iterCount)")
+ print()
+ for (index, result) in results {
let benchmark = Benchmarks(rawValue: result.name)?.initialized(result.input)
-
- print("\(result.name) => \(result.input), iterations = \(result.iterCount)")
-
- print("NEW\(newMetadatas[metadataUUID]!): average = \(benchmark.format(result.avg)), standard deviation = \(benchmark.format(result.std))")
- if let baseResults = baseResults[resultId] {
- for (other, baseUUID) in baseResults {
- print("BASE\(baseMetadatas[baseUUID]!): average = \(benchmark.format(other.avg)), standard deviation = \(benchmark.format(other.std))")
- result.printComparison(with: other)
+ if let warmup = result.warmup {
+ print("NEW(\(index + 1)): average = \(benchmark.format(result.avg)), standard deviation = \(benchmark.format(result.std)), warmup = \(benchmark.format(warmup))")
+ } else {
+ print("NEW(\(index + 1)): average = \(benchmark.format(result.avg)), standard deviation = \(benchmark.format(result.std))")
+ }
+ if let baseResults = baseResults[id] {
+ for (baseIndex, baseResult) in baseResults {
+ if let baseWarmup = baseResult.warmup {
+ print("BASE(\(baseIndex + 1)): average = \(benchmark.format(baseResult.avg)), standard deviation = \(benchmark.format(baseResult.std)), warmup = \(benchmark.format(baseWarmup))")
+ } else {
+ print("BASE(\(baseIndex + 1)): average = \(benchmark.format(baseResult.avg)), standard deviation = \(benchmark.format(baseResult.std))")
+ }
+ result.printComparison(with: baseResult)
}
}
-
- print()
}
}
}
+ private func printMetadatas(runs: [SaveFile.Run], name: String) {
+ for (index, run) in runs.enumerated() {
+ print("\(name)(\(index + 1)) Metadata")
+ print("---------------")
+ if self.printUuid {
+ print("UUID: \(run.uuid)")
+ }
+ run.metadata.print()
+ }
+ }
+
}
fileprivate extension Optional where Wrapped == Benchmark {
diff --git a/Sources/swcomp/Benchmarks/SpeedFormatter.swift b/Sources/swcomp/Benchmarks/SpeedFormatter.swift
index 8a2e9ad7..3f496601 100644
--- a/Sources/swcomp/Benchmarks/SpeedFormatter.swift
+++ b/Sources/swcomp/Benchmarks/SpeedFormatter.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/swcomp/Containers/7ZipCommand.swift b/Sources/swcomp/Containers/7ZipCommand.swift
index 3fdb8875..b4015615 100644
--- a/Sources/swcomp/Containers/7ZipCommand.swift
+++ b/Sources/swcomp/Containers/7ZipCommand.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/swcomp/Containers/CommonFunctions.swift b/Sources/swcomp/Containers/CommonFunctions.swift
index a5b9bd8c..89db6a9d 100644
--- a/Sources/swcomp/Containers/CommonFunctions.swift
+++ b/Sources/swcomp/Containers/CommonFunctions.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -147,4 +147,4 @@ func writeFile(_ entry: T, _ outputURL: URL, _ verbose: Bool)
}
try fileManager.setAttributes(attributes, ofItemAtPath: entryFullURL.path)
-}
\ No newline at end of file
+}
diff --git a/Sources/swcomp/Containers/ContainerCommand.swift b/Sources/swcomp/Containers/ContainerCommand.swift
index d2a074a7..3fc09788 100644
--- a/Sources/swcomp/Containers/ContainerCommand.swift
+++ b/Sources/swcomp/Containers/ContainerCommand.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/swcomp/Containers/TarCommand.swift b/Sources/swcomp/Containers/TarCommand.swift
index a01b6645..b706c83c 100644
--- a/Sources/swcomp/Containers/TarCommand.swift
+++ b/Sources/swcomp/Containers/TarCommand.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -119,7 +119,7 @@ final class TarCommand: Command {
return false
}
}
- try handle.closeCompat()
+ try handle.close()
}
} else if let outputPath = self.extract {
guard try isValidOutputDirectory(outputPath, create: true)
@@ -167,7 +167,7 @@ final class TarCommand: Command {
return false
}
}
- try handle.closeCompat()
+ try handle.close()
for tuple in directoryAttributes {
try fileManager.setAttributes(tuple.attributes, ofItemAtPath: tuple.path)
@@ -222,7 +222,7 @@ final class TarCommand: Command {
var writer = TarWriter(fileHandle: handle, force: self.useFormat ?? .pax)
try TarEntry.generateEntries(&writer, inputPath, verbose)
try writer.finalize()
- try handle.closeCompat()
+ try handle.close()
}
}
}
diff --git a/Sources/swcomp/Containers/ZipCommand.swift b/Sources/swcomp/Containers/ZipCommand.swift
index ddf19124..7d685ca6 100644
--- a/Sources/swcomp/Containers/ZipCommand.swift
+++ b/Sources/swcomp/Containers/ZipCommand.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/swcomp/Extensions/CompressionMethod+CustomStringConvertible.swift b/Sources/swcomp/Extensions/CompressionMethod+CustomStringConvertible.swift
index d0c01536..5a3f66d3 100644
--- a/Sources/swcomp/Extensions/CompressionMethod+CustomStringConvertible.swift
+++ b/Sources/swcomp/Extensions/CompressionMethod+CustomStringConvertible.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import SWCompression
extension CompressionMethod: CustomStringConvertible {
diff --git a/Sources/swcomp/Extensions/ContainerEntryInfo+CustomStringConvertible.swift b/Sources/swcomp/Extensions/ContainerEntryInfo+CustomStringConvertible.swift
index 345f27b0..e885adad 100644
--- a/Sources/swcomp/Extensions/ContainerEntryInfo+CustomStringConvertible.swift
+++ b/Sources/swcomp/Extensions/ContainerEntryInfo+CustomStringConvertible.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import SWCompression
extension ContainerEntryInfo where Self: CustomStringConvertible {
diff --git a/Sources/swcomp/Extensions/FileHandle+CloseCompat.swift b/Sources/swcomp/Extensions/FileHandle+CloseCompat.swift
deleted file mode 100644
index 941930f8..00000000
--- a/Sources/swcomp/Extensions/FileHandle+CloseCompat.swift
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2024 Timofey Solomko
-// Licensed under MIT License
-//
-// See LICENSE for license informations
-
-import Foundation
-
-extension FileHandle {
-
- func closeCompat() throws {
- #if compiler(<5.2)
- self.closeFile()
- #else
- if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) {
- try self.close()
- } else {
- self.closeFile()
- }
- #endif
- }
-
-}
diff --git a/Sources/swcomp/Extensions/FileSystemType+CustomStringConvertible.swift b/Sources/swcomp/Extensions/FileSystemType+CustomStringConvertible.swift
index 4a2cb0d0..6c0f40c8 100644
--- a/Sources/swcomp/Extensions/FileSystemType+CustomStringConvertible.swift
+++ b/Sources/swcomp/Extensions/FileSystemType+CustomStringConvertible.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import SWCompression
extension FileSystemType: CustomStringConvertible {
diff --git a/Sources/swcomp/Extensions/GzipHeader+CustomStringConvertible.swift b/Sources/swcomp/Extensions/GzipHeader+CustomStringConvertible.swift
index a4631594..a0cc5fa2 100644
--- a/Sources/swcomp/Extensions/GzipHeader+CustomStringConvertible.swift
+++ b/Sources/swcomp/Extensions/GzipHeader+CustomStringConvertible.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import SWCompression
extension GzipHeader: CustomStringConvertible {
diff --git a/Sources/swcomp/Extensions/TarEntry+Create.swift b/Sources/swcomp/Extensions/TarEntry+Create.swift
index 664725d7..5d0effa2 100644
--- a/Sources/swcomp/Extensions/TarEntry+Create.swift
+++ b/Sources/swcomp/Extensions/TarEntry+Create.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Sources/swcomp/Extensions/TarFormat+ConvertibleFromString.swift b/Sources/swcomp/Extensions/TarFormat+ConvertibleFromString.swift
index 436361f2..5d1e9427 100644
--- a/Sources/swcomp/Extensions/TarFormat+ConvertibleFromString.swift
+++ b/Sources/swcomp/Extensions/TarFormat+ConvertibleFromString.swift
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
-import Foundation
import SwiftCLI
import SWCompression
diff --git a/Sources/swcomp/SwcompError.swift b/Sources/swcomp/SwcompError.swift
index 5a88f023..33b740d6 100644
--- a/Sources/swcomp/SwcompError.swift
+++ b/Sources/swcomp/SwcompError.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -19,6 +19,12 @@ enum SwcompError {
case benchmarkReaderTarNoInputSize(String)
case benchmarkCannotGetSubcommandPathWindows
case benchmarkCannotAppendToDirectory
+ case benchmarkBadUUID
+ case benchmarkNoUUID
+ case benchmarkUnrecognizedSaveFile
+ case benchmarkUnrecognizedFormatVersion
+ case benchmarkUnsupportedFormatVersion(Int)
+ case benchmarkOldFormatNoUUIDMetadata(UUID)
case containerSymLinkDestPath(String)
case containerHardLinkDestPath(String)
case containerNoEntryData(String)
@@ -54,6 +60,18 @@ enum SwcompError {
return 206
case .benchmarkCannotAppendToDirectory:
return 207
+ case .benchmarkBadUUID:
+ return 208
+ case .benchmarkNoUUID:
+ return 218
+ case .benchmarkUnrecognizedSaveFile:
+ return 209
+ case .benchmarkUnrecognizedFormatVersion:
+ return 219
+ case .benchmarkUnsupportedFormatVersion:
+ return 229
+ case .benchmarkOldFormatNoUUIDMetadata:
+ return 239
case .containerSymLinkDestPath:
return 301
case .containerHardLinkDestPath:
@@ -99,6 +117,18 @@ enum SwcompError {
return "Cannot get subcommand path on Windows. (This error should never be shown!)"
case .benchmarkCannotAppendToDirectory:
return "Cannot append results to the save path since it is a directory."
+ case .benchmarkBadUUID:
+ return "Specified run UUID is not well-formed."
+ case .benchmarkNoUUID:
+ return "Specified run UUID is not found in the file."
+ case .benchmarkUnrecognizedSaveFile:
+ return "The save file format is not recognized."
+ case .benchmarkUnrecognizedFormatVersion:
+ return "The save file format version is not recognized."
+ case .benchmarkUnsupportedFormatVersion(let formatVersion):
+ return "The save file format version \(formatVersion) is not supported."
+ case .benchmarkOldFormatNoUUIDMetadata(let uuid):
+ return "No metadata found in an old format save file for UUID = \(uuid)."
case .containerSymLinkDestPath(let entryName):
return "Unable to get destination path for symbolic link \(entryName)."
case .containerHardLinkDestPath(let entryName):
diff --git a/Sources/swcomp/main.swift b/Sources/swcomp/main.swift
index 31dfabed..71edf6d1 100644
--- a/Sources/swcomp/main.swift
+++ b/Sources/swcomp/main.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -7,7 +7,7 @@ import Foundation
import SWCompression
import SwiftCLI
-let _SWC_VERSION = "4.8.7"
+let _SWC_VERSION = "4.9.0"
let cli = CLI(name: "swcomp", version: _SWC_VERSION,
description: """
diff --git a/Tests/BZip2CompressionTests.swift b/Tests/BZip2CompressionTests.swift
index 4b127a01..41a8fe4d 100644
--- a/Tests/BZip2CompressionTests.swift
+++ b/Tests/BZip2CompressionTests.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Tests/BZip2Tests.swift b/Tests/BZip2Tests.swift
index c03fdd2c..6d59f7ba 100644
--- a/Tests/BZip2Tests.swift
+++ b/Tests/BZip2Tests.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -96,4 +96,54 @@ class BZip2Tests: XCTestCase {
}
}
+ func testBZip2Truncation() throws {
+ for i in 1...9 {
+ let testName = "test\(i)"
+ let testData = try Constants.data(forTest: testName, withType: BZip2Tests.testType)
+ // It would be better to increase amount of different truncations tested, but we hit runtime limits in CI.
+ for _ in 0..<5 {
+ let truncationIndex = Int.random(in: (testData.startIndex + 1).. Data {
let url = Constants.url(forTest: name, withType: ext)
- return try Data(contentsOf: url, options: .mappedIfSafe)
+ return try Data(contentsOf: url)
}
static func handle(forTest name: String, withType ext: String) throws -> FileHandle {
@@ -35,7 +35,7 @@ class Constants {
static func data(forAnswer name: String) throws -> Data {
let url = Constants.url(forAnswer: name)
- return try Data(contentsOf: url, options: .mappedIfSafe)
+ return try Data(contentsOf: url)
}
private static func url(forAnswer name: String) -> URL {
diff --git a/Tests/DeflateCompressionTests.swift b/Tests/DeflateCompressionTests.swift
index de9e1016..1b143d33 100644
--- a/Tests/DeflateCompressionTests.swift
+++ b/Tests/DeflateCompressionTests.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Tests/DeflateTests.swift b/Tests/DeflateTests.swift
new file mode 100644
index 00000000..6878ab3b
--- /dev/null
+++ b/Tests/DeflateTests.swift
@@ -0,0 +1,196 @@
+// Copyright (c) 2026 Timofey Solomko
+// Licensed under MIT License
+//
+// See LICENSE for license information
+
+import XCTest
+import BitByteData
+import SWCompression
+
+class DeflateTests: XCTestCase {
+
+ private static let testType: String = "deflate"
+
+ func testTruncation() throws {
+ // In this test we check that there is no crash when dealing with the truncation in the middle of the Deflate
+ // compressed data. The idea is to take three different types of Deflate blocks (uncompressed, static Huffman,
+ // and dynamic Huffman), truncate the input data manually at a random point inside it, and then test if an
+ // appropriate error is thrown. To make test a bit more sophisticated we generate a number of random truncations
+ // for each tested file.
+
+ // test6 contains dynamic Huffman Deflate block.
+ // test8 contains static Huffman Deflate block.
+ // test9 contains uncompressed Deflate block.
+
+ for testName in ["test6", "test8", "test9"] {
+ let testData = try Constants.data(forTest: testName, withType: DeflateTests.testType)
+ for _ in 0..<10 {
+ let truncationIndex = Int.random(in: (testData.startIndex + 1)..version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3|
|:---:|:---:|:---:|:---:|---|---|---|
-|4.8.0|2.0.1|12.1|5.5.2|2.366 ± 0.092 MB/s|2.569 ± 0.113 MB/s|2.106 ± 0.088 MB/s|
-|4.7.0|2.0.1|12.0.1|5.5.1|2.552 ± 0.080 MB/s|2.894 ± 0.052 MB/s|2.391 ± 0.062 MB/s|
-|4.6.0|2.0.1|11.5.2|5.4.2|2.764 ± 0.037 MB/s|2.973 ± 0.088 MB/s|2.410 ± 0.088 MB/s|
-|4.6.0-unchecked|2.0.1|11.5.2|5.4.2|3.279 ± 0.429 MB/s|4.361 ± 0.306 MB/s|3.169 ± 0.180 MB/s|
+|4.8.6|2.0.4|26.2|6.2.3|5.225 ± 0.032 MB/s|5.997 ± 0.009 MB/s|5.172 ± 0.021 MB/s|
+|4.9.0|2.1.0|26.5|6.3.2|5.495 ± 0.277 MB/s|6.538 ± 0.093 MB/s|5.455 ± 0.138 MB/s|
## XZ Unarchive (LZMA/LZMA2 Decompress)
|SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3|
|:---:|:---:|:---:|:---:|---|---|---|
-|4.8.0|2.0.1|12.1|5.5.2|5.006 ± 0.304 MB/s|5.359 ± 0.130 MB/s|5.095 ± 0.220 MB/s|
-|4.7.0|2.0.1|12.0.1|5.5.1|5.210 ± 0.198 MB/s|5.436 ± 0.124 MB/s|5.224 ± 0.176 MB/s|
-|4.6.0|2.0.1|11.5.2|5.4.2|5.246 ± 0.279 MB/s|5.543 ± 0.143 MB/s|5.322 ± 0.260 MB/s|
-|4.6.0-unchecked|2.0.1|11.5.2|5.4.2|6.015 ± 0.319 MB/s|6.374 ± 0.143 MB/s|5.841 ± 0.212 MB/s|
+|4.8.6|2.0.4|26.2|6.2.3|13.722 ± 0.042 MB/s|14.311 ± 0.023 MB/s|13.447 ± 0.162 MB/s|
+|4.9.0|2.1.0|26.5|6.3.2|12.712 ± 0.888 MB/s|14.273 ± 0.313 MB/s|14.009 ± 0.047 MB/s|
## GZip Unarchive (Deflate Decompress)
|SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3|
|:---:|:---:|:---:|:---:|---|---|---|
-|4.8.0|2.0.1|12.1|5.5.2|4.982 ± 0.128 MB/s|4.994 ± 0.122 MB/s|4.899 ± 0.326 MB/s|
-|4.7.0|2.0.1|12.0.1|5.5.1|5.912 ± 0.232 MB/s|5.997 ± 0.100 MB/s|6.004 ± 0.259 MB/s|
-|4.6.0|2.0.1|11.5.2|5.4.2|5.946 ± 0.432 MB/s|6.034 ± 0.175 MB/s|6.071 ± 0.250 MB/s|
-|4.6.0-unchecked|2.0.1|11.5.2|5.4.2|8.562 ± 0.388 MB/s|9.908 ± 0.204 MB/s|8.286 ± 0.250 MB/s|
+|4.8.6|2.0.4|26.2|6.2.3|11.899 ± 0.067 MB/s|11.487 ± 0.078 MB/s|11.806 ± 0.094 MB/s|
+|4.9.0|2.1.0|26.5|6.3.2|13.570 ± 0.015 MB/s|13.392 ± 0.014 MB/s|13.373 ± 0.068 MB/s|
## LZ4 Decompress
@@ -76,33 +72,29 @@ this setting may significantly affect performance.
|SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3|
|:---:|:---:|:---:|:---:|---|---|---|
-|4.8.0|2.0.1|12.1|5.5.2|17.079 ± 0.943 MB/s|22.253 ± 0.872 MB/s|16.650 ± 0.406 MB/s|
-|4.7.0|2.0.1|12.0.1|5.5.1|17.496 ± 0.817 MB/s|22.480 ± 0.524 MB/s|16.751 ± 0.902 MB/s|
+|4.8.6|2.0.4|26.2|6.2.3|54.521 ± 1.030 MB/s|70.316 ± 0.602 MB/s|50.517 ± 0.515 MB/s|
+|4.9.0|2.1.0|26.5|6.3.2|54.635 ± 0.315 MB/s|70.268 ± 0.562 MB/s|51.792 ± 0.396 MB/s|
### Dependent blocks
|SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3|
|:---:|:---:|:---:|:---:|---|---|---|
-|4.8.0|2.0.1|12.1|5.5.2|17.235 ± 0.720 MB/s|21.056 ± 0.884 MB/s|15.950 ± 0.773 MB/s|
-|4.7.0|2.0.1|12.0.1|5.5.1|17.065 ± 1.109 MB/s|22.508 ± 0.568 MB/s|16.735 ± 0.605 MB/s|
-
-## BZip2 Compress
-
-|SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3|
-|:---:|:---:|:---:|:---:|---|---|---|
-|4.8.0|2.0.1|12.1|5.5.2|3.450 ± 0.088 MB/s (3.493)|2.793 ± 0.034 MB/s (2.635)|4.182 ± 0.084 MB/s (3.875)|
-|4.7.0|2.0.1|12.0.1|5.5.1|3.466 ± 0.048 MB/s (3.493)|2.767 ± 0.013 MB/s (2.635)|4.077 ± 0.069 MB/s (3.875)|
-|4.6.0|2.0.1|11.5.2|5.4.2|3.540 ± 0.055 MB/s|2.862 ± 0.013 MB/s|4.253 ± 0.077 MB/s|
-|4.6.0-unchecked|2.0.1|11.5.2|5.4.2|3.816 ± 0.078 MB/s|3.060 ± 0.028 MB/s|4.647 ± 0.085 MB/s|
+|4.8.6|2.0.4|26.2|6.2.3|52.712 ± 0.644 MB/s|69.514 ± 0.935 MB/s|49.180 ± 0.818 MB/s|
+|4.9.0|2.1.0|26.5|6.3.2|53.845 ± 0.799 MB/s|70.483 ± 0.431 MB/s|51.268 ± 0.401 MB/s|
## Deflate Compress
|SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3|
|:---:|:---:|:---:|:---:|---|---|---|
-|4.8.0|2.0.1|12.1|5.5.2|15.354 ± 0.330 MB/s (2.540)|11.420 ± 0.138 MB/s (2.266)|17.306 ± 0.614 MB/s (2.831)|
-|4.7.0|2.0.1|12.0.1|5.5.1|15.703 ± 0.538 MB/s (2.540)|11.592 ± 0.219 MB/s (2.266)|17.547 ± 0.731 MB/s (2.831)|
-|4.6.0|2.0.1|11.5.2|5.4.2|12.177 ± 0.259 MB/s|8.809 ± 0.088 MB/s|13.594 ± 0.355 MB/s|
-|4.6.0-unchecked|2.0.1|11.5.2|5.4.2|12.914 ± 0.355 MB/s|9.361 ± 0.078 MB/s|15.020 ± 0.277 MB/s|
+|4.8.6|2.0.4|26.2|6.2.3|36.484 ± 0.519 MB/s (2.540)|28.093 ± 0.478 MB/s (2.266)|39.824 ± 1.121 MB/s (2.831)|
+|4.9.0|2.1.0|26.5|6.3.2|66.732 ± 0.256 MB/s (2.540)|46.724 ± 1.092 MB/s (2.266)|73.479 ± 0.286 MB/s (2.831)|
+
+## BZip2 Compress
+
+|SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3|
+|:---:|:---:|:---:|:---:|---|---|---|
+|4.8.6|2.0.4|26.2|6.2.3|8.133 ± 0.146 MB/s (3.493)|5.878 ± 0.031 MB/s (2.635)|9.690 ± 0.098 MB/s (3.875)|
+|4.9.0|2.1.0|26.5|6.3.2|9.235 ± 0.162 MB/s (3.493)|6.594 ± 0.017 MB/s (2.635)|11.074 ± 0.009 MB/s (3.875)|
## LZ4 Compress
@@ -113,60 +105,54 @@ this setting may significantly affect performance.
|SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3|
|:---:|:---:|:---:|:---:|---|---|---|
-|4.8.0|2.0.1|12.1|5.5.2|32.921 ± 0.923 MB/s (2.620)|22.635 ± 0.368 MB/s (2.278)|35.672 ± 2.053 MB/s (2.981)|
-|4.7.0|2.0.1|12.0.1|5.5.1|32.692 ± 1.990 MB/s (2.620)|22.702 ± 0.423 MB/s (2.278)|35.405 ± 2.119 MB/s (2.981)|
+|4.8.6|2.0.4|26.2|6.2.3|92.564 ± 0.606 MB/s (2.620)|68.459 ± 0.367 MB/s (2.278)|96.908 ± 0.787 MB/s (2.981)|
+|4.9.0|2.1.0|26.5|6.3.2|105.739 ± 1.363 MB/s (2.620)|76.388 ± 0.923 MB/s (2.278)|111.100 ± 6.260 MB/s (2.981)|
### Dependent blocks
|SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3|
|:---:|:---:|:---:|:---:|---|---|---|
-|4.8.0|2.0.1|12.1|5.5.2|31.991 ± 1.434 MB/s (2.622)|21.973 ± 0.433 MB/s (2.280)|35.187 ± 1.315 MB/s (2.983)|
-|4.7.0|2.0.1|12.0.1|5.5.1|32.234 ± 1.673 MB/s (2.622)|21.960 ± 0.531 MB/s (2.280)|34.729 ± 1.996 MB/s (2.983)|
+|4.8.6|2.0.4|26.2|6.2.3|91.005 ± 0.527 MB/s (2.622)|67.450 ± 0.180 MB/s (2.280)|95.715 ± 0.621 MB/s (2.983)|
+|4.9.0|2.1.0|26.5|6.3.2|104.768 ± 1.075 MB/s (2.622)|75.345 ± 0.304 MB/s (2.280)|111.121 ± 1.698 MB/s (2.983)|
## 7-Zip Info Function
|SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3|
|:---:|:---:|:---:|:---:|---|---|---|
-|4.8.0|2.0.1|12.1|5.5.2|89.018 ± 10.089 MB/s|125.155 ± 10.977 MB/s|42.353 ± 1.286 MB/s|
-|4.7.0|2.0.1|12.0.1|5.5.1|86.216 ± 11.562 MB/s|135.372 ± 12.394 MB/s|43.499 ± 1.771 MB/s|
-|4.6.0|2.0.1|11.5.2|5.4.2|77.072 ± 6.625 MB/s|111.246 ± 4.855 MB/s|38.721 ± 2.681 MB/s|
-|4.6.0-unchecked|2.0.1|11.5.2|5.4.2|82.367 ± 7.381 MB/s|116.923 ± 3.812 MB/s|38.519 ± 2.988 MB/s|
+|4.8.6|2.0.4|26.2|6.2.3|805.462 ± 4.777 MB/s|1.714 ± 0.004 GB/s|418.213 ± 1.013 MB/s|
+|4.9.0|2.1.0|26.5|6.3.2|835.139 ± 4.655 MB/s|1.818 ± 0.007 GB/s|438.974 ± 1.137 MB/s|
## TAR Info Function
|SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3|
|:---:|:---:|:---:|:---:|---|---|---|
-|4.8.0|2.0.1|12.1|5.5.2|953.555 ± 113.704 MB/s|1.016 ± 0.086 GB/s|223.068 ± 15.446 MB/s|
-|4.7.0|2.0.1|12.0.1|5.5.1|963.424 ± 107.448 MB/s|1.035 ± 0.078 GB/s|224.862 ± 8.902 MB/s|
-|4.6.0|2.0.1|11.5.2|5.4.2|967.792 ± 58.436 MB/s|1.006 ± 0.069 GB/s|217.082 ± 19.783 MB/s|
-|4.6.0-unchecked|2.0.1|11.5.2|5.4.2|1.073 ± 0.115 GB/s|1.053 ± 0.071 GB/s|246.854 ± 6.763 MB/s|
-
-## TAR Reader
-
-|SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3|
-|:---:|:---:|:---:|:---:|---|---|---|
-|4.8.0|2.0.1|12.1|5.5.2|467.082 ± 27.789 MB/s|526.799 ± 47.046 MB/s|139.247 ± 8.510 MB/s|
+|4.8.6|2.0.4|26.2|6.2.3|3.520 ± 0.059 GB/s|4.333 ± 0.017 GB/s|936.204 ± 8.830 MB/s|
+|4.9.0|2.1.0|26.5|6.3.2|3.647 ± 0.038 GB/s|4.465 ± 0.061 GB/s|953.367 ± 6.691 MB/s|
## ZIP Info Function
|SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3|
|:---:|:---:|:---:|:---:|---|---|---|
-|4.8.0|2.0.1|12.1|5.5.2|615.038 ± 90.624 MB/s|1.888 ± 0.224 GB/s|377.717 ± 48.155 MB/s|
-|4.7.0|2.0.1|12.0.1|5.5.1|587.776 ± 159.763 MB/s|2.150 ± 0.148 GB/s|389.327 ± 104.806 MB/s|
-|4.6.0|2.0.1|11.5.2|5.4.2|597.002 ± 75.017 MB/s|1.941 ± 0.135 GB/s|389.626 ± 22.006 MB/s|
-|4.6.0-unchecked|2.0.1|11.5.2|5.4.2|685.266 ± 53.895 MB/s|2.147 ± 0.097 GB/s|420.461 ± 15.156 MB/s|
+|4.8.6|2.0.4|26.2|6.2.3|2.223 ± 0.017 GB/s|6.524 ± 0.031 GB/s|1.373 ± 0.007 GB/s|
+|4.9.0|2.1.0|26.5|6.3.2|2.134 ± 0.017 GB/s|6.272 ± 0.075 GB/s|1.327 ± 0.008 GB/s|
## TAR Create Function
|SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3|
|:---:|:---:|:---:|:---:|---|---|---|
-|4.8.0|2.0.1|12.1|5.5.2|145.817 ± 10.452 MB/s|486.193 ± 24.090 MB/s|237.893 ± 17.378 MB/s|
-|4.7.0|2.0.1|12.0.1|5.5.1|143.668 ± 9.892 MB/s|476.701 ± 26.328 MB/s|233.040 ± 14.406 MB/s|
-|4.6.0|2.0.1|11.5.2|5.4.2|139.649 ± 8.310 MB/s|446.101 ± 21.476 MB/s|215.556 ± 17.5 MB/s|
-|4.6.0-unchecked|2.0.1|11.5.2|5.4.2|142.681 ± 5.664 MB/s|459.403 ± 17.862 MB/s|220.238 ± 3.476 MB/s|
+|4.8.6|2.0.4|26.2|6.2.3|525.215 ± 2.978 MB/s|831.375 ± 8.157 MB/s|1.823 ± 0.063 GB/s|
+|4.9.0|2.1.0|26.5|6.3.2|602.061 ± 2.170 MB/s|962.294 ± 2.548 MB/s|2.134 ± 0.083 GB/s|
+
+## TAR Reader
+
+|SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3|
+|:---:|:---:|:---:|:---:|---|---|---|
+|4.8.6|2.0.4|26.2|6.2.3|1.586 ± 0.017 GB/s|2.063 ± 0.020 GB/s|498.778 ± 3.265 MB/s|
+|4.9.0|2.1.0|26.5|6.3.2|1.695 ± 0.022 GB/s|2.200 ± 0.011 GB/s|530.854 ± 2.667 MB/s|
## TAR Writer
|SWCompression
version|BitByteData
version|macOS
version|Swift
version|Test 1|Test 2|Test 3|
|:---:|:---:|:---:|:---:|---|---|---|
-|4.8.0|2.0.1|12.1|5.5.2|19.881 ± 0.766 MB/s|83.901 ± 4.690 MB/s|28.614 ± 2.938 MB/s|
+|4.8.6|2.0.4|26.2|6.2.3|36.198 ± 29.341 MB/s|61.920 ± 35.293 MB/s|196.107 ± 64.376 MB/s|
+|4.9.0|2.1.0|26.5|6.3.2|46.998 ± 26.850 MB/s|72.109 ± 29.039 MB/s|216.273 ± 23.042 MB/s|
diff --git a/Tests/SevenZipTests.swift b/Tests/SevenZipTests.swift
index d9aed7c6..ba3a9ad3 100644
--- a/Tests/SevenZipTests.swift
+++ b/Tests/SevenZipTests.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -343,6 +343,17 @@ class SevenZipTests: XCTestCase {
XCTAssertEqual(entries[1].data, "Hello, Windows!".data(using: .utf8))
}
+ func test7z_LzmaBigDict() throws {
+ // Verifying the issue fixed by PR #61.
+ // Previously, there was a crash, if LZMA dictionary size was encoded using all 4 bytes.
+ let testData = try Constants.data(forTest: "test_lzma_big_dict", withType: SevenZipTests.testType)
+ let entries = try SevenZipContainer.open(container: testData)
+ XCTAssertEqual(entries.count, 1)
+ XCTAssertEqual(entries[0].info.name, "data")
+ XCTAssertEqual(entries[0].info.type, .regular)
+ XCTAssertEqual(entries[0].info.size, 16 * 1024 * 1024)
+ }
+
func testEmptyFile() throws {
let testData = try Constants.data(forTest: "test_empty_file", withType: SevenZipTests.testType)
let entries = try SevenZipContainer.open(container: testData)
diff --git a/Tests/Sha256Tests.swift b/Tests/Sha256Tests.swift
index cb78202b..83468bc8 100644
--- a/Tests/Sha256Tests.swift
+++ b/Tests/Sha256Tests.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Tests/TarCreateTests.swift b/Tests/TarCreateTests.swift
index ae0c4cd0..76e55514 100644
--- a/Tests/TarCreateTests.swift
+++ b/Tests/TarCreateTests.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Tests/TarReaderTests.swift b/Tests/TarReaderTests.swift
index 2bf525cd..4ad4a866 100644
--- a/Tests/TarReaderTests.swift
+++ b/Tests/TarReaderTests.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -16,7 +16,7 @@ class TarReaderTests: XCTestCase {
let testHandle = try Constants.handle(forTest: "test7", withType: "answer")
var reader = TarReader(fileHandle: testHandle)
XCTAssertThrowsError(try reader.read())
- try testHandle.closeCompat()
+ try testHandle.close()
}
func test() throws {
@@ -44,7 +44,7 @@ class TarReaderTests: XCTestCase {
}
}
XCTAssertEqual(entriesCount, 1)
- try testHandle.closeCompat()
+ try testHandle.close()
}
@@ -77,7 +77,7 @@ class TarReaderTests: XCTestCase {
}
}
XCTAssertEqual(entriesCount, 5)
- try testHandle.closeCompat()
+ try testHandle.close()
}
func testFormats() throws {
@@ -104,7 +104,7 @@ class TarReaderTests: XCTestCase {
}
XCTAssertEqual(entriesCount, 1)
XCTAssertNil(try reader.read())
- try testHandle.closeCompat()
+ try testHandle.close()
}
}
@@ -126,7 +126,7 @@ class TarReaderTests: XCTestCase {
}
XCTAssertEqual(entriesCount, 6)
XCTAssertNil(try reader.read())
- try testHandle.closeCompat()
+ try testHandle.close()
}
}
@@ -160,7 +160,7 @@ class TarReaderTests: XCTestCase {
XCTAssertEqual(entry!.data, "Hello, Windows!".data(using: .utf8))
}
XCTAssertNil(try reader.read())
- try testHandle.closeCompat()
+ try testHandle.close()
}
func testEmptyFile() throws {
@@ -181,7 +181,7 @@ class TarReaderTests: XCTestCase {
}
XCTAssertNil(try reader.read())
- try testHandle.closeCompat()
+ try testHandle.close()
}
func testEmptyDirectory() throws {
@@ -201,7 +201,7 @@ class TarReaderTests: XCTestCase {
XCTAssertNil(entry!.data)
}
XCTAssertNil(try reader.read())
- try testHandle.closeCompat()
+ try testHandle.close()
}
func testOnlyDirectoryHeader() throws {
@@ -223,21 +223,21 @@ class TarReaderTests: XCTestCase {
XCTAssertNil(entry!.data)
}
XCTAssertNil(try reader.read())
- try testHandle.closeCompat()
+ try testHandle.close()
}
func testEmptyContainer() throws {
let testHandle = try Constants.handle(forTest: "test_empty_cont", withType: TarReaderTests.testType)
var reader = TarReader(fileHandle: testHandle)
XCTAssertNil(try reader.read())
- try testHandle.closeCompat()
+ try testHandle.close()
}
func testBigContainer() throws {
let testHandle = try Constants.handle(forTest: "SWCompressionSourceCode", withType: TarReaderTests.testType)
var reader = TarReader(fileHandle: testHandle)
while try reader.read() != nil { }
- try testHandle.closeCompat()
+ try testHandle.close()
}
func testUnicodeUstar() throws {
@@ -258,7 +258,7 @@ class TarReaderTests: XCTestCase {
}
XCTAssertNil(try reader.read())
- try testHandle.closeCompat()
+ try testHandle.close()
}
func testUnicodePax() throws {
@@ -279,7 +279,7 @@ class TarReaderTests: XCTestCase {
}
XCTAssertNil(try reader.read())
- try testHandle.closeCompat()
+ try testHandle.close()
}
func testGnuIncrementalFormat() throws {
@@ -304,7 +304,7 @@ class TarReaderTests: XCTestCase {
}
XCTAssertEqual(entriesCount, 3)
XCTAssertNil(try reader.read())
- try testHandle.closeCompat()
+ try testHandle.close()
}
// This test is impossible to implement using TarReader since the test file doesn't contain actual entry data.
@@ -328,7 +328,7 @@ class TarReaderTests: XCTestCase {
}
// Test that reading after reaching EOF returns nil.
XCTAssertNil(try reader.read())
- try testHandle.closeCompat()
+ try testHandle.close()
}
}
diff --git a/Tests/TarTests.swift b/Tests/TarTests.swift
index b31aedd5..015397f1 100644
--- a/Tests/TarTests.swift
+++ b/Tests/TarTests.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Tests/TarWriterTests.swift b/Tests/TarWriterTests.swift
index b19de81a..261ff50d 100644
--- a/Tests/TarWriterTests.swift
+++ b/Tests/TarWriterTests.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -36,7 +36,7 @@ class TarWriterTests: XCTestCase {
try writer.append(entry)
}
try writer.finalize()
- try handle.closeCompat()
+ try handle.close()
return try Data(contentsOf: tempFileUrl)
}
diff --git a/Tests/Test Files b/Tests/Test Files
index 83a2bcad..fae11e64 160000
--- a/Tests/Test Files
+++ b/Tests/Test Files
@@ -1 +1 @@
-Subproject commit 83a2bcadb7efb27c1835a2b1324b1a0617bcf901
+Subproject commit fae11e64d12cf7c4e0788f6dbe7fadd19005ae8b
diff --git a/Tests/TestZipExtraField.swift b/Tests/TestZipExtraField.swift
index 45676511..084a1501 100644
--- a/Tests/TestZipExtraField.swift
+++ b/Tests/TestZipExtraField.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Tests/XxHash32Tests.swift b/Tests/XxHash32Tests.swift
index d773af4e..87e15276 100644
--- a/Tests/XxHash32Tests.swift
+++ b/Tests/XxHash32Tests.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Tests/XzTests.swift b/Tests/XzTests.swift
index d243bdf9..708bf111 100644
--- a/Tests/XzTests.swift
+++ b/Tests/XzTests.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Tests/ZipTests.swift b/Tests/ZipTests.swift
index 5aea5218..d2bfa497 100644
--- a/Tests/ZipTests.swift
+++ b/Tests/ZipTests.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
diff --git a/Tests/ZlibTests.swift b/Tests/ZlibTests.swift
index d6aa7c55..e950c7b6 100644
--- a/Tests/ZlibTests.swift
+++ b/Tests/ZlibTests.swift
@@ -1,4 +1,4 @@
-// Copyright (c) 2024 Timofey Solomko
+// Copyright (c) 2026 Timofey Solomko
// Licensed under MIT License
//
// See LICENSE for license information
@@ -74,4 +74,14 @@ class ZlibTests: XCTestCase {
}
}
+ func testZlibTruncation() throws {
+ for testName in ["test", "random_file", "test_empty"] {
+ let testData = try Constants.data(forTest: testName, withType: ZlibTests.testType)
+ for _ in 0..<100 {
+ let truncationIndex = Int.random(in: (testData.startIndex + 1).. /dev/null
+ xcodebuild -downloadPlatform iOS
+ xcodebuild -downloadPlatform watchOS
+ xcodebuild -downloadPlatform tvOS
+ displayName: 'Install Darwin platforms'
- script: ./utils.py ci script-macos
displayName: 'Build & Test'
- script: swift build -c release
@@ -99,30 +86,24 @@ stages:
- job: linux
strategy:
matrix:
- linuxSwift53:
- imageName: 'ubuntu-20.04'
- containerImage: 'swift:5.3.3-focal'
- linuxSwift54:
- imageName: 'ubuntu-20.04'
- containerImage: 'swift:5.4.3-focal'
- linuxSwift55:
- imageName: 'ubuntu-20.04'
- containerImage: 'swift:5.5.3-focal'
- linuxSwift56:
- imageName: 'ubuntu-20.04'
- containerImage: 'swift:5.6.3-focal'
- linuxSwift57:
- imageName: 'ubuntu-20.04'
- containerImage: 'swift:5.7.3-focal'
- linuxSwift58:
- imageName: 'ubuntu-20.04'
- containerImage: 'swift:5.8.1-focal'
linuxSwift59:
- imageName: 'ubuntu-20.04'
- containerImage: 'swift:5.9.2-focal'
+ imageName: 'ubuntu-22.04'
+ containerImage: 'swift:5.9.2-jammy'
linuxSwift510:
- imageName: 'ubuntu-20.04'
- containerImage: 'swift:5.10-focal'
+ imageName: 'ubuntu-24.04'
+ containerImage: 'swift:5.10.1-noble'
+ linuxSwift60:
+ imageName: 'ubuntu-24.04'
+ containerImage: 'swift:6.0.3-noble'
+ linuxSwift61:
+ imageName: 'ubuntu-24.04'
+ containerImage: 'swift:6.1.3-noble'
+ linuxSwift62:
+ imageName: 'ubuntu-24.04'
+ containerImage: 'swift:6.2.3-noble'
+ linuxSwift63:
+ imageName: 'ubuntu-24.04'
+ containerImage: 'swift:6.3.2-noble'
pool:
vmImage: $(imageName)
container: $[ variables['containerImage'] ]
@@ -136,62 +117,32 @@ stages:
- job: windows
strategy:
matrix:
- windowsSwift54:
- imageName: 'windows-2019'
- SWIFT_VERSION: '5.4.3'
- ICU_PATH: 'C:\Library\icu-67\usr\bin'
- SWIFT_DEV_PATH: 'C:\Library\Swift-development\bin'
- windowsSwift55:
- imageName: 'windows-2019'
- SWIFT_VERSION: '5.5.3'
- ICU_PATH: 'C:\Library\icu-67\usr\bin'
- SWIFT_DEV_PATH: 'C:\Library\Swift-development\bin'
- windowsSwift56:
- imageName: 'windows-2019'
- SWIFT_VERSION: '5.6.3'
- ICU_PATH: 'C:\Library\icu-69.1\usr\bin'
- SWIFT_DEV_PATH: 'C:\Library\Swift-development\bin'
- windowsSwift57:
- imageName: 'windows-2019'
- SWIFT_VERSION: '5.7.3'
- ICU_PATH: 'C:\Program Files\swift\icu-69.1\usr\bin'
- SWIFT_DEV_PATH: 'C:\Program Files\swift\runtime-development\usr\bin'
- windowsSwift58:
- imageName: 'windows-2019'
- SWIFT_VERSION: '5.8.1'
- ICU_PATH: 'C:\Program Files\swift\icu-69.1\usr\bin'
- SWIFT_DEV_PATH: 'C:\Program Files\swift\runtime-development\usr\bin'
windowsSwift59:
- imageName: 'windows-2019'
- SWIFT_VERSION: '5.9.2'
- ICU_PATH: 'C:\Program Files\swift\icu-69.1\usr\bin'
- SWIFT_DEV_PATH: 'C:\Program Files\swift\runtime-development\usr\bin'
- # TODO: There seems to be an unknown problem with the Swift installation step.
+ imageName: 'windows-2022'
+ containerImage: 'swift:5.9.2-windowsservercore-ltsc2022'
# windowsSwift510:
- # imageName: 'windows-2019'
- # SWIFT_VERSION: '5.10'
- # ICU_PATH: 'C:\Program Files\swift\icu-69.1\usr\bin'
- # SWIFT_DEV_PATH: 'C:\Program Files\swift\runtime-development\usr\bin'
+ # imageName: 'windows-2022'
+ # containerImage: 'swift:5.10.1-windowsservercore-ltsc2022'
+ windowsSwift60:
+ imageName: 'windows-2022'
+ containerImage: 'swift:6.0.3-windowsservercore-ltsc2022'
+ windowsSwift61:
+ imageName: 'windows-2022'
+ containerImage: 'swift:6.1.3-windowsservercore-ltsc2022'
+ windowsSwift62:
+ imageName: 'windows-2022'
+ containerImage: 'swift:6.2.3-windowsservercore-ltsc2022'
+ windowsSwift63:
+ imageName: 'windows-2022'
+ containerImage: 'swift:6.3.2-windowsservercore-ltsc2022'
pool:
vmImage: $(imageName)
- variables:
- DEVELOPER_DIR: 'C:\Library\Developer'
- SDKROOT: 'C:\Library\Developer\Platforms\Windows.platform\Developer\SDKs\Windows.sdk'
- SWIFTFLAGS: '--sdk $(SDKROOT) -Xswiftc -sdk -Xswiftc $(SDKROOT) -Xswiftc -resource-dir -Xswiftc $(SDKROOT)/usr/lib/swift -Xswiftc -I -Xswiftc $(SDKROOT)/usr/lib/swift -Xswiftc -L -Xswiftc $(SDKROOT)/usr/lib/swift/windows'
+ container: $[ variables['containerImage'] ]
steps:
- - pwsh: Invoke-WebRequest -Uri https://swift.org/builds/swift-$(SWIFT_VERSION)-release/windows10/swift-$(SWIFT_VERSION)-RELEASE/swift-$(SWIFT_VERSION)-RELEASE-windows10.exe -OutFile swift-install.exe
- displayName: 'Download Swift'
- - task: BatchScript@1
- inputs:
- filename: .\swift-install.exe
- arguments: /install /quiet
- modifyEnvironment: true
- displayName: 'Install Swift'
- script: |
- set PATH=C:\Library\Developer\Toolchains\unknown-Asserts-development.xctoolchain\usr\bin;%SWIFT_DEV_PATH%;%ICU_PATH%;%PATH%
swift.exe --version
- swift.exe build --target SWCompression %SWIFTFLAGS%
- swift.exe build -c release --target SWCompression %SWIFTFLAGS%
+ swift.exe build
+ swift.exe build -c release
displayName: 'Build SPM Debug & Release'
- stage: deploy
displayName: Deploy
@@ -202,9 +153,7 @@ stages:
- job: ghPages
displayName: 'Publish API docs to GH Pages'
pool:
- vmImage: 'macOS-12'
- variables:
- DEVELOPER_DIR: '/Applications/Xcode_14.2.app'
+ vmImage: 'macOS-26'
steps:
- script: |
set -e -o xtrace
diff --git a/utils.py b/utils.py
index aef6e96d..d582adeb 100755
--- a/utils.py
+++ b/utils.py
@@ -23,9 +23,9 @@ def _ci_script_macos():
_sprun_shell("xcodebuild -version")
_sprun(["swift", "--version"])
xcodebuild_command_parts = ["xcodebuild", "-quiet", "-project", "SWCompression.xcodeproj", "-scheme", "SWCompression"]
- destinations_actions = [(["-destination 'platform=OS X'"], ["clean", "test"]),
+ destinations_actions = [(["-destination 'platform=macOS'"], ["clean", "test"]),
(["-destination 'platform=iOS Simulator,name=" + os.environ["IOS_SIMULATOR"] + "'"], ["clean", "test"]),
- (["-destination 'platform=watchOS Simulator,name=" + os.environ["WATCHOS_SIMULATOR"] + "'"], [os.environ["WATCHOS_ACTIONS"]]),
+ (["-destination 'platform=watchOS Simulator,name=" + os.environ["WATCHOS_SIMULATOR"] + "'"], ["clean", "test"]),
(["-destination 'platform=tvOS Simulator,name=Apple TV'"], ["clean", "test"])]
for destination, actions in destinations_actions:
@@ -43,40 +43,16 @@ def action_ci(args):
def action_cw(args):
_sprun(["rm", "-rf", "build/"])
- _sprun(["rm", "-rf", "Carthage/"])
_sprun(["rm", "-rf", "docs/"])
- _sprun(["rm", "-rf", "Pods/"])
_sprun(["rm", "-rf", ".build/"])
- _sprun(["rm", "-f", "Cartfile.resolved"])
_sprun(["rm", "-f", "docs.json"])
_sprun(["rm", "-f", "Package.resolved"])
_sprun(["rm", "-f", "SWCompression.framework.zip"])
-def action_dbm(args):
- print("=> Downloading BitByteData dependency using Carthage")
- script = ["carthage", "bootstrap", "--no-use-binaries", "--use-xcframeworks"]
- if args.debug:
- script += ["--configuration", "Debug"]
- if args.xros:
- script += ["--platform", "macOS,iOS,watchOS,tvOS,visionOS"]
- else:
- script += ["--platform", "macOS,iOS,watchOS,tvOS"]
- _sprun(script)
-
def action_pr(args):
_sprun(["agvtool", "next-version", "-all"])
_sprun(["agvtool", "new-marketing-version", args.version])
- f = open("SWCompression.podspec", "r", encoding="utf-8")
- lines = f.readlines()
- f.close()
- f = open("SWCompression.podspec", "w", encoding="utf-8")
- for line in lines:
- if line.startswith(" s.version = "):
- line = " s.version = \"" + args.version + "\"\n"
- f.write(line)
- f.close()
-
f = open(".jazzy.yaml", "r", encoding="utf-8")
lines = f.readlines()
f.close()
@@ -114,15 +90,6 @@ parser_cw = subparsers.add_parser("cleanup-workspace", help="cleanup workspace",
description="cleans workspace from files produced by various build systems")
parser_cw.set_defaults(func=action_cw)
-# Parser for 'download-bbd-macos' command.
-parser_dbm = subparsers.add_parser("download-bbd-macos", help="download BitByteData",
- description="downloads BitByteData dependency using Carthage (macOS only)")
-parser_dbm.add_argument("--debug", "-d", action="store_true", dest="debug",
- help="build BitByteData in Debug configuration")
-parser_dbm.add_argument("--xros", action="store_true", dest="xros",
- help="build BitByteData for visionOS as well (requires Apple Silicon)")
-parser_dbm.set_defaults(func=action_dbm)
-
# Parser for 'prepare-release' command.
parser_pr = subparsers.add_parser("prepare-release", help="prepare next release",
description="prepare next release of SWCompression")