@@ -15,7 +15,8 @@ import Foundation
15
15
@_exported import Compression
16
16
17
17
/// Compression algorithms, wraps the C API constants.
18
- public enum Algorithm : CaseIterable {
18
+ @available ( macOS 9999 , iOS 9999 , watchOS 9999 , tvOS 9999 , * )
19
+ public enum Algorithm : CaseIterable , RawRepresentable {
19
20
20
21
/// LZFSE
21
22
case lzfse
@@ -29,6 +30,16 @@ public enum Algorithm: CaseIterable {
29
30
/// LZMA in a XZ container
30
31
case lzma
31
32
33
+ public init ? ( rawValue: compression_algorithm ) {
34
+ switch rawValue {
35
+ case COMPRESSION_LZFSE: self = . lzfse
36
+ case COMPRESSION_ZLIB: self = . zlib
37
+ case COMPRESSION_LZ4: self = . lz4
38
+ case COMPRESSION_LZMA: self = . lzma
39
+ default : return nil
40
+ }
41
+ }
42
+
32
43
public var rawValue : compression_algorithm {
33
44
switch self {
34
45
case . lzfse: return COMPRESSION_LZFSE
@@ -39,26 +50,24 @@ public enum Algorithm: CaseIterable {
39
50
}
40
51
}
41
52
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
53
/// Compression filter direction of operation, compress/decompress
55
- public enum FilterOperation {
54
+ @available ( macOS 9999 , iOS 9999 , watchOS 9999 , tvOS 9999 , * )
55
+ public enum FilterOperation : RawRepresentable {
56
+
56
57
/// Compress raw data to a compressed payload
57
58
case compress
58
59
59
60
/// Decompress a compressed payload to raw data
60
61
case decompress
61
62
63
+ public init ? ( rawValue: compression_stream_operation ) {
64
+ switch rawValue {
65
+ case COMPRESSION_STREAM_ENCODE: self = . compress
66
+ case COMPRESSION_STREAM_DECODE: self = . decompress
67
+ default : return nil
68
+ }
69
+ }
70
+
62
71
public var rawValue : compression_stream_operation {
63
72
switch self {
64
73
case . compress: return COMPRESSION_STREAM_ENCODE
@@ -67,34 +76,48 @@ public enum FilterOperation {
67
76
}
68
77
}
69
78
70
- @available ( macOS 10 . 12 , iOS 10 . 0 , watchOS 3 . 0 , tvOS 10 . 0 , * )
79
+ /// Compression errors
80
+ @available ( macOS 9999 , iOS 9999 , watchOS 9999 , tvOS 9999 , * )
81
+ public enum FilterError : Error {
82
+
83
+ /// Filter failed to initialize,
84
+ /// or invalid internal state,
85
+ /// or invalid parameters
86
+ case invalidState
87
+
88
+ /// Invalid data in a call to compression_stream_process,
89
+ /// or non-empty write after an output filter has been finalized
90
+ case invalidData
91
+ }
92
+
93
+ @available ( macOS 9999 , iOS 9999 , watchOS 9999 , tvOS 9999 , * )
71
94
extension compression_stream {
72
95
73
96
/// Initialize a compression_stream struct
74
97
///
75
98
/// - Parameter operation: direction of operation
76
99
/// - Parameter algorithm: compression algorithm
77
100
///
78
- /// - Throws: `FilterError.filterInitError ` if `algorithm` is not supported
101
+ /// - Throws: `FilterError.invalidState ` if `algorithm` is not supported
79
102
/// by the Compression stream API
80
103
///
81
104
internal init ( operation: FilterOperation , algorithm: Algorithm ) throws {
82
- self . init ( dst_ptr: UnsafeMutablePointer< UInt8> . allocate ( capacity : 0 ) ,
105
+ self . init ( dst_ptr: UnsafeMutablePointer < UInt8 > ( bitPattern : - 1 ) ! ,
83
106
dst_size: 0 ,
84
- src_ptr: UnsafeMutablePointer< UInt8> . allocate ( capacity : 0 ) ,
107
+ src_ptr: UnsafeMutablePointer < UInt8 > ( bitPattern : - 1 ) ! ,
85
108
src_size: 0 ,
86
109
state: nil )
87
110
let status = compression_stream_init ( & self , operation. rawValue, algorithm. rawValue)
88
- guard status == COMPRESSION_STATUS_OK else { throw FilterError . filterInitError }
111
+ guard status == COMPRESSION_STATUS_OK else { throw FilterError . invalidState }
89
112
}
90
113
}
91
114
92
- @available ( macOS 10 . 12 , iOS 10 . 0 , watchOS 3 . 0 , tvOS 10 . 0 , * )
115
+ @available ( macOS 9999 , iOS 9999 , watchOS 9999 , tvOS 9999 , * )
93
116
public class OutputFilter {
94
117
private var _stream : compression_stream
95
118
private var _buf : UnsafeMutablePointer < UInt8 >
96
119
private let _bufCapacity : Int
97
- private let _writeFunc : ( Data ? ) throws -> ( )
120
+ private let _writeFunc : ( Data ? ) throws -> Void
98
121
private var _finalized : Bool = false
99
122
100
123
/// Initialize an output filter
@@ -105,12 +128,12 @@ public class OutputFilter {
105
128
/// - bufferCapacity: capacity of the internal data buffer
106
129
/// - writeFunc: called to write the processed data
107
130
///
108
- /// - Throws: `FilterError.StreamInitError ` if stream initialization failed
131
+ /// - Throws: `FilterError.invalidState ` if stream initialization failed
109
132
public init (
110
133
_ operation: FilterOperation ,
111
134
using algorithm: Algorithm ,
112
135
bufferCapacity: Int = 65536 ,
113
- writingTo writeFunc: @escaping ( Data ? ) throws -> ( )
136
+ writingTo writeFunc: @escaping ( Data ? ) throws -> Void
114
137
) throws {
115
138
_stream = try compression_stream ( operation: operation, algorithm: algorithm)
116
139
_buf = UnsafeMutablePointer< UInt8> . allocate( capacity: bufferCapacity)
@@ -127,21 +150,22 @@ public class OutputFilter {
127
150
/// - Parameter data: data to process
128
151
///
129
152
/// - 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 {
153
+ /// `FilterError.invalidData` if an error occurs during processing,
154
+ /// or if `data` is not empty/nil, and the filter is the finalized state
155
+ public func write< D : DataProtocol > ( _ data: D ? ) throws {
134
156
// Finalize if data is empty/nil
135
157
if data == nil || data!. isEmpty { try finalize ( ) ; return }
136
158
137
159
// Fail if already finalized
138
- if _finalized { throw FilterError . writeToFinalizedFilter }
160
+ if _finalized { throw FilterError . invalidData }
139
161
140
162
// 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 ) }
163
+ for region in data!. regions {
164
+ try region. withUnsafeBytes { ( raw_src_ptr: UnsafeRawBufferPointer ) in
165
+ _stream. src_size = region. count
166
+ _stream. src_ptr = raw_src_ptr. baseAddress!. assumingMemoryBound ( to: UInt8 . self)
167
+ while ( _stream. src_size > 0 ) { _ = try _process ( finalizing: false ) }
168
+ }
145
169
}
146
170
}
147
171
@@ -151,15 +175,15 @@ public class OutputFilter {
151
175
/// When all output has been sent, the writingTo closure is called one last time with nil data.
152
176
/// Once the stream is finalized, writing non empty/nil data to the stream will throw an exception.
153
177
///
154
- /// - Throws: `FilterError.StreamProcessError ` if an error occurs during processing
178
+ /// - Throws: `FilterError.invalidData ` if an error occurs during processing
155
179
public func finalize( ) throws {
156
180
// Do nothing if already finalized
157
181
if _finalized { return }
158
182
159
183
// Finalize stream
160
184
_stream. src_size = 0
161
185
var status = COMPRESSION_STATUS_OK
162
- while ( status != COMPRESSION_STATUS_END) { status = try process ( finalizing: true ) }
186
+ while ( status != COMPRESSION_STATUS_END) { status = try _process ( finalizing: true ) }
163
187
164
188
// Update state
165
189
_finalized = true
@@ -180,13 +204,13 @@ public class OutputFilter {
180
204
181
205
// Call compression_stream_process with current src, and dst set to _buf, then write output to the closure
182
206
// Return status
183
- private func process ( finalizing finalize: Bool ) throws -> compression_status {
207
+ private func _process ( finalizing finalize: Bool ) throws -> compression_status {
184
208
// Process current input, and write to buf
185
209
_stream. dst_ptr = _buf
186
210
_stream. dst_size = _bufCapacity
187
211
188
212
let status = compression_stream_process ( & _stream, ( finalize ? Int32 ( COMPRESSION_STREAM_FINALIZE . rawValue) : 0 ) )
189
- guard status != COMPRESSION_STATUS_ERROR else { throw FilterError . filterProcessError }
213
+ guard status != COMPRESSION_STATUS_ERROR else { throw FilterError . invalidData }
190
214
191
215
// Number of bytes written to buf
192
216
let writtenBytes = _bufCapacity - _stream. dst_size
@@ -202,12 +226,72 @@ public class OutputFilter {
202
226
203
227
}
204
228
205
- @available ( macOS 10 . 12 , iOS 10 . 0 , watchOS 3 . 0 , tvOS 10 . 0 , * )
206
- public class InputFilter {
229
+ @available ( macOS 9999 , iOS 9999 , watchOS 9999 , tvOS 9999 , * )
230
+ public class InputFilter < D: DataProtocol > {
231
+
232
+ // Internal buffer to read bytes from a DataProtocol implementation
233
+ private class InputFilterBuffer < D: DataProtocol > {
234
+ private var _data : D // current input data
235
+ private var _remaining : Int // total bytes remaining to process in _data
236
+ private var _regionIndex : D . Regions . Index // region being read in _data
237
+ private var _regionRemaining : Int // remaining bytes to read in region being read in _data
238
+
239
+ public init ( _ data: D ) throws {
240
+ _data = data
241
+ _remaining = _data. count
242
+ _regionRemaining = 0
243
+ _regionIndex = _data. regions. startIndex
244
+ if _regionIndex != _data. regions. endIndex { _regionRemaining = _data. regions [ _regionIndex] . count }
245
+ // Advance to first non-zero region
246
+ try advance ( by: 0 )
247
+ }
248
+
249
+ // Return number of remaining bytes
250
+ public func remaining( ) -> Int { return _remaining }
251
+
252
+ // Call f with a buffer to the remaining bytes of the current contiguous region
253
+ public func withUnsafeBytes< R> ( _ body: ( UnsafeRawBufferPointer ) throws -> R ) rethrows -> R ? {
254
+ if _remaining == 0 {
255
+ return try body ( UnsafeRawBufferPointer ( start: nil , count: 0 ) )
256
+ } else {
257
+ let r = _data. regions [ _regionIndex]
258
+ return try r. withUnsafeBytes { ( region_buf: UnsafeRawBufferPointer ) in
259
+ let src_buf = UnsafeRawBufferPointer (
260
+ start: region_buf. baseAddress!. assumingMemoryBound ( to: UInt8 . self) + region_buf. count - _regionRemaining,
261
+ count: _regionRemaining)
262
+ return try body ( src_buf)
263
+ }
264
+ }
265
+ }
266
+
267
+ // Consume n bytes in the current region (n can be 0, up to _regionRemaining), and move to next non-empty region if needed
268
+ // post-condition: _remaining == 0 || _regionRemaining > 0
269
+ public func advance( by n: Int ) throws {
270
+
271
+ // Sanity checks
272
+ if n > _regionRemaining { throw FilterError . invalidState } // invalid n
273
+
274
+ // Update counters
275
+ _regionRemaining -= n
276
+ _remaining -= n
277
+
278
+ // Move to next non-empty region if we are done with the current one
279
+ let r = _data. regions
280
+ while _regionRemaining == 0 {
281
+ r. formIndex ( after: & _regionIndex)
282
+ if _regionIndex == r. endIndex { break }
283
+ _regionRemaining = r [ _regionIndex] . count
284
+ }
285
+
286
+ // Sanity checks
287
+ if _remaining != 0 && _regionRemaining == 0 { throw FilterError . invalidState }
288
+ }
289
+ }
290
+
291
+ private let _readCapacity : Int // size to use when calling _readFunc
292
+ private let _readFunc : ( Int ) throws -> D ? // caller-provided read function
207
293
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 ?
294
+ private var _buf : InputFilterBuffer < D > ? = nil // input
211
295
private var _eofReached : Bool = false // did we read end-of-file from the input?
212
296
private var _endReached : Bool = false // did we reach end-of-file from the decoder stream?
213
297
@@ -219,15 +303,15 @@ public class InputFilter {
219
303
/// - bufferCapacity: capacity of the internal data buffer
220
304
/// - readFunc: called to read the input data
221
305
///
222
- /// - Throws: `FilterError.filterInitError ` if filter initialization failed
306
+ /// - Throws: `FilterError.invalidState ` if filter initialization failed
223
307
public init (
224
308
_ operation: FilterOperation ,
225
309
using algorithm: Algorithm ,
226
310
bufferCapacity: Int = 65536 ,
227
- readingFrom readFunc: @escaping ( Int ) throws -> Data ?
311
+ readingFrom readFunc: @escaping ( Int ) throws -> D ?
228
312
) throws {
229
313
_stream = try compression_stream ( operation: operation, algorithm: algorithm)
230
- _bufCapacity = bufferCapacity
314
+ _readCapacity = bufferCapacity
231
315
_readFunc = readFunc
232
316
}
233
317
@@ -243,7 +327,7 @@ public class InputFilter {
243
327
/// - Returns: a new Data object containing at most `count` output bytes, or nil if no more data is available
244
328
///
245
329
/// - Throws:
246
- /// `FilterError.filterProcessError ` if an error occurs during processing
330
+ /// `FilterError.invalidData ` if an error occurs during processing
247
331
public func readData( ofLength count: Int ) throws -> Data ? {
248
332
// Sanity check
249
333
precondition ( count > 0 , " number of bytes to read can't be 0 " )
@@ -263,32 +347,37 @@ public class InputFilter {
263
347
while _stream. dst_size > 0 && !_endReached {
264
348
265
349
// 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
350
+ if ( _buf? . remaining ( ) ?? 0 ) == 0 && !_eofReached {
351
+ let data = try _readFunc ( _readCapacity) // may be nil, or empty
352
+ if data? . count ?? 0 == 0 { // nil or empty -> EOF
353
+ _eofReached = true
354
+ _buf = nil
355
+ } else {
356
+ _buf = try InputFilterBuffer ( data!)
357
+ }
271
358
}
272
359
273
360
// Process some data
274
361
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
-
362
+ try buf. withUnsafeBytes { ( src_buf: UnsafeRawBufferPointer ) in
363
+ // Point to buffer
364
+ _stream. src_ptr = src_buf. baseAddress!. assumingMemoryBound ( to: UInt8 . self)
365
+ _stream. src_size = src_buf. count
280
366
let status = compression_stream_process ( & _stream, ( _eofReached ? Int32 ( COMPRESSION_STREAM_FINALIZE . rawValue) : 0 ) )
281
- guard status != COMPRESSION_STATUS_ERROR else { throw FilterError . filterProcessError }
367
+ guard status != COMPRESSION_STATUS_ERROR else { throw FilterError . invalidData }
282
368
if status == COMPRESSION_STATUS_END { _endReached = true }
369
+ // Advance by the number of consumed bytes
370
+ let consumed = src_buf. count - _stream. src_size
371
+ try buf. advance ( by: consumed)
283
372
}
284
- }
285
- else {
373
+ } else {
374
+ // No data available, process until END reached
286
375
let status = compression_stream_process ( & _stream, ( _eofReached ? Int32 ( COMPRESSION_STREAM_FINALIZE . rawValue) : 0 ) )
287
- guard status != COMPRESSION_STATUS_ERROR else { throw FilterError . filterProcessError }
376
+ guard status != COMPRESSION_STATUS_ERROR else { throw FilterError . invalidData }
288
377
if status == COMPRESSION_STATUS_END { _endReached = true }
289
-
290
378
}
291
- }
379
+
380
+ } // _stream.dst_size > 0 && !_endReached
292
381
293
382
} // result.withUnsafeMutableBytes
294
383
0 commit comments