Gzip related code moved into separate file; added two protocols; extended serviceInfo structure.

This commit is contained in:
Timofey Solomko
2016-10-29 22:27:11 +03:00
parent e557cce7ed
commit bcaa85e0e8
5 changed files with 141 additions and 82 deletions
+9 -1
View File
@@ -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 = "<group>"; };
063364DC1DC51D33007E313F /* empty.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = empty.txt; sourceTree = "<group>"; };
063364DD1DC51D33007E313F /* empty.txt.gz */ = {isa = PBXFileReference; lastKnownFileType = archive.gzip; path = empty.txt.gz; sourceTree = "<group>"; };
063364E01DC52970007E313F /* Protocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Protocols.swift; sourceTree = "<group>"; };
063364E21DC52979007E313F /* GzipArchive.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GzipArchive.swift; sourceTree = "<group>"; };
06AC9B0C1DC1027900A5066B /* HuffmanLength.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HuffmanLength.swift; sourceTree = "<group>"; };
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 = "<group>"; };
@@ -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 = "<group>";
@@ -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;
+3 -78
View File
@@ -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..<startPoint - 1]), encoding: .utf8) ??
"Unable to get file name")
}
// Some archives may contain comment (this part also ends with zero)
if serviceInfo.flags & Flags.fcomment != 0 {
let fcommentStart = startPoint
while true {
let byte = data[startPoint]
startPoint += 1
guard byte != 0 else { break }
}
print(String(data: Data(data[fcommentStart..<startPoint - 1]), encoding: .utf8) ??
"Unable to get comment")
}
// Some archives may contain 2-bytes checksum
if serviceInfo.flags & Flags.fhcrc != 0 {
let crc = Data(data[startPoint...startPoint + 1]).to(type: UInt16.self)
startPoint += 2
print("\(crc)")
}
public class Deflate: DecompressionAlgorithm {
public static func decompress(compressedData data: Data) throws -> 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
+105
View File
@@ -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..<serviceInfo.startPoint - 1]),
encoding: .utf8) ?? ""
}
// Some archives may contain comment (this part also ends with zero)
if serviceInfo.flags & Flags.fcomment != 0 {
let fcommentStart = serviceInfo.startPoint
while true {
let byte = data[serviceInfo.startPoint]
serviceInfo.startPoint += 1
guard byte != 0 else { break }
}
serviceInfo.comment = String(data: Data(data[fcommentStart..<serviceInfo.startPoint - 1]),
encoding: .utf8) ?? ""
}
// Some archives may contain 2-bytes checksum
if serviceInfo.flags & Flags.fhcrc != 0 {
serviceInfo.crc = Data(data[serviceInfo.startPoint...serviceInfo.startPoint + 1]).to(type: UInt16.self)
serviceInfo.startPoint += 2
}
return serviceInfo
}
public static func unarchive(archiveData data: Data) throws -> Data {
let info = try serviceInfo(archiveData: data)
return try Deflate.decompress(compressedData: Data(data[info.startPoint..<data.count]))
}
}
+21
View File
@@ -0,0 +1,21 @@
//
// Protocols.swift
// SWCompression
//
// Created by Timofey Solomko on 29.10.16.
// Copyright © 2016 tsolomko. All rights reserved.
//
import Foundation
public protocol Archive {
static func unarchive(archiveData: Data) throws -> Data
}
public protocol DecompressionAlgorithm {
static func decompress(compressedData: Data) throws -> Data
}
+3 -3
View File
@@ -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)