Skip to content

Commit fce2c19

Browse files
authored
Merge pull request #21939 from ebainville/compression-overlay
InputFilter, OutputFilter overlay for the Darwin Compression stream API
2 parents 63f761d + 18f4ffe commit fce2c19

File tree

5 files changed

+474
-3
lines changed

5 files changed

+474
-3
lines changed

stdlib/public/Darwin/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ if(SWIFT_BUILD_STATIC_SDK_OVERLAY)
88
list(APPEND SWIFT_SDK_OVERLAY_LIBRARY_BUILD_TYPES STATIC)
99
endif()
1010

11-
set(all_overlays "Accelerate;AppKit;ARKit;AssetsLibrary;AVFoundation;CallKit;CloudKit;Contacts;CoreAudio;CoreData;CoreFoundation;CoreGraphics;CoreImage;CoreLocation;CoreMedia;CryptoTokenKit;Dispatch;Foundation;GameplayKit;GLKit;HomeKit;IOKit;Intents;MapKit;MediaPlayer;Metal;MetalKit;ModelIO;NaturalLanguage;Network;ObjectiveC;OpenCL;os;Photos;QuartzCore;SafariServices;SceneKit;simd;SpriteKit;UIKit;Vision;WatchKit;XCTest;XPC")
11+
set(all_overlays "Accelerate;AppKit;ARKit;AssetsLibrary;AVFoundation;CallKit;CloudKit;Compression;Contacts;CoreAudio;CoreData;CoreFoundation;CoreGraphics;CoreImage;CoreLocation;CoreMedia;CryptoTokenKit;Dispatch;Foundation;GameplayKit;GLKit;HomeKit;IOKit;Intents;MapKit;MediaPlayer;Metal;MetalKit;ModelIO;NaturalLanguage;Network;ObjectiveC;OpenCL;os;Photos;QuartzCore;SafariServices;SceneKit;simd;SpriteKit;UIKit;Vision;WatchKit;XCTest;XPC")
1212

1313
if(DEFINED SWIFT_OVERLAY_TARGETS)
1414
set(overlays_to_build ${SWIFT_OVERLAY_TARGETS})
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
cmake_minimum_required(VERSION 3.4.3)
2+
include("../../../../cmake/modules/StandaloneOverlay.cmake")
3+
4+
add_swift_target_library(swiftCompression ${SWIFT_SDK_OVERLAY_LIBRARY_BUILD_TYPES} IS_SDK_OVERLAY
5+
Compression.swift
6+
7+
SWIFT_COMPILE_FLAGS "${SWIFT_RUNTIME_SWIFT_COMPILE_FLAGS}"
8+
LINK_FLAGS "${SWIFT_RUNTIME_SWIFT_LINK_FLAGS}" "-lcompression"
9+
SWIFT_MODULE_DEPENDS_OSX Darwin Foundation
10+
SWIFT_MODULE_DEPENDS_IOS Darwin Foundation
11+
SWIFT_MODULE_DEPENDS_TVOS Darwin Foundation
12+
SWIFT_MODULE_DEPENDS_WATCHOS Darwin Foundation
13+
14+
DEPLOYMENT_VERSION_OSX ${SWIFTLIB_DEPLOYMENT_VERSION_COMPRESSION_OSX}
15+
DEPLOYMENT_VERSION_IOS ${SWIFTLIB_DEPLOYMENT_VERSION_COMPRESSION_IOS}
16+
DEPLOYMENT_VERSION_TVOS ${SWIFTLIB_DEPLOYMENT_VERSION_COMPRESSION_TVOS}
17+
DEPLOYMENT_VERSION_WATCHOS ${SWIFTLIB_DEPLOYMENT_VERSION_COMPRESSION_WATCHOS}
18+
)
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Darwin
14+
import Foundation
15+
@_exported import Compression
16+
17+
/// Compression algorithms, wraps the C API constants.
18+
public enum Algorithm: CaseIterable {
19+
20+
/// LZFSE
21+
case lzfse
22+
23+
/// Deflate (conforming to RFC 1951)
24+
case zlib
25+
26+
/// LZ4 with simple frame encapsulation
27+
case lz4
28+
29+
/// LZMA in a XZ container
30+
case lzma
31+
32+
public var rawValue: compression_algorithm {
33+
switch self {
34+
case .lzfse: return COMPRESSION_LZFSE
35+
case .zlib: return COMPRESSION_ZLIB
36+
case .lz4: return COMPRESSION_LZ4
37+
case .lzma: return COMPRESSION_LZMA
38+
}
39+
}
40+
}
41+
42+
/// Compression errors
43+
public enum FilterError: Error {
44+
/// Filter failed to initialize
45+
case filterInitError
46+
47+
/// Invalid data in a call to compression_stream_process
48+
case filterProcessError
49+
50+
/// Non-empty write after an output filter has been finalized
51+
case writeToFinalizedFilter
52+
}
53+
54+
/// Compression filter direction of operation, compress/decompress
55+
public enum FilterOperation {
56+
/// Compress raw data to a compressed payload
57+
case compress
58+
59+
/// Decompress a compressed payload to raw data
60+
case decompress
61+
62+
public var rawValue: compression_stream_operation {
63+
switch self {
64+
case .compress: return COMPRESSION_STREAM_ENCODE
65+
case .decompress: return COMPRESSION_STREAM_DECODE
66+
}
67+
}
68+
}
69+
70+
@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
71+
extension compression_stream {
72+
73+
/// Initialize a compression_stream struct
74+
///
75+
/// - Parameter operation: direction of operation
76+
/// - Parameter algorithm: compression algorithm
77+
///
78+
/// - Throws: `FilterError.filterInitError` if `algorithm` is not supported
79+
/// by the Compression stream API
80+
///
81+
internal init(operation: FilterOperation, algorithm: Algorithm) throws {
82+
self.init(dst_ptr: UnsafeMutablePointer<UInt8>.allocate(capacity:0),
83+
dst_size: 0,
84+
src_ptr: UnsafeMutablePointer<UInt8>.allocate(capacity:0),
85+
src_size: 0,
86+
state: nil)
87+
let status = compression_stream_init(&self, operation.rawValue, algorithm.rawValue)
88+
guard status == COMPRESSION_STATUS_OK else { throw FilterError.filterInitError }
89+
}
90+
}
91+
92+
@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
93+
public class OutputFilter {
94+
private var _stream: compression_stream
95+
private var _buf: UnsafeMutablePointer<UInt8>
96+
private let _bufCapacity: Int
97+
private let _writeFunc: (Data?) throws -> ()
98+
private var _finalized: Bool = false
99+
100+
/// Initialize an output filter
101+
///
102+
/// - Parameters:
103+
/// - operation: direction of operation
104+
/// - algorithm: compression algorithm
105+
/// - bufferCapacity: capacity of the internal data buffer
106+
/// - writeFunc: called to write the processed data
107+
///
108+
/// - Throws: `FilterError.StreamInitError` if stream initialization failed
109+
public init(
110+
_ operation: FilterOperation,
111+
using algorithm: Algorithm,
112+
bufferCapacity: Int = 65536,
113+
writingTo writeFunc: @escaping (Data?) throws -> ()
114+
) throws {
115+
_stream = try compression_stream(operation: operation, algorithm: algorithm)
116+
_buf = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferCapacity)
117+
_bufCapacity = bufferCapacity
118+
_writeFunc = writeFunc
119+
}
120+
121+
/// Send data to output filter
122+
///
123+
/// Processed output will be sent to the output closure.
124+
/// A call with empty/nil data is interpreted as finalize().
125+
/// Writing non empty/nil data to a finalized stream is an error.
126+
///
127+
/// - Parameter data: data to process
128+
///
129+
/// - Throws:
130+
/// `FilterError.filterProcessError` if an error occurs during processing
131+
/// `FilterError.writeToFinalizedFilter` if `data` is not empty/nil, and the
132+
/// filter is the finalized state
133+
public func write(_ data: Data?) throws {
134+
// Finalize if data is empty/nil
135+
if data == nil || data!.isEmpty { try finalize() ; return }
136+
137+
// Fail if already finalized
138+
if _finalized { throw FilterError.writeToFinalizedFilter }
139+
140+
// Process all incoming data
141+
try data!.withUnsafeBytes { (src_ptr: UnsafePointer<UInt8>) in
142+
_stream.src_size = data!.count
143+
_stream.src_ptr = src_ptr
144+
while (_stream.src_size > 0) { _ = try process(finalizing: false) }
145+
}
146+
}
147+
148+
/// Finalize the stream, i.e. flush all data remaining in the stream
149+
///
150+
/// Processed output will be sent to the output closure.
151+
/// When all output has been sent, the writingTo closure is called one last time with nil data.
152+
/// Once the stream is finalized, writing non empty/nil data to the stream will throw an exception.
153+
///
154+
/// - Throws: `FilterError.StreamProcessError` if an error occurs during processing
155+
public func finalize() throws {
156+
// Do nothing if already finalized
157+
if _finalized { return }
158+
159+
// Finalize stream
160+
_stream.src_size = 0
161+
var status = COMPRESSION_STATUS_OK
162+
while (status != COMPRESSION_STATUS_END) { status = try process(finalizing: true) }
163+
164+
// Update state
165+
_finalized = true
166+
167+
// Notify end of stream
168+
try _writeFunc(nil)
169+
}
170+
171+
// Cleanup resources. The filter is finalized now if it was not finalized yet.
172+
deinit {
173+
// Finalize now if not done earlier
174+
try? finalize()
175+
176+
// Cleanup
177+
_buf.deallocate()
178+
compression_stream_destroy(&_stream)
179+
}
180+
181+
// Call compression_stream_process with current src, and dst set to _buf, then write output to the closure
182+
// Return status
183+
private func process(finalizing finalize: Bool) throws -> compression_status {
184+
// Process current input, and write to buf
185+
_stream.dst_ptr = _buf
186+
_stream.dst_size = _bufCapacity
187+
188+
let status = compression_stream_process(&_stream, (finalize ? Int32(COMPRESSION_STREAM_FINALIZE.rawValue) : 0))
189+
guard status != COMPRESSION_STATUS_ERROR else { throw FilterError.filterProcessError }
190+
191+
// Number of bytes written to buf
192+
let writtenBytes = _bufCapacity - _stream.dst_size
193+
194+
// Write output
195+
if writtenBytes > 0 {
196+
let outData = Data(bytesNoCopy: _buf, count: writtenBytes, deallocator: .none)
197+
try _writeFunc(outData)
198+
}
199+
200+
return status
201+
}
202+
203+
}
204+
205+
@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
206+
public class InputFilter {
207+
private var _stream: compression_stream
208+
private var _buf: Data? = nil // current input data
209+
private let _bufCapacity: Int // size to read when refilling _buf
210+
private let _readFunc: (Int) throws -> Data?
211+
private var _eofReached: Bool = false // did we read end-of-file from the input?
212+
private var _endReached: Bool = false // did we reach end-of-file from the decoder stream?
213+
214+
/// Initialize an input filter
215+
///
216+
/// - Parameters:
217+
/// - operation: direction of operation
218+
/// - algorithm: compression algorithm
219+
/// - bufferCapacity: capacity of the internal data buffer
220+
/// - readFunc: called to read the input data
221+
///
222+
/// - Throws: `FilterError.filterInitError` if filter initialization failed
223+
public init(
224+
_ operation: FilterOperation,
225+
using algorithm: Algorithm,
226+
bufferCapacity: Int = 65536,
227+
readingFrom readFunc: @escaping (Int) throws -> Data?
228+
) throws {
229+
_stream = try compression_stream(operation: operation, algorithm: algorithm)
230+
_bufCapacity = bufferCapacity
231+
_readFunc = readFunc
232+
}
233+
234+
/// Read processed data from the filter
235+
///
236+
/// Input data, when needed, is obtained from the input closure
237+
/// When the input closure returns a nil or empty Data object, the filter will be
238+
/// finalized, and after all processed data has been read, readData will return nil
239+
/// to signal end of input
240+
///
241+
/// - Parameter count: max number of bytes to read from the filter
242+
///
243+
/// - Returns: a new Data object containing at most `count` output bytes, or nil if no more data is available
244+
///
245+
/// - Throws:
246+
/// `FilterError.filterProcessError` if an error occurs during processing
247+
public func readData(ofLength count: Int) throws -> Data? {
248+
// Sanity check
249+
precondition(count > 0, "number of bytes to read can't be 0")
250+
251+
// End reached, return early, nothing to do
252+
if _endReached { return nil }
253+
254+
// Allocate result
255+
var result = Data(count: count)
256+
257+
try result.withUnsafeMutableBytes { (dst_ptr: UnsafeMutablePointer<UInt8>) in
258+
259+
// Write to result until full, or end reached
260+
_stream.dst_size = count
261+
_stream.dst_ptr = dst_ptr
262+
263+
while _stream.dst_size > 0 && !_endReached {
264+
265+
// Refill _buf if needed, and EOF was not yet read
266+
if _stream.src_size == 0 && !_eofReached {
267+
_buf = try _readFunc(_bufCapacity) // may be nil
268+
// Reset src_size to full _buf size
269+
if _buf?.count ?? 0 == 0 { _eofReached = true }
270+
_stream.src_size = _buf?.count ?? 0
271+
}
272+
273+
// Process some data
274+
if let buf = _buf {
275+
try buf.withUnsafeBytes { (src_ptr: UnsafePointer<UInt8>) in
276+
277+
// Next byte to read
278+
_stream.src_ptr = src_ptr + buf.count - _stream.src_size
279+
280+
let status = compression_stream_process(&_stream, (_eofReached ? Int32(COMPRESSION_STREAM_FINALIZE.rawValue) : 0))
281+
guard status != COMPRESSION_STATUS_ERROR else { throw FilterError.filterProcessError }
282+
if status == COMPRESSION_STATUS_END { _endReached = true }
283+
}
284+
}
285+
else {
286+
let status = compression_stream_process(&_stream, (_eofReached ? Int32(COMPRESSION_STREAM_FINALIZE.rawValue) : 0))
287+
guard status != COMPRESSION_STATUS_ERROR else { throw FilterError.filterProcessError }
288+
if status == COMPRESSION_STATUS_END { _endReached = true }
289+
290+
}
291+
}
292+
293+
} // result.withUnsafeMutableBytes
294+
295+
// Update actual size
296+
result.count = count - _stream.dst_size
297+
return result
298+
}
299+
300+
// Cleanup resources
301+
deinit {
302+
compression_stream_destroy(&_stream)
303+
}
304+
305+
}

0 commit comments

Comments
 (0)