[#1481] Add a JdkZlibDecoder which has no dependencies
This commit is contained in:
parent
9acf130adb
commit
39cabcd36c
@ -0,0 +1,339 @@
|
||||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.compression;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.Inflater;
|
||||
|
||||
|
||||
/**
|
||||
* Decompress a {@link ByteBuf} using the inflate algorithm.
|
||||
*/
|
||||
public class JdkZlibDecoder extends ZlibDecoder {
|
||||
private static final int FHCRC = 0x02;
|
||||
private static final int FEXTRA = 0x04;
|
||||
private static final int FNAME = 0x08;
|
||||
private static final int FCOMMENT = 0x10;
|
||||
private static final int FRESERVED = 0xE0;
|
||||
|
||||
private final Inflater inflater;
|
||||
private final byte[] dictionary;
|
||||
|
||||
// GZIP related
|
||||
private final CRC32 crc;
|
||||
|
||||
private enum GzipState {
|
||||
HEADER_START,
|
||||
HEADER_END,
|
||||
FLG_READ,
|
||||
XLEN_READ,
|
||||
SKIP_FNAME,
|
||||
SKIP_COMMENT,
|
||||
PROCESS_FHCRC,
|
||||
FOOTER_START,
|
||||
}
|
||||
|
||||
private GzipState gzipState = GzipState.HEADER_START;
|
||||
private int flags = -1;
|
||||
private int xlen = -1;
|
||||
|
||||
private volatile boolean finished;
|
||||
|
||||
/**
|
||||
* Creates a new instance with the default wrapper ({@link ZlibWrapper#ZLIB}).
|
||||
*/
|
||||
public JdkZlibDecoder() {
|
||||
this(ZlibWrapper.ZLIB, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified preset dictionary. The wrapper
|
||||
* is always {@link ZlibWrapper#ZLIB} because it is the only format that
|
||||
* supports the preset dictionary.
|
||||
*/
|
||||
public JdkZlibDecoder(byte[] dictionary) {
|
||||
this(ZlibWrapper.ZLIB, dictionary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified wrapper.
|
||||
* Be aware that only {@link ZlibWrapper#GZIP}, {@link ZlibWrapper#ZLIB} and {@link ZlibWrapper#NONE} are
|
||||
* supported atm.
|
||||
*/
|
||||
public JdkZlibDecoder(ZlibWrapper wrapper) {
|
||||
this(wrapper, null);
|
||||
}
|
||||
|
||||
private JdkZlibDecoder(ZlibWrapper wrapper, byte[] dictionary) {
|
||||
if (wrapper == null) {
|
||||
throw new NullPointerException("wrapper");
|
||||
}
|
||||
switch (wrapper) {
|
||||
case GZIP:
|
||||
inflater = new Inflater(true);
|
||||
crc = new CRC32();
|
||||
break;
|
||||
case NONE:
|
||||
inflater = new Inflater(true);
|
||||
crc = null;
|
||||
break;
|
||||
case ZLIB:
|
||||
inflater = new Inflater();
|
||||
crc = null;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Only GZIP or ZLIB is supported, but you used " + wrapper);
|
||||
}
|
||||
this.dictionary = dictionary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return finished;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
if (!in.isReadable() && finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (crc != null) {
|
||||
switch (gzipState) {
|
||||
case FOOTER_START:
|
||||
if (readGZIPFooter(in)) {
|
||||
finished = true;
|
||||
}
|
||||
return;
|
||||
default:
|
||||
if (gzipState != GzipState.HEADER_END) {
|
||||
if (!readGZIPHeader(in)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int readableBytes = in.readableBytes();
|
||||
if (in.hasArray()) {
|
||||
inflater.setInput(in.array(), in.arrayOffset() + in.readerIndex(), in.readableBytes());
|
||||
} else {
|
||||
byte[] array = new byte[in.readableBytes()];
|
||||
in.getBytes(in.readerIndex(), array);
|
||||
inflater.setInput(array);
|
||||
}
|
||||
|
||||
int maxOutputLength = inflater.getRemaining() << 1;
|
||||
ByteBuf decompressed = ctx.alloc().heapBuffer(maxOutputLength);
|
||||
try {
|
||||
boolean readFooter = false;
|
||||
while (!inflater.needsInput()) {
|
||||
byte[] outArray = decompressed.array();
|
||||
int outIndex = decompressed.arrayOffset() + decompressed.writerIndex();
|
||||
int length = outArray.length - outIndex;
|
||||
|
||||
int outputLength = inflater.inflate(outArray, outIndex, length);
|
||||
|
||||
if (outputLength > 0) {
|
||||
decompressed.writerIndex(decompressed.writerIndex() + outputLength);
|
||||
if (crc != null) {
|
||||
crc.update(outArray, outIndex, length);
|
||||
}
|
||||
} else {
|
||||
if (inflater.needsDictionary()) {
|
||||
if (dictionary == null) {
|
||||
throw new DecompressionException(
|
||||
"decompression failure, unable to set dictionary as non was specified");
|
||||
}
|
||||
inflater.setDictionary(dictionary);
|
||||
}
|
||||
}
|
||||
|
||||
if (inflater.finished()) {
|
||||
if (crc == null) {
|
||||
finished = true; // Do not decode anymore.
|
||||
} else {
|
||||
readFooter = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
in.skipBytes(readableBytes - inflater.getRemaining());
|
||||
|
||||
if (readFooter) {
|
||||
gzipState = GzipState.FOOTER_START;
|
||||
if (readGZIPFooter(in)) {
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
} catch (DataFormatException e) {
|
||||
throw new DecompressionException("decompression failure", e);
|
||||
} finally {
|
||||
|
||||
if (decompressed.isReadable()) {
|
||||
out.add(decompressed);
|
||||
} else {
|
||||
decompressed.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
|
||||
super.handlerRemoved0(ctx);
|
||||
inflater.end();
|
||||
}
|
||||
|
||||
private boolean readGZIPHeader(ByteBuf in) {
|
||||
switch (gzipState) {
|
||||
case HEADER_START:
|
||||
if (in.readableBytes() < 10) {
|
||||
return false;
|
||||
}
|
||||
// read magic numbers
|
||||
int magic0 = in.readByte();
|
||||
int magic1 = in.readByte();
|
||||
|
||||
if (magic0 != 31) {
|
||||
throw new CompressionException("Input is not in the GZIP format");
|
||||
}
|
||||
crc.update(magic0);
|
||||
crc.update(magic1);
|
||||
|
||||
int method = in.readUnsignedByte();
|
||||
if (method != Deflater.DEFLATED) {
|
||||
throw new CompressionException("Unsupported compression method "
|
||||
+ method + " in the GZIP header");
|
||||
}
|
||||
crc.update(method);
|
||||
|
||||
flags = in.readUnsignedByte();
|
||||
crc.update(flags);
|
||||
|
||||
if ((flags & FRESERVED) != 0) {
|
||||
throw new CompressionException(
|
||||
"Reserved flags are set in the GZIP header");
|
||||
}
|
||||
|
||||
// mtime (int)
|
||||
crc.update(in.readByte());
|
||||
crc.update(in.readByte());
|
||||
crc.update(in.readByte());
|
||||
crc.update(in.readByte());
|
||||
|
||||
crc.update(in.readUnsignedByte()); // extra flags
|
||||
crc.update(in.readUnsignedByte()); // operating system
|
||||
|
||||
gzipState = GzipState.FLG_READ;
|
||||
case FLG_READ:
|
||||
if ((flags & FEXTRA) != 0) {
|
||||
if (in.readableBytes() < 2) {
|
||||
return false;
|
||||
}
|
||||
int xlen1 = in.readUnsignedByte();
|
||||
int xlen2 = in.readUnsignedByte();
|
||||
crc.update(xlen1);
|
||||
crc.update(xlen2);
|
||||
|
||||
xlen |= xlen1 << 8 | xlen2;
|
||||
}
|
||||
gzipState = GzipState.XLEN_READ;
|
||||
case XLEN_READ:
|
||||
if (xlen != -1) {
|
||||
if (in.readableBytes() < xlen) {
|
||||
return false;
|
||||
}
|
||||
byte[] xtra = new byte[xlen];
|
||||
in.readBytes(xtra);
|
||||
crc.update(xtra);
|
||||
}
|
||||
gzipState = GzipState.SKIP_FNAME;
|
||||
case SKIP_FNAME:
|
||||
if ((flags & FNAME) != 0) {
|
||||
if (!in.isReadable()) {
|
||||
return false;
|
||||
}
|
||||
do {
|
||||
int b = in.readUnsignedByte();
|
||||
crc.update(b);
|
||||
if (b == 0x00) {
|
||||
break;
|
||||
}
|
||||
} while (in.isReadable());
|
||||
}
|
||||
gzipState = GzipState.SKIP_COMMENT;
|
||||
case SKIP_COMMENT:
|
||||
if ((flags & FCOMMENT) != 0) {
|
||||
if (!in.isReadable()) {
|
||||
return false;
|
||||
}
|
||||
do {
|
||||
int b = in.readUnsignedByte();
|
||||
crc.update(b);
|
||||
if (b == 0x00) {
|
||||
break;
|
||||
}
|
||||
} while (in.isReadable());
|
||||
}
|
||||
gzipState = GzipState.PROCESS_FHCRC;
|
||||
case PROCESS_FHCRC:
|
||||
if ((flags & FHCRC) != 0) {
|
||||
if (!in.isReadable()) {
|
||||
return false;
|
||||
}
|
||||
int headerCrc = in.readShort();
|
||||
int readCrc = (int) crc.getValue() & 0xffff;
|
||||
if (headerCrc != readCrc) {
|
||||
throw new CompressionException(
|
||||
"Header CRC value missmatch. Expected: " + headerCrc + ", Got: " + readCrc);
|
||||
}
|
||||
}
|
||||
crc.reset();
|
||||
gzipState = GzipState.HEADER_END;
|
||||
case HEADER_END:
|
||||
return true;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean readGZIPFooter(ByteBuf buf) {
|
||||
if (buf.readableBytes() < 8) {
|
||||
return false;
|
||||
}
|
||||
int dataCrc = buf.readInt();
|
||||
int readCrc = (int) crc.getValue() & 0xffff;
|
||||
if (dataCrc != readCrc) {
|
||||
throw new CompressionException(
|
||||
"Data CRC value missmatch. Expected: " + dataCrc + ", Got: " + readCrc);
|
||||
}
|
||||
|
||||
int dataLength = buf.readInt();
|
||||
int readLength = inflater.getTotalOut();
|
||||
if (dataLength != readLength) {
|
||||
throw new CompressionException(
|
||||
"Number of bytes missmatch. Expected: " + dataLength + ", Got: " + readLength);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -79,15 +79,32 @@ public final class ZlibCodecFactory {
|
||||
}
|
||||
|
||||
public static ZlibDecoder newZlibDecoder() {
|
||||
if (PlatformDependent.javaVersion() < 7) {
|
||||
return new JZlibDecoder();
|
||||
} else {
|
||||
return new JdkZlibDecoder();
|
||||
}
|
||||
}
|
||||
|
||||
public static ZlibDecoder newZlibDecoder(ZlibWrapper wrapper) {
|
||||
switch (wrapper) {
|
||||
case ZLIB_OR_NONE:
|
||||
return new JZlibDecoder(wrapper);
|
||||
default:
|
||||
if (PlatformDependent.javaVersion() < 7) {
|
||||
return new JZlibDecoder(wrapper);
|
||||
} else {
|
||||
return new JdkZlibDecoder(wrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ZlibDecoder newZlibDecoder(byte[] dictionary) {
|
||||
if (PlatformDependent.javaVersion() < 7) {
|
||||
return new JZlibDecoder(dictionary);
|
||||
} else {
|
||||
return new JdkZlibDecoder(dictionary);
|
||||
}
|
||||
}
|
||||
|
||||
private ZlibCodecFactory() {
|
||||
|
@ -18,6 +18,7 @@ package io.netty.handler.codec.compression;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
@ -26,7 +27,7 @@ public class JZlibTest {
|
||||
|
||||
@Test
|
||||
public void testZLIB() throws Exception {
|
||||
ByteBuf data = Unpooled.wrappedBuffer("test".getBytes());
|
||||
ByteBuf data = Unpooled.copiedBuffer("test", CharsetUtil.UTF_8);
|
||||
|
||||
EmbeddedChannel chEncoder = new EmbeddedChannel(new JZlibEncoder(ZlibWrapper.ZLIB));
|
||||
|
||||
@ -52,7 +53,7 @@ public class JZlibTest {
|
||||
|
||||
@Test
|
||||
public void testNONE() throws Exception {
|
||||
ByteBuf data = Unpooled.wrappedBuffer("test".getBytes());
|
||||
ByteBuf data = Unpooled.copiedBuffer("test", CharsetUtil.UTF_8);
|
||||
|
||||
EmbeddedChannel chEncoder = new EmbeddedChannel(new JZlibEncoder(ZlibWrapper.NONE));
|
||||
|
||||
@ -79,7 +80,7 @@ public class JZlibTest {
|
||||
|
||||
@Test
|
||||
public void testGZIP() throws Exception {
|
||||
ByteBuf data = Unpooled.wrappedBuffer("test".getBytes());
|
||||
ByteBuf data = Unpooled.copiedBuffer("test", CharsetUtil.UTF_8);
|
||||
|
||||
EmbeddedChannel chEncoder = new EmbeddedChannel(new JZlibEncoder(ZlibWrapper.GZIP));
|
||||
|
||||
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.compression;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class JdkZlibTest {
|
||||
|
||||
@Test
|
||||
public void testZLIB() throws Exception {
|
||||
ByteBuf data = Unpooled.copiedBuffer("test", CharsetUtil.UTF_8);
|
||||
|
||||
EmbeddedChannel chEncoder = new EmbeddedChannel(new JdkZlibEncoder(ZlibWrapper.ZLIB));
|
||||
|
||||
chEncoder.writeOutbound(data.copy());
|
||||
assertTrue(chEncoder.finish());
|
||||
|
||||
ByteBuf deflatedData = (ByteBuf) chEncoder.readOutbound();
|
||||
|
||||
EmbeddedChannel chDecoderZlib = new EmbeddedChannel(new JdkZlibDecoder(ZlibWrapper.ZLIB));
|
||||
|
||||
chDecoderZlib.writeInbound(deflatedData.copy());
|
||||
assertTrue(chDecoderZlib.finish());
|
||||
|
||||
assertEquals(data, chDecoderZlib.readInbound());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNONE() throws Exception {
|
||||
ByteBuf data = Unpooled.copiedBuffer("test", CharsetUtil.UTF_8);
|
||||
|
||||
EmbeddedChannel chEncoder = new EmbeddedChannel(new JdkZlibEncoder(ZlibWrapper.NONE));
|
||||
|
||||
chEncoder.writeOutbound(data.copy());
|
||||
assertTrue(chEncoder.finish());
|
||||
|
||||
ByteBuf deflatedData = (ByteBuf) chEncoder.readOutbound();
|
||||
|
||||
EmbeddedChannel chDecoderZlibNone = new EmbeddedChannel(new JdkZlibDecoder(ZlibWrapper.NONE));
|
||||
|
||||
chDecoderZlibNone.writeInbound(deflatedData.copy());
|
||||
assertTrue(chDecoderZlibNone.finish());
|
||||
|
||||
assertEquals(data, chDecoderZlibNone.readInbound());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testZLIB_OR_NONE() throws Exception {
|
||||
new JdkZlibDecoder(ZlibWrapper.ZLIB_OR_NONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGZIP() throws Exception {
|
||||
ByteBuf data = Unpooled.copiedBuffer("test", CharsetUtil.UTF_8);
|
||||
|
||||
EmbeddedChannel chEncoder = new EmbeddedChannel(new JdkZlibEncoder(ZlibWrapper.GZIP));
|
||||
|
||||
chEncoder.writeOutbound(data.copy());
|
||||
assertTrue(chEncoder.finish());
|
||||
|
||||
ByteBuf deflatedData = (ByteBuf) chEncoder.readOutbound();
|
||||
|
||||
EmbeddedChannel chDecoderGZip = new EmbeddedChannel(new JdkZlibDecoder(ZlibWrapper.GZIP));
|
||||
|
||||
chDecoderGZip.writeInbound(deflatedData.copy());
|
||||
assertTrue(chDecoderGZip.finish());
|
||||
|
||||
assertEquals(data, chDecoderGZip.readInbound());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user