Skip to content

Instantly share code, notes, and snippets.

@helje5
Created July 15, 2024 11:44
Show Gist options
  • Save helje5/fbbac65a491762aa6cc68dd6175ce6a5 to your computer and use it in GitHub Desktop.
Save helje5/fbbac65a491762aa6cc68dd6175ce6a5 to your computer and use it in GitHub Desktop.
Compression in Swift on Darwin Platforms
//
// ZzCompression.swift
// Core/S
//
// Created by Helge Heß on 25.10.19.
//
import Compression
public extension Data {
enum ZzCompressionOperation {
case compress, decompress
}
enum ZzCompressionAlgorithm {
case lz4, zlib, lzma, lz4Raw
case lzfse // Apple specific
}
func zzDecompress(using algorithm: ZzCompressionAlgorithm = .lzfse) -> Data? {
return zzCompress(using: algorithm, operation: .decompress)
}
func zzCompress(using algorithm: ZzCompressionAlgorithm = .lzfse,
operation: ZzCompressionOperation = .compress)
-> Data?
{
return self.withUnsafeBytes { rbp in
return rbp.compress(using: algorithm, operation: operation)
}
}
}
fileprivate extension UnsafeRawBufferPointer {
func compress(using algorithm: Data.ZzCompressionAlgorithm,
operation: Data.ZzCompressionOperation) -> Data?
{
return self.bindMemory(to: UInt8.self).compress(using: algorithm,
operation: operation)
}
}
fileprivate extension UnsafeBufferPointer where Element == UInt8 {
func compress(using algorithm: Data.ZzCompressionAlgorithm,
operation: Data.ZzCompressionOperation) -> Data?
{
guard !isEmpty, let base = baseAddress else { return nil }
let destBufferSize : Int = Swift.min(32768, count)
let destinationBuffer : UnsafeMutablePointer<UInt8> =
UnsafeMutablePointer<UInt8>.allocate(capacity: destBufferSize)
defer { destinationBuffer.deallocate() }
var stream = compression_stream(dst_ptr : destinationBuffer,
dst_size : destBufferSize,
src_ptr : base,
src_size : count, state: nil)
let rc = compression_stream_init(&stream, operation.streamOperation,
algorithm.algorithm)
guard rc != COMPRESSION_STATUS_ERROR else {
console.error("could not create compression stream!")
assertionFailure("could not create compression stream!")
return nil
}
defer { compression_stream_destroy(&stream) }
var destinationData = Data()
destinationData.reserveCapacity(count > 512 ? (count / 3) : count)
// Yup, this is required, the init overrides the ctor fields.
stream.src_ptr = base
stream.src_size = count
repeat {
let flags = COMPRESSION_STREAM_FINALIZE
stream.dst_ptr = destinationBuffer
stream.dst_size = destBufferSize
let rc = compression_stream_process(&stream, Int32(flags.rawValue))
let encodedCount = stream.dst_ptr - destinationBuffer
if encodedCount > 0 {
destinationData.append(destinationBuffer, count: encodedCount)
}
if rc == COMPRESSION_STATUS_END {
break
}
else if rc == COMPRESSION_STATUS_OK {
continue
}
else {
console.error("compression failed with error:", rc)
return nil
}
}
while true
return destinationData
}
}
public extension Data.ZzCompressionOperation {
var streamOperation: compression_stream_operation {
switch self {
case .compress : return COMPRESSION_STREAM_ENCODE
case .decompress : return COMPRESSION_STREAM_DECODE
}
}
}
public extension Data.ZzCompressionAlgorithm {
var algorithm : compression_algorithm {
switch self {
case .lz4 : return COMPRESSION_LZ4
case .zlib : return COMPRESSION_ZLIB
case .lzma : return COMPRESSION_LZMA
case .lz4Raw : return COMPRESSION_LZ4_RAW
case .lzfse : return COMPRESSION_LZFSE
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment