1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty5.handler.codec.compression;
17
18 import io.netty5.buffer.api.Buffer;
19 import io.netty5.buffer.api.BufferAllocator;
20
21 import java.nio.ByteBuffer;
22 import java.util.function.Supplier;
23 import java.util.zip.CRC32;
24 import java.util.zip.Deflater;
25
26 import static io.netty5.util.internal.ObjectUtil.checkInRange;
27 import static java.util.Objects.requireNonNull;
28
29
30
31
32 public final class ZlibCompressor implements Compressor {
33 private final ZlibWrapper wrapper;
34 private final Deflater deflater;
35
36
37
38
39 private final CRC32 crc = new CRC32();
40 private static final byte[] gzipHeader = {0x1f, (byte) 0x8b, Deflater.DEFLATED, 0, 0, 0, 0, 0, 0, 0};
41
42 private enum State {
43 PROCESSING,
44 FINISHED,
45 CLOSED
46 }
47 private State state = State.PROCESSING;
48 private boolean writeHeader = true;
49
50 private ZlibCompressor(ZlibWrapper wrapper, int compressionLevel) {
51 this.wrapper = wrapper;
52 deflater = new Deflater(compressionLevel, wrapper != ZlibWrapper.ZLIB);
53 }
54
55 private ZlibCompressor(int compressionLevel, byte[] dictionary) {
56 wrapper = ZlibWrapper.ZLIB;
57 deflater = new Deflater(compressionLevel);
58 deflater.setDictionary(dictionary);
59 }
60
61
62
63
64
65
66
67
68 public static Supplier<ZlibCompressor> newFactory() {
69 return newFactory(6);
70 }
71
72
73
74
75
76
77
78
79
80
81
82
83 public static Supplier<ZlibCompressor> newFactory(int compressionLevel) {
84 return newFactory(ZlibWrapper.ZLIB, compressionLevel);
85 }
86
87
88
89
90
91
92
93
94 public static Supplier<ZlibCompressor> newFactory(ZlibWrapper wrapper) {
95 return newFactory(wrapper, 6);
96 }
97
98
99
100
101
102
103
104
105
106
107
108
109 public static Supplier<ZlibCompressor> newFactory(ZlibWrapper wrapper, int compressionLevel) {
110 checkInRange(compressionLevel, 0, 9 , "compressionLevel");
111 requireNonNull(wrapper, "wrapper");
112 if (wrapper == ZlibWrapper.ZLIB_OR_NONE) {
113 throw new IllegalArgumentException(
114 "wrapper '" + ZlibWrapper.ZLIB_OR_NONE + "' is not " +
115 "allowed for compression.");
116 }
117
118 return () -> new ZlibCompressor(wrapper, compressionLevel);
119 }
120
121
122
123
124
125
126
127
128
129
130
131 public static Supplier<ZlibCompressor> newFactory(byte[] dictionary) {
132 return newFactory(6, dictionary);
133 }
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149 public static Supplier<ZlibCompressor> newFactory(int compressionLevel, byte[] dictionary) {
150 if (compressionLevel < 0 || compressionLevel > 9) {
151 throw new IllegalArgumentException(
152 "compressionLevel: " + compressionLevel + " (expected: 0-9)");
153 }
154 requireNonNull(dictionary, "dictionary");
155
156 return () -> new ZlibCompressor(compressionLevel, dictionary);
157 }
158
159 @Override
160 public Buffer compress(Buffer uncompressed, BufferAllocator allocator) throws CompressionException {
161 switch (state) {
162 case CLOSED:
163 throw new CompressionException("Compressor closed");
164 case FINISHED:
165 return allocator.allocate(0);
166 case PROCESSING:
167 return compressData(uncompressed, allocator);
168 default:
169 throw new IllegalStateException();
170 }
171 }
172
173 private Buffer compressData(Buffer uncompressed, BufferAllocator allocator) {
174 int len = uncompressed.readableBytes();
175 if (len == 0) {
176 return allocator.allocate(0);
177 }
178
179 int sizeEstimate = (int) Math.ceil(len * 1.001) + 12;
180 if (writeHeader) {
181 switch (wrapper) {
182 case GZIP:
183 sizeEstimate += gzipHeader.length;
184 break;
185 case ZLIB:
186 sizeEstimate += 2;
187 break;
188 default:
189
190 }
191 }
192 Buffer out = allocator.allocate(sizeEstimate);
193
194 try (var readableIteration = uncompressed.forEachReadable()) {
195 for (var readableComponent = readableIteration.first();
196 readableComponent != null; readableComponent = readableComponent.next()) {
197 compressData(readableComponent.readableBuffer(), out);
198 }
199 }
200
201 return out;
202 }
203
204 private void compressData(ByteBuffer in, Buffer out) {
205 try {
206 if (writeHeader) {
207 writeHeader = false;
208 if (wrapper == ZlibWrapper.GZIP) {
209 out.writeBytes(gzipHeader);
210 }
211 }
212
213 if (wrapper == ZlibWrapper.GZIP) {
214 int position = in.position();
215 crc.update(in);
216 in.position(position);
217 }
218
219 deflater.setInput(in);
220 for (;;) {
221 deflate(out);
222 if (deflater.needsInput()) {
223
224 break;
225 } else {
226 if (out.writableBytes() == 0) {
227
228
229 out.ensureWritable(out.writerOffset());
230 }
231 }
232 }
233 } catch (Throwable cause) {
234 out.close();
235 throw cause;
236 }
237 }
238
239 @Override
240 public Buffer finish(BufferAllocator allocator) {
241 switch (state) {
242 case CLOSED:
243 throw new CompressionException("Compressor closed");
244 case FINISHED:
245 case PROCESSING:
246 state = State.FINISHED;
247 Buffer footer = allocator.allocate(256);
248 try {
249 if (writeHeader && wrapper == ZlibWrapper.GZIP) {
250
251 writeHeader = false;
252 footer.writeBytes(gzipHeader);
253 }
254
255 deflater.finish();
256
257 while (!deflater.finished()) {
258 deflate(footer);
259 }
260 if (wrapper == ZlibWrapper.GZIP) {
261 int crcValue = (int) crc.getValue();
262 int uncBytes = deflater.getTotalIn();
263 footer.writeByte((byte) crcValue);
264 footer.writeByte((byte) (crcValue >>> 8));
265 footer.writeByte((byte) (crcValue >>> 16));
266 footer.writeByte((byte) (crcValue >>> 24));
267 footer.writeByte((byte) uncBytes);
268 footer.writeByte((byte) (uncBytes >>> 8));
269 footer.writeByte((byte) (uncBytes >>> 16));
270 footer.writeByte((byte) (uncBytes >>> 24));
271 }
272 deflater.end();
273 return footer;
274 } catch (Throwable cause) {
275 footer.close();
276 throw cause;
277 }
278 default:
279 throw new IllegalStateException();
280 }
281 }
282
283 @Override
284 public boolean isFinished() {
285 return state != State.PROCESSING;
286 }
287
288 @Override
289 public boolean isClosed() {
290 return state == State.CLOSED;
291 }
292
293 @Override
294 public void close() {
295 if (state == State.PROCESSING) {
296 deflater.end();
297 }
298 state = State.CLOSED;
299 }
300
301 private void deflate(Buffer out) {
302 try (var writableIteration = out.forEachWritable()) {
303 for (var writableComponent = writableIteration.first();
304 writableComponent != null; writableComponent = writableComponent.next()) {
305 if (writableComponent.hasWritableArray()) {
306 for (;;) {
307 int numBytes = deflater.deflate(
308 writableComponent.writableArray(), writableComponent.writableArrayOffset(),
309 writableComponent.writableBytes(), Deflater.SYNC_FLUSH);
310 if (numBytes <= 0) {
311 break;
312 }
313 writableComponent.skipWritableBytes(numBytes);
314 }
315 } else {
316 for (;;) {
317 int numBytes = deflater.deflate(writableComponent.writableBuffer(), Deflater.SYNC_FLUSH);
318 if (numBytes <= 0) {
319 break;
320 }
321 writableComponent.skipWritableBytes(numBytes);
322 }
323 }
324 }
325 }
326 }
327 }