Skip to content

Commit 9dedf2c

Browse files
authored
Expose ZlibCompressor/Decompressor (#10)
* Expose ZlibCompressor and make it non-Copyable * Use latest swift image in api-breakage.yml * Make ZlibAlgorithm sendable * Update package.swift * Revert ZlibCompressor/Decompressor back to a class Also clean up interface so when they are allocated they are active already, instead of having non-initialized state * Add finishWindowedStream back in and deprecate it * Update README
1 parent a52d3ac commit 9dedf2c

14 files changed

+1145
-199
lines changed

.github/workflows/api-breakage.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
linux:
99
runs-on: ubuntu-latest
1010
container:
11-
image: swift:5.7
11+
image: swift:latest
1212
steps:
1313
- name: Checkout
1414
uses: actions/checkout@v3

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
runs-on: macOS-latest
1717
steps:
1818
- name: Checkout
19-
uses: actions/checkout@v2
19+
uses: actions/checkout@v4
2020
- name: SPM tests
2121
run: swift test --enable-code-coverage --parallel
2222
- name: Convert coverage files
@@ -34,14 +34,14 @@ jobs:
3434
strategy:
3535
matrix:
3636
image:
37-
- 'swift:5.7'
38-
- 'swift:5.8'
3937
- 'swift:5.9'
38+
- 'swift:5.10'
39+
- 'swift:6.0'
4040
container:
4141
image: ${{ matrix.image }}
4242
steps:
4343
- name: Checkout
44-
uses: actions/checkout@v3
44+
uses: actions/checkout@v4
4545
- name: Test
4646
run: |
4747
swift --version

Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
// swift-tools-version:5.7
1+
// swift-tools-version:5.9
22
import PackageDescription
33

44
let package = Package(
55
name: "compress-nio",
6+
platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13)],
67
products: [
78
.library(name: "CompressNIO", targets: ["CompressNIO"]),
89
],

README.md

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@ var uncompressedBuffer = buffer.decompress(with: .gzip)
1111
These methods allocate a new `ByteBuffer` for you. The `decompress` method can allocate multiple `ByteBuffers` while it is uncompressing depending on how well compressed the original `ByteBuffer` is. It is preferable to know in advance the size of buffer you need and allocate it yourself just the once and use the following functions.
1212
```swift
1313
let uncompressedSize = buffer.readableBytes
14-
let maxCompressedSize = CompressionAlgorithm.deflate.compressor.maxSize(from:buffer)
15-
var compressedBuffer = ByteBufferAllocator().buffer(capacity: maxCompressedSize)
14+
var compressedBuffer = ByteBufferAllocator().buffer(capacity: knownCompressedSize)
1615
try buffer.compress(to: &compressedBuffer, with: .deflate)
1716
var uncompressedBuffer = ByteBufferAllocator().buffer(capacity: uncompressedSize)
1817
try compressedBuffer.decompress(to: &uncompressedBuffer, with: .deflate)
1918
```
20-
In the above example there is a call to a function `CompressionAlgorithm.deflate.compressor.maxSize(from:buffer)`. This returns the maximum size of buffer required to write out compressed data for the `deflate` compression algorithm.
19+
This returns the maximum size of buffer required to write out compressed data for the `deflate` compression algorithm.
2120

2221
If you provide a buffer that is too small a `CompressNIO.bufferOverflow` error is thrown. You will need to provide a larger `ByteBuffer` to complete your operation.
2322

@@ -31,29 +30,29 @@ There are three methods for doing stream compressing: window, allocating and raw
3130
#### Window method
3231
For the window method you provide a working buffer for the compressor to use. When you call `compressStream` it compresses into this buffer and when the buffer is full it will call a `process` closure you have provided.
3332
```swift
34-
let compressor = CompressionAlgorithm.gzip.compressor
35-
compressor.window = ByteBufferAllocator().buffer(capacity: 64*1024)
36-
try compressor.startStream()
33+
let compressor = ZlibCompressor(algorithm: .gzip)
34+
var window = ByteBufferAllocator().buffer(capacity: 64*1024)
3735
while var buffer = getData() {
38-
try buffer.compressStream(with: compressor, flush: .finish) { buffer in
36+
try buffer.compressStream(with: compressor, window: window, flush: .finish) { buffer in
3937
// process your compressed data
4038
}
4139
}
42-
try compressor.finishStream()
40+
try compressor.reset()
4341
```
4442
#### Allocation method
4543
With the allocating method you leave the compressor to allocate the ByteBuffers for output data. It will calculate the maximum possible size the compressed data could be and allocates that amount of space for each compressed data block. The last compressed block needs to have the `flush` parameter set to `.finish`
4644
```swift
47-
let compressor = CompressionAlgorithm.gzip.compressor
48-
try compressor.startStream()
45+
let compressor = ZlibCompressor(algorithm: .gzip)
4946
while var buffer = getData() {
5047
let flush: CompressNIOFlush = isThisTheFinalBlock ? .finish : .sync
5148
let compressedBuffer = try buffer.compressStream(with: compressor, flush: flush, allocator: ByteBufferAllocator())
5249
}
53-
try compressor.finishStream()
50+
try compressor.reset()
5451
```
55-
If you don't know when you are receiving your last data block you can always compress an empty `ByteBuffer` with the `flush` set to `.finish` to get your final block. Also note that the flush parameter is set to `.sync` in the loop. This is required otherwise the next `compressStream` cannot successfully estimate its buffer size as there might be buffered data still waiting to be output.
52+
If you don't know what is your final data block you can always compress an empty `ByteBuffer` with the `flush` set to `.finish` to get your final block. Also note that the flush parameter is set to `.sync` in the loop. This is required otherwise the next `compressStream` cannot successfully estimate its buffer size as there might be buffered data still waiting to be output.
53+
5654
#### Raw method
55+
5756
With this mehod you call the lowest level function and deal with `.bufferOverflow` errors thrown whenever you run out of space in your output buffer. You will need a loop for receiving data and then you will need an inner loop for compressing that data. You call the `compress` until you have no more data to compress. Everytime you receive a `.bufferOverflow` error you have to provide a new output data. Once you have read all the input data you do the same again but with the `flush` parameter set to `.finish`.
5857

5958
## Decompressing

Sources/CompressNIO/ByteBuffer+compress.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ extension ByteBuffer {
2525
allocator: ByteBufferAllocator = ByteBufferAllocator()
2626
) throws -> ByteBuffer {
2727
let compressor = algorithm.compressor
28+
try compressor.startStream()
2829
var buffer = allocator.buffer(capacity: compressor.maxSize(from: self))
29-
try compressor.deflate(from: &self, to: &buffer)
30+
try compressor.streamDeflate(from: &self, to: &buffer, flush: .finish)
3031
return buffer
3132
}
3233

Sources/CompressNIO/CompressionAlgorithm.swift

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,12 @@ public struct CompressionAlgorithm: CustomStringConvertible, Sendable {
2323
/// - Parameter windowSize: Window size to use in compressor. Window size is 2^windowSize
2424
public var compressor: NIOCompressor {
2525
switch self.algorithm {
26-
case .gzip(var configuration):
27-
configuration.windowSize = 16 + configuration.windowSize
28-
return ZlibCompressor(configuration: configuration)
26+
case .gzip(let configuration):
27+
return ZlibCompressorWrapper(configuration: configuration, algorithm: .gzip)
2928
case .zlib(let configuration):
30-
return ZlibCompressor(configuration: configuration)
31-
case .deflate(var configuration):
32-
configuration.windowSize = -configuration.windowSize
33-
return ZlibCompressor(configuration: configuration)
29+
return ZlibCompressorWrapper(configuration: configuration, algorithm: .zlib)
30+
case .deflate(let configuration):
31+
return ZlibCompressorWrapper(configuration: configuration, algorithm: .deflate)
3432
}
3533
}
3634

@@ -40,11 +38,11 @@ public struct CompressionAlgorithm: CustomStringConvertible, Sendable {
4038
public var decompressor: NIODecompressor {
4139
switch self.algorithm {
4240
case .gzip(let configuration):
43-
return ZlibDecompressor(windowSize: 16 + configuration.windowSize)
41+
return ZlibDecompressorWrapper(windowSize: configuration.windowSize, algorithm: .gzip)
4442
case .zlib(let configuration):
45-
return ZlibDecompressor(windowSize: configuration.windowSize)
43+
return ZlibDecompressorWrapper(windowSize: configuration.windowSize, algorithm: .zlib)
4644
case .deflate(let configuration):
47-
return ZlibDecompressor(windowSize: -configuration.windowSize)
45+
return ZlibDecompressorWrapper(windowSize: configuration.windowSize, algorithm: .deflate)
4846
}
4947
}
5048

Sources/CompressNIO/Compressor.swift

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,22 @@ public protocol NIODecompressor: AnyObject {
88
/// - from: source byte buffer
99
/// - to: destination byte buffer
1010
func inflate(from: inout ByteBuffer, to: inout ByteBuffer) throws
11-
11+
1212
/// Working buffer for window based compression
1313
var window: ByteBuffer? { get set }
14-
14+
1515
/// Setup decompressor for stream decompression
1616
func startStream() throws
17-
17+
1818
/// Decompress block as part of a stream to another ByteBuffer
1919
/// - Parameters:
2020
/// - from: source byte buffer
2121
/// - to: destination byte buffer
2222
func streamInflate(from: inout ByteBuffer, to: inout ByteBuffer) throws
23-
23+
2424
/// Finished using thie decompressor for stream decompression
2525
func finishStream() throws
26-
26+
2727
/// equivalent of calling `finishStream` followed by `startStream`.
2828
func resetStream() throws
2929
}
@@ -60,13 +60,13 @@ public protocol NIOCompressor: AnyObject {
6060
/// - from: source byte buffer
6161
/// - to: destination byte buffer
6262
func deflate(from: inout ByteBuffer, to: inout ByteBuffer) throws
63-
63+
6464
/// Working buffer for window based compression
6565
var window: ByteBuffer? { get set }
66-
66+
6767
/// Setup compressor for stream compression
6868
func startStream() throws
69-
69+
7070
/// Compress block as part of a stream compression
7171
/// - Parameters:
7272
/// - from: source byte buffer
@@ -81,9 +81,6 @@ public protocol NIOCompressor: AnyObject {
8181
/// Finish using this compressor for stream compression
8282
func finishStream() throws
8383

84-
/// Finish using this compressor for stream compression
85-
func finishWindowedStream(process: (ByteBuffer)->()) throws
86-
8784
/// equivalent of calling `finishStream` followed by `startStream`. There maybe implementation of this that are more optimal
8885
func resetStream() throws
8986

@@ -99,13 +96,14 @@ extension NIOCompressor {
9996
try streamDeflate(from: &from, to: &to, flush: .finish)
10097
try finishStream()
10198
}
102-
99+
103100
/// Default implementation of `reset`.
104101
public func resetStream() throws {
105102
try finishStream()
106103
try startStream()
107104
}
108105

106+
@available(*, deprecated, message: "This function isn't used anymore")
109107
public func finishWindowedStream(process: (ByteBuffer)->()) throws {
110108
guard var window = self.window else { preconditionFailure("finishWindowedStream requires your compressor has a window buffer") }
111109
while true {
@@ -124,4 +122,3 @@ extension NIOCompressor {
124122
}
125123
}
126124
}
127-

Sources/CompressNIO/Error.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public struct CompressNIOError: Swift.Error, CustomStringConvertible, Equatable
1010
case noMoreMemory
1111
case unfinished
1212
case internalError
13+
case uninitializedStream
1314
}
1415

1516
fileprivate let error: ErrorEnum
@@ -27,6 +28,8 @@ public struct CompressNIOError: Swift.Error, CustomStringConvertible, Equatable
2728
public static let noMoreMemory = CompressNIOError(error: .noMoreMemory)
2829
/// called `streamFinish`while there is still data to process
2930
public static let unfinished = CompressNIOError(error: .unfinished)
31+
/// called stream function while stream was unintialised
32+
public static let uninitializedStream = CompressNIOError(error: .uninitializedStream)
3033
/// error internal to system
3134
public static let internalError = CompressNIOError(error: .internalError)
3235
}

0 commit comments

Comments
 (0)