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 }