Skip to content

Commit 1d9136a

Browse files
authored
Merge pull request #24076 from moiseev/compression-dataprotocol-5.1
[5.1] DataProtocol inputs in Compression overlay
2 parents b25eb21 + b7ac241 commit 1d9136a

File tree

2 files changed

+153
-64
lines changed

2 files changed

+153
-64
lines changed

stdlib/public/Darwin/Compression/Compression.swift

Lines changed: 150 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import Foundation
1515
@_exported import Compression
1616

1717
/// 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 {
1920

2021
/// LZFSE
2122
case lzfse
@@ -29,6 +30,16 @@ public enum Algorithm: CaseIterable {
2930
/// LZMA in a XZ container
3031
case lzma
3132

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+
3243
public var rawValue: compression_algorithm {
3344
switch self {
3445
case .lzfse: return COMPRESSION_LZFSE
@@ -39,26 +50,24 @@ public enum Algorithm: CaseIterable {
3950
}
4051
}
4152

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-
5453
/// 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+
5657
/// Compress raw data to a compressed payload
5758
case compress
5859

5960
/// Decompress a compressed payload to raw data
6061
case decompress
6162

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+
6271
public var rawValue: compression_stream_operation {
6372
switch self {
6473
case .compress: return COMPRESSION_STREAM_ENCODE
@@ -67,34 +76,48 @@ public enum FilterOperation {
6776
}
6877
}
6978

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, *)
7194
extension compression_stream {
7295

7396
/// Initialize a compression_stream struct
7497
///
7598
/// - Parameter operation: direction of operation
7699
/// - Parameter algorithm: compression algorithm
77100
///
78-
/// - Throws: `FilterError.filterInitError` if `algorithm` is not supported
101+
/// - Throws: `FilterError.invalidState` if `algorithm` is not supported
79102
/// by the Compression stream API
80103
///
81104
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)!,
83106
dst_size: 0,
84-
src_ptr: UnsafeMutablePointer<UInt8>.allocate(capacity:0),
107+
src_ptr: UnsafeMutablePointer<UInt8>(bitPattern: -1)!,
85108
src_size: 0,
86109
state: nil)
87110
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 }
89112
}
90113
}
91114

92-
@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
115+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
93116
public class OutputFilter {
94117
private var _stream: compression_stream
95118
private var _buf: UnsafeMutablePointer<UInt8>
96119
private let _bufCapacity: Int
97-
private let _writeFunc: (Data?) throws -> ()
120+
private let _writeFunc: (Data?) throws -> Void
98121
private var _finalized: Bool = false
99122

100123
/// Initialize an output filter
@@ -105,12 +128,12 @@ public class OutputFilter {
105128
/// - bufferCapacity: capacity of the internal data buffer
106129
/// - writeFunc: called to write the processed data
107130
///
108-
/// - Throws: `FilterError.StreamInitError` if stream initialization failed
131+
/// - Throws: `FilterError.invalidState` if stream initialization failed
109132
public init(
110133
_ operation: FilterOperation,
111134
using algorithm: Algorithm,
112135
bufferCapacity: Int = 65536,
113-
writingTo writeFunc: @escaping (Data?) throws -> ()
136+
writingTo writeFunc: @escaping (Data?) throws -> Void
114137
) throws {
115138
_stream = try compression_stream(operation: operation, algorithm: algorithm)
116139
_buf = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferCapacity)
@@ -127,21 +150,22 @@ public class OutputFilter {
127150
/// - Parameter data: data to process
128151
///
129152
/// - 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 {
134156
// Finalize if data is empty/nil
135157
if data == nil || data!.isEmpty { try finalize() ; return }
136158

137159
// Fail if already finalized
138-
if _finalized { throw FilterError.writeToFinalizedFilter }
160+
if _finalized { throw FilterError.invalidData }
139161

140162
// 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+
}
145169
}
146170
}
147171

@@ -151,15 +175,15 @@ public class OutputFilter {
151175
/// When all output has been sent, the writingTo closure is called one last time with nil data.
152176
/// Once the stream is finalized, writing non empty/nil data to the stream will throw an exception.
153177
///
154-
/// - Throws: `FilterError.StreamProcessError` if an error occurs during processing
178+
/// - Throws: `FilterError.invalidData` if an error occurs during processing
155179
public func finalize() throws {
156180
// Do nothing if already finalized
157181
if _finalized { return }
158182

159183
// Finalize stream
160184
_stream.src_size = 0
161185
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) }
163187

164188
// Update state
165189
_finalized = true
@@ -180,13 +204,13 @@ public class OutputFilter {
180204

181205
// Call compression_stream_process with current src, and dst set to _buf, then write output to the closure
182206
// Return status
183-
private func process(finalizing finalize: Bool) throws -> compression_status {
207+
private func _process(finalizing finalize: Bool) throws -> compression_status {
184208
// Process current input, and write to buf
185209
_stream.dst_ptr = _buf
186210
_stream.dst_size = _bufCapacity
187211

188212
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 }
190214

191215
// Number of bytes written to buf
192216
let writtenBytes = _bufCapacity - _stream.dst_size
@@ -202,12 +226,72 @@ public class OutputFilter {
202226

203227
}
204228

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
207293
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
211295
private var _eofReached: Bool = false // did we read end-of-file from the input?
212296
private var _endReached: Bool = false // did we reach end-of-file from the decoder stream?
213297

@@ -219,15 +303,15 @@ public class InputFilter {
219303
/// - bufferCapacity: capacity of the internal data buffer
220304
/// - readFunc: called to read the input data
221305
///
222-
/// - Throws: `FilterError.filterInitError` if filter initialization failed
306+
/// - Throws: `FilterError.invalidState` if filter initialization failed
223307
public init(
224308
_ operation: FilterOperation,
225309
using algorithm: Algorithm,
226310
bufferCapacity: Int = 65536,
227-
readingFrom readFunc: @escaping (Int) throws -> Data?
311+
readingFrom readFunc: @escaping (Int) throws -> D?
228312
) throws {
229313
_stream = try compression_stream(operation: operation, algorithm: algorithm)
230-
_bufCapacity = bufferCapacity
314+
_readCapacity = bufferCapacity
231315
_readFunc = readFunc
232316
}
233317

@@ -243,7 +327,7 @@ public class InputFilter {
243327
/// - Returns: a new Data object containing at most `count` output bytes, or nil if no more data is available
244328
///
245329
/// - Throws:
246-
/// `FilterError.filterProcessError` if an error occurs during processing
330+
/// `FilterError.invalidData` if an error occurs during processing
247331
public func readData(ofLength count: Int) throws -> Data? {
248332
// Sanity check
249333
precondition(count > 0, "number of bytes to read can't be 0")
@@ -263,32 +347,37 @@ public class InputFilter {
263347
while _stream.dst_size > 0 && !_endReached {
264348

265349
// 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+
}
271358
}
272359

273360
// Process some data
274361
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
280366
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 }
282368
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)
283372
}
284-
}
285-
else {
373+
} else {
374+
// No data available, process until END reached
286375
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 }
288377
if status == COMPRESSION_STATUS_END { _endReached = true }
289-
290378
}
291-
}
379+
380+
} // _stream.dst_size > 0 && !_endReached
292381

293382
} // result.withUnsafeMutableBytes
294383

test/stdlib/Compression.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class DataSource {
2929

3030
}
3131

32-
@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
32+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
3333
func ofiltercompress_ifilterdecompress(
3434
_ contents: Data, using algo: Algorithm
3535
) throws -> Bool {
@@ -61,7 +61,7 @@ func ofiltercompress_ifilterdecompress(
6161
return contents == decompressed
6262
}
6363

64-
@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
64+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
6565
func ifiltercompress_ofilterdecompress(
6666
_ contents: Data, using algo: Algorithm
6767
) throws -> Bool {
@@ -113,7 +113,7 @@ func randomString(withBlockLength n: Int) -> String {
113113

114114
let tests = TestSuite("Compression")
115115

116-
if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
116+
if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) {
117117

118118
do {
119119
for blockLength in [0, 1, 2, 5, 10, 100] {

0 commit comments

Comments
 (0)