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.util.function.Supplier;
22 import java.util.zip.Adler32;
23 import java.util.zip.Checksum;
24
25 import static io.netty5.handler.codec.compression.FastLz.BLOCK_TYPE_COMPRESSED;
26 import static io.netty5.handler.codec.compression.FastLz.BLOCK_TYPE_NON_COMPRESSED;
27 import static io.netty5.handler.codec.compression.FastLz.BLOCK_WITHOUT_CHECKSUM;
28 import static io.netty5.handler.codec.compression.FastLz.BLOCK_WITH_CHECKSUM;
29 import static io.netty5.handler.codec.compression.FastLz.CHECKSUM_OFFSET;
30 import static io.netty5.handler.codec.compression.FastLz.LEVEL_1;
31 import static io.netty5.handler.codec.compression.FastLz.LEVEL_2;
32 import static io.netty5.handler.codec.compression.FastLz.LEVEL_AUTO;
33 import static io.netty5.handler.codec.compression.FastLz.MAGIC_NUMBER;
34 import static io.netty5.handler.codec.compression.FastLz.MAX_CHUNK_LENGTH;
35 import static io.netty5.handler.codec.compression.FastLz.MIN_LENGTH_TO_COMPRESSION;
36 import static io.netty5.handler.codec.compression.FastLz.OPTIONS_OFFSET;
37 import static io.netty5.handler.codec.compression.FastLz.calculateOutputBufferLength;
38
39
40
41
42
43
44 public final class FastLzCompressor implements Compressor {
45
46
47
48 private final int level;
49
50
51
52
53 private final BufferChecksum checksum;
54
55 private enum State {
56 PROCESSING,
57 FINISHED,
58 CLOSED
59 }
60
61 private State state = State.PROCESSING;
62
63
64
65
66
67
68
69
70
71
72
73
74 private FastLzCompressor(int level, Checksum checksum) {
75 this.level = level;
76 this.checksum = checksum == null ? null : new BufferChecksum(checksum);
77 }
78
79
80
81
82
83
84 public static Supplier<FastLzCompressor> newFactory() {
85 return newFactory(LEVEL_AUTO, null);
86 }
87
88
89
90
91
92
93
94
95
96
97 public static Supplier<FastLzCompressor> newFactory(int level) {
98 return newFactory(level, null);
99 }
100
101
102
103
104
105
106
107
108
109
110
111
112 public static Supplier<FastLzCompressor> newFactory(boolean validateChecksums) {
113 return newFactory(LEVEL_AUTO, validateChecksums ? new Adler32() : null);
114 }
115
116
117
118
119
120
121
122
123
124
125
126
127
128 public static Supplier<FastLzCompressor> newFactory(int level, Checksum checksum) {
129 if (level != LEVEL_AUTO && level != LEVEL_1 && level != LEVEL_2) {
130 throw new IllegalArgumentException(String.format(
131 "level: %d (expected: %d or %d or %d)", level, LEVEL_AUTO, LEVEL_1, LEVEL_2));
132 }
133 return () -> new FastLzCompressor(level, checksum);
134 }
135
136 @Override
137 public Buffer compress(Buffer in, BufferAllocator allocator) throws CompressionException {
138 switch (state) {
139 case CLOSED:
140 throw new CompressionException("Compressor closed");
141 case FINISHED:
142 return allocator.allocate(0);
143 case PROCESSING:
144 return compressData(in, allocator);
145 default:
146 throw new IllegalStateException();
147 }
148 }
149
150 private Buffer compressData(Buffer in, BufferAllocator allocator) {
151 final BufferChecksum checksum = this.checksum;
152
153 Buffer out = allocator.allocate((int) ((double) in.readableBytes() / 1.5));
154 for (;;) {
155 if (in.readableBytes() == 0) {
156 return out;
157 }
158 final int idx = in.readerOffset();
159 final int length = Math.min(in.readableBytes(), MAX_CHUNK_LENGTH);
160
161 final int outputIdx = out.writerOffset();
162 out.ensureWritable(4);
163 out.setMedium(outputIdx, MAGIC_NUMBER);
164 int outputOffset = outputIdx + CHECKSUM_OFFSET + (checksum != null ? 4 : 0);
165
166 final byte blockType;
167 final int chunkLength;
168 if (length < MIN_LENGTH_TO_COMPRESSION) {
169 blockType = BLOCK_TYPE_NON_COMPRESSED;
170
171 out.ensureWritable(outputOffset + 2 + length);
172 final int outputPtr = outputOffset + 2;
173
174 if (checksum != null) {
175 checksum.reset();
176 checksum.update(in, idx, length);
177 out.setInt(outputIdx + CHECKSUM_OFFSET, (int) checksum.getValue());
178 }
179 in.copyInto(idx, out, outputPtr, length);
180 chunkLength = length;
181 } else {
182
183 if (checksum != null) {
184 checksum.reset();
185 checksum.update(in, idx, length);
186 out.setInt(outputIdx + CHECKSUM_OFFSET, (int) checksum.getValue());
187 }
188
189 final int maxOutputLength = calculateOutputBufferLength(length);
190 out.ensureWritable(outputOffset + 4 + maxOutputLength);
191 final int outputPtr = outputOffset + 4;
192
193 final int compressedLength =
194 FastLz.compress(in, in.readerOffset(), length, out, outputPtr, level);
195
196 if (compressedLength < length) {
197 blockType = BLOCK_TYPE_COMPRESSED;
198 chunkLength = compressedLength;
199
200 out.setShort(outputOffset, (short) chunkLength);
201 outputOffset += 2;
202 } else {
203 blockType = BLOCK_TYPE_NON_COMPRESSED;
204 in.copyInto(idx, out, outputOffset + 2, length);
205
206 chunkLength = length;
207 }
208 }
209 out.setShort(outputOffset, (short) length);
210
211 out.setByte(outputIdx + OPTIONS_OFFSET,
212 (byte) (blockType | (checksum != null ? BLOCK_WITH_CHECKSUM : BLOCK_WITHOUT_CHECKSUM)));
213 out.writerOffset(outputOffset + 2 + chunkLength);
214 in.skipReadableBytes(length);
215 }
216 }
217
218 @Override
219 public Buffer finish(BufferAllocator allocator) {
220 switch (state) {
221 case CLOSED:
222 throw new CompressionException("Compressor closed");
223 case FINISHED:
224 case PROCESSING:
225 state = State.FINISHED;
226 return allocator.allocate(0);
227 default:
228 throw new IllegalStateException();
229 }
230 }
231
232 @Override
233 public boolean isFinished() {
234 return state != State.PROCESSING;
235 }
236
237 @Override
238 public boolean isClosed() {
239 return state == State.CLOSED;
240 }
241
242 @Override
243 public void close() {
244 state = State.CLOSED;
245 }
246 }