Skip to content

Commit 556f79d

Browse files
committed
InputFilter, OutputFilter overlay for the Darwin Compression stream API, and corresponding test
1 parent 4d128b1 commit 556f79d

File tree

4 files changed

+496
-1
lines changed

4 files changed

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

0 commit comments

Comments
 (0)