// Copyright (c) 2026 Timofey Solomko // Licensed under MIT License // // See LICENSE for license information import Foundation import BitByteData extension BZip2: CompressionAlgorithm { /** Compresses `data` with BZip2 algortihm. - Parameter data: Data to compress. - Note: Input data will be split into blocks of size 100 KB. Use `BZip2.compress(data:blockSize:)` function to specify the size of a block. */ public static func compress(data: Data) -> Data { return compress(data: data, blockSize: .one) } private static let blockMarker: [UInt8] = [ 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1 ] private static let eosMarker: [UInt8] = [ 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0 ] /** Compresses `data` with BZip2 algortihm, splitting data into blocks of specified `blockSize`. - Parameter data: Data to compress. - Parameter blockSize: Size of blocks in which `data` will be split. */ public static func compress(data: Data, blockSize: BlockSize) -> Data { let bitWriter = MsbBitWriter() // We intentionally use smaller block size for compression to account for potential data size expansion // after intial RLE, which seems to be not being expected by original BZip2 implementation. // In the worst case initial RLE causes expansion by a factor of 1.25, so 1000 / 1.25 = 800. let rawBlockSize = blockSize.sizeInKilobytes * 800 // BZip2 Header. bitWriter.write(unsignedNumber: 0x425a, bitsCount: 16) // Magic number = 'BZ'. bitWriter.write(unsignedNumber: 0x68, bitsCount: 8) // Version = 'h'. bitWriter.write(number: blockSize.headerByte, bitsCount: 8) // Block size. var totalCRC: UInt32 = 0 for i in stride(from: data.startIndex, to: data.endIndex, by: rawBlockSize) { let block = data[i..> 31) totalCRC ^= blockCRC // Start block header. bitWriter.write(bits: blockMarker) // Block magic number. bitWriter.write(number: blockCRC.toInt(), bitsCount: 32) // Block crc32. process(block, bitWriter) } // EOS magic number. bitWriter.write(bits: eosMarker) // Total crc32. bitWriter.write(number: totalCRC.toInt(), bitsCount: 32) bitWriter.align() return bitWriter.data } private static func process(_ block: [UInt8], _ bitWriter: MsbBitWriter) { var out = initialRle(block) var pointer = 0 (out, pointer) = BurrowsWheeler.transform(bytes: out) let usedBytes = Set(out).sorted() var maxSymbol = 0 (out, maxSymbol) = mtfRle(out, characters: usedBytes) // First, we analyze data and create Huffman trees and selectors. // Then we will perform encoding itself. // These are separate stages because all information about trees is stored at the beginning of the block, // and it is hard to modify it later. var processed = 50 var tables = [EncodingTree]() var tablesLengths = [[Int]]() var selectors = [Int]() // Algorithm for code lengths calculations skips any symbol with frequency equal to 0. // Unfortunately, we need such unused symbols in tree creation, so we cannot skip them. // To prevent skipping, we set default value of 1 for every symbol's frequency. var stats = Array(repeating: 1, count: maxSymbol + 2) for i in 0.. length { // Bits: 11 -> 11_11 -> 11_11_11 -> 11_11_11_11 -> ... // Numbers: 3 -> 15 -> 63 -> 255 -> ... // https://oeis.org/A024036 let diff = prevLength - length bitWriter.write(unsignedNumber: (1 << (2 * diff)) - 1, bitsCount: 2 * diff) } else if prevLength < length { // Bits: 10 -> 10_10 -> 10_10_10 -> 10_10_10_10 -> ... // Numbers: 2 -> 10 -> 42 -> 170 -> ... // https://oeis.org/A020988 let diff = length - prevLength bitWriter.write(unsignedNumber: ((1 << (2 * diff)) - 1) * 2 / 3 , bitsCount: 2 * diff) } prevLength = length bitWriter.write(bit: 0) } } // Contents. var encoded = 0 var table = tables[selectors[selectors.startIndex]] var selectorIndex = selectors.startIndex &+ 1 for symbol in out { // New table is selected every 50 symbols. if encoded >= 50 { // Selectors were added every 50 symbols. So by construction `selectorIndex` can never exceed the // amount of available selectors. assert(selectorIndex < selectors.endIndex, "Incorrect selectorIndex.") table = tables[selectors[selectorIndex]] selectorIndex &+= 1 encoded = 0 } table.code(symbol: symbol) encoded &+= 1 } } /// Initial Run Length Encoding. private static func initialRle(_ block: [UInt8]) -> [Int] { var out = [Int]() var index = block.startIndex while index < block.endIndex { var runLength = 1 while index + 1 < block.endIndex && block[index] == block[index + 1] && runLength < 255 { runLength += 1 index += 1 } let byte = block[index].toInt() for _ in 0..= 4 { out.append(runLength - 4) } index += 1 } return out } /// Assumes that the characters are given by a list of integers from 0 up to and including `maxValue`. private static func mtf(_ array: [Int], maxValue: Int) -> [Int] { var out = [Int]() var dictionary = Array(0...maxValue) for i in 0.. ([Int], Int) { var out = [Int]() /// Mutable copy of `characters`. var dictionary = characters var lengthOfZerosRun = 0 var maxSymbol = 1 for i in 0.. 0 { let digitsNumber = Int(floor(log2(Double(lengthOfZerosRun) + 1))) var remainder = lengthOfZerosRun for _ in 0.. maxSymbol { maxSymbol = newSymbol } } // Move to the front. let old = dictionary.remove(at: byte) dictionary.insert(old, at: 0) } // Add the 'end of stream' symbol. out.append(maxSymbol + 1) return (out, maxSymbol) } }