ZLIB/GZIP DEFLATE 圧縮データの解凍処理は難しくないので作ってみる(Swift版)

# 解凍処理の内容
主に、二種類の復号処理で解凍出来ます。

1. LZ 符号の復号
2. ハフマン符号の復号

## LZ 符号の復号
次のようなイメージです。

```text:LZ符号の例
LZ符号:
    'code length, literal/' {6,16} ' alphabet, distance' {11,19} 'ab' {9,2}

復号処理部 = {文字数, 前方距離}
    { 6,16} -> 'length'
    {11,19} -> ' alphabet, '
    { 9, 2} -> 'ababababa'

復号結果:
    'code length, literal/length alphabet, distance alphabet, abababababa'
```

何文字前から、何文字複写する、というだけなので処理内容は簡単です。

## ハフマン符号の復号
特定のビット並び（可変長）に対して、文字が割り当てられています。

```text:ハフマン符号の辞書例
辞書:
    文字 パターン
    ' ' 1110
    'a' 100  
    'c' 10100
    'd' 1111 
    'e' 000  
    'f' 001  
    'h' 10101
    'm' 010  
    'n' 10110
    'o' 10111
    'p' 11000
    'r' 11001
    's' 011  
    't' 11010
    'u' 11011
```

復号は、例えば 100 のビット列がきたら 'a' の文字に置き換る、という処理を繰り返します。

```text:ハフマン符号の例
ハフマン符号:
    10101110110010010101001011011101010010111010110001100100001101100011111110111110011010100
復号結果:
    'huffman compressed data'

符号: 10101 11011 001 001 010 100 10110 1110 10100 10111 010 11000 11001 000 011 011 000 1111 1110 1111 100 11010 100
復号: 'h'   'u'   'f' 'f' 'm' 'a' 'n'   ' '  'c'   'o'   'm' 'p'   'r'   'e' 's' 's' 'e' 'd'  ' '  'd'  'a' 't'   'a'
```

こんな感じです。

# 解凍プログラム
実行効率は重視していないので、かなり遅いです。

以下、XCode 12.4 の Console アプリとして作った解凍プログラム。

<details><summary>解凍プログラム（短くないので折りたたみ）</summary><div>

```swift:main.swift
import Foundation

/**
 テーブル参照によるビット列反転
 
 静的関数のみ
 - inverse(x,width) : 指定幅のビット列反転
 - inverse8(x) : 8 ビットのビット列反転
 - inverse16(x) : 16 ビットのビット列反転
 */
struct BitInverse {
    /// 8ビット分の変換テーブル
    static let table = Array<UInt>(unsafeUninitializedCapacity: 256) {
        buffer, size in
        for n in 0..<256 {
            var x = UInt(n)
            let b0: UInt = ((x & 0x80) != 0) ? 0x01 : 0
            let b1: UInt = ((x & 0x40) != 0) ? 0x02 : 0
            let b2: UInt = ((x & 0x20) != 0) ? 0x04 : 0
            let b3: UInt = ((x & 0x10) != 0) ? 0x08 : 0
            let b4: UInt = ((x & 0x08) != 0) ? 0x10 : 0
            let b5: UInt = ((x & 0x04) != 0) ? 0x20 : 0
            let b6: UInt = ((x & 0x02) != 0) ? 0x40 : 0
            let b7: UInt = ((x & 0x01) != 0) ? 0x80 : 0
            buffer[n] = b0 | b1 | b2 | b3 | b4 | b5 | b6 | b7
        }
        size = 256
    }

    /// 下位 8 ビットのビット列反転
    /// - Parameter x: 対象値
    /// - Returns: ビット列反転値
    static func inverse8(_ x: UInt) -> UInt {
        return table[Int(x & 0xff)]
    }

    /// 下位 16 ビットのビット列反転
    /// - Parameter x: 対象値
    /// - Returns: ビット列反転値
    static func inverse16(_ x: UInt) -> UInt {
        let v = Int(x & 0xffff)
        let l = table[v >> 8]
        let h = table[v & 0xff]
        return (h << 8) | l
    }

    /// 指定幅下位ビットのビット列反転
    /// - Parameters:
    ///   - x: 対象値
    ///   - width: 反転するビット数（上限は 16）
    /// - Returns: ビット列反転値
    static func inverse(_ x: UInt, _ width: UInt) -> UInt {
        return inverse16(x) >> (16 - width)
    }
}

/**
 Adler-32 算出用
 */
class Adler32 {
    /// Adler-32 下位 16 ビット
    private var valueL: UInt = 1
    /// Adler-32 上位 16 ビット
    private var valueH: UInt = 0

    /// Adler-32 値
    var value: UInt {
        get {
            return ((valueH << 16) | valueL)
        }
    }

    /// 初期状態にする
    func reset() {
        valueL = 1
        valueH = 0
    }

    /// 1 バイト分の Adler-32 更新
    /// - Parameter x: バイト データ
    func update(_ x: UInt8) {
        valueL = (valueL + UInt(x)) % 65521
        valueH = (valueH + valueL) % 65521
    }
}

/**
 CRC-32 算出用
 */
class CRC32 {
    /// 8 ビット分の演算用テーブル
    static let table = Array<UInt>(unsafeUninitializedCapacity: 256) {
        buffer, size in
        let p = UInt(0xedb88320)
        for x in 0..<256 {
            var y = UInt(x)
            for _ in 0...7 {
                y = (y >> 1) ^ (((y & 1) != 0) ? p : 0)
            }
            buffer[x] = y
        }
        size = 256
    }
    
    /// 内部算出値
    private var crc: UInt = 0xffff_ffff

    /// CRC-32 値
    var value: UInt {
        get {
            return crc ^ 0xffff_ffff
        }
    }

    /// 初期状態にする
    func reset() {
        crc = 0xffff_ffff
    }

    /// 1バイト分の CRC-32 更新
    /// - Parameter x: バイト データ
    func update(_ x: UInt8) {
        crc = (crc >> 8) ^ CRC32.table[Int((crc ^ UInt(x)) & 0xff)]
    }
}

/// I/O エラー例外
enum IOError : Error {
    /// 読み込み用で開けなかった
    case readOpen(String)

    /// 読み込みに失敗
    case readData(String)

    /// データがない
    case readEnd(String)

    /// 書き込み用で開けなかった
    case writeOpen(String)

    /// 書き込みに失敗
    case writeData(String)
}

/**
 バイト データ列の逐次取得用プロトコル
 
 - Requires: 1バイトのデータ取得 :  func read() throws -> UInt
 */
protocol InputByteStream {
    /// 1バイトのデータ取得
    /// - Returns: 1 バイトのデータ
    func read() throws -> UInt
}

/**
 バイト データ列の逐次出力用プロトコル
 
 - Requires: 1 バイトのデータ出力 :  func write(data: Int) throws
 */
protocol OutputByteStream {
    /// 1 バイトのデータ出力
    /// - Parameter data: 1 バイトのデータ
    func write(_ data: UInt8) throws
}

/**
 InputByteStream / OutputByteStream 用の小道具
 
 静的関数のみ
 + リトル・エンディアン用
 - read16le(stream) : 16 ビット幅のデータを取得
 - read32le(stream) : 32 ビット幅のデータを取得
 + ビッグ・エンディアン用
 - read16be(stream) : 16 ビット幅のデータを取得
 - read32be(stream) : 32 ビット幅のデータを取得
 */
struct ByteStreamUtil {
    /// リトル・エンディアン用 16 ビット幅のデータを取得
    /// - Parameter istream: InputByteStream プロトコル実装オブジェクト
    /// - Throws: istream 依存
    /// - Returns: リトル・エンディアンの 16 ビット・データ
    static func read16le(_ istream: InputByteStream) throws -> UInt {
        let l = try istream.read()
        let h = try istream.read()
        return l | (h << 8)
    }

    /// リトル・エンディアン用 32 ビット幅のデータを取得
    /// - Parameter istream: InputByteStream プロトコル実装オブジェクト
    /// - Throws: istream 依存
    /// - Returns: リトル・エンディアンの 32 ビット・データ
    static func read32le(_ istream: InputByteStream) throws -> UInt {
        let l = try ByteStreamUtil.read16le(istream)
        let h = try ByteStreamUtil.read16le(istream)
        return l | (h << 16)
    }

    /// ビッグ・エンディアン用 16 ビット幅のデータを取得
    /// - Parameter istream: InputByteStream プロトコル実装オブジェクト
    /// - Throws: istream 依存
    /// - Returns: ビッグ・エンディアンの 16 ビット・データ
    static func read16be(_ istream: InputByteStream) throws -> UInt {
        let h = try istream.read()
        let l = try istream.read()
        return (h << 8) | l
    }

    /// ビッグ・エンディアン用 32 ビット幅のデータを取得
    /// - Parameter istream: InputByteStream プロトコル実装オブジェクト
    /// - Throws: istream 依存
    /// - Returns: ビッグ・エンディアンの 32 ビット・データ
    static func read32be(_ istream: InputByteStream) throws -> UInt {
        let h = try ByteStreamUtil.read16be(istream)
        let l = try ByteStreamUtil.read16be(istream)
        return (h << 16) | l
    }
}

/**
 ファイルからバイト データ列の逐次取得
 */
class InputByteStreamFile : InputByteStream {
    /// ファイル名
    var name: String

    /// 入力ストリーム
    private var stream: InputStream

    /// 初期化
    /// - Parameter path: パス名
    /// - Throws: IOErrpr.readOpen
    init(_ path: String) throws {
        guard let s = InputStream(fileAtPath: path) else {
            throw IOError.readOpen(path)
        }
        s.open()
        name = path
        stream = s
    }
    
    /// 1 バイトの読み取り
    /// - Throws: IOError.readData, IOError.readEnd
    /// - Returns: 1 バイトのデータ
    func read() throws -> UInt {
        var buffer = [UInt8(0)]
        let res = stream.read(&buffer, maxLength: 1)
        if res < 0 {
            throw IOError.readData(name)
        }
        if res != 1 {
            throw IOError.readEnd(name)
        }
        return UInt(buffer[0])
    }
}

/**
 バイト データ列の逐次取得と CRC-32 の算出
 */
class InputByteStreamCRC32 : InputByteStream {
    /// バイト列の入力元
    private let input: InputByteStream

    /// CRC-32 算出用
    private let crc = CRC32()

    /// CRC-32 値
    var crc32 : UInt {
        get {
            return crc.value
        }
    }

    /// 初期化
    /// - Parameter istream: InputByteStream プロトコル実装オブジェクト
    init(_ istream: InputByteStream) {
        input = istream
    }

    /// 1 バイトの読み取りと CRC-32 の更新
    /// - Throws: input 依存
    /// - Returns: 1 バイトのデータ
    func read() throws -> UInt {
        let data = try input.read()
        crc.update(UInt8(data))
        return data
    }
}

/**
 InputByteStream を使ったビット単位でのデータを取得
 */
class InputBitStream {
    /// 一時的なビット データのバッファ
    private var data: UInt = 0

    /// data で保持されているビット数
    private var size: UInt = 0

    /// InputByteStream プロトコル実装オブジェクト
    let input: InputByteStream

    /// 初期化
    /// - Parameter istream: InputByteStream プロトコル実装オブジェクト
    init(_ istream: InputByteStream) {
        input = istream
    }

    /// バイト境界までのデータを破棄する
    func align() {
        let gap = size & 7
        data >>= gap
        size -= gap
    }

    /// 指定ビット数のデータを取得する
    /// - Parameter width: ビット数（最大 16 ビット）
    /// - Throws: input 依存
    /// - Returns: 指定ビット数のデータ
    func read(_ width: UInt) throws -> UInt {
        while size < width {
            let d = try input.read()
            data |= d << size
            size += 8
        }
        let value = data & ((1 << width) - 1)
        data >>= width
        size -= width
        return value
    }
}

/**
 ファイルを全て読み込む方式の InputByteStream
 */
class FileReadAll : InputByteStream {
    /// ファイル名
    let name: String

    /// ファイルの全データ
    let data: Data

    /// InputByteStream 用データ位置
    var position: Data.Index = 0

    /// 初期化 :ファイルを全て読み込む
    /// - Parameter path: パス名
    /// - Throws: IOError.readOpen, IOError.readData および FileManager 依存
    init(_ path: String) throws {
        guard let contents = FileManager().contents(atPath: path) else {
            throw IOError.readOpen(path)
        }
        name = path
        data = contents
    }

    /// InputByteStream.read の実装
    /// - Throws: IOError.readEnd
    /// - Returns: 1 バイトのデータ
    func read() throws -> UInt {
        if position < data.count {
            let value = data[position]
            position += 1
            return UInt(value)
        }
        throw IOError.readEnd(name)
    }
}

/**
 メモリ上に解凍データを保持
 */
class OutputByteStreamMemory : OutputByteStream {
    /// 解凍データ
    var data = Data()

    /// OutputByteStream.write の実装
    /// - Parameter byteData: 出力データ
    /// - Throws: なし
    func write(_ byteData: UInt8) throws {
        data.append(byteData)
    }

    /// 解凍データをファイルへ出力
    /// - Parameter path: パス名
    /// - Throws: IOError.writeOpen および FileManager 依存
    func writeToFile(_ path: String) throws {
        if !FileManager().createFile(atPath: path, contents: data) {
            throw IOError.writeOpen(path)
        }
    }
}

/**
 ファイルへバイト データ列を逐次出力
 */
class OutputByteStreamFile : OutputByteStream {
    /// ファイル名
    var name: String

    /// 出力ストリーム
    private var stream: OutputStream

    /// 初期化
    /// - Parameter path: パス名
    /// - Throws: IOError.writeOpen
    init(_ path: String) throws {
        guard let s = OutputStream(toFileAtPath: path, append: false) else {
            throw IOError.writeOpen(path)
        }
        s.open()
        name = path
        stream = s
    }

    /// 1 バイトの書き込み
    /// - Parameter data: 1 バイトの出力データ
    /// - Throws: FileHandle 依存
    func write(_ data: UInt8) throws {
        var byte = data
        let res = stream.write(&byte, maxLength: 1)
        if res != 1 {
            throw IOError.writeData(name)
        }
    }
}

/**
 バイト データ列の逐次出力と Adler-32 の算出
 */
class OutputByteStreamAdler32 : OutputByteStream {
    /// バイト列の出力先
    let output: OutputByteStream

    /// Adler-32 算出用
    private var adler = Adler32()

    /// Adler-32 値
    var adler32 : UInt {
        get {
            return adler.value
        }
    }

    /// 初期化
    /// - Parameter ostream: OutputByteStream プロトコル実装オブジェクト
    init(_ ostream: OutputByteStream) {
        output = ostream
    }

    /// 1 バイトの出力と Adler-32 の更新
    /// - Parameter byteData: 1 バイトの出力データ
    /// - Throws: output 依存
    func write(_ byteData: UInt8) throws {
        try output.write(byteData)
        adler.update(byteData)
    }
}

/**
 バイト データ列の逐次出力と CRC-32 の算出
 */
class OutputByteStreamCRC32 : OutputByteStream {
    /// バイト列の出力先
    let output: OutputByteStream

    /// CRC-32 算出用
    private var crc = CRC32()

    /// CRC-32 値
    var crc32 : UInt {
        get {
            return crc.value
        }
    }

    /// 解凍サイズ
    var isize: UInt = 0

    /// 初期化
    /// - Parameter ostream: OutputByteStream プロトコル実装オブジェクト
    init(_ ostream: OutputByteStream) {
        output = ostream
    }

    /// 1 バイトのデータ出力と CRC-32 および出力サイズの更新
    /// - Parameter byteData: 1 バイトの出力データ
    /// - Throws: output 依存
    func write(_ byteData: UInt8) throws {
        try output.write(byteData)
        crc.update(byteData)
        isize += 1
    }
}

/**
 LZ 復号出力
 
 "文字" または "{長さ,距離}" 形式の出力を実行します
 */
class OutputLZStream {
    /// LZ 複写用窓
    private var window = Data(count: 0x8000)

    /// 窓中の位置
    private var position = 0

    /// 窓アクセス時のインデックス範囲マスク
    private var mask = 0x7fff

    /// バイト列の出力先
    let output: OutputByteStream

    /// 初期化
    /// - Parameter ostream: OutputByteStream プロトコル実装オブジェクト
    init(_ ostream: OutputByteStream) {
        output = ostream
    }

    /// 1 バイト（文字）のデータ出力と LZ 複写用窓の更新
    /// - Parameter literal: 文字
    /// - Throws: output 依存
    func write(_ literal: UInt8) throws {
        try output.write(literal)
        window[position] = literal
        position = (position + 1) & mask
    }

    /// LZ 符号 "{長さ,距離}" での複写出力
    /// - Parameters:
    ///   - length: 長さ
    ///   - distance: 距離
    /// - Throws: output 依存
    func write(_ length: UInt, _ distance: UInt) throws {
        var offset = (position - Int(distance)) & mask
        for _ in 0..<length {
            try write(window[offset])
            offset = (offset + 1) & mask
        }
    }
}

/**
 動的辞書またはハフマン復号の例外
 */
enum HuffmanDecodeError : Error {
    /// 符号長辞書エラー
    case length

    // 文字（長）辞書エラー
    case literal

    // 距離辞書エラー
    case distance
}

/**
 ハフマン復号用プロトコル
 
 - Requires: 復号： func decode(stream: InputBitStream) throws -> UInt
 */
protocol HuffmanDecoder {
    func decode(_ stream: InputBitStream) throws -> UInt
}

/**
 文字（長）用固定辞書のハフマン復号
 */
class FixedLiteralHuffmanDecoder : HuffmanDecoder {
    /// ビット列から文字（長）を復号
    /// - Parameter stream: ビット列の入力元
    /// - Throws: stream 依存
    /// - Returns: 復号文字（長）
    func decode(_ stream: InputBitStream) throws -> UInt {
        var code = BitInverse.inverse(try stream.read(7), 7)
        if code < 0x18 {
            return code + 256
        }
        code = (code << 1) | (try stream.read(1))
        if code < 0xc0 {
            return code - 0x30
        }
        if code < 0xc8 {
            return code - 0xc0 + 280
        }
        code = (code << 1) | (try stream.read(1))
        return code - 0x190 + 144
    }
}

/**
 距離情報用固定辞書のハフマン復号
 */
class FixedDistanceHuffmanDecoder : HuffmanDecoder {
    /// ビット列から距離情報を復号
    /// - Parameter stream: ビット列の入力元
    /// - Throws: stream 依存
    /// - Returns: 復号距離情報
    func decode(_ stream: InputBitStream) throws -> UInt {
        return BitInverse.inverse8((try stream.read(5)) << 3)
    }
}

/**
 動的辞書のハフマン復号
 */
class DynamicHuffmanDecoder : HuffmanDecoder {
    /// 符号長に対する文字（長）一覧
    private class LengthInfo {
        /// 符号長
        var length: UInt = 0

        /// LengthInfo 一覧における直前の length との差
        var lengthStep: UInt = 0

        /// 符号範囲の開始
        var codeStart: UInt = 0

        /// 符号範囲の終了 + 1
        var codeEnd: UInt = 0

        /// 文字の一覧
        private var literals: [UInt] = []

        /// 文字の数
        var count: UInt {
            get {
                return UInt(literals.count)
            }
        }

        /// 初期化
        /// - Parameter width: 符号長
        init(_ width: UInt) {
            length = width
        }

        /// 文字を追加
        /// - Parameter literal: 文字
        func append(_ literal: UInt) {
            literals.append(literal)
        }
        
        subscript(_ index: UInt) -> UInt {
            return literals[Int(index)]
        }
    }

    /// LengthInfo の表
    private var table: [LengthInfo] = []

    /// 例外オブジェクト
    let error: HuffmanDecodeError

    /// 初期化
    /// - Parameters:
    ///   - maxLength: ハフマン符号の最大長
    ///   - errcode: 例外オブジェクト
    init(_ maxLength: UInt, _ errcode: HuffmanDecodeError) {
        error = errcode
        for n in 0...maxLength {
            table.append(LengthInfo(n))
        }
    }

    /// 文字（長）と、符号長を設定する
    /// - Parameters:
    ///   - length: 符号長（ビット数）
    ///   - literal: 文字（長）
    func set(_ length: UInt, _ literal: UInt) {
        if length > 0 {
            table[Int(length)].append(literal)
        }
    }

    /// 文字（長）と、符号長を設定して、復号の準備をする
    /// - Parameter lengths: 符号長一覧
    /// - Throws: error 依存
    func set(_ lengths: [UInt]) throws {
        for literal in 0..<lengths.count {
            set(lengths[literal], UInt(literal))
        }
        try prepareDecode()
    }

    /// 文字（長）と、符号長を設定して、復号の準備をする
    /// - Parameter lengths: 符号長一覧
    /// - Throws: error 依存
    func set(_ lengths: ArraySlice<UInt>) throws {
        let start = lengths.startIndex
        for literal in 0..<lengths.count {
            set(lengths[literal + start], UInt(literal))
        }
        try prepareDecode()
    }
    /// 復号の準備
    /// - Throws: HuffmanDecodeError
    func prepareDecode() throws {
        var newTable: [LengthInfo] = []
        for li in table {
            if li.count != 0 {
                newTable.append(li)
            }
        }
        if newTable.count == 0 {
            throw error
        }
        table = newTable

        var last: UInt = 0
        var code: UInt = 0
        for li in table {
            let length = li.length
            let count = li.count
            let step = length - last
            let start = code << step
            let end = start + count

            li.lengthStep = step
            li.codeStart = start
            li.codeEnd = end

            last = length
            code = end
        }
        if code > (1 << last) {
            throw error
        }
    }

    /// 復号
    /// - Parameter stream: ビット列の入力元
    /// - Throws: HuffmanDecodeError
    /// - Returns: 復号した情報
    func decode(_ stream: InputBitStream) throws -> UInt {
        var pending: UInt = 0
        var code: UInt = 0
        for li in table {
            let step = li.lengthStep
            let start = li.codeStart
            let end = li.codeEnd
            code <<= step
            pending += step
            if code < end {
                code |= BitInverse.inverse(try stream.read(pending), pending)
                if code < end {
                    return li[code - start]
                }
                pending = 0
            }
        }
        throw error
    }
}

/**
 圧縮ブロック解凍時の例外
 */
enum InflateBlockError : Error {
    case btype                   // BTYPE=11
    case btype00Len(UInt, UInt)  // LEN != ~NLEN
    case btype10Length           // 動的辞書復号エラー
    case btype10Literal          // 文字（長）の動的辞書エラー
    case btype10Distance         // 距離の動的辞書エラー
}

/**
 圧縮ブロックの解凍
 */
class InflateBlock {
    /// 圧縮ブロックの全体を解凍
    /// - Parameters:
    ///   - istream: 入力元（バイト列）
    ///   - ostream: 出力先
    /// - Throws: HuffmanDecodeError, InflateBlockError および istream, ostream 依存
    static func inflate(_ istream: InputByteStream, _ ostream: OutputByteStream) throws {
        let input = InputBitStream(istream)
        let output = OutputLZStream(ostream)
        var bfinal = false
        while !bfinal {
            bfinal = (try input.read(1) != 0)
            let btype = try input.read(2)
            switch btype {
            case 0:
                try blockType00(input, output)
            case 1:
                try blockType01(input, output)
            case 2:
                try blockType10(input, output)
            default:
                throw InflateBlockError.btype
            }
        }
        input.align()
    }

    /// BTYPE=00 （非圧縮）の処理
    /// - Parameters:
    ///   - input: 入力元（ビット列）
    ///   - output: 出力先（LZ 符号）
    /// - Throws: input, output 依存
    private static func blockType00(_ input: InputBitStream, _ output: OutputLZStream) throws {
        input.align()
        let len = try input.read(16)
        let nlen = try input.read(16)
        if len != (nlen ^ 0xffff) {
            throw InflateBlockError.btype00Len(len, nlen)
        }
        for _ in 1...len {
            try output.write(UInt8(try input.read(8)))
        }
    }

    /// BTYPE=01 （固定辞書による圧縮）の解凍
    /// - Parameters:
    ///   - input: 入力元（ビット列）
    ///   - output: 出力先（LZ 符号）
    /// - Throws: HuffmanDecodeError, InflateBlockError および input, output 依存
    private static func blockType01(_ input: InputBitStream, _ output: OutputLZStream) throws {
        try blockData(input, output, FixedLiteralHuffmanDecoder(), FixedDistanceHuffmanDecoder())
    }

    /// BTYPE=10 用テーブル
    private static let btype10LengthOrder = [
        16, 17, 18,
        0,  8,  7,  9,  6, 10,  5, 11,
        4, 12,  3, 13,  2, 14,  1, 15,
    ]

    /// BTYPE=10 （動的辞書による圧縮）の解凍
    /// - Parameters:
    ///   - input: 入力元（ビット列）
    ///   - output: 出力先（LZ 符号）
    /// - Throws: HuffmanDecodeError, InflateBlockError および input, output 依存
    private static func blockType10(_ input: InputBitStream, _ output: OutputLZStream) throws {
        let lengthDecoder = DynamicHuffmanDecoder(7, HuffmanDecodeError.length)
        let literalDecoder = DynamicHuffmanDecoder(15, HuffmanDecodeError.literal)
        let distanceDecoder = DynamicHuffmanDecoder(15, HuffmanDecodeError.distance)
        let hlit = try input.read(5) + 257
        let hdist = try input.read(5) + 1
        let hclen = try input.read(4) + 4
        var codeLength = Array<UInt>(repeating: 0, count: 19)
        for hcindex in 0..<Int(hclen) {
            codeLength[btype10LengthOrder[hcindex]] = try input.read(3)
        }
        try lengthDecoder.set(codeLength)
        let lengths = try dynamicDictionary(input, lengthDecoder, hlit + hdist)
        try literalDecoder.set(lengths[0..<Int(hlit)])
        try distanceDecoder.set(lengths[Int(hlit)...])
        try blockData(input, output, literalDecoder, distanceDecoder)
    }

    /// 動的辞書の生成
    /// - Parameters:
    ///   - input: 入力元（ビット列）
    ///   - lengthDecoder: 符号長の復号辞書
    ///   - count: 文字（長）の数
    ///   - error: エラー時の例外
    /// - Throws: error で指定
    /// - Returns: 符号長一覧
    private static func dynamicDictionary(
        _ input: InputBitStream,
        _ lengthDecoder: HuffmanDecoder,
        _ count: UInt
    ) throws -> [UInt] {
        var output: [UInt] = []
        var literal: UInt = 0
        var last: UInt = 0
        while literal < count {
            let length = try lengthDecoder.decode(input)
            var repcnt: UInt = 0
            switch length {
            case 0...15:
                output.append(length)
                last = length
                literal += 1
                continue
            case 16:
                repcnt = 3 + (try input.read(2))
            case 17:
                repcnt = 3 + (try input.read(3))
                last = 0
            case 18:
                repcnt = 11 + (try input.read(7))
                last = 0
            default:
                throw InflateBlockError.btype10Length
            }
            literal += repcnt
            if literal > count {
                throw InflateBlockError.btype10Length
            }
            for _ in 1...repcnt {
                output.append(last)
            }
        }
        return output
    }

    /// 圧縮データの解凍
    /// - Parameters:
    ///   - input: 入力元（ビット列）
    ///   - output: 出力先（LZ 符号）
    ///   - literalDictionary: 文字（長）の辞書
    ///   - distanceDictionary: 距離の辞書
    /// - Throws: HuffmanDecodeError, InflateBlockError および input, output 依存
    private static func blockData(
        _ input: InputBitStream,
        _ output: OutputLZStream,
        _ literalDictionary: HuffmanDecoder,
        _ distanceDictionary: HuffmanDecoder
    ) throws {
        while true {
            let literal = try literalDictionary.decode(input)
            var length: UInt = 0
            switch literal {
            case 0...255:
                try output.write(UInt8(literal))
                continue
            case 256:
                return
            case 257...264:
                length = literal - 257 + 3
            case 265...284:
                let lenCode = literal - 261
                let lenBits = lenCode >> 2
                let lenBase = (lenCode & 3) + 4
                length = 3 + (lenBase << lenBits) + (try input.read(lenBits))
                break
            case 285:
                length = 258
            default:
                throw HuffmanDecodeError.literal
            }

            var distance = try distanceDictionary.decode(input)
            switch (distance) {
            case 0...3:
                distance += 1
            case 4...29:
                let distCode = distance - 2
                let distBits = distCode >> 1
                let distBase = (distCode & 1) + 2
                distance = 1 + (distBase << distBits) + (try input.read(distBits))
            default:
                throw HuffmanDecodeError.distance
            }
            try output.write(length, distance)
        }
    }
}

/**
 ZLIB 圧縮データ形式解凍時の例外
 */
enum ZLIBInflateError : Error {
    case cm(UInt)             // 未知の圧縮形式
    case cminfo(UInt)         // CMINFO エラー
    case fdict                // FDICT エラー
    case fcheck(UInt)         // FCHECK エラー
    case adler32(UInt, UInt)  // Adler-32 不一致
}

/**
 ZLIB 圧縮データ形式の解凍
 */
class ZLIBInflate {
    /// 圧縮形式と付随情報
    let cmf: UInt

    /// FLaGs: フラグ類
    let flg: UInt

    /// Adler-32 値
    let adler32: UInt

    /// ZLIB 圧縮形式の解凍
    /// - Parameters:
    ///   - input: 入力元
    ///   - output: 出力先
    /// - Throws: HuffmanDecodeError, InflateBlockError, ZLIBInflateError および input, output 依存
    init(_ input: InputByteStream, _ output: OutputByteStream) throws {
        cmf = try input.read()
        if (cmf & 0x0f) != 8 {
            throw ZLIBInflateError.cm(cmf & 0x0f)
        }
        if (cmf >> 4) >= 8 {
            throw ZLIBInflateError.cminfo(cmf >> 4)
        }

        flg = try input.read()
        if (flg & 0x20) != 0 {
            throw ZLIBInflateError.fdict
        }

        let fcheck = cmf * 256 + flg
        if (fcheck % 31) != 0 {
            throw ZLIBInflateError.fcheck(fcheck)
        }

        let ostream = OutputByteStreamAdler32(output)
        try InflateBlock.inflate(input, ostream)
        adler32 = try ByteStreamUtil.read32be(input)
        if adler32 != ostream.adler32 {
            throw ZLIBInflateError.adler32(adler32, ostream.adler32)
        }
    }
}

/**
 GZIP 圧縮データ形式解凍時の例外
 */
enum GZIPInflateError : Error {
    case id1(UInt)          // 未知の ID1
    case id2(UInt)          // 未知の ID2
    case cm(UInt)           // 未知の圧縮形式
    case hcrc(UInt, UInt)   // HCRC 不一致
    case crc32(UInt, UInt)  // CRC32 不一致
    case isize(UInt, UInt)  // ISIZE 不一致
}

/**
 GZIP 圧縮データ形式の解凍
 */
class GZIPInflate {
    /// ID1
    let id1: UInt

    /// ID2
    let id2: UInt

    /// Compression Method
    let cm: UInt

    /// FLaGs
    let flg: UInt

    /// MTIME
    let mtime: UInt

    /// eXtra FLags
    let xfl: UInt

    /// OS
    let os: UInt

    /// FLG.FEXTRA=1 : 拡張情報
    let extra: Array<UInt8>

    /// FLG.FNAME=1 : 名前
    let name: String

    /// FLG.FCOMMENT=1 : コメント
    let comment: String

    /// FLG.FHCRC=1 : ヘッダ部の CRC32 下位 16 ビット
    let hcrc: UInt

    /// CRC-32 値
    let crc32: UInt

    /// 元データ（解凍）サイズ
    let isize: UInt

    /// ZLIB 圧縮形式の解凍
    /// - Parameters:
    ///   - input: 入力元
    ///   - output: 出力先
    /// - Throws: HuffmanDecodeError, InflateBlockError, GZIPInflateError および input, output 依存
    init(_ input: InputByteStream, _ output: OutputByteStream) throws {
        let istream = InputByteStreamCRC32(input)
        id1 = try istream.read()
        if id1 != 0x1f {
            throw GZIPInflateError.id1(id1)
        }
        id2 = try istream.read()
        if id2 != 0x8b {
            throw GZIPInflateError.id1(id1)
        }
        cm = try istream.read()
        if cm != 8 {
            throw GZIPInflateError.id1(id1)
        }
        flg = try istream.read()
        mtime = try ByteStreamUtil.read32le(istream)
        xfl = try istream.read()
        os = try istream.read()

        var newExtra = Array<UInt8>()
        if (flg & 0x04) != 0 {
            for _ in 1...(try ByteStreamUtil.read16le(istream)) {
                newExtra.append(UInt8(try istream.read()))
            }
        }
        extra = newExtra

        var newName = ""
        if (flg & 0x08) != 0 {
            while true {
                let c = try istream.read()
                if c == 0 {
                    break
                }
                newName += String(c)
            }
        }
        name = newName

        var newComment = ""
        if (flg & 0x10) != 0 {
            while true {
                let c = try istream.read()
                if c == 0 {
                    break
                }
                newComment += String(c)
            }
        }
        comment = newComment

        let sum16 = istream.crc32 & 0xffff
        var crc16 = sum16
        if (flg & 0x02) != 0 {
            crc16 = try ByteStreamUtil.read16le(istream)
            if crc16 != sum16 {
                throw GZIPInflateError.hcrc(crc16, sum16)
            }
        }
        hcrc = crc16

        let ostream = OutputByteStreamCRC32(output)
        try InflateBlock.inflate(input, ostream)

        crc32 = try ByteStreamUtil.read32le(istream)
        let sum32 = ostream.crc32
        if crc32 != sum32 {
            throw GZIPInflateError.crc32(crc32, sum32)
        }

        isize = try ByteStreamUtil.read32le(istream)
        if isize != ostream.isize {
            throw GZIPInflateError.isize(isize, ostream.isize)
        }
    }
}

/*
 gzip 形式の解凍処理
 */

// ファイルをメモリ上に読み込み、メモリ上に解凍してファイルへ書き込む
func gunzipMemory(_ input_file: String, _ output_file: String) throws {
    let istream = try FileReadAll(input_file)
    let ostream = OutputByteStreamMemory()
    let _ = try GZIPInflate(istream, ostream)
    try ostream.writeToFile(output_file)
}

// ファイルへのアクセスは、解凍処理中に行う
func gunzipDirect(_ input_file: String, _ output_file: String) throws {
    let istream = try InputByteStreamFile(input_file)
    let ostream = try OutputByteStreamFile(output_file)
    let _ = try GZIPInflate(istream, ostream)
}

/*
 gzip 形式の解凍処理
 */

func gunzip(_ input_file:String, _ output_file: String, _ method: (String, String) throws -> Void) -> Bool {
    do {
        try method(input_file, output_file)
        return true
    } catch is HuffmanDecodeError {
        print("error: Corrupted BTYPE=10: \(input_file)")
    } catch InflateBlockError.btype {
        print("error: BTYPE=11: : \(input_file)")
    } catch InflateBlockError.btype00Len(let len, let nlen) {
        print("error: BTYPE=00: \(len) != \(nlen ^ 0xffff): : \(input_file)")
    } catch InflateBlockError.btype10Length,
            InflateBlockError.btype10Literal,
            InflateBlockError.btype10Distance {
        print("error: Corrupted BTYPE=10: \(input_file)")
    } catch GZIPInflateError.id1(let id1) {
        print("error: id1=\(id1): \(input_file)")
    } catch GZIPInflateError.id2(let id2) {
        print("error: id2=\(id2): \(input_file)")
    } catch GZIPInflateError.cm(let cm) {
        print("error: cm=\(cm): \(input_file)")
    } catch GZIPInflateError.hcrc(let hcrc, let xcrc) {
        print("error: hcrc: \(hcrc) != \(xcrc): \(input_file)")
    } catch GZIPInflateError.crc32(let crc32, let xcrc) {
        print("error: crc32: \(crc32) != \(xcrc): \(input_file)")
    } catch GZIPInflateError.isize(let isize, let xsize) {
        print("error: isize: \(isize) != \(xsize): \(input_file)")
    } catch IOError.readOpen(let name) {
        print("error: open: \(name)")
    } catch IOError.readData(let name) {
        print("error: read: \(name)")
    } catch IOError.readEnd(let name) {
        print("error: not enough data: \(name)")
    } catch IOError.writeOpen(let name) {
        print("error: open: \(name)")
    } catch IOError.writeData(let name) {
        print("error: read: \(name)")
    } catch {
        print("error: \(error)")
    }
    return false
}

/*
 以下、コマンド・ライン処理
 */

class CommandLineParser {
    var cmd_args = CommandLine.arguments
    let program_path: String

    let description = [
        "Usage: \(String(CommandLine.arguments[0].split(separator: "/").last!)) " +
        "[options] input_file output_file",
        "",
        "options are:",
        "    -D  set direct mode.",
        "    -M  set memory mode.",
    ]

    var args = [String]()
    var option: [String: Any] = [
        "D" : false,
        "M" : true,
    ]

    enum OptionError : Error {
        case unknown(String)
        case invalid(String)
    }

    init() throws {
        program_path = cmd_args.removeFirst()
        do {
            try parse_args()
        } catch OptionError.invalid(let name) {
            print("bug: invalid option '\(name)' parameter")
        } catch OptionError.unknown(let name) {
            print("unknown option: \(name)")
            usage()
        }
    }

    func parse_args () throws {
        while cmd_args.count != 0 {
            var arg = cmd_args.removeFirst()
            if arg.count == 0 {
                continue
            }
            if arg.first != "-" {
                args.append(arg)
                continue
            }
            arg.removeFirst()
            if arg.count == 0 {
                args.append("-")
                continue
            }
            if arg.first != "-" {
                try parse_short_option(arg)
                continue
            }
            arg.removeFirst()
            try parse_long_option(arg)
        }
    }

    func parse_short_option(_ opts: String) throws {
        var arg = opts
        while arg.count != 0 {
            let key = String(arg.removeFirst())
            try update_option("-", key)
        }
    }

    func parse_long_option(_ name: String) throws {
        if let index = name.firstIndex(of: "=") {
            let key = String(name[name.startIndex ..< index])
            let value = String(name[name.index(after: index)...])
            try update_option("--", key, value)
        } else {
            try update_option("--", name)
        }
    }

    func update_option(_ prefix: String, _ key: String, _ opt: String? = nil) throws {
        let val = option[key]
        if val == nil {
            throw OptionError.unknown(prefix + key)
        }
        switch val {
        case is Bool:
            option[key] = !(val as! Bool)
        case is Int:
            option[key] = Int(next_arg(opt))
        case is String:
            option[key] = next_arg(opt)
        default:
            throw OptionError.invalid(prefix + key)
        }
    }
    func next_arg(_ opt: String?) -> String {
        return (opt == nil) ? args.removeFirst() : opt!
    }

    func usage() -> Never {
        for line in description {
            print(line)
        }
        exit(1)
    }
}

func main() -> Never {
    let cmdline = try! CommandLineParser()
    var args = cmdline.args
    if args.count != 2 {
        cmdline.usage()
    }
    let input_file = args.removeFirst()
    let output_file = args.removeFirst()
    if cmdline.option["D"] as! Bool  {
        exit(gunzip(input_file, output_file, gunzipDirect(_:_:)) ? 0 : 1)
    }
    exit(gunzip(input_file, output_file, gunzipMemory(_:_:)) ? 0 : 1)
}
main()
```

</div></details>

---

データ形式の詳細は、[Python 版](https://qiita.com/ikiuo/items/cd56a1e7a8790bd86073)の記事などを参照してください。