// // Deflate.swift // SWCompression // // Created by Timofey Solomko on 23.10.16. // Copyright © 2016 Timofey Solomko. All rights reserved. // import Foundation // TODO: Rename HuffmanTableError to HuffmanTreeError. // TODO: Expand HuffmanTreeError to more specific errors. /** Error happened during deflate decompression. It may indicate that either the data is damaged or it might not be compressed with DEFLATE at all. - `WrongBlockLengths`: `length` and `nlength` bytes of uncompressed block were not compatible. - `HuffmanTableError`: either error occured while parsing bytes related to Huffman coding or problem is happened during various calculations of Huffman coding. - `UnknownBlockType`: unsupported block type (not 0, 1 or 2). */ public enum DeflateError: Error { /// Uncompressed block' `length` and `nlength` bytes were not compatible. case WrongBlockLengths /// Either error occured while parsing bytes related to Huffman coding or problem is happened during various calculations of Huffman coding. case HuffmanTableError // TODO: Rename to WrongBlockType. /// Unknown block type (not from 0 to 2). case UnknownBlockType } /// Provides function to decompress data, which were compressed with DEFLATE public class Deflate: DecompressionAlgorithm { /** Decompresses `compressedData` with DEFLATE algortihm. If data passed is not actually compressed with DEFLATE, `DeflateError` will be thrown. - Parameter compressedData: Data compressed with DEFLATE. - Throws: `DeflateError` if unexpected byte (bit) sequence was encountered in `compressedData`. It may indicate that either the data is damaged or it might not be compressed with DEFLATE at all. - Returns: Decompressed data. */ public static func decompress(compressedData data: Data) throws -> Data { /// Object with input data which supports convenient work with bit shifts. let pointerData = DataWithPointer(data: data, bitOrder: .reversed) return try decompress(pointerData: pointerData) } static func decompress(pointerData: DataWithPointer) throws -> Data { /// An array for storing output data var out: [UInt8] = [] while true { /// Is this a last block? let isLastBit = pointerData.bit() /// Type of the current block. let blockType = [UInt8](pointerData.bits(count: 2).reversed()) if blockType == [0, 0] { // Uncompressed block. pointerData.skipUntilNextByte() /// Length of the uncompressed data. let length = pointerData.intFromBits(count: 16) /// 1-complement of the length. let nlength = pointerData.intFromBits(count: 16) // Check if lengths are OK (nlength should be a 1-complement of length). // TODO: Rename WrongBlockLengths to WrongUncompressedBlockLengths (or something else) guard length & nlength == 0 else { throw DeflateError.WrongBlockLengths } // Process uncompressed data into the output // TODO: Replace precondition with guard and error throwing. precondition(pointerData.bitMask == 1, "Misaligned byte.") var ind = out.count out.append(contentsOf: Array(repeating: 0, count: length)) for _ 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. count = pointerData.intFromBits(count: 2) + 3 what = codeLengths.last! } else if symbol == 17 { // Repeat code length 0 for 3 to 10 times. // Next three bits show how many times we need to copy. count = pointerData.intFromBits(count: 3) + 3 what = 0 } else if symbol == 18 { // Put code length 0 in table 11 to 138 times. // Next seven bits show how many times we need to do this. count = pointerData.intFromBits(count: 7) + 11 what = 0 } else { throw DeflateError.HuffmanTableError } codeLengths.append(contentsOf: Array(repeating: what, count: count)) n += count } // We have read codeLengths for both tables at once. // Now we need to split them and make corresponding tables. mainLiterals = HuffmanTree(lengthsToOrder: Array(codeLengths[0..= 0 && nextSymbol <= 255 { // It is a literal symbol so we add it straight to the output data. out.append(nextSymbol.toUInt8()) } else if nextSymbol == 256 { // It is a symbol indicating the end of data. break } else if nextSymbol >= 257 && nextSymbol <= 285 { // It is a length symbol. // Depending on the value of nextSymbol there might be additional bits in data, // which we need to add to nextSymbol to get the full length. let extraLength = (257 <= nextSymbol && nextSymbol <= 260) || nextSymbol == 285 ? 0 : (((nextSymbol - 257) >> 2) - 1) // Actually, nextSymbol is not a starting value of length but an index for special array of starting values. let length = HuffmanTree.Constants.lengthBase[nextSymbol - 257] + pointerData.intFromBits(count: extraLength) // Then we need to get distance code. let distanceCode = mainDistances.findNextSymbol(in: pointerData) guard distanceCode != -1 else { throw DeflateError.HuffmanTableError } if distanceCode >= 0 && distanceCode <= 29 { // Again, depending on the distanceCode's value there might be additional bits in data, // which we need to combine with distanceCode to get the actual distance. let extraDistance = distanceCode == 0 || distanceCode == 1 ? 0 : ((distanceCode >> 1) - 1) // And yes, distanceCode is not a first part of distance but rather an index for special array. let distance = HuffmanTree.Constants.distanceBase[distanceCode] + pointerData.intFromBits(count: extraDistance) // We should repeat last 'distance' amount of data. // The amount of times we do this is round(length / distance). // length actually indicates the amount of data we get from this nextSymbol. let repeatCount: Int = length / distance let arrayToRepeat = Array(repeating: out[out.count - distance..