1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.netty.handler.codec.compression;
18
19 import com.aayushatharva.brotli4j.decoder.DecoderJNI;
20 import io.netty.buffer.ByteBuf;
21 import io.netty.buffer.ByteBufAllocator;
22 import io.netty.channel.ChannelHandlerContext;
23 import io.netty.handler.codec.ByteToMessageDecoder;
24 import io.netty.util.internal.ObjectUtil;
25
26 import java.nio.ByteBuffer;
27 import java.util.List;
28
29
30
31
32
33
34 public final class BrotliDecoder extends ByteToMessageDecoder {
35
36 private enum State {
37 DONE, NEEDS_MORE_INPUT, ERROR
38 }
39
40 static {
41 try {
42 Brotli.ensureAvailability();
43 } catch (Throwable throwable) {
44 throw new ExceptionInInitializerError(throwable);
45 }
46 }
47
48 private final int inputBufferSize;
49 private DecoderJNI.Wrapper decoder;
50 private boolean destroyed;
51
52
53
54
55 public BrotliDecoder() {
56 this(8 * 1024);
57 }
58
59
60
61
62
63 public BrotliDecoder(int inputBufferSize) {
64 this.inputBufferSize = ObjectUtil.checkPositive(inputBufferSize, "inputBufferSize");
65 }
66
67 private ByteBuf pull(ByteBufAllocator alloc) {
68 ByteBuffer nativeBuffer = decoder.pull();
69
70 ByteBuf copy = alloc.buffer(nativeBuffer.remaining());
71 copy.writeBytes(nativeBuffer);
72 return copy;
73 }
74
75 private State decompress(ByteBuf input, List<Object> output, ByteBufAllocator alloc) {
76 for (;;) {
77 switch (decoder.getStatus()) {
78 case DONE:
79 return State.DONE;
80
81 case OK:
82 decoder.push(0);
83 break;
84
85 case NEEDS_MORE_INPUT:
86 if (decoder.hasOutput()) {
87 output.add(pull(alloc));
88 }
89
90 if (!input.isReadable()) {
91 return State.NEEDS_MORE_INPUT;
92 }
93
94 ByteBuffer decoderInputBuffer = decoder.getInputBuffer();
95 decoderInputBuffer.clear();
96 int readBytes = readBytes(input, decoderInputBuffer);
97 decoder.push(readBytes);
98 break;
99
100 case NEEDS_MORE_OUTPUT:
101 output.add(pull(alloc));
102 break;
103
104 default:
105 return State.ERROR;
106 }
107 }
108 }
109
110 private static int readBytes(ByteBuf in, ByteBuffer dest) {
111 int limit = Math.min(in.readableBytes(), dest.remaining());
112 ByteBuffer slice = dest.slice();
113 slice.limit(limit);
114 in.readBytes(slice);
115 dest.position(dest.position() + limit);
116 return limit;
117 }
118
119 @Override
120 public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
121 decoder = new DecoderJNI.Wrapper(inputBufferSize);
122 }
123
124 @Override
125 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
126 if (destroyed) {
127
128 in.skipBytes(in.readableBytes());
129 return;
130 }
131
132 if (!in.isReadable()) {
133 return;
134 }
135
136 try {
137 State state = decompress(in, out, ctx.alloc());
138 if (state == State.DONE) {
139 destroy();
140 } else if (state == State.ERROR) {
141 throw new DecompressionException("Brotli stream corrupted");
142 }
143 } catch (Exception e) {
144 destroy();
145 throw e;
146 }
147 }
148
149 private void destroy() {
150 if (!destroyed) {
151 destroyed = true;
152 decoder.destroy();
153 }
154 }
155
156 @Override
157 protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
158 try {
159 destroy();
160 } finally {
161 super.handlerRemoved0(ctx);
162 }
163 }
164
165 @Override
166 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
167 try {
168 destroy();
169 } finally {
170 super.channelInactive(ctx);
171 }
172 }
173 }