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 io.netty.buffer.ByteBuf;
19  import io.netty.buffer.ByteBufInputStream;
20  import io.netty.buffer.ByteBufOutputStream;
21  import io.netty.channel.ChannelHandlerContext;
22  import io.netty.handler.codec.MessageToByteEncoder;
23  import io.netty.util.internal.logging.InternalLogger;
24  import io.netty.util.internal.logging.InternalLoggerFactory;
25  import lzma.sdk.lzma.Base;
26  import lzma.sdk.lzma.Encoder;
27  
28  import java.io.InputStream;
29  
30  import static lzma.sdk.lzma.Encoder.EMatchFinderTypeBT4;
31  
32  
33  
34  
35  
36  
37  
38  
39  public class LzmaFrameEncoder extends MessageToByteEncoder<ByteBuf> {
40  
41      private static final InternalLogger logger = InternalLoggerFactory.getInstance(LzmaFrameEncoder.class);
42  
43      private static final int MEDIUM_DICTIONARY_SIZE = 1 << 16;
44  
45      private static final int MIN_FAST_BYTES = 5;
46      private static final int MEDIUM_FAST_BYTES = 0x20;
47      private static final int MAX_FAST_BYTES = Base.kMatchMaxLen;
48  
49      private static final int DEFAULT_MATCH_FINDER = EMatchFinderTypeBT4;
50  
51      private static final int DEFAULT_LC = 3;
52      private static final int DEFAULT_LP = 0;
53      private static final int DEFAULT_PB = 2;
54  
55      
56  
57  
58      private final Encoder encoder;
59  
60      
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72      private final byte properties;
73  
74      
75  
76  
77      private final int littleEndianDictionarySize;
78  
79      
80  
81  
82      private static boolean warningLogged;
83  
84      
85  
86  
87      public LzmaFrameEncoder() {
88          this(MEDIUM_DICTIONARY_SIZE);
89      }
90  
91      
92  
93  
94  
95      public LzmaFrameEncoder(int lc, int lp, int pb) {
96          this(lc, lp, pb, MEDIUM_DICTIONARY_SIZE);
97      }
98  
99      
100 
101 
102 
103 
104 
105     public LzmaFrameEncoder(int dictionarySize) {
106         this(DEFAULT_LC, DEFAULT_LP, DEFAULT_PB, dictionarySize);
107     }
108 
109     
110 
111 
112     public LzmaFrameEncoder(int lc, int lp, int pb, int dictionarySize) {
113         this(lc, lp, pb, dictionarySize, false, MEDIUM_FAST_BYTES);
114     }
115 
116     
117 
118 
119 
120 
121 
122 
123 
124 
125 
126 
127 
128 
129 
130 
131 
132 
133 
134 
135 
136 
137     public LzmaFrameEncoder(int lc, int lp, int pb, int dictionarySize, boolean endMarkerMode, int numFastBytes) {
138         super(ByteBuf.class);
139         if (lc < 0 || lc > 8) {
140             throw new IllegalArgumentException("lc: " + lc + " (expected: 0-8)");
141         }
142         if (lp < 0 || lp > 4) {
143             throw new IllegalArgumentException("lp: " + lp + " (expected: 0-4)");
144         }
145         if (pb < 0 || pb > 4) {
146             throw new IllegalArgumentException("pb: " + pb + " (expected: 0-4)");
147         }
148         if (lc + lp > 4) {
149             if (!warningLogged) {
150                 logger.warn("The latest versions of LZMA libraries (for example, XZ Utils) " +
151                         "has an additional requirement: lc + lp <= 4. Data which don't follow " +
152                         "this requirement cannot be decompressed with this libraries.");
153                 warningLogged = true;
154             }
155         }
156         if (dictionarySize < 0) {
157             throw new IllegalArgumentException("dictionarySize: " + dictionarySize + " (expected: 0+)");
158         }
159         if (numFastBytes < MIN_FAST_BYTES || numFastBytes > MAX_FAST_BYTES) {
160             throw new IllegalArgumentException(String.format(
161                     "numFastBytes: %d (expected: %d-%d)", numFastBytes, MIN_FAST_BYTES, MAX_FAST_BYTES
162             ));
163         }
164 
165         encoder = new Encoder();
166         encoder.setDictionarySize(dictionarySize);
167         encoder.setEndMarkerMode(endMarkerMode);
168         encoder.setMatchFinder(DEFAULT_MATCH_FINDER);
169         encoder.setNumFastBytes(numFastBytes);
170         encoder.setLcLpPb(lc, lp, pb);
171 
172         properties = (byte) ((pb * 5 + lp) * 9 + lc);
173         littleEndianDictionarySize = Integer.reverseBytes(dictionarySize);
174     }
175 
176     @Override
177     protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception {
178         final int length = in.readableBytes();
179         try (InputStream bbIn = new ByteBufInputStream(in);
180              ByteBufOutputStream bbOut = new ByteBufOutputStream(out)) {
181             bbOut.writeByte(properties);
182             bbOut.writeInt(littleEndianDictionarySize);
183             bbOut.writeLong(Long.reverseBytes(length));
184             encoder.code(bbIn, bbOut, -1, -1, null);
185         }
186     }
187 
188     @Override
189     protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf in, boolean preferDirect) throws Exception {
190         final int length = in.readableBytes();
191         final int maxOutputLength = maxOutputBufferLength(length);
192         return ctx.alloc().ioBuffer(maxOutputLength);
193     }
194 
195     
196 
197 
198     private static int maxOutputBufferLength(int inputLength) {
199         double factor;
200         if (inputLength < 200) {
201             factor = 1.5;
202         } else if (inputLength < 500) {
203             factor = 1.2;
204         } else if (inputLength < 1000) {
205             factor = 1.1;
206         } else if (inputLength < 10000) {
207             factor = 1.05;
208         } else {
209             factor = 1.02;
210         }
211         return 13 + (int) (inputLength * factor);
212     }
213 }