1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.compression;
17
18 import com.github.luben.zstd.ZstdInputStreamNoFinalizer;
19 import io.netty.buffer.ByteBuf;
20 import io.netty.buffer.ByteBufAllocator;
21 import io.netty.buffer.Unpooled;
22 import io.netty.util.internal.ObjectUtil;
23 import io.netty.util.internal.UnstableApi;
24
25 import java.io.IOException;
26 import java.io.InputStream;
27
28
29
30
31
32 @UnstableApi
33 public final class ZstdDecompressor implements Decompressor {
34
35
36
37
38 public static final int DEFAULT_MAX_WINDOW_LOG = 27;
39 private static final int MIN_WINDOW_LOG = 10;
40 private static final int MAX_WINDOW_LOG = 31;
41 private static final int DEFAULT_MAX_FORWARD_BYTES = CompressionUtil.DEFAULT_MAX_FORWARD_BYTES;
42
43 private final ByteBufAllocator allocator;
44
45 private final MutableByteBufInputStream mutableInput = new MutableByteBufInputStream();
46 private final ZstdInputStreamNoFinalizer output;
47
48 ZstdDecompressor(Builder builder, ByteBufAllocator allocator) {
49
50 try {
51 Zstd.ensureAvailability();
52 } catch (Throwable throwable) {
53 throw new ExceptionInInitializerError(throwable);
54 }
55 this.allocator = allocator;
56 ZstdInputStreamNoFinalizer output = null;
57 try {
58 output = new ZstdInputStreamNoFinalizer(mutableInput);
59 output.setContinuous(true);
60 output.setLongMax(builder.maxWindowLog);
61 this.output = output;
62 } catch (IOException e) {
63 if (output != null) {
64 try {
65 output.close();
66 } catch (IOException closeException) {
67 e.addSuppressed(closeException);
68 }
69 }
70 throw new DecompressionException(e);
71 }
72 }
73
74 @Override
75 public Status status() throws DecompressionException {
76 try {
77 if (output.available() == 0) {
78 if (!output.getContinuous()) {
79 return Status.COMPLETE;
80 }
81 return Status.NEED_INPUT;
82 }
83 return Status.NEED_OUTPUT;
84 } catch (IOException e) {
85 throw new DecompressionException(e);
86 }
87 }
88
89 @Override
90 public void addInput(ByteBuf buf) throws DecompressionException {
91 if (!buf.isReadable()) {
92 buf.release();
93 return;
94 }
95 if (mutableInput.current != null) {
96 mutableInput.current.release();
97 }
98 mutableInput.current = buf;
99 }
100
101 @Override
102 public void endOfInput() throws DecompressionException {
103 try {
104 output.setContinuous(false);
105 if (output.read() != -1) {
106 throw new DecompressionException("Unexpected output after end of input");
107 }
108 } catch (IOException e) {
109 throw new DecompressionException(e);
110 }
111 }
112
113 @Override
114 public ByteBuf takeOutput() throws DecompressionException {
115 ByteBuf buf = allocator.buffer(DEFAULT_MAX_FORWARD_BYTES, DEFAULT_MAX_FORWARD_BYTES);
116 try {
117 buf.writeBytes(output, DEFAULT_MAX_FORWARD_BYTES);
118 } catch (IOException e) {
119 buf.release();
120 throw new DecompressionException(e);
121 }
122 if (buf.isReadable()) {
123 return buf;
124 }
125 buf.release();
126 return Unpooled.EMPTY_BUFFER;
127 }
128
129 @Override
130 public void close() {
131 if (mutableInput.current != null) {
132 mutableInput.current.release();
133 mutableInput.current = null;
134 }
135 try {
136 output.close();
137 } catch (IOException ignored) {
138
139 }
140 }
141
142 @UnstableApi
143 public static Builder builder() {
144 return new Builder();
145 }
146
147 @UnstableApi
148 public static final class Builder extends AbstractDecompressorBuilder {
149 private int maxWindowLog = DEFAULT_MAX_WINDOW_LOG;
150
151 Builder() {
152 }
153
154
155
156
157
158
159
160
161
162
163
164 @UnstableApi
165 public Builder maxWindowLog(int maxWindowLog) {
166 this.maxWindowLog = ObjectUtil.checkInRange(
167 maxWindowLog, MIN_WINDOW_LOG, MAX_WINDOW_LOG, "maxWindowLog");
168 return this;
169 }
170
171 @Override
172 public Decompressor build(ByteBufAllocator allocator) throws DecompressionException {
173 return new DefensiveDecompressor(new ZstdDecompressor(this, allocator));
174 }
175 }
176
177 private static final class MutableByteBufInputStream extends InputStream {
178 ByteBuf current;
179
180 @Override
181 public int read() {
182 if (available() == 0) {
183 return -1;
184 }
185 return current.readByte() & 0xff;
186 }
187
188 @Override
189 public int read(byte[] b, int off, int len) {
190 int available = available();
191 if (available == 0) {
192 return -1;
193 }
194
195 len = Math.min(available, len);
196 current.readBytes(b, off, len);
197 return len;
198 }
199
200 @Override
201 public int available() {
202 return current == null ? 0 : current.readableBytes();
203 }
204 }
205 }