 /// - Throws: Can throw to indicate errors in the data source.
 public typealias Provider = (_ position: Int, _ size: Int) throws -> Data
 
-/// The lookup table used to calculate `CRC32` checksums.
-let crcTable: [CRC32] = [
+extension Data {
+    enum CompressionError: Error {
+        case invalidStream
+        case corruptedData
+    }
+
+    /// Calculate the `CRC32` checksum of the receiver.
+    ///
+    /// - Parameter checksum: The starting seed.
+    /// - Returns: The checksum calculated from the bytes of the receiver and the starting seed.
+    public func crc32(checksum: CRC32) -> CRC32 {
+        #if canImport(zlib)
+        return withUnsafeBytes { bufferPointer in
+            let length = UInt32(count)
+            return CRC32(zlib.crc32(UInt(checksum), bufferPointer.bindMemory(to: UInt8.self).baseAddress, length))
+        }
+        #else
+        return self.builtInCRC32(checksum: checksum)
+        #endif
+    }
+
+    /// Compress the output of `provider` and pass it to `consumer`.
+    /// - Parameters:
+    ///   - size: The uncompressed size of the data to be compressed.
+    ///   - bufferSize: The maximum size of the compression buffer.
+    ///   - provider: A closure that accepts a position and a chunk size. Returns a `Data` chunk.
+    ///   - consumer: A closure that processes the result of the compress operation.
+    /// - Returns: The checksum of the processed content.
+    public static func compress(size: Int, bufferSize: Int, provider: Provider, consumer: Consumer) throws -> CRC32 {
+        #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
+        return try self.process(operation: COMPRESSION_STREAM_ENCODE, size: size, bufferSize: bufferSize,
+                                provider: provider, consumer: consumer)
+        #else
+        return try self.encode(size: size, bufferSize: bufferSize, provider: provider, consumer: consumer)
+        #endif
+    }
+
+    /// Decompress the output of `provider` and pass it to `consumer`.
+    /// - Parameters:
+    ///   - size: The compressed size of the data to be decompressed.
+    ///   - bufferSize: The maximum size of the decompression buffer.
+    ///   - skipCRC32: Optional flag to skip calculation of the CRC32 checksum to improve performance.
+    ///   - provider: A closure that accepts a position and a chunk size. Returns a `Data` chunk.
+    ///   - consumer: A closure that processes the result of the decompress operation.
+    /// - Returns: The checksum of the processed content.
+    public static func decompress(size: Int, bufferSize: Int, skipCRC32: Bool,
+                                  provider: Provider, consumer: Consumer) throws -> CRC32 {
+        #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
+        return try self.process(operation: COMPRESSION_STREAM_DECODE, size: size, bufferSize: bufferSize,
+                                skipCRC32: skipCRC32, provider: provider, consumer: consumer)
+        #else
+        return try self.decode(bufferSize: bufferSize, skipCRC32: skipCRC32, provider: provider, consumer: consumer)
+        #endif
+    }
+}
+
+// MARK: - Apple Platforms
+
+#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
+import Compression
+
+extension Data {
+
+    static func process(operation: compression_stream_operation, size: Int, bufferSize: Int, skipCRC32: Bool = false,
+                        provider: Provider, consumer: Consumer) throws -> CRC32 {
+        var crc32 = CRC32(0)
+        let destPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
+        defer { destPointer.deallocate() }
+        let streamPointer = UnsafeMutablePointer<compression_stream>.allocate(capacity: 1)
+        defer { streamPointer.deallocate() }
+        var stream = streamPointer.pointee
+        var status = compression_stream_init(&stream, operation, COMPRESSION_ZLIB)
+        guard status != COMPRESSION_STATUS_ERROR else { throw CompressionError.invalidStream }
+        defer { compression_stream_destroy(&stream) }
+        stream.src_size = 0
+        stream.dst_ptr = destPointer
+        stream.dst_size = bufferSize
+        var position = 0
+        var sourceData: Data?
+        repeat {
+            if stream.src_size == 0 {
+                do {
+                    sourceData = try provider(position, Swift.min((size - position), bufferSize))
+                    position += stream.prepare(for: sourceData)
+                } catch { throw error }
+            }
+            if let sourceData = sourceData {
+                sourceData.withUnsafeBytes { (rawBufferPointer) in
+                    if let baseAddress = rawBufferPointer.baseAddress {
+                        let pointer = baseAddress.assumingMemoryBound(to: UInt8.self)
+                        stream.src_ptr = pointer.advanced(by: sourceData.count - stream.src_size)
+                        let flags = sourceData.count < bufferSize ? Int32(COMPRESSION_STREAM_FINALIZE.rawValue) : 0
+                        status = compression_stream_process(&stream, flags)
+                    }
+                }
+                if operation == COMPRESSION_STREAM_ENCODE && !skipCRC32 { crc32 = sourceData.crc32(checksum: crc32) }
+            }
+            switch status {
+            case COMPRESSION_STATUS_OK, COMPRESSION_STATUS_END:
+                let outputData = Data(bytesNoCopy: destPointer, count: bufferSize - stream.dst_size, deallocator: .none)
+                try consumer(outputData)
+                if operation == COMPRESSION_STREAM_DECODE && !skipCRC32 { crc32 = outputData.crc32(checksum: crc32) }
+                stream.dst_ptr = destPointer
+                stream.dst_size = bufferSize
+            default: throw CompressionError.corruptedData
+            }
+        } while status == COMPRESSION_STATUS_OK
+        return crc32
+    }
+}
+
+private extension compression_stream {
+
+    mutating func prepare(for sourceData: Data?) -> Int {
+        guard let sourceData = sourceData else { return 0 }
+
+        self.src_size = sourceData.count
+        return sourceData.count
+    }
+}
+
+// MARK: - Linux
+
+#else
+import CZlib
+
+extension Data {
+    static func encode(size: Int, bufferSize: Int, provider: Provider, consumer: Consumer) throws -> CRC32 {
+        var stream = z_stream()
+        let streamSize = Int32(MemoryLayout<z_stream>.size)
+        var result = deflateInit2_(&stream, Z_DEFAULT_COMPRESSION,
+                                   Z_DEFLATED, -MAX_WBITS, 9, Z_DEFAULT_STRATEGY, ZLIB_VERSION, streamSize)
+        defer { deflateEnd(&stream) }
+        guard result == Z_OK else { throw CompressionError.invalidStream }
+        var flush = Z_NO_FLUSH
+        var position = 0
+        var zipCRC32 = CRC32(0)
+        repeat {
+            let readSize = Swift.min((size - position), bufferSize)
+            var inputChunk = try provider(position, readSize)
+            zipCRC32 = inputChunk.crc32(checksum: zipCRC32)
+            stream.avail_in = UInt32(inputChunk.count)
+            try inputChunk.withUnsafeMutableBytes { (rawBufferPointer) in
+                if let baseAddress = rawBufferPointer.baseAddress {
+                    let pointer = baseAddress.assumingMemoryBound(to: UInt8.self)
+                    stream.next_in = pointer
+                    flush = position + bufferSize >= size ? Z_FINISH : Z_NO_FLUSH
+                } else if rawBufferPointer.count > 0 {
+                    throw CompressionError.corruptedData
+                } else {
+                    stream.next_in = nil
+                    flush = Z_FINISH
+                }
+                var outputChunk = Data(count: bufferSize)
+                repeat {
+                    stream.avail_out = UInt32(bufferSize)
+                    try outputChunk.withUnsafeMutableBytes { (rawBufferPointer) in
+                        guard let baseAddress = rawBufferPointer.baseAddress, rawBufferPointer.count > 0 else {
+                            throw CompressionError.corruptedData
+                        }
+                        let pointer = baseAddress.assumingMemoryBound(to: UInt8.self)
+                        stream.next_out = pointer
+                        result = deflate(&stream, flush)
+                    }
+                    guard result >= Z_OK else { throw CompressionError.corruptedData }
+
+                    outputChunk.count = bufferSize - Int(stream.avail_out)
+                    try consumer(outputChunk)
+                } while stream.avail_out == 0
+            }
+            position += readSize
+        } while flush != Z_FINISH
+        return zipCRC32
+    }
+
+    static func decode(bufferSize: Int, skipCRC32: Bool, provider: Provider, consumer: Consumer) throws -> CRC32 {
+        var stream = z_stream()
+        let streamSize = Int32(MemoryLayout<z_stream>.size)
+        var result = inflateInit2_(&stream, -MAX_WBITS, ZLIB_VERSION, streamSize)
+        defer { inflateEnd(&stream) }
+        guard result == Z_OK else { throw CompressionError.invalidStream }
+        var unzipCRC32 = CRC32(0)
+        var position = 0
+        repeat {
+            stream.avail_in = UInt32(bufferSize)
+            var chunk = try provider(position, bufferSize)
+            position += chunk.count
+            try chunk.withUnsafeMutableBytes { (rawBufferPointer) in
+                if let baseAddress = rawBufferPointer.baseAddress, rawBufferPointer.count > 0 {
+                    let pointer = baseAddress.assumingMemoryBound(to: UInt8.self)
+                    stream.next_in = pointer
+                    repeat {
+                        var outputData = Data(count: bufferSize)
+                        stream.avail_out = UInt32(bufferSize)
+                        try outputData.withUnsafeMutableBytes { (rawBufferPointer) in
+                            if let baseAddress = rawBufferPointer.baseAddress, rawBufferPointer.count > 0 {
+                                let pointer = baseAddress.assumingMemoryBound(to: UInt8.self)
+                                stream.next_out = pointer
+                            } else {
+                                throw CompressionError.corruptedData
+                            }
+                            result = inflate(&stream, Z_NO_FLUSH)
+                            guard result != Z_NEED_DICT &&
+                                result != Z_DATA_ERROR &&
+                                result != Z_MEM_ERROR else {
+                                    throw CompressionError.corruptedData
+                            }
+                        }
+                        let remainingLength = UInt32(bufferSize) - stream.avail_out
+                        outputData.count = Int(remainingLength)
+                        try consumer(outputData)
+                        if !skipCRC32 { unzipCRC32 = outputData.crc32(checksum: unzipCRC32) }
+                    } while stream.avail_out == 0
+                }
+            }
+        } while result != Z_STREAM_END
+        return unzipCRC32
+    }
+}
+
+#endif
+
+/// The lookup table used to calculate `CRC32` checksums when using the built-in
+/// CRC32 implementation.
+private let crcTable: [CRC32] = [
     0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419,
     0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4,
     0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07,

                }
            }
        }
        #else
        self.withUnsafeBytes { (bytes) in
            let bins = stride(from: 0, to: self.count, by: 256)
            for bin in bins {

// We provide a compatible method for targets that use Swift 4.x so that we can use the new version
// across all language versions.

extension Data {
    func withUnsafeBytes<T>(_ body: (UnsafeRawBufferPointer) throws -> T) rethrows -> T {
        let count = self.count
        return try withUnsafeBytes { (pointer: UnsafePointer<UInt8>) throws -> T in
