diff --git a/SWCompression.xcodeproj/project.pbxproj b/SWCompression.xcodeproj/project.pbxproj index 14028d07..0f6c6569 100644 --- a/SWCompression.xcodeproj/project.pbxproj +++ b/SWCompression.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ 061FCE311DBCC8BF0052F7BE /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 061FCE301DBCC8BF0052F7BE /* Extensions.swift */; }; 063364DE1DC51D33007E313F /* empty.txt in Resources */ = {isa = PBXBuildFile; fileRef = 063364DC1DC51D33007E313F /* empty.txt */; }; 063364DF1DC51D33007E313F /* empty.txt.gz in Resources */ = {isa = PBXBuildFile; fileRef = 063364DD1DC51D33007E313F /* empty.txt.gz */; }; + 063364E11DC52970007E313F /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063364E01DC52970007E313F /* Protocols.swift */; }; + 063364E31DC52979007E313F /* GzipArchive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063364E21DC52979007E313F /* GzipArchive.swift */; }; 06AC9B0D1DC1027900A5066B /* HuffmanLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AC9B0C1DC1027900A5066B /* HuffmanLength.swift */; }; 06BE1AD21DB410F100EE0F59 /* SWCompression.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06BE1AC81DB410F100EE0F59 /* SWCompression.framework */; }; 06BE1AD71DB410F100EE0F59 /* SWCompressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06BE1AD61DB410F100EE0F59 /* SWCompressionTests.swift */; }; @@ -41,6 +43,8 @@ 061FCE301DBCC8BF0052F7BE /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 063364DC1DC51D33007E313F /* empty.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = empty.txt; sourceTree = ""; }; 063364DD1DC51D33007E313F /* empty.txt.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = empty.txt.gz; sourceTree = ""; }; + 063364E01DC52970007E313F /* Protocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Protocols.swift; sourceTree = ""; }; + 063364E21DC52979007E313F /* GzipArchive.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GzipArchive.swift; sourceTree = ""; }; 06AC9B0C1DC1027900A5066B /* HuffmanLength.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HuffmanLength.swift; sourceTree = ""; }; 06BE1AC81DB410F100EE0F59 /* SWCompression.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SWCompression.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 06BE1ACB1DB410F100EE0F59 /* SWCompression.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SWCompression.h; sourceTree = ""; }; @@ -106,12 +110,14 @@ 06BE1ACA1DB410F100EE0F59 /* Sources */ = { isa = PBXGroup; children = ( - 06BE1ACB1DB410F100EE0F59 /* SWCompression.h */, + 063364E01DC52970007E313F /* Protocols.swift */, + 063364E21DC52979007E313F /* GzipArchive.swift */, 061FCE251DBCC4BE0052F7BE /* Deflate.swift */, 06E961C91DBD5FA1008C47F2 /* HuffmanTable.swift */, 061FCE301DBCC8BF0052F7BE /* Extensions.swift */, 06AC9B0C1DC1027900A5066B /* HuffmanLength.swift */, 06BE1ACC1DB410F100EE0F59 /* Info.plist */, + 06BE1ACB1DB410F100EE0F59 /* SWCompression.h */, ); path = Sources; sourceTree = ""; @@ -246,9 +252,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 063364E11DC52970007E313F /* Protocols.swift in Sources */, 06E961CA1DBD5FA1008C47F2 /* HuffmanTable.swift in Sources */, 06AC9B0D1DC1027900A5066B /* HuffmanLength.swift in Sources */, 061FCE261DBCC4BE0052F7BE /* Deflate.swift in Sources */, + 063364E31DC52979007E313F /* GzipArchive.swift in Sources */, 061FCE311DBCC8BF0052F7BE /* Extensions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sources/Deflate.swift b/Sources/Deflate.swift index 69d36bd6..8079058b 100644 --- a/Sources/Deflate.swift +++ b/Sources/Deflate.swift @@ -9,99 +9,24 @@ import Foundation public enum DeflateError: Error { - case WrongMagic - case UnknownCompressionMethod case WrongBlockLengths case HuffmanTableError case UnknownBlockType } -public class Deflate { - - struct Flags { - static let ftext: UInt8 = 0x01 - static let fhcrc: UInt8 = 0x02 - static let fextra: UInt8 = 0x04 - static let fname: UInt8 = 0x08 - static let fcomment: UInt8 = 0x10 - } - - struct ServiceInfo { - let magic: [UInt8] - let method: UInt8 - let flags: UInt8 - let mtime: UInt64 - let extraFlags: UInt8 - let osType: UInt8 - } - - public static func decompress(data: Data) throws -> Data { - // First two bytes should be correct 'magic' bytes - let magic = data.bytes(from: 0..<2) - guard magic == [31, 139] else { throw DeflateError.WrongMagic } - - // Third byte is a method of compression. Only type 8 (DEFLATE) compression is supported - let method = data[2] - guard method == 8 else { throw DeflateError.UnknownCompressionMethod } - - // Next bytes present some service information - let serviceInfo = ServiceInfo(magic: magic, - method: method, - flags: data[3], - mtime: Data(data[4...7]).to(type: UInt64.self), - extraFlags: data[8], - osType: data[9]) - - var startPoint = 10 // Index in data of 'actual data' - - // Some archives may contain extra fields - if serviceInfo.flags & Flags.fextra != 0 { - let xlen = Data(data[startPoint...startPoint + 1]).to(type: UInt16.self).toInt() - startPoint += 2 + xlen - } - - // Some archives may contain source file name (this part ends with zero byte) - if serviceInfo.flags & Flags.fname != 0 { - let fnameStart = startPoint - while true { - let byte = data[startPoint] - startPoint += 1 - guard byte != 0 else { break } - } - print(String(data: Data(data[fnameStart.. Data { var out: [String] = [] // Current point of processing in data - var index = startPoint + var index = 0 while true { // Is this a last block? let isLastBit = data[index][0] // Type of the current block let blockType = [UInt8](data[index][1..<3].reversed()) var shift = 3 - print("blockType: \(convertToInt(uint8Array: blockType))") if blockType == [0, 0] { // Uncompressed block // Get length of the uncompressed data diff --git a/Sources/GzipArchive.swift b/Sources/GzipArchive.swift new file mode 100644 index 00000000..6b7be0ca --- /dev/null +++ b/Sources/GzipArchive.swift @@ -0,0 +1,105 @@ +// +// GzipArchive.swift +// SWCompression +// +// Created by Timofey Solomko on 29.10.16. +// Copyright © 2016 tsolomko. All rights reserved. +// + +import Foundation + +public enum GzipError: Error { + case WrongMagic + case UnknownCompressionMethod +} + +public class GzipArchive: Archive { + + struct Flags { + static let ftext: UInt8 = 0x01 + static let fhcrc: UInt8 = 0x02 + static let fextra: UInt8 = 0x04 + static let fname: UInt8 = 0x08 + static let fcomment: UInt8 = 0x10 + } + + struct ServiceInfo { + let magic: [UInt8] + let method: UInt8 + let flags: UInt8 + let mtime: UInt64 + let extraFlags: UInt8 + let osType: UInt8 + // Optional fields + var startPoint: Int + var fileName: String + var comment: String + var crc: UInt16 + } + + static func serviceInfo(archiveData data: Data) throws -> ServiceInfo { + // First two bytes should be correct 'magic' bytes + let magic = data.bytes(from: 0..<2) + guard magic == [31, 139] else { throw GzipError.WrongMagic } + + // Third byte is a method of compression. Only type 8 (DEFLATE) compression is supported + let method = data[2] + guard method == 8 else { throw GzipError.UnknownCompressionMethod } + + // Next bytes present some service information + var serviceInfo = ServiceInfo(magic: magic, + method: method, + flags: data[3], + mtime: Data(data[4...7]).to(type: UInt64.self), + extraFlags: data[8], + osType: data[9], + startPoint: 10, + fileName: "", + comment: "", + crc: 0) + + // Some archives may contain extra fields + if serviceInfo.flags & Flags.fextra != 0 { + let xlen = Data(data[serviceInfo.startPoint...serviceInfo.startPoint + 1]).to(type: UInt16.self).toInt() + serviceInfo.startPoint += 2 + xlen + } + + // Some archives may contain source file name (this part ends with zero byte) + if serviceInfo.flags & Flags.fname != 0 { + let fnameStart = serviceInfo.startPoint + while true { + let byte = data[serviceInfo.startPoint] + serviceInfo.startPoint += 1 + guard byte != 0 else { break } + } + serviceInfo.fileName = String(data: Data(data[fnameStart.. Data { + let info = try serviceInfo(archiveData: data) + return try Deflate.decompress(compressedData: Data(data[info.startPoint.. Data + +} + +public protocol DecompressionAlgorithm { + + static func decompress(compressedData: Data) throws -> Data + +} diff --git a/Tests/SWCompressionTests.swift b/Tests/SWCompressionTests.swift index 04900e39..2b85c1a9 100644 --- a/Tests/SWCompressionTests.swift +++ b/Tests/SWCompressionTests.swift @@ -14,7 +14,7 @@ class SWCompressionTests: XCTestCase { func testHelloWorldFile() { let testData = try? Data(contentsOf: Constants.helloWorldArchivePath) XCTAssertNotNil(testData, "Failed to load test archive") - let decompressedData = try? Deflate.decompress(data: testData!) + let decompressedData = try? GzipArchive.unarchive(archiveData: testData!) XCTAssertNotNil(decompressedData, "Failed to decompress") guard decompressedData != nil else { return } let decompressedString = String(data: decompressedData!, encoding: .utf8) @@ -25,7 +25,7 @@ class SWCompressionTests: XCTestCase { func testSecondTestFile() { let testData = try? Data(contentsOf: Constants.secondTestArchivePath) XCTAssertNotNil(testData, "Failed to load test archive") - let decompressedData = try? Deflate.decompress(data: testData!) + let decompressedData = try? GzipArchive.unarchive(archiveData: testData!) XCTAssertNotNil(decompressedData, "Failed to decompress") guard decompressedData != nil else { return } let decompressedString = String(data: decompressedData!, encoding: .utf8) @@ -39,7 +39,7 @@ class SWCompressionTests: XCTestCase { func testEmptyFile() { let testData = try? Data(contentsOf: Constants.emptyFileArchivePath) XCTAssertNotNil(testData, "Failed to load test archive") - let decompressedData = try? Deflate.decompress(data: testData!) + let decompressedData = try? GzipArchive.unarchive(archiveData: testData!) XCTAssertNotNil(decompressedData, "Failed to decompress") guard decompressedData != nil else { return } let decompressedString = String(data: decompressedData!, encoding: .utf8)