// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information import Foundation import BitByteData /// Represents the header of a Zlib archive. public struct ZlibHeader: Sendable { /// Levels of compression which can be used to create Zlib archive. public enum CompressionLevel: Int, Sendable { /// 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 `.deflate` for Zlib archives. public let compressionMethod: CompressionMethod = .deflate /// Level of compression used in archive. public let compressionLevel: CompressionLevel /// Size of 'window': moving interval of data which was used to make archive. public let windowSize: Int /** Initializes the structure with the values from Zlib `archive`. If data passed is not actually a Zlib archive, `ZlibError` will be thrown. - Parameter archive: Data archived with zlib. - Throws: `ZlibError`. It may indicate that either archive is damaged or it might not be archived with Zlib at all. */ public init(archive data: Data) throws { let reader = LsbBitReader(data: data) try self.init(reader) } init(_ reader: LsbBitReader) throws { // Valid Zlib header must contain at least 2 bytes of data. guard reader.bytesLeft >= 2 else { throw ZlibError.wrongCompressionMethod } // compressionMethod and compressionInfo combined are needed later for integrity check. let cmf = reader.byte() // First four bits are compression method. // Only compression method = 8 (DEFLATE) is supported. let compressionMethod = cmf & 0xF guard compressionMethod == 8 else { throw ZlibError.wrongCompressionMethod } // Remaining four bits indicate window size. // For Deflate it must not be more than 7. let compressionInfo = (cmf & 0xF0) >> 4 guard compressionInfo <= 7 else { throw ZlibError.wrongCompressionInfo } let windowSize = 1 << (compressionInfo.toInt() + 8) self.windowSize = windowSize // fcheck, fdict and compresionLevel together make flags byte which is used in integrity check. let flags = reader.byte() // First five bits are fcheck bits which are used for integrity check: // let fcheck = flags & 0x1F // Sixth bit indicate if archive contain Adler-32 checksum of preset dictionary. let fdict = (flags & 0x20) >> 5 // Remaining bits indicate compression level. guard let compressionLevel = ZlibHeader.CompressionLevel(rawValue: (flags.toInt() & 0xC0) >> 6) else { throw ZlibError.wrongCompressionLevel } self.compressionLevel = compressionLevel 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 } } }