blob: ecf5851006e18b2873c2f725ebf90554e905eb8a [file] [log] [blame]
/*
*/
package org.apache.tomcat.lite.http;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.apache.tomcat.lite.io.BBucket;
import org.apache.tomcat.lite.io.IOBuffer;
import com.jcraft.jzlib.JZlib;
import com.jcraft.jzlib.ZStream;
public class CompressFilter {
// Stream format: RFC1950
// 1CMF 1FLG [4DICTID] DATA 4ADLER
// CMF: CINFO + CM (compression method). == x8
// 78 == deflate with 32k window, i.e. max window
// FLG: 2bit level, 1 bit FDICT, 5 bit FCHECK
// Cx, Dx - no dict; Fx, Ex - dict ( for BEST_COMPRESSION )
// Overhead: 6 bytes without dict, 10 with dict
// data is encoded in blocks - there is a 'block end' marker and
// 'last block'.
// Flush: http://www.bolet.org/~pornin/deflate-flush.html
// inflater needs about 9 bits
// Z_SYNC_FLUSH: send empty block, 00 00 FF FF - seems recomended
// PPP can skip this - there is a record format on top
// Z_PARTIAL_FLUSH: standard for SSH
ZStream cStream;
ZStream dStream;
byte[] dict;
long dictId;
public CompressFilter() {
}
public void recycle() {
if (cStream == null) {
return;
}
cStream.free();
cStream = null;
dStream.free();
dStream = null;
}
public void init() {
if (cStream != null) {
return;
}
// can't call: cStream.free(); - will kill the adler, NPE
cStream = new ZStream();
// BEST_COMRESSION results in 256Kb per Deflate
// 15 == default = 32k window
cStream.deflateInit(JZlib.Z_BEST_SPEED, 10);
dStream = new ZStream();
dStream.inflateInit();
}
CompressFilter setDictionary(byte[] dict, long id) {
init();
this.dict = dict;
this.dictId = id;
cStream.deflateSetDictionary(dict, dict.length);
return this;
}
void compress(IOBuffer in, IOBuffer out) throws IOException {
init();
BBucket bb = in.popFirst();
while (bb != null) {
// TODO: only the last one needs flush
// TODO: size missmatches ?
compress(bb, out, false);
bb = in.popFirst();
}
if (in.isClosedAndEmpty()) {
compressEnd(out);
}
}
void compress(BBucket bb, IOBuffer out, boolean last) throws IOException {
// TODO: only the last one needs flush
// TODO: size missmatches ?
init();
int flush = JZlib.Z_PARTIAL_FLUSH;
cStream.next_in = bb.array();
cStream.next_in_index = bb.position();
cStream.avail_in = bb.remaining();
while (true) {
ByteBuffer outB = out.getWriteBuffer();
cStream.next_out = outB.array();
cStream.next_out_index = outB.position();
cStream.avail_out = outB.remaining();
int err = cStream.deflate(flush);
check(err, cStream);
outB.position(cStream.next_out_index);
out.releaseWriteBuffer(1);
if (cStream.avail_out > 0 || cStream.avail_in == 0) {
break;
}
}
if (last) {
compressEnd(out);
}
}
private void compressEnd(IOBuffer out) throws IOException {
while (true) {
ByteBuffer outB = out.getWriteBuffer();
cStream.next_out = outB.array();
cStream.next_out_index = outB.position();
cStream.avail_out = outB.remaining();
cStream.deflate(JZlib.Z_FINISH);
cStream.deflateEnd();
outB.position(cStream.next_out_index);
out.releaseWriteBuffer(1);
if (cStream.avail_out > 0) {
break;
}
}
}
void decompress(IOBuffer in, IOBuffer out) throws IOException {
decompress(in, out, in.available());
}
void decompress(IOBuffer in, IOBuffer out, int len) throws IOException {
init();
BBucket bb = in.peekFirst();
while (bb != null && len > 0) {
dStream.next_in = bb.array();
dStream.next_in_index = bb.position();
int rd = Math.min(bb.remaining(), len);
dStream.avail_in = rd;
while (true) {
ByteBuffer outB = out.getWriteBuffer();
dStream.next_out = outB.array();
dStream.next_out_index = outB.position();
dStream.avail_out = outB.remaining();
int err = dStream.inflate(JZlib.Z_SYNC_FLUSH);
if (err == JZlib.Z_NEED_DICT && dict != null) {
// dStream.adler has the dict id - not sure how to check
if (dictId != 0 && dStream.adler != dictId) {
throw new IOException("Invalid dictionary");
}
if (dictId == 0) {
// initDict should pass a real dict id.
System.err.println("Missing dict ID: " + dStream.adler);
}
dStream.inflateSetDictionary(dict, dict.length);
err = dStream.inflate(JZlib.Z_SYNC_FLUSH);
}
outB.position(dStream.next_out_index);
out.releaseWriteBuffer(1);
if (err == JZlib.Z_STREAM_END) {
err = dStream.inflateEnd();
out.close();
check(err, dStream);
// move in back, not consummed
bb.position(dStream.next_in_index);
return;
}
check(err, dStream);
if (dStream.avail_out > 0 || dStream.avail_in == 0) {
break;
}
}
in.advance(rd); // consummed
len -= rd;
bb = in.peekFirst();
}
if (in.isClosedAndEmpty()) {
// Shouldn't happen - input was not properly closed..
// This should throw an exception, inflateEnd will check the CRC
int err = dStream.inflateEnd();
out.close();
check(err, dStream);
out.close();
}
}
private void check(int err, ZStream stream) throws IOException {
if (err != JZlib.Z_OK) {
throw new IOException(err + " " + stream.msg);
}
}
boolean isCompressed(HttpMessage http) {
return false;
}
boolean needsCompression(HttpMessage in, HttpMessage out) {
return false;
}
}