Files
SWCompression/Sources/ZlibArchive.swift
T
2017-01-28 21:02:56 +03:00

158 lines
5.9 KiB
Swift

//
// ZlibArchive.swift
// SWCompression
//
// Created by Timofey Solomko on 30.10.16.
// Copyright © 2017 Timofey Solomko. All rights reserved.
//
import Foundation
/**
Error happened during unarchiving Zlib archive.
It may indicate that either the data is damaged or it might not be Zlib archive at all.
- `WrongCompressionMethod`: unsupported compression method (not 8).
- `WrongCompressionInfo`: unsupported compression info (greater than 7).
- `WrongFcheck`: first two bytes were inconsistent with each other.
- `WrongCompressionLevel`: unsupported compression level (not 0, 1, 2 or 3).
- `WrongAdler32`: computed Adler-32 checksum of uncompressed data didn't match the archive's value.
Associated value contains already decompressed data.
*/
public enum ZlibError: Error {
/// Compression method was other than 8 which is the only supported one.
case WrongCompressionMethod
/// Compression info was greater than 7 which is uncompatible number 8 compression method.
case WrongCompressionInfo
/// First two bytes were inconsistent with each other.
case WrongFcheck
/// Compression level was other than 0, 1, 2, 3.
case WrongCompressionLevel
/**
Computed Adler-32 sum of uncompressed data didn't match the value stored in the archive.
Associated value contains already decompressed data.
*/
case WrongAdler32(Data)
}
/// A structure which provides information about zlib archive.
public struct ZlibHeader {
/// Supported compression methods in zlib archive.
public enum CompressionMethod: Int {
/// The only one supported compression method (Deflate).
case deflate = 8
}
/// Levels of compression which can be used to create zlib archive.
public enum CompressionLevel: Int {
/// Fastest algorithm.
case fastestAlgorithm = 0
/// Fast algorithm.
case fastAlgorithm = 1
/// Default algorithm.
case defaultAlgorithm = 2
/// Slowest algorithm but with maximum compression.
case slowAlgorithm = 3
}
/// Compression method of archive. Always equals to `.deflate`.
public let compressionMethod: CompressionMethod
/// Level of compression in the archive.
public let compressionLevel: CompressionLevel
/// Size of 'window': moving interval of data which was used to make the archive
public let windowSize: Int
/**
Initializes the structure with the values from zlib archive presented in `archiveData`.
If data passed is not actually a zlib archive, `ZlibError` will be thrown.
- Parameter archiveData: Data compressed with zlib.
- Throws: `ZlibError`. It may indicate that either the data is damaged or
it might not be compressed with zlib at all.
*/
public init(archiveData: Data) throws {
let pointerData = DataWithPointer(data: archiveData, bitOrder: .reversed)
try self.init(pointerData)
}
init(_ pointerData: DataWithPointer) throws {
// First four bits are compression method.
// Only compression method = 8 (DEFLATE) is supported.
let compressionMethod = pointerData.intFromBits(count: 4)
guard compressionMethod == 8 else { throw ZlibError.WrongCompressionMethod }
self.compressionMethod = .deflate
// Remaining four bits indicate window size
// For DEFLATE it must not be more than 7
let compressionInfo = pointerData.intFromBits(count: 4)
guard compressionInfo <= 7 else { throw ZlibError.WrongCompressionInfo }
let windowSize = 1 << (compressionInfo + 8)
self.windowSize = windowSize
// compressionMethod and compressionInfo combined are needed later for integrity check
let cmf = compressionInfo << 4 + compressionMethod
// Next five bits are fcheck bits which are supposed to be integrity check
let fcheck = pointerData.intFromBits(count: 5)
// Sixth bit indicate if archive contain Adler-32 checksum of preset dictionary
let fdict = pointerData.intFromBits(count: 1)
// Remaining bits indicate compression level
guard let compressionLevel = ZlibHeader.CompressionLevel(rawValue:
pointerData.intFromBits(count: 2)) else { throw ZlibError.WrongCompressionLevel }
self.compressionLevel = compressionLevel
// fcheck, fdict and compresionLevel together make flags byte which is used in integrity check
let flags = compressionLevel.rawValue << 6 + fdict << 5 + fcheck
guard (UInt(cmf) * 256 + UInt(flags)) % 31 == 0 else { throw ZlibError.WrongFcheck }
// If preset dictionary is present 4 bytes will be skipped
if fdict == 1 {
pointerData.index += 4
}
}
}
/// Provides unarchive function for Zlib archives.
public final class ZlibArchive: Archive {
/**
Unarchives Zlib archive stored in `archiveData`.
If data passed is not actually a zlib archive, `ZlibError` will be thrown.
If data inside the archive is not actually compressed with DEFLATE algorithm, `DeflateError` will be thrown.
- Note: This function is specification compliant.
- Parameter archiveData: Data compressed with zlib.
- Throws: `DeflateError` or `ZlibError` depending on the type of inconsistency in data.
It may indicate that either the data is damaged or it might not be compressed with zlib or DEFLATE at all.
- Returns: Unarchived data.
*/
public static func unarchive(archiveData data: Data) throws -> Data {
/// Object with input data which supports convenient work with bit shifts.
var pointerData = DataWithPointer(data: data, bitOrder: .reversed)
_ = try ZlibHeader(pointerData)
let out = try Deflate.decompress(&pointerData)
let adler32 = pointerData.intFromAlignedBytes(count: 4).reverseBytes()
guard CheckSums.adler32(out) == adler32 else { throw ZlibError.WrongAdler32(Data(bytes: out)) }
return Data(bytes: out)
}
}