1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.netty5.handler.codec.compression;
18
19 import com.aayushatharva.brotli4j.decoder.DecoderJNI;
20 import io.netty5.buffer.api.Buffer;
21 import io.netty5.buffer.api.BufferAllocator;
22 import io.netty5.util.internal.ObjectUtil;
23
24 import java.io.IOException;
25 import java.nio.ByteBuffer;
26 import java.util.function.Supplier;
27
28
29
30
31
32
33 public final class BrotliDecompressor implements Decompressor {
34
35 static {
36 try {
37 Brotli.ensureAvailability();
38 } catch (Throwable throwable) {
39 throw new ExceptionInInitializerError(throwable);
40 }
41 }
42
43 private enum State {
44 PROCESSING,
45 FINISHED,
46 CLOSED
47 }
48
49 private State state = State.PROCESSING;
50
51 private final DecoderJNI.Wrapper decoder;
52
53
54
55
56
57
58 public static Supplier<BrotliDecompressor> newFactory() {
59 return newFactory(8 * 1024);
60 }
61
62
63
64
65
66
67
68 public static Supplier<BrotliDecompressor> newFactory(int inputBufferSize) {
69 ObjectUtil.checkPositive(inputBufferSize, "inputBufferSize");
70 return () -> {
71 try {
72 return new BrotliDecompressor(inputBufferSize);
73 } catch (IOException e) {
74 throw new DecompressionException(e);
75 }
76 };
77 }
78
79
80
81
82
83
84 private BrotliDecompressor(int inputBufferSize) throws IOException {
85 decoder = new DecoderJNI.Wrapper(inputBufferSize);
86 }
87
88 private Buffer pull(BufferAllocator alloc) {
89 ByteBuffer nativeBuffer = decoder.pull();
90
91 return alloc.copyOf(nativeBuffer);
92 }
93
94 private static int readBytes(Buffer in, ByteBuffer dest) {
95 int limit = Math.min(in.readableBytes(), dest.remaining());
96 ByteBuffer slice = dest.slice();
97 slice.limit(limit);
98 in.readBytes(slice);
99 dest.position(dest.position() + limit);
100 return limit;
101 }
102
103 @Override
104 public Buffer decompress(Buffer input, BufferAllocator allocator) throws DecompressionException {
105 switch (state) {
106 case CLOSED:
107 throw new DecompressionException("Decompressor closed");
108 case FINISHED:
109 return allocator.allocate(0);
110 case PROCESSING:
111 for (;;) {
112 switch (decoder.getStatus()) {
113 case DONE:
114 state = State.FINISHED;
115 return null;
116 case OK:
117 decoder.push(0);
118 break;
119 case NEEDS_MORE_INPUT:
120 if (decoder.hasOutput()) {
121 return pull(allocator);
122 }
123
124 if (input.readableBytes() == 0) {
125 return null;
126 }
127
128 ByteBuffer decoderInputBuffer = decoder.getInputBuffer();
129 decoderInputBuffer.clear();
130 int readBytes = readBytes(input, decoderInputBuffer);
131 decoder.push(readBytes);
132 break;
133 case NEEDS_MORE_OUTPUT:
134 return pull(allocator);
135 default:
136 state = State.FINISHED;
137 throw new DecompressionException("Brotli stream corrupted");
138 }
139 }
140 default:
141 throw new IllegalStateException();
142 }
143 }
144
145 @Override
146 public boolean isFinished() {
147 return state != State.PROCESSING;
148 }
149
150 @Override
151 public boolean isClosed() {
152 return state == State.CLOSED;
153 }
154
155 @Override
156 public void close() {
157 if (state != State.FINISHED) {
158 state = State.FINISHED;
159 decoder.destroy();
160 }
161 }
162 }