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 }